Removed a global variable
[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 <fstream>
22 #include <sstream>
23 #include <cassert>
24 #include <cstdio>
25 #include <cstdlib>
26 #include <cmath>
27 #include <cstring>
28 #include <cerrno>
29 #include <unistd.h>
30 #include <ctime>
31 #include <stdexcept>
32
33 #include <SDL.h>
34
35 #include "game_session.hpp"
36 #include "log.hpp"
37 #include "worldmap/worldmap.hpp"
38 #include "mainloop.hpp"
39 #include "video/screen.hpp"
40 #include "audio/sound_manager.hpp"
41 #include "gui/menu.hpp"
42 #include "sector.hpp"
43 #include "level.hpp"
44 #include "tile.hpp"
45 #include "player_status.hpp"
46 #include "object/particlesystem.hpp"
47 #include "object/background.hpp"
48 #include "object/gradient.hpp"
49 #include "object/tilemap.hpp"
50 #include "object/camera.hpp"
51 #include "object/player.hpp"
52 #include "object/level_time.hpp"
53 #include "lisp/lisp.hpp"
54 #include "lisp/parser.hpp"
55 #include "resources.hpp"
56 #include "statistics.hpp"
57 #include "timer.hpp"
58 #include "options_menu.hpp"
59 #include "object/fireworks.hpp"
60 #include "textscroller.hpp"
61 #include "control/codecontroller.hpp"
62 #include "control/joystickkeyboardcontroller.hpp"
63 #include "main.hpp"
64 #include "file_system.hpp"
65 #include "gameconfig.hpp"
66 #include "gettext.hpp"
67 #include "console.hpp"
68 #include "flip_level_transformer.hpp"
69
70 // the engine will be run with a logical framerate of 64fps.
71 // We chose 64fps here because it is a power of 2, so 1/64 gives an "even"
72 // binary fraction...
73 static const float LOGICAL_FPS = 64.0;
74
75 enum GameMenuIDs {
76   MNID_CONTINUE,
77   MNID_ABORTLEVEL
78 };
79
80 GameSession* GameSession::current_ = NULL;
81
82 GameSession::GameSession(const std::string& levelfile_, Statistics* statistics)
83   : level(0), currentsector(0),
84     end_sequence(NO_ENDSEQUENCE), end_sequence_controller(0),
85     levelfile(levelfile_), best_level_statistics(statistics),
86     capture_demo_stream(0), playback_demo_stream(0), demo_controller(0)
87 {
88   current_ = this;
89   currentsector = NULL;
90   
91   game_pause = false;
92
93   statistics_backdrop.reset(new Surface("images/engine/menu/score-backdrop.png"));
94
95   restart_level(true);
96
97   game_menu.reset(new Menu());
98   game_menu->add_label(_("Pause"));
99   game_menu->add_hl();
100   game_menu->add_entry(MNID_CONTINUE, _("Continue"));
101   game_menu->add_submenu(_("Options"), get_options_menu());
102   game_menu->add_hl();
103   game_menu->add_entry(MNID_ABORTLEVEL, _("Abort Level"));
104 }
105
106 void
107 GameSession::restart_level(bool fromBeginning)
108 {
109   game_pause   = false;
110   end_sequence = NO_ENDSEQUENCE;
111
112   main_controller->reset();
113
114   currentsector = 0;
115
116   level.reset(new Level);
117   level->load(levelfile);
118
119   level->stats.reset();
120   level->stats.set_total_points(COINS_COLLECTED_STAT, level->get_total_coins());
121   level->stats.set_total_points(BADGUYS_KILLED_STAT, level->get_total_badguys());
122   
123   // get time
124   int time = 0;
125   for(std::vector<Sector*>::iterator i = level->sectors.begin(); i != level->sectors.end(); ++i)
126   {
127     Sector* sec = *i;
128
129     for(std::vector<GameObject*>::iterator j = sec->gameobjects.begin();
130         j != sec->gameobjects.end(); ++j)
131     {
132       GameObject* obj = *j;
133       
134       LevelTime* lt = dynamic_cast<LevelTime*> (obj);
135       if(lt)
136         time += int(lt->get_level_time());
137     }
138   }
139   level->stats.set_total_points(TIME_NEEDED_STAT, (time == 0) ? -1 : time);
140
141   if (fromBeginning) reset_sector="";
142   if(reset_sector != "") {
143     currentsector = level->get_sector(reset_sector);
144     if(!currentsector) {
145       std::stringstream msg;
146       msg << "Couldn't find sector '" << reset_sector << "' for resetting tux.";
147       throw std::runtime_error(msg.str());
148     }
149     currentsector->activate(reset_pos);
150   } else {
151     currentsector = level->get_sector("main");
152     if(!currentsector)
153       throw std::runtime_error("Couldn't find main sector");
154     currentsector->activate("main");
155   }
156   
157   levelintro();
158
159   currentsector->play_music(LEVEL_MUSIC);
160
161   if(capture_file != "")
162     record_demo(capture_file);
163 }
164
165 GameSession::~GameSession()
166 {
167   delete capture_demo_stream;
168   delete playback_demo_stream;
169   delete demo_controller;
170
171   delete end_sequence_controller;
172
173   current_ = NULL;
174 }
175
176 void
177 GameSession::record_demo(const std::string& filename)
178 {
179   delete capture_demo_stream;
180   
181   capture_demo_stream = new std::ofstream(filename.c_str()); 
182   if(!capture_demo_stream->good()) {
183     std::stringstream msg;
184     msg << "Couldn't open demo file '" << filename << "' for writing.";
185     throw std::runtime_error(msg.str());
186   }
187   capture_file = filename;
188 }
189
190 void
191 GameSession::play_demo(const std::string& filename)
192 {
193   delete playback_demo_stream;
194   delete demo_controller;
195   
196   playback_demo_stream = new std::ifstream(filename.c_str());
197   if(!playback_demo_stream->good()) {
198     std::stringstream msg;
199     msg << "Couldn't open demo file '" << filename << "' for reading.";
200     throw std::runtime_error(msg.str());
201   }
202
203   Player& tux = *currentsector->player;
204   demo_controller = new CodeController();
205   tux.set_controller(demo_controller);
206 }
207
208 void
209 GameSession::levelintro()
210 {
211   char str[60];
212
213   sound_manager->stop_music();
214
215   DrawingContext context;
216   for(Sector::GameObjects::iterator i = currentsector->gameobjects.begin();
217       i != currentsector->gameobjects.end(); ++i) {
218     Background* background = dynamic_cast<Background*> (*i);
219     if(background) {
220       background->draw(context);
221     }
222     Gradient* gradient = dynamic_cast<Gradient*> (*i);
223     if(gradient) {
224       gradient->draw(context);
225     }
226   }
227
228 //  context.draw_text(gold_text, level->get_name(), Vector(SCREEN_WIDTH/2, 160),
229 //      CENTER_ALLIGN, LAYER_FOREGROUND1);
230   context.draw_center_text(gold_text, level->get_name(), Vector(0, 160),
231       LAYER_FOREGROUND1);
232
233   sprintf(str, "Coins: %d", player_status->coins);
234   context.draw_text(white_text, str, Vector(SCREEN_WIDTH/2, 210),
235       CENTER_ALLIGN, LAYER_FOREGROUND1);
236
237   if((level->get_author().size()) && (level->get_author() != "SuperTux Team"))
238     //TODO make author check case/blank-insensitive
239     context.draw_text(white_small_text,
240       std::string(_("contributed by ")) + level->get_author(), 
241       Vector(SCREEN_WIDTH/2, 350), CENTER_ALLIGN, LAYER_FOREGROUND1);
242
243
244   if(best_level_statistics != NULL)
245     best_level_statistics->draw_message_info(context, _("Best Level Statistics"));
246
247   wait_for_event(1.0, 3.0);
248 }
249
250 void
251 GameSession::on_escape_press()
252 {
253   if(currentsector->player->is_dying() || end_sequence != NO_ENDSEQUENCE)
254     return;   // don't let the player open the menu, when he is dying
255   
256   if (!Menu::current()) {
257     Menu::set_current(game_menu.get());
258     game_menu->set_active_item(MNID_CONTINUE);
259     game_pause = true;
260   } else {
261     Menu::set_current(NULL);
262     game_pause = false;
263   }
264 }
265
266 void
267 GameSession::process_events()
268 {
269   Player& tux = *currentsector->player;
270
271   // end of pause mode?
272   if(!Menu::current() && game_pause) {
273     game_pause = false;
274   }
275
276   if (end_sequence != NO_ENDSEQUENCE) {
277     if(end_sequence_controller == 0) {
278       end_sequence_controller = new CodeController();
279       tux.set_controller(end_sequence_controller);
280     }
281
282     end_sequence_controller->press(Controller::RIGHT);
283     
284     if (int(last_x_pos) == int(tux.get_pos().x))
285       end_sequence_controller->press(Controller::JUMP);    
286     last_x_pos = tux.get_pos().x;
287   }
288
289   // playback a demo?
290   if(playback_demo_stream != 0) {
291     demo_controller->update();
292     char left = false;
293     char right = false;
294     char up = false;
295     char down = false;
296     char jump = false;
297     char action = false;
298     playback_demo_stream->get(left);
299     playback_demo_stream->get(right);
300     playback_demo_stream->get(up);
301     playback_demo_stream->get(down);
302     playback_demo_stream->get(jump);
303     playback_demo_stream->get(action);
304     demo_controller->press(Controller::LEFT, left);
305     demo_controller->press(Controller::RIGHT, right);
306     demo_controller->press(Controller::UP, up);
307     demo_controller->press(Controller::DOWN, down);
308     demo_controller->press(Controller::JUMP, jump);
309     demo_controller->press(Controller::ACTION, action);
310   }
311
312   // save input for demo?
313   if(capture_demo_stream != 0) {
314     capture_demo_stream ->put(main_controller->hold(Controller::LEFT));
315     capture_demo_stream ->put(main_controller->hold(Controller::RIGHT));
316     capture_demo_stream ->put(main_controller->hold(Controller::UP));
317     capture_demo_stream ->put(main_controller->hold(Controller::DOWN));
318     capture_demo_stream ->put(main_controller->hold(Controller::JUMP));   
319     capture_demo_stream ->put(main_controller->hold(Controller::ACTION));
320   }
321 }
322
323 void
324 GameSession::check_end_conditions()
325 {
326   Player* tux = currentsector->player;
327
328   /* End of level? */
329   if(end_sequence && endsequence_timer.check()) {
330     finish(true);
331     return;
332   } else if (!end_sequence && tux->is_dead()) {
333     if (player_status->coins < 0) { 
334       // No more coins: restart level from beginning
335       player_status->coins = 0;
336       restart_level(true);
337     } else { 
338       // Still has coins: restart level from last reset point
339       restart_level(false);
340     }
341     
342     return;
343   }
344 }
345
346 void 
347 GameSession::draw(DrawingContext& context)
348 {
349   currentsector->draw(context);
350   drawstatus(context);
351
352   if(game_pause)
353     draw_pause(context);
354 }
355
356 void
357 GameSession::draw_pause(DrawingContext& context)
358 {
359   context.draw_filled_rect(
360       Vector(0,0), Vector(SCREEN_WIDTH, SCREEN_HEIGHT),
361       Color(.2, .2, .2, .5), LAYER_FOREGROUND1);
362 }
363   
364 void
365 GameSession::process_menu()
366 {
367   Menu* menu = Menu::current();
368   if(menu) {
369     menu->update();
370
371     if(menu == game_menu.get()) {
372       switch (game_menu->check()) {
373         case MNID_CONTINUE:
374           Menu::set_current(0);
375           break;
376         case MNID_ABORTLEVEL:
377           Menu::set_current(0);
378           main_loop->exit_screen();
379           break;
380       }
381     }
382   }
383 }
384
385 void
386 GameSession::setup()
387 {
388   Menu::set_current(NULL);
389   current_ = this;
390
391   // Eat unneeded events
392   SDL_Event event;
393   while(SDL_PollEvent(&event))
394   {}
395 }
396
397 void
398 GameSession::update(float elapsed_time)
399 {
400   process_events();
401   process_menu();
402
403   check_end_conditions();
404
405   // handle controller
406   if(main_controller->pressed(Controller::PAUSE_MENU))
407     on_escape_press();
408   
409   // respawning in new sector?
410   if(newsector != "" && newspawnpoint != "") {
411     Sector* sector = level->get_sector(newsector);
412     if(sector == 0) {
413       log_warning << "Sector '" << newsector << "' not found" << std::endl;
414     }
415     sector->activate(newspawnpoint);
416     sector->play_music(LEVEL_MUSIC);
417     currentsector = sector;
418     newsector = "";
419     newspawnpoint = "";
420   }
421
422   // Update the world state and all objects in the world
423   if(!game_pause) {
424     // Update the world
425     if (end_sequence == ENDSEQUENCE_RUNNING) {
426       currentsector->update(elapsed_time/2);
427     } else if(end_sequence == NO_ENDSEQUENCE) {
428       if(!currentsector->player->growing_timer.started())
429         currentsector->update(elapsed_time);
430     } 
431   }
432
433   // update sounds
434   sound_manager->set_listener_position(currentsector->player->get_pos());
435
436   /* Handle music: */
437   if (end_sequence)
438     return;
439   
440   if(currentsector->player->invincible_timer.started()) {
441     if(currentsector->player->invincible_timer.get_timeleft() <=
442        TUX_INVINCIBLE_TIME_WARNING) {
443       currentsector->play_music(HERRING_WARNING_MUSIC);
444     } else {
445       currentsector->play_music(HERRING_MUSIC);
446     }
447   } else if(currentsector->get_music_type() != LEVEL_MUSIC) {
448     currentsector->play_music(LEVEL_MUSIC);
449   }
450 }
451
452 void
453 GameSession::finish(bool win)
454 {
455   using namespace WorldMapNS;
456
457   if(win) {
458     if(WorldMap::current())
459       WorldMap::current()->finished_level(level.get());
460   }
461   
462   main_loop->exit_screen();
463 }
464
465 void
466 GameSession::respawn(const std::string& sector, const std::string& spawnpoint)
467 {
468   newsector = sector;
469   newspawnpoint = spawnpoint;
470 }
471
472 void
473 GameSession::set_reset_point(const std::string& sector, const Vector& pos)
474 {
475   reset_sector = sector;
476   reset_pos = pos;
477 }
478
479 std::string
480 GameSession::get_working_directory()
481 {
482   return FileSystem::dirname(levelfile);
483 }
484
485 void
486 GameSession::display_info_box(const std::string& text)
487 {
488   InfoBox* box = new InfoBox(text);
489
490   bool running = true;
491   DrawingContext context;
492   
493   while(running)  {
494
495     // TODO make a screen out of this, another mainloop is ugly
496     main_controller->update();
497     SDL_Event event;
498     while (SDL_PollEvent(&event)) {
499       main_controller->process_event(event);
500       if(event.type == SDL_QUIT)
501         main_loop->quit();
502     }
503
504     if(main_controller->pressed(Controller::JUMP)
505         || main_controller->pressed(Controller::ACTION)
506         || main_controller->pressed(Controller::PAUSE_MENU)
507         || main_controller->pressed(Controller::MENU_SELECT))
508       running = false;
509     else if(main_controller->pressed(Controller::DOWN))
510       box->scrolldown();
511     else if(main_controller->pressed(Controller::UP))
512       box->scrollup();
513     box->draw(context);
514     draw(context);
515     context.do_drawing();
516     sound_manager->update();
517   }
518
519   delete box;
520 }
521
522 void
523 GameSession::start_sequence(const std::string& sequencename)
524 {
525   if(sequencename == "endsequence" || sequencename == "fireworks") {
526     if(end_sequence)
527       return;
528
529     end_sequence = ENDSEQUENCE_RUNNING;
530     endsequence_timer.start(7.3);
531     last_x_pos = -1;
532     sound_manager->play_music("music/leveldone.ogg", false);
533     currentsector->player->invincible_timer.start(7.3);
534
535     // Stop all clocks.
536     for(std::vector<GameObject*>::iterator i = currentsector->gameobjects.begin();
537         i != currentsector->gameobjects.end(); ++i)
538     {
539       GameObject* obj = *i;
540
541       LevelTime* lt = dynamic_cast<LevelTime*> (obj);
542       if(lt)
543         lt->stop();
544     }
545
546     // add time spent to statistics
547     int tottime = 0, remtime = 0;
548     for(std::vector<Sector*>::iterator i = level->sectors.begin(); i != level->sectors.end(); ++i)
549     {
550       Sector* sec = *i;
551
552       for(std::vector<GameObject*>::iterator j = sec->gameobjects.begin();
553           j != sec->gameobjects.end(); ++j)
554       {
555         GameObject* obj = *j;
556
557         LevelTime* lt = dynamic_cast<LevelTime*> (obj);
558         if(lt)
559         {
560           tottime += int(lt->get_level_time());
561           remtime += int(lt->get_remaining_time());
562         }
563       }
564     }
565     level->stats.set_points(TIME_NEEDED_STAT, (tottime == 0 ? -1 : (tottime-remtime)));
566
567     if(sequencename == "fireworks") {
568       currentsector->add_object(new Fireworks());
569     }
570   } else if(sequencename == "stoptux") {
571     if(!end_sequence) {
572       log_warning << "Final target reached without an active end sequence" << std::endl;
573       this->start_sequence("endsequence");
574     }
575     end_sequence =  ENDSEQUENCE_WAITING;
576   } else {
577     log_warning << "Unknown sequence '" << sequencename << "'" << std::endl;
578   }
579 }
580
581 /* (Status): */
582 void
583 GameSession::drawstatus(DrawingContext& context)
584 {
585   player_status->draw(context);
586
587   // draw level stats while end_sequence is running
588   if (end_sequence) {
589     level->stats.draw_endseq_panel(context, best_level_statistics, statistics_backdrop.get());
590   }
591 }
592