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
45GameSession::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
80void
81GameSession::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
92int
93GameSession::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
156void
157GameSession::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
176void
177GameSession::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
194void
195GameSession::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
207bool
208GameSession::is_active() const
209{
210 return !m_game_pause && m_active && !m_end_sequence;
211}
212
213void
214GameSession::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
233void
234GameSession::force_ghost_mode()
235{
236 m_currentsector->get_player().set_ghost_mode(true);
237}
238
239void
240GameSession::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
252void
253GameSession::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
264void
265GameSession::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
273void
274GameSession::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
294void
295GameSession::leave()
296{
297}
298
299void
300GameSession::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
421void
422GameSession::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
450void
451GameSession::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
460void
461GameSession::set_reset_point(const std::string& sector, const Vector& pos)
462{
463 m_reset_sector = sector;
464 m_reset_pos = pos;
465}
466
467std::string
468GameSession::get_working_directory() const
469{
470 return FileSystem::dirname(m_levelfile);
471}
472
473void
474GameSession::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): */
544void
545GameSession::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