1 | // SuperTux |
2 | // Copyright (C) 2006 Matthias Braun <matze@braunis.de> |
3 | // |
4 | // This program is free software: you can redistribute it and/or modify |
5 | // it under the terms of the GNU General Public License as published by |
6 | // the Free Software Foundation, either version 3 of the License, or |
7 | // (at your option) any later version. |
8 | // |
9 | // This program is distributed in the hope that it will be useful, |
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | // GNU General Public License for more details. |
13 | // |
14 | // You should have received a copy of the GNU General Public License |
15 | // along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | |
17 | #include "supertux/game_session.hpp" |
18 | |
19 | #include "audio/sound_manager.hpp" |
20 | #include "control/input_manager.hpp" |
21 | #include "gui/menu_manager.hpp" |
22 | #include "object/camera.hpp" |
23 | #include "object/endsequence_fireworks.hpp" |
24 | #include "object/endsequence_walkleft.hpp" |
25 | #include "object/endsequence_walkright.hpp" |
26 | #include "object/level_time.hpp" |
27 | #include "object/music_object.hpp" |
28 | #include "object/player.hpp" |
29 | #include "supertux/fadetoblack.hpp" |
30 | #include "supertux/gameconfig.hpp" |
31 | #include "supertux/level.hpp" |
32 | #include "supertux/level_parser.hpp" |
33 | #include "supertux/levelintro.hpp" |
34 | #include "supertux/levelset_screen.hpp" |
35 | #include "supertux/menu/menu_storage.hpp" |
36 | #include "supertux/savegame.hpp" |
37 | #include "supertux/screen_manager.hpp" |
38 | #include "supertux/sector.hpp" |
39 | #include "util/file_system.hpp" |
40 | #include "video/compositor.hpp" |
41 | #include "video/drawing_context.hpp" |
42 | #include "video/surface.hpp" |
43 | #include "worldmap/worldmap.hpp" |
44 | |
45 | GameSession::GameSession(const std::string& levelfile_, Savegame& savegame, Statistics* statistics) : |
46 | GameSessionRecorder(), |
47 | reset_button(false), |
48 | reset_checkpoint_button(false), |
49 | m_level(), |
50 | m_old_level(), |
51 | m_statistics_backdrop(Surface::from_file("images/engine/menu/score-backdrop.png" )), |
52 | m_scripts(), |
53 | m_currentsector(nullptr), |
54 | m_end_sequence(nullptr), |
55 | m_game_pause(false), |
56 | m_speed_before_pause(ScreenManager::current()->get_speed()), |
57 | m_levelfile(levelfile_), |
58 | m_reset_sector(), |
59 | m_reset_pos(), |
60 | m_newsector(), |
61 | m_newspawnpoint(), |
62 | m_pastinvincibility(false), |
63 | m_newinvincibilityperiod(0), |
64 | m_best_level_statistics(statistics), |
65 | m_savegame(savegame), |
66 | m_play_time(0), |
67 | m_edit_mode(false), |
68 | m_levelintro_shown(false), |
69 | m_coins_at_start(), |
70 | m_bonus_at_start(), |
71 | m_max_fire_bullets_at_start(), |
72 | m_max_ice_bullets_at_start(), |
73 | m_active(false), |
74 | m_end_seq_started(false) |
75 | { |
76 | if (restart_level() != 0) |
77 | throw std::runtime_error ("Initializing the level failed." ); |
78 | } |
79 | |
80 | void |
81 | GameSession::reset_level() |
82 | { |
83 | m_currentsector->get_player().set_bonus(m_bonus_at_start); |
84 | PlayerStatus& currentStatus = m_savegame.get_player_status(); |
85 | currentStatus.coins = m_coins_at_start; |
86 | currentStatus.max_fire_bullets = m_max_fire_bullets_at_start; |
87 | currentStatus.max_ice_bullets = m_max_ice_bullets_at_start; |
88 | m_reset_sector = "" ; |
89 | m_reset_pos = Vector(); |
90 | } |
91 | |
92 | int |
93 | GameSession::restart_level(bool after_death) |
94 | { |
95 | const PlayerStatus& currentStatus = m_savegame.get_player_status(); |
96 | m_coins_at_start = currentStatus.coins; |
97 | m_bonus_at_start = currentStatus.bonus; |
98 | m_max_fire_bullets_at_start = currentStatus.max_fire_bullets; |
99 | m_max_ice_bullets_at_start = currentStatus.max_ice_bullets; |
100 | |
101 | if (m_edit_mode) { |
102 | force_ghost_mode(); |
103 | return (-1); |
104 | } |
105 | |
106 | m_game_pause = false; |
107 | m_end_sequence = nullptr; |
108 | |
109 | InputManager::current()->reset(); |
110 | |
111 | m_currentsector = nullptr; |
112 | |
113 | const std::string base_dir = FileSystem::dirname(m_levelfile); |
114 | if (base_dir == "./" ) { |
115 | m_levelfile = FileSystem::basename(m_levelfile); |
116 | } |
117 | |
118 | try { |
119 | m_old_level = std::move(m_level); |
120 | m_level = LevelParser::from_file(m_levelfile, false, false); |
121 | |
122 | if (!m_reset_sector.empty()) { |
123 | m_currentsector = m_level->get_sector(m_reset_sector); |
124 | if (!m_currentsector) { |
125 | std::stringstream msg; |
126 | msg << "Couldn't find sector '" << m_reset_sector << "' for resetting tux." ; |
127 | throw std::runtime_error(msg.str()); |
128 | } |
129 | m_currentsector->activate(m_reset_pos); |
130 | } else { |
131 | m_currentsector = m_level->get_sector("main" ); |
132 | if (!m_currentsector) |
133 | throw std::runtime_error("Couldn't find main sector" ); |
134 | m_play_time = 0; |
135 | m_currentsector->activate("main" ); |
136 | } |
137 | } catch(std::exception& e) { |
138 | log_fatal << "Couldn't start level: " << e.what() << std::endl; |
139 | ScreenManager::current()->pop_screen(); |
140 | return (-1); |
141 | } |
142 | |
143 | auto& music_object = m_currentsector->get_singleton_by_type<MusicObject>(); |
144 | if (after_death == true) { |
145 | music_object.resume_music(); |
146 | } else { |
147 | SoundManager::current()->stop_music(); |
148 | music_object.play_music(LEVEL_MUSIC); |
149 | } |
150 | |
151 | start_recording(); |
152 | |
153 | return (0); |
154 | } |
155 | |
156 | void |
157 | GameSession::on_escape_press() |
158 | { |
159 | if ((m_currentsector->get_player().is_dying() && m_play_time > 2.0f) || m_end_sequence) |
160 | { |
161 | // Let the timers run out, we fast-forward them to force past a sequence |
162 | if (m_end_sequence) |
163 | m_end_sequence->stop(); |
164 | |
165 | m_currentsector->get_player().m_dying_timer.start(FLT_EPSILON); |
166 | return; // don't let the player open the menu, when Tux is dying |
167 | } |
168 | |
169 | if (m_level->m_name != "Credits" ) { |
170 | toggle_pause(); |
171 | } else { |
172 | abort_level(); |
173 | } |
174 | } |
175 | |
176 | void |
177 | GameSession::toggle_pause() |
178 | { |
179 | // pause |
180 | if (!m_game_pause && !MenuManager::instance().is_active()) |
181 | { |
182 | m_speed_before_pause = ScreenManager::current()->get_speed(); |
183 | ScreenManager::current()->set_speed(0); |
184 | MenuManager::instance().set_menu(MenuStorage::GAME_MENU); |
185 | SoundManager::current()->pause_sounds(); |
186 | m_currentsector->stop_looping_sounds(); |
187 | SoundManager::current()->pause_music(); |
188 | m_game_pause = true; |
189 | } |
190 | |
191 | // unpause is done in update() after the menu is processed |
192 | } |
193 | |
194 | void |
195 | GameSession::abort_level() |
196 | { |
197 | MenuManager::instance().clear_menu_stack(); |
198 | ScreenManager::current()->pop_screen(); |
199 | m_currentsector->get_player().set_bonus(m_bonus_at_start); |
200 | PlayerStatus& currentStatus = m_savegame.get_player_status(); |
201 | currentStatus.coins = m_coins_at_start; |
202 | currentStatus.max_fire_bullets = m_max_fire_bullets_at_start; |
203 | currentStatus.max_ice_bullets = m_max_ice_bullets_at_start; |
204 | SoundManager::current()->stop_sounds(); |
205 | } |
206 | |
207 | bool |
208 | GameSession::is_active() const |
209 | { |
210 | return !m_game_pause && m_active && !m_end_sequence; |
211 | } |
212 | |
213 | void |
214 | GameSession::set_editmode(bool edit_mode_) |
215 | { |
216 | if (m_edit_mode == edit_mode_) return; |
217 | m_edit_mode = edit_mode_; |
218 | |
219 | m_currentsector->get_player().set_edit_mode(edit_mode_); |
220 | |
221 | if (edit_mode_) { |
222 | |
223 | // entering edit mode |
224 | |
225 | } else { |
226 | |
227 | // leaving edit mode |
228 | restart_level(); |
229 | |
230 | } |
231 | } |
232 | |
233 | void |
234 | GameSession::force_ghost_mode() |
235 | { |
236 | m_currentsector->get_player().set_ghost_mode(true); |
237 | } |
238 | |
239 | void |
240 | GameSession::check_end_conditions() |
241 | { |
242 | Player& tux = m_currentsector->get_player(); |
243 | |
244 | /* End of level? */ |
245 | if (m_end_sequence && m_end_sequence->is_done()) { |
246 | finish(true); |
247 | } else if (!m_end_sequence && tux.is_dead()) { |
248 | restart_level(true); |
249 | } |
250 | } |
251 | |
252 | void |
253 | GameSession::draw(Compositor& compositor) |
254 | { |
255 | auto& context = compositor.make_context(); |
256 | |
257 | m_currentsector->draw(context); |
258 | drawstatus(context); |
259 | |
260 | if (m_game_pause) |
261 | draw_pause(context); |
262 | } |
263 | |
264 | void |
265 | GameSession::draw_pause(DrawingContext& context) |
266 | { |
267 | context.color().draw_filled_rect( |
268 | Rectf(0, 0, static_cast<float>(context.get_width()), static_cast<float>(context.get_height())), |
269 | Color(0.0f, 0.0f, 0.0f, 0.25f), |
270 | LAYER_FOREGROUND1); |
271 | } |
272 | |
273 | void |
274 | GameSession::setup() |
275 | { |
276 | if (m_currentsector == nullptr) |
277 | return; |
278 | |
279 | if (m_currentsector != Sector::current()) { |
280 | m_currentsector->activate(m_currentsector->get_player().get_pos()); |
281 | } |
282 | m_currentsector->get_singleton_by_type<MusicObject>().play_music(LEVEL_MUSIC); |
283 | |
284 | int total_stats_to_be_collected = m_level->m_stats.m_total_coins + m_level->m_stats.m_total_badguys + m_level->m_stats.m_total_secrets; |
285 | if ((!m_levelintro_shown) && (total_stats_to_be_collected > 0)) { |
286 | m_levelintro_shown = true; |
287 | m_active = false; |
288 | ScreenManager::current()->push_screen(std::make_unique<LevelIntro>(*m_level, m_best_level_statistics, m_savegame.get_player_status())); |
289 | } |
290 | ScreenManager::current()->set_screen_fade(std::make_unique<FadeToBlack>(FadeToBlack::FADEIN, 1)); |
291 | m_end_seq_started = false; |
292 | } |
293 | |
294 | void |
295 | GameSession::leave() |
296 | { |
297 | } |
298 | |
299 | void |
300 | GameSession::update(float dt_sec, const Controller& controller) |
301 | { |
302 | // Set active flag |
303 | if (!m_active) |
304 | { |
305 | m_active = true; |
306 | } |
307 | // handle controller |
308 | |
309 | if (controller.pressed(Control::ESCAPE) || |
310 | controller.pressed(Control::START)) |
311 | { |
312 | on_escape_press(); |
313 | } |
314 | |
315 | if (controller.pressed(Control::CHEAT_MENU) && g_config->developer_mode) |
316 | { |
317 | if (!MenuManager::instance().is_active()) |
318 | { |
319 | m_game_pause = true; |
320 | MenuManager::instance().set_menu(MenuStorage::CHEAT_MENU); |
321 | } |
322 | } |
323 | |
324 | if (controller.pressed(Control::DEBUG_MENU) && g_config->developer_mode) |
325 | { |
326 | if (!MenuManager::instance().is_active()) |
327 | { |
328 | m_game_pause = true; |
329 | MenuManager::instance().set_menu(MenuStorage::DEBUG_MENU); |
330 | } |
331 | } |
332 | |
333 | process_events(); |
334 | |
335 | // Unpause the game if the menu has been closed |
336 | if (m_game_pause && !MenuManager::instance().is_active()) { |
337 | ScreenManager::current()->set_speed(m_speed_before_pause); |
338 | SoundManager::current()->resume_music(); |
339 | SoundManager::current()->resume_sounds(); |
340 | m_currentsector->play_looping_sounds(); |
341 | m_game_pause = false; |
342 | } |
343 | |
344 | check_end_conditions(); |
345 | |
346 | // respawning in new sector? |
347 | if (!m_newsector.empty() && !m_newspawnpoint.empty()) { |
348 | auto sector = m_level->get_sector(m_newsector); |
349 | if (sector == nullptr) { |
350 | log_warning << "Sector '" << m_newsector << "' not found" << std::endl; |
351 | sector = m_level->get_sector("main" ); |
352 | } |
353 | m_currentsector->stop_looping_sounds(); |
354 | sector->activate(m_newspawnpoint); |
355 | sector->get_singleton_by_type<MusicObject>().play_music(LEVEL_MUSIC); |
356 | m_currentsector = sector; |
357 | m_currentsector->play_looping_sounds(); |
358 | |
359 | if (is_playing_demo()) |
360 | { |
361 | reset_demo_controller(); |
362 | } |
363 | //Keep persistent across sectors |
364 | if (m_edit_mode) |
365 | m_currentsector->get_player().set_edit_mode(m_edit_mode); |
366 | m_newsector = "" ; |
367 | m_newspawnpoint = "" ; |
368 | // retain invincibility if the player has it |
369 | if (m_pastinvincibility) { |
370 | m_currentsector->get_player().m_invincible_timer.start(static_cast<float>(m_newinvincibilityperiod)); |
371 | } |
372 | } |
373 | |
374 | // Update the world state and all objects in the world |
375 | if (!m_game_pause) { |
376 | // Update the world |
377 | if (!m_end_sequence) { |
378 | m_play_time += dt_sec; //TODO: make sure we don't count cutscene time |
379 | m_level->m_stats.finish(m_play_time); |
380 | m_currentsector->update(dt_sec); |
381 | } else { |
382 | if (!m_end_sequence->is_tux_stopped()) { |
383 | m_currentsector->update(dt_sec); |
384 | } else { |
385 | m_end_sequence->update(dt_sec); |
386 | } |
387 | } |
388 | } |
389 | |
390 | if (m_currentsector == nullptr) |
391 | return; |
392 | |
393 | // update sounds |
394 | SoundManager::current()->set_listener_position(m_currentsector->get_camera().get_center()); |
395 | |
396 | /* Handle music: */ |
397 | if (m_end_sequence) |
398 | return; |
399 | |
400 | if (m_currentsector->get_player().m_invincible_timer.started()) { |
401 | if (m_currentsector->get_player().m_invincible_timer.get_timeleft() <= |
402 | TUX_INVINCIBLE_TIME_WARNING) { |
403 | m_currentsector->get_singleton_by_type<MusicObject>().play_music(HERRING_WARNING_MUSIC); |
404 | } else { |
405 | m_currentsector->get_singleton_by_type<MusicObject>().play_music(HERRING_MUSIC); |
406 | } |
407 | } else if (m_currentsector->get_singleton_by_type<MusicObject>().get_music_type() != LEVEL_MUSIC) { |
408 | m_currentsector->get_singleton_by_type<MusicObject>().play_music(LEVEL_MUSIC); |
409 | } |
410 | if (reset_button) { |
411 | reset_button = false; |
412 | reset_level(); |
413 | restart_level(); |
414 | } else if(reset_checkpoint_button) { |
415 | reset_checkpoint_button = false; |
416 | |
417 | get_current_sector().get_player().kill(true); |
418 | } |
419 | } |
420 | |
421 | void |
422 | GameSession::finish(bool win) |
423 | { |
424 | if (m_end_seq_started) |
425 | return; |
426 | m_end_seq_started = true; |
427 | |
428 | using namespace worldmap; |
429 | |
430 | if (m_edit_mode) { |
431 | force_ghost_mode(); |
432 | return; |
433 | } |
434 | |
435 | if (win) { |
436 | if (WorldMap::current()) |
437 | { |
438 | WorldMap::current()->finished_level(m_level.get()); |
439 | } |
440 | |
441 | if (LevelsetScreen::current()) |
442 | { |
443 | LevelsetScreen::current()->finished_level(win); |
444 | } |
445 | } |
446 | |
447 | ScreenManager::current()->pop_screen(); |
448 | } |
449 | |
450 | void |
451 | GameSession::respawn(const std::string& sector, const std::string& spawnpoint, |
452 | const bool invincibility, const int invincibilityperiod) |
453 | { |
454 | m_newsector = sector; |
455 | m_newspawnpoint = spawnpoint; |
456 | m_pastinvincibility = invincibility; |
457 | m_newinvincibilityperiod = invincibilityperiod; |
458 | } |
459 | |
460 | void |
461 | GameSession::set_reset_point(const std::string& sector, const Vector& pos) |
462 | { |
463 | m_reset_sector = sector; |
464 | m_reset_pos = pos; |
465 | } |
466 | |
467 | std::string |
468 | GameSession::get_working_directory() const |
469 | { |
470 | return FileSystem::dirname(m_levelfile); |
471 | } |
472 | |
473 | void |
474 | GameSession::start_sequence(Sequence seq, const SequenceData* data) |
475 | { |
476 | // do not play sequences when in edit mode |
477 | if (m_edit_mode) { |
478 | force_ghost_mode(); |
479 | return; |
480 | } |
481 | |
482 | // handle special "stoptux" sequence |
483 | if (seq == SEQ_STOPTUX) { |
484 | if (!m_end_sequence) { |
485 | log_warning << "Final target reached without an active end sequence" << std::endl; |
486 | start_sequence(SEQ_ENDSEQUENCE); |
487 | } |
488 | if (m_end_sequence) m_end_sequence->stop_tux(); |
489 | return; |
490 | } |
491 | |
492 | // abort if a sequence is already playing |
493 | if (m_end_sequence) |
494 | return; |
495 | |
496 | std::unique_ptr<EndSequence> end_sequence; |
497 | if (seq == SEQ_ENDSEQUENCE) { |
498 | if (m_currentsector->get_player().get_physic().get_velocity_x() < 0) { |
499 | end_sequence = std::make_unique<EndSequenceWalkLeft>(); |
500 | } else { |
501 | end_sequence = std::make_unique<EndSequenceWalkRight>(); |
502 | } |
503 | } else if (seq == SEQ_FIREWORKS) { |
504 | end_sequence = std::make_unique<EndSequenceFireworks>(); |
505 | } else { |
506 | log_warning << "Unknown sequence '" << static_cast<int>(seq) << "'. Ignoring." << std::endl; |
507 | return; |
508 | } |
509 | |
510 | if (const auto& worldmap = worldmap::WorldMap::current()) |
511 | { |
512 | if (data != nullptr) |
513 | { |
514 | if (!data->fade_tilemap.empty()) |
515 | { |
516 | worldmap->set_initial_fade_tilemap(data->fade_tilemap, data->fade_type); |
517 | } |
518 | if (!data->spawnpoint.empty()) |
519 | { |
520 | worldmap->set_initial_spawnpoint(data->spawnpoint); |
521 | } |
522 | } |
523 | } |
524 | |
525 | /* slow down the game for end-sequence */ |
526 | ScreenManager::current()->set_speed(0.5f); |
527 | |
528 | m_end_sequence = static_cast<EndSequence*>(&m_currentsector->add_object(std::move(end_sequence))); |
529 | m_end_sequence->start(); |
530 | |
531 | SoundManager::current()->play_music("music/misc/leveldone.ogg" , false); |
532 | m_currentsector->get_player().set_winning(); |
533 | |
534 | // Stop all clocks. |
535 | for (const auto& obj : m_currentsector->get_objects()) |
536 | { |
537 | auto lt = dynamic_cast<LevelTime*>(obj.get()); |
538 | if (lt) |
539 | lt->stop(); |
540 | } |
541 | } |
542 | |
543 | /* (Status): */ |
544 | void |
545 | GameSession::drawstatus(DrawingContext& context) |
546 | { |
547 | // draw level stats while end_sequence is running |
548 | if (m_end_sequence) { |
549 | m_level->m_stats.draw_endseq_panel(context, m_best_level_statistics, m_statistics_backdrop); |
550 | } |
551 | } |
552 | |
553 | /* EOF */ |
554 | |