Fix for #453 (Menu frame lingers)
[supertux.git] / src / game_session.cpp
1 //  $Id$
2 //
3 //  SuperTux
4 //  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
5 //
6 //  This program is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU General Public License
8 //  as published by the Free Software Foundation; either version 2
9 //  of the License, or (at your option) any later version.
10 //
11 //  This program is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //  GNU General Public License for more details.
15 //
16 //  You should have received a copy of the GNU General Public License
17 //  along with this program; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19 #include <config.h>
20
21 #include "game_session.hpp"
22
23 #include <fstream>
24 #include <sstream>
25 #include <assert.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <math.h>
29 #include <string.h>
30 #include <errno.h>
31 #include <unistd.h>
32 #include <time.h>
33 #include <stdexcept>
34 #include <float.h>
35
36 #include <SDL.h>
37
38 #include "file_system.hpp"
39 #include "gameconfig.hpp"
40 #include "gettext.hpp"
41 #include "level.hpp"
42 #include "levelintro.hpp"
43 #include "log.hpp"
44 #include "main.hpp"
45 #include "mainloop.hpp"
46 #include "player_status.hpp"
47 #include "options_menu.hpp"
48 #include "random_generator.hpp"
49 #include "sector.hpp"
50 #include "statistics.hpp"
51 #include "timer.hpp"
52 #include "audio/sound_manager.hpp"
53 #include "control/codecontroller.hpp"
54 #include "control/joystickkeyboardcontroller.hpp"
55 #include "gui/menu.hpp"
56 #include "object/camera.hpp"
57 #include "object/endsequence_fireworks.hpp"
58 #include "object/endsequence_walkleft.hpp"
59 #include "object/endsequence_walkright.hpp"
60 #include "object/level_time.hpp"
61 #include "object/player.hpp"
62 #include "scripting/squirrel_util.hpp"
63 #include "worldmap/worldmap.hpp"
64
65 enum GameMenuIDs {
66   MNID_CONTINUE,
67   MNID_ABORTLEVEL
68 };
69
70 GameSession* GameSession::current_ = NULL;
71
72 GameSession::GameSession(const std::string& levelfile_, Statistics* statistics)
73   : level(0), currentsector(0),
74     end_sequence(0),
75     levelfile(levelfile_), best_level_statistics(statistics),
76     capture_demo_stream(0), playback_demo_stream(0), demo_controller(0),
77     play_time(0), edit_mode(false), levelintro_shown(false)
78 {
79   current_ = this;
80   currentsector = NULL;
81
82   game_pause = false;
83   speed_before_pause = main_loop->get_speed();
84
85   statistics_backdrop.reset(new Surface("images/engine/menu/score-backdrop.png"));
86
87   restart_level();
88
89   game_menu.reset(new Menu());
90   game_menu->add_label(level->name);
91   game_menu->add_hl();
92   game_menu->add_entry(MNID_CONTINUE, _("Continue"));
93   game_menu->add_submenu(_("Options"), get_options_menu());
94   game_menu->add_hl();
95   game_menu->add_entry(MNID_ABORTLEVEL, _("Abort Level"));
96 }
97
98 void
99 GameSession::restart_level()
100 {
101
102   if (edit_mode) {
103     force_ghost_mode();
104     return;
105   }
106
107   game_pause   = false;
108   end_sequence = 0;
109
110   main_controller->reset();
111
112   currentsector = 0;
113
114   level.reset(new Level);
115   try {
116     level->load(levelfile);
117     level->stats.total_coins = level->get_total_coins();
118     level->stats.total_badguys = level->get_total_badguys();
119     level->stats.total_secrets = level->get_total_secrets();
120     level->stats.reset();
121
122     if(reset_sector != "") {
123       currentsector = level->get_sector(reset_sector);
124       if(!currentsector) {
125         std::stringstream msg;
126         msg << "Couldn't find sector '" << reset_sector << "' for resetting tux.";
127         throw std::runtime_error(msg.str());
128       }
129       level->stats.declare_invalid();
130       currentsector->activate(reset_pos);
131     } else {
132       currentsector = level->get_sector("main");
133       if(!currentsector)
134         throw std::runtime_error("Couldn't find main sector");
135       play_time = 0;
136       currentsector->activate("main");
137     }
138   } catch(std::exception& e) {
139     log_warning << "Couldn't start level: " << e.what() << std::endl;
140     main_loop->exit_screen();
141   }
142
143   sound_manager->stop_music();
144   currentsector->play_music(LEVEL_MUSIC);
145
146   if(capture_file != "") {
147     int newSeed=0;               // next run uses a new seed
148     while (newSeed == 0)            // which is the next non-zero random num.
149         newSeed = systemRandom.rand();
150     config->random_seed = systemRandom.srand(newSeed);
151     log_info << "Next run uses random seed " <<config->random_seed <<std::endl;
152     record_demo(capture_file);
153   }
154 }
155
156 GameSession::~GameSession()
157 {
158   delete capture_demo_stream;
159   delete playback_demo_stream;
160   delete demo_controller;
161   free_options_menu();
162
163   current_ = NULL;
164 }
165
166 void
167 GameSession::record_demo(const std::string& filename)
168 {
169   delete capture_demo_stream;
170
171   capture_demo_stream = new std::ofstream(filename.c_str());
172   if(!capture_demo_stream->good()) {
173     std::stringstream msg;
174     msg << "Couldn't open demo file '" << filename << "' for writing.";
175     throw std::runtime_error(msg.str());
176   }
177   capture_file = filename;
178
179   char buf[30];                            // save the seed in the demo file
180   snprintf(buf, sizeof(buf), "random_seed=%10d", config->random_seed);
181   for (int i=0; i==0 || buf[i-1]; i++)
182     capture_demo_stream->put(buf[i]);
183 }
184
185 int
186 GameSession::get_demo_random_seed(const std::string& filename)
187 {
188   std::istream* test_stream = new std::ifstream(filename.c_str());
189   if(test_stream->good()) {
190     char buf[30];                     // recall the seed from the demo file
191     int seed;
192     for (int i=0; i<30 && (i==0 || buf[i-1]); i++)
193       test_stream->get(buf[i]);
194     if (sscanf(buf, "random_seed=%10d", &seed) == 1) {
195       log_info << "Random seed " << seed << " from demo file" << std::endl;
196          return seed;
197     }
198     else
199       log_info << "Demo file contains no random number" << std::endl;
200   }
201   return 0;
202 }
203
204 void
205 GameSession::play_demo(const std::string& filename)
206 {
207   delete playback_demo_stream;
208   delete demo_controller;
209
210   playback_demo_stream = new std::ifstream(filename.c_str());
211   if(!playback_demo_stream->good()) {
212     std::stringstream msg;
213     msg << "Couldn't open demo file '" << filename << "' for reading.";
214     throw std::runtime_error(msg.str());
215   }
216
217   Player& tux = *currentsector->player;
218   demo_controller = new CodeController();
219   tux.set_controller(demo_controller);
220
221   // skip over random seed, if it exists in the file
222   char buf[30];                            // ascii decimal seed
223   int seed;
224   for (int i=0; i<30 && (i==0 || buf[i-1]); i++)
225     playback_demo_stream->get(buf[i]);
226   if (sscanf(buf, "random_seed=%010d", &seed) != 1)
227     playback_demo_stream->seekg(0);     // old style w/o seed, restart at beg
228 }
229
230 void
231 GameSession::on_escape_press()
232 {
233   if(currentsector->player->is_dying() || end_sequence)
234   {
235     // Let the timers run out, we fast-forward them to force past a sequence
236     if (end_sequence)
237       end_sequence->stop();
238
239     currentsector->player->dying_timer.start(FLT_EPSILON);
240     return;   // don't let the player open the menu, when he is dying
241   }
242
243   if(level->on_menukey_script != "") {
244     std::istringstream in(level->on_menukey_script);
245     run_script(in, "OnMenuKeyScript");
246   } else {
247     toggle_pause();
248   }
249 }
250
251 void
252 GameSession::toggle_pause()
253 {
254   // pause
255   if(!game_pause) {
256     speed_before_pause = main_loop->get_speed();
257     main_loop->set_speed(0);
258     Menu::set_current(game_menu.get());
259     game_menu->set_active_item(MNID_CONTINUE);
260     game_pause = true;
261   }
262
263   // unpause is done in update() after the menu is processed
264 }
265
266 void
267 GameSession::set_editmode(bool edit_mode)
268 {
269   if (this->edit_mode == edit_mode) return;
270   this->edit_mode = edit_mode;
271
272   currentsector->get_players()[0]->set_edit_mode(edit_mode);
273
274   if (edit_mode) {
275
276     // entering edit mode
277
278   } else {
279
280     // leaving edit mode
281     restart_level();
282
283   }
284 }
285
286 void
287 GameSession::force_ghost_mode()
288 {
289   currentsector->get_players()[0]->set_ghost_mode(true);
290 }
291
292 HSQUIRRELVM
293 GameSession::run_script(std::istream& in, const std::string& sourcename)
294 {
295   using namespace Scripting;
296
297   // garbage collect thread list
298   for(ScriptList::iterator i = scripts.begin();
299       i != scripts.end(); ) {
300     HSQOBJECT& object = *i;
301     HSQUIRRELVM vm = object_to_vm(object);
302
303     if(sq_getvmstate(vm) != SQ_VMSTATE_SUSPENDED) {
304       sq_release(global_vm, &object);
305       i = scripts.erase(i);
306       continue;
307     }
308
309     ++i;
310   }
311
312   HSQOBJECT object = create_thread(global_vm);
313   scripts.push_back(object);
314
315   HSQUIRRELVM vm = object_to_vm(object);
316
317   compile_and_run(vm, in, sourcename);
318
319   return vm;
320 }
321
322 void
323 GameSession::process_events()
324 {
325   // end of pause mode?
326   // XXX this looks like a fail-safe to unpause the game if there's no menu
327   // XXX having it enabled causes some unexpected problems
328   // XXX hopefully disabling it won't...
329   /*
330   if(!Menu::current() && game_pause) {
331     game_pause = false;
332   }
333   */
334
335   // playback a demo?
336   if(playback_demo_stream != 0) {
337     demo_controller->update();
338     char left = false;
339     char right = false;
340     char up = false;
341     char down = false;
342     char jump = false;
343     char action = false;
344     playback_demo_stream->get(left);
345     playback_demo_stream->get(right);
346     playback_demo_stream->get(up);
347     playback_demo_stream->get(down);
348     playback_demo_stream->get(jump);
349     playback_demo_stream->get(action);
350     demo_controller->press(Controller::LEFT, left);
351     demo_controller->press(Controller::RIGHT, right);
352     demo_controller->press(Controller::UP, up);
353     demo_controller->press(Controller::DOWN, down);
354     demo_controller->press(Controller::JUMP, jump);
355     demo_controller->press(Controller::ACTION, action);
356   }
357
358   // save input for demo?
359   if(capture_demo_stream != 0) {
360     capture_demo_stream ->put(main_controller->hold(Controller::LEFT));
361     capture_demo_stream ->put(main_controller->hold(Controller::RIGHT));
362     capture_demo_stream ->put(main_controller->hold(Controller::UP));
363     capture_demo_stream ->put(main_controller->hold(Controller::DOWN));
364     capture_demo_stream ->put(main_controller->hold(Controller::JUMP));
365     capture_demo_stream ->put(main_controller->hold(Controller::ACTION));
366   }
367 }
368
369 void
370 GameSession::check_end_conditions()
371 {
372   Player* tux = currentsector->player;
373
374   /* End of level? */
375   if(end_sequence && end_sequence->is_done()) {
376     finish(true);
377   } else if (!end_sequence && tux->is_dead()) {
378     restart_level();
379   }
380 }
381
382 void
383 GameSession::draw(DrawingContext& context)
384 {
385   currentsector->draw(context);
386   drawstatus(context);
387
388   if(game_pause)
389     draw_pause(context);
390 }
391
392 void
393 GameSession::draw_pause(DrawingContext& context)
394 {
395   context.draw_filled_rect(
396       Vector(0,0), Vector(SCREEN_WIDTH, SCREEN_HEIGHT),
397       Color(0.0f, 0.0f, 0.0f, .25f), LAYER_FOREGROUND1);
398 }
399
400 void
401 GameSession::process_menu()
402 {
403   Menu* menu = Menu::current();
404   if(menu) {
405     if(menu == game_menu.get()) {
406       switch (game_menu->check()) {
407         case MNID_CONTINUE:
408           Menu::set_current(0);
409           toggle_pause();
410           break;
411         case MNID_ABORTLEVEL:
412           Menu::set_current(0);
413           main_loop->exit_screen();
414           break;
415       }
416     }
417   }
418 }
419
420 void
421 GameSession::setup()
422 {
423   current_ = this;
424
425   if(currentsector != Sector::current()) {
426         currentsector->activate(currentsector->player->get_pos());
427   }
428   currentsector->play_music(LEVEL_MUSIC);
429
430   // Eat unneeded events
431   SDL_Event event;
432   while(SDL_PollEvent(&event))
433   {}
434
435   if (!levelintro_shown) {
436     levelintro_shown = true;
437     main_loop->push_screen(new LevelIntro(level.get(), best_level_statistics));
438   }
439 }
440
441 void
442 GameSession::update(float elapsed_time)
443 {
444   // handle controller
445   if(main_controller->pressed(Controller::PAUSE_MENU))
446     on_escape_press();
447
448   process_events();
449   process_menu();
450
451   // Unpause the game if the menu has been closed
452   if (game_pause && !Menu::current()) {
453     main_loop->set_speed(speed_before_pause);
454     game_pause = false;
455   }
456
457   check_end_conditions();
458
459   // respawning in new sector?
460   if(newsector != "" && newspawnpoint != "") {
461     Sector* sector = level->get_sector(newsector);
462     if(sector == 0) {
463       log_warning << "Sector '" << newsector << "' not found" << std::endl;
464       sector = level->get_sector("main");
465     }
466     sector->activate(newspawnpoint);
467     sector->play_music(LEVEL_MUSIC);
468     currentsector = sector;
469     //Keep persistent across sectors
470     if(edit_mode)
471       currentsector->get_players()[0]->set_edit_mode(edit_mode);
472     newsector = "";
473     newspawnpoint = "";
474   }
475
476   // Update the world state and all objects in the world
477   if(!game_pause) {
478     // Update the world
479     if (!end_sequence) {
480       play_time += elapsed_time; //TODO: make sure we don't count cutscene time
481       level->stats.time = play_time;
482       currentsector->update(elapsed_time);
483     } else {
484       if (!end_sequence->is_tux_stopped()) {
485         currentsector->update(elapsed_time);
486       } else {
487         end_sequence->update(elapsed_time);
488       }
489     }
490   }
491
492   // update sounds
493   if (currentsector && currentsector->camera) sound_manager->set_listener_position(currentsector->camera->get_center());
494
495   /* Handle music: */
496   if (end_sequence)
497     return;
498
499   if(currentsector->player->invincible_timer.started()) {
500     if(currentsector->player->invincible_timer.get_timeleft() <=
501        TUX_INVINCIBLE_TIME_WARNING) {
502       currentsector->play_music(HERRING_WARNING_MUSIC);
503     } else {
504       currentsector->play_music(HERRING_MUSIC);
505     }
506   } else if(currentsector->get_music_type() != LEVEL_MUSIC) {
507     currentsector->play_music(LEVEL_MUSIC);
508   }
509 }
510
511 void
512 GameSession::finish(bool win)
513 {
514   using namespace WorldMapNS;
515
516   if (edit_mode) {
517     force_ghost_mode();
518     return;
519   }
520
521   if(win) {
522     if(WorldMap::current())
523       WorldMap::current()->finished_level(level.get());
524   }
525
526   main_loop->exit_screen();
527 }
528
529 void
530 GameSession::respawn(const std::string& sector, const std::string& spawnpoint)
531 {
532   newsector = sector;
533   newspawnpoint = spawnpoint;
534 }
535
536 void
537 GameSession::set_reset_point(const std::string& sector, const Vector& pos)
538 {
539   reset_sector = sector;
540   reset_pos = pos;
541 }
542
543 std::string
544 GameSession::get_working_directory()
545 {
546   return FileSystem::dirname(levelfile);
547 }
548
549 void
550 GameSession::start_sequence(const std::string& sequencename)
551 {
552   // do not play sequences when in edit mode
553   if (edit_mode) {
554     force_ghost_mode();
555     return;
556   }
557
558   // handle special "stoptux" sequence
559   if (sequencename == "stoptux") {
560     if (!end_sequence) {
561       log_warning << "Final target reached without an active end sequence" << std::endl;
562       this->start_sequence("endsequence");
563     }
564     if (end_sequence) end_sequence->stop_tux();
565     return;
566   }
567
568   // abort if a sequence is already playing
569   if (end_sequence)
570           return;
571
572   if (sequencename == "endsequence") {
573     if (currentsector->get_players()[0]->physic.get_velocity_x() < 0) {
574       end_sequence = new EndSequenceWalkLeft();
575     } else {
576       end_sequence = new EndSequenceWalkRight();
577     }
578   } else if (sequencename == "fireworks") {
579     end_sequence = new EndSequenceFireworks();
580   } else {
581     log_warning << "Unknown sequence '" << sequencename << "'. Ignoring." << std::endl;
582     return;
583   }
584
585   /* slow down the game for end-sequence */
586   main_loop->set_speed(0.5f);
587
588   currentsector->add_object(end_sequence);
589   end_sequence->start();
590
591   sound_manager->play_music("music/leveldone.ogg", false);
592   currentsector->player->invincible_timer.start(10000.0f);
593
594   // Stop all clocks.
595   for(std::vector<GameObject*>::iterator i = currentsector->gameobjects.begin();
596                   i != currentsector->gameobjects.end(); ++i)
597   {
598     GameObject* obj = *i;
599
600     LevelTime* lt = dynamic_cast<LevelTime*> (obj);
601     if(lt)
602       lt->stop();
603   }
604 }
605
606 /* (Status): */
607 void
608 GameSession::drawstatus(DrawingContext& context)
609 {
610   player_status->draw(context);
611
612   // draw level stats while end_sequence is running
613   if (end_sequence) {
614     level->stats.draw_endseq_panel(context, best_level_statistics, statistics_backdrop.get());
615   }
616 }