1652bef69ba5e8720bfacee7e7f566b37cd2de31
[supertux.git] / src / gameloop.cpp
1 //  $Id$
2 // 
3 //  SuperTux
4 //  Copyright (C) 2000 Bill Kendrick <bill@newbreedsoftware.com>
5 //  Copyright (C) 2004 Tobias Glaesser <tobi.web@gmx.de>
6 //  Copyright (C) 2004 Ingo Ruhnke <grumbel@gmx.de>
7 //
8 //  This program is free software; you can redistribute it and/or
9 //  modify it under the terms of the GNU General Public License
10 //  as published by the Free Software Foundation; either version 2
11 //  of the License, or (at your option) any later version.
12 //
13 //  This program is distributed in the hope that it will be useful,
14 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 //  GNU General Public License for more details.
17 // 
18 //  You should have received a copy of the GNU General Public License
19 //  along with this program; if not, write to the Free Software
20 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
21
22 #include <iostream>
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
32 #include "SDL.h"
33
34 #ifndef WIN32
35 #include <sys/types.h>
36 #include <ctype.h>
37 #endif
38
39 #include "defines.h"
40 #include "globals.h"
41 #include "gameloop.h"
42 #include "screen/screen.h"
43 #include "setup.h"
44 #include "high_scores.h"
45 #include "menu.h"
46 #include "badguy.h"
47 #include "sector.h"
48 #include "special.h"
49 #include "player.h"
50 #include "level.h"
51 #include "scene.h"
52 #include "collision.h"
53 #include "tile.h"
54 #include "particlesystem.h"
55 #include "resources.h"
56 #include "background.h"
57 #include "tilemap.h"
58 #include "gettext.h"
59
60 GameSession* GameSession::current_ = 0;
61
62 GameSession::GameSession(const std::string& levelname_, int mode)
63   : level(0), currentsector(0), st_gl_mode(mode),
64     end_sequence(NO_ENDSEQUENCE), levelname(levelname_)
65 {
66   current_ = this;
67   
68   global_frame_counter = 0;
69   game_pause = false;
70   fps_fps = 0;
71
72   fps_timer.init(true);            
73   frame_timer.init(true);
74
75   context = new DrawingContext();
76
77   restart_level();
78 }
79
80 void
81 GameSession::restart_level()
82 {
83   game_pause   = false;
84   exit_status  = ES_NONE;
85   end_sequence = NO_ENDSEQUENCE;
86
87   fps_timer.init(true);
88   frame_timer.init(true);
89
90 #if 0
91   float old_x_pos = -1;
92   if (world)
93     { // Tux has lost a life, so we try to respawn him at the nearest reset point
94       old_x_pos = world->get_tux()->base.x;
95     }
96 #endif
97   
98   delete level;
99   currentsector = 0;
100
101   level = new Level;
102   level->load(levelname);
103   currentsector = level->get_sector("main");
104   if(!currentsector)
105     st_abort("Level has no main sector.", "");
106   currentsector->activate("main");
107
108 #if 0 // TODO
109   // Set Tux to the nearest reset point
110   if (old_x_pos != -1)
111     {
112       ResetPoint best_reset_point = { -1, -1 };
113       for(std::vector<ResetPoint>::iterator i = get_level()->reset_points.begin();
114           i != get_level()->reset_points.end(); ++i)
115         {
116           if (i->x < old_x_pos && best_reset_point.x < i->x)
117             best_reset_point = *i;
118         }
119       
120       if (best_reset_point.x != -1)
121         {
122           world->get_tux()->base.x = best_reset_point.x;
123           world->get_tux()->base.y = best_reset_point.y;
124         }
125     }
126 #endif
127     
128   if (st_gl_mode != ST_GL_DEMO_GAME)
129     {
130       if(st_gl_mode == ST_GL_PLAY || st_gl_mode == ST_GL_LOAD_LEVEL_FILE)
131         levelintro();
132     }
133
134   time_left.init(true);
135   start_timers();
136   currentsector->play_music(LEVEL_MUSIC);
137 }
138
139 GameSession::~GameSession()
140 {
141   delete level;
142   delete context;
143 }
144
145 void
146 GameSession::levelintro(void)
147 {
148   sound_manager->halt_music();
149   
150   char str[60];
151
152   DrawingContext context;
153   currentsector->background->draw(context);
154
155   context.draw_text_center(gold_text, level->get_name(), Vector(0, 220),
156       LAYER_FOREGROUND1);
157
158   sprintf(str, "TUX x %d", player_status.lives);
159   context.draw_text_center(white_text, str, Vector(0, 240),
160       LAYER_FOREGROUND1);
161   
162   context.draw_text_center(white_small_text,
163       std::string(_("by ")) + level->get_author(), 
164       Vector(0, 400), LAYER_FOREGROUND1);
165
166   context.do_drawing();
167
168   SDL_Event event;
169   wait_for_event(event,1000,3000,true);
170 }
171
172 /* Reset Timers */
173 void
174 GameSession::start_timers()
175 {
176   time_left.start(level->time_left*1000);
177   st_pause_ticks_init();
178   update_time = st_get_ticks();
179 }
180
181 void
182 GameSession::on_escape_press()
183 {
184   if(currentsector->player->dying || end_sequence != NO_ENDSEQUENCE)
185     return;   // don't let the player open the menu, when he is dying
186   
187   if(game_pause)
188     return;
189
190   if(st_gl_mode == ST_GL_TEST)
191     {
192       exit_status = ES_LEVEL_ABORT;
193     }
194   else if (!Menu::current())
195     {
196       /* Tell Tux that the keys are all down, otherwise
197         it could have nasty bugs, like going allways to the right
198         or whatever that key does */
199       Player& tux = *(currentsector->player);
200       tux.key_event((SDLKey)keymap.jump, UP);
201       tux.key_event((SDLKey)keymap.duck, UP);
202       tux.key_event((SDLKey)keymap.left, UP);
203       tux.key_event((SDLKey)keymap.right, UP);
204       tux.key_event((SDLKey)keymap.fire, UP);
205
206       Menu::set_current(game_menu);
207       st_pause_ticks_start();
208     }
209 }
210
211 void
212 GameSession::process_events()
213 {
214   if (end_sequence != NO_ENDSEQUENCE)
215     {
216       Player& tux = *currentsector->player;
217          
218       tux.input.fire  = UP;
219       tux.input.left  = UP;
220       tux.input.right = DOWN;
221       tux.input.down  = UP; 
222
223       if (int(last_x_pos) == int(tux.base.x))
224         tux.input.up    = DOWN; 
225       else
226         tux.input.up    = UP; 
227
228       last_x_pos = tux.base.x;
229
230       SDL_Event event;
231       while (SDL_PollEvent(&event))
232         {
233           /* Check for menu-events, if the menu is shown */
234           if (Menu::current())
235             {
236               Menu::current()->event(event);
237               if(!Menu::current())
238               st_pause_ticks_stop();
239             }
240
241           switch(event.type)
242             {
243             case SDL_QUIT:        /* Quit event - quit: */
244               st_abort("Received window close", "");
245               break;
246               
247             case SDL_KEYDOWN:     /* A keypress! */
248               {
249                 SDLKey key = event.key.keysym.sym;
250            
251                 switch(key)
252                   {
253                   case SDLK_ESCAPE:    /* Escape: Open/Close the menu: */
254                     on_escape_press();
255                     break;
256                   default:
257                     break;
258                   }
259               }
260           
261             case SDL_JOYBUTTONDOWN:
262               if (event.jbutton.button == joystick_keymap.start_button)
263                 on_escape_press();
264               break;
265             }
266         }
267     }
268   else // normal mode
269     {
270       if(!Menu::current() && !game_pause)
271         st_pause_ticks_stop();
272
273       SDL_Event event;
274       while (SDL_PollEvent(&event))
275         {
276           /* Check for menu-events, if the menu is shown */
277           if (Menu::current())
278             {
279               Menu::current()->event(event);
280               if(!Menu::current())
281                 st_pause_ticks_stop();
282             }
283           else
284             {
285               Player& tux = *currentsector->player;
286   
287               switch(event.type)
288                 {
289                 case SDL_QUIT:        /* Quit event - quit: */
290                   st_abort("Received window close", "");
291                   break;
292
293                 case SDL_KEYDOWN:     /* A keypress! */
294                   {
295                     SDLKey key = event.key.keysym.sym;
296             
297                     if(tux.key_event(key,DOWN))
298                       break;
299
300                     switch(key)
301                       {
302                       case SDLK_ESCAPE:    /* Escape: Open/Close the menu: */
303                         on_escape_press();
304                         break;
305                       default:
306                         break;
307                       }
308                   }
309                   break;
310                 case SDL_KEYUP:      /* A keyrelease! */
311                   {
312                     SDLKey key = event.key.keysym.sym;
313
314                     if(tux.key_event(key, UP))
315                       break;
316
317                     switch(key)
318                       {
319                       case SDLK_a:
320                         if(debug_mode)
321                         {
322                           char buf[160];
323                           snprintf(buf, sizeof(buf), "P: %4.1f,%4.1f",
324                               tux.base.x, tux.base.y);
325                           context->draw_text(white_text, buf,
326                               Vector(0, screen->h - white_text->get_height()),
327                               LAYER_FOREGROUND1);
328                           context->do_drawing();
329                           SDL_Delay(1000);
330                         }
331                         break;
332                       case SDLK_p:
333                         if(!Menu::current())
334                           {
335                             if(game_pause)
336                               {
337                                 game_pause = false;
338                                 st_pause_ticks_stop();
339                               }
340                             else
341                               {
342                                 game_pause = true;
343                                 st_pause_ticks_start();
344                               }
345                           }
346                         break;
347                       case SDLK_TAB:
348                         if(debug_mode)
349                           {
350                             tux.grow(false);
351                           }
352                         break;
353                       case SDLK_END:
354                         if(debug_mode)
355                           player_status.distros += 50;
356                         break;
357                       case SDLK_DELETE:
358                         if(debug_mode)
359                           tux.got_power = tux.FIRE_POWER;
360                         break;
361                       case SDLK_HOME:
362                         if(debug_mode)
363                           tux.got_power = tux.ICE_POWER;
364                         break;
365                       case SDLK_INSERT:
366                         if(debug_mode)
367                           tux.invincible_timer.start(TUX_INVINCIBLE_TIME);
368                         break;
369                       case SDLK_l:
370                         if(debug_mode)
371                           --player_status.lives;
372                         break;
373                       case SDLK_s:
374                         if(debug_mode)
375                           player_status.score += 1000;
376                       case SDLK_f:
377                         if(debug_fps)
378                           debug_fps = false;
379                         else
380                           debug_fps = true;
381                         break;
382                       default:
383                         break;
384                       }
385                   }
386                   break;
387
388                 case SDL_JOYAXISMOTION:
389                   if (event.jaxis.axis == joystick_keymap.x_axis)
390                     {
391                       if (event.jaxis.value < -joystick_keymap.dead_zone)
392                         {
393                           tux.input.left  = DOWN;
394                           tux.input.right = UP;
395                         }
396                       else if (event.jaxis.value > joystick_keymap.dead_zone)
397                         {
398                           tux.input.left  = UP;
399                           tux.input.right = DOWN;
400                         }
401                       else
402                         {
403                           tux.input.left  = DOWN;
404                           tux.input.right = DOWN;
405                         }
406                     }
407                   else if (event.jaxis.axis == joystick_keymap.y_axis)
408                     {
409                       if (event.jaxis.value > joystick_keymap.dead_zone)
410                         tux.input.down = DOWN;
411                       else if (event.jaxis.value < -joystick_keymap.dead_zone)
412                         tux.input.down = UP;
413                       else
414                         tux.input.down = UP;
415                     }
416                   break;
417             
418                 case SDL_JOYBUTTONDOWN:
419                   if (event.jbutton.button == joystick_keymap.a_button)
420                     tux.input.up = DOWN;
421                   else if (event.jbutton.button == joystick_keymap.b_button)
422                     tux.input.fire = DOWN;
423                   else if (event.jbutton.button == joystick_keymap.start_button)
424                     on_escape_press();
425                   break;
426                 case SDL_JOYBUTTONUP:
427                   if (event.jbutton.button == joystick_keymap.a_button)
428                     tux.input.up = UP;
429                   else if (event.jbutton.button == joystick_keymap.b_button)
430                     tux.input.fire = UP;
431                   break;
432
433                 default:
434                   break;
435                 }  /* switch */
436             }
437         } /* while */
438     }
439 }
440
441 void
442 GameSession::check_end_conditions()
443 {
444   Player* tux = currentsector->player;
445
446   /* End of level? */
447   Tile* endtile = collision_goal(tux->base);
448
449   if(end_sequence && !endsequence_timer.check())
450     {
451       exit_status = ES_LEVEL_FINISHED;
452       return;
453     }
454   else if(end_sequence == ENDSEQUENCE_RUNNING && endtile && endtile->data >= 1)
455     {
456       end_sequence = ENDSEQUENCE_WAITING;
457     }
458   else if(!end_sequence && endtile && endtile->data == 0)
459     {
460       end_sequence = ENDSEQUENCE_RUNNING;
461       last_x_pos = -1;
462       sound_manager->play_music(level_end_song, 0);
463       endsequence_timer.start(7000); // 5 seconds until we finish the map
464       tux->invincible_timer.start(7000); //FIXME: Implement a winning timer for the end sequence (with special winning animation etc.)
465     }
466   else if (!end_sequence && tux->is_dead())
467     {
468       player_status.bonus = PlayerStatus::NO_BONUS;
469
470       if (player_status.lives < 0)
471         { // No more lives!?
472           exit_status = ES_GAME_OVER;
473         }
474       else
475         { // Still has lives, so reset Tux to the levelstart
476           restart_level();
477         }
478
479       return;
480     }
481 }
482
483 void
484 GameSession::action(double frame_ratio)
485 {
486   if (exit_status == ES_NONE && !currentsector->player->growing_timer.check())
487     {
488       // Update Tux and the World
489       currentsector->action(frame_ratio);
490     }
491
492   // respawning in new sector?
493   if(newsector != "" && newspawnpoint != "") {
494     Sector* sector = level->get_sector(newsector);
495     currentsector = sector;
496     currentsector->activate(newspawnpoint);
497     currentsector->play_music(LEVEL_MUSIC);
498     newsector = newspawnpoint = "";
499   }
500 }
501
502 void 
503 GameSession::draw()
504 {
505   currentsector->draw(*context);
506   drawstatus(*context);
507
508   if(game_pause)
509     {
510       int x = screen->h / 20;
511       for(int i = 0; i < x; ++i)
512         {
513           context->draw_filled_rect(
514               Vector(i % 2 ? (pause_menu_frame * i)%screen->w :
515                 -((pause_menu_frame * i)%screen->w)
516                 ,(i*20+pause_menu_frame)%screen->h),
517               Vector(screen->w,10),
518               Color(20,20,20, rand() % 20 + 1), LAYER_FOREGROUND1+1);
519         }
520       context->draw_filled_rect(
521           Vector(0,0), Vector(screen->w, screen->h),
522           Color(rand() % 50, rand() % 50, rand() % 50, 128), LAYER_FOREGROUND1);
523       context->draw_text_center(blue_text, _("PAUSE - Press 'P' To Play"),
524           Vector(0, 230), LAYER_FOREGROUND1+2);
525     }
526
527   if(Menu::current())
528     {
529       Menu::current()->draw(*context);
530       mouse_cursor->draw(*context);
531     }
532
533   context->do_drawing();
534 }
535
536 void
537 GameSession::process_menu()
538 {
539   Menu* menu = Menu::current();
540   if(menu)
541     {
542       menu->action();
543
544       if(menu == game_menu)
545         {
546           switch (game_menu->check())
547             {
548             case MNID_CONTINUE:
549               st_pause_ticks_stop();
550               break;
551             case MNID_ABORTLEVEL:
552               st_pause_ticks_stop();
553               exit_status = ES_LEVEL_ABORT;
554               break;
555             }
556         }
557       else if(menu == options_menu)
558         {
559           process_options_menu();
560         }
561       else if(menu == load_game_menu )
562         {
563           process_load_game_menu();
564         }
565     }
566 }
567
568 GameSession::ExitStatus
569 GameSession::run()
570 {
571   Menu::set_current(0);
572   current_ = this;
573   
574   int fps_cnt = 0;
575
576   update_time = last_update_time = st_get_ticks();
577
578   // Eat unneeded events
579   SDL_Event event;
580   while (SDL_PollEvent(&event)) {}
581
582   draw();
583
584   while (exit_status == ES_NONE)
585     {
586       /* Calculate the movement-factor */
587       double frame_ratio = ((double)(update_time-last_update_time))/((double)FRAME_RATE);
588
589       if(!frame_timer.check())
590         {
591           frame_timer.start(25);
592           ++global_frame_counter;
593         }
594
595       /* Handle events: */
596       currentsector->player->input.old_fire 
597         = currentsector->player->input.fire;
598
599       process_events();
600       process_menu();
601
602       // Update the world state and all objects in the world
603       // Do that with a constante time-delta so that the game will run
604       // determistic and not different on different machines
605       if(!game_pause && !Menu::current())
606         {
607           // Update the world
608           check_end_conditions();
609           if (end_sequence == ENDSEQUENCE_RUNNING)
610              action(frame_ratio/2);
611           else if(end_sequence == NO_ENDSEQUENCE)
612              action(frame_ratio);
613         }
614       else
615         {
616           ++pause_menu_frame;
617           SDL_Delay(50);
618         }
619
620       draw();
621
622       /* Time stops in pause mode */
623       if(game_pause || Menu::current())
624         {
625           continue;
626         }
627
628       /* Set the time of the last update and the time of the current update */
629       last_update_time = update_time;
630       update_time      = st_get_ticks();
631
632       /* Pause till next frame, if the machine running the game is too fast: */
633       /* FIXME: Works great for in OpenGl mode, where the CPU doesn't have to do that much. But
634          the results in SDL mode aren't perfect (thought the 100 FPS are reached), even on an AMD2500+. */
635       if(last_update_time >= update_time - 12) 
636         {
637           SDL_Delay(10);
638           update_time = st_get_ticks();
639         }
640
641       /* Handle time: */
642       if (!time_left.check() && currentsector->player->dying == DYING_NOT
643               && !end_sequence)
644         currentsector->player->kill(Player::KILL);
645
646       /* Handle music: */
647       if(currentsector->player->invincible_timer.check() && !end_sequence)
648         {
649           currentsector->play_music(HERRING_MUSIC);
650         }
651       /* are we low on time ? */
652       else if (time_left.get_left() < TIME_WARNING && !end_sequence)
653         {
654           currentsector->play_music(HURRYUP_MUSIC);
655         }
656       /* or just normal music? */
657       else if(currentsector->get_music_type() != LEVEL_MUSIC && !end_sequence)
658         {
659           currentsector->play_music(LEVEL_MUSIC);
660         }
661
662       /* Calculate frames per second */
663       if(show_fps)
664         {
665           ++fps_cnt;
666           fps_fps = (1000.0 / (float)fps_timer.get_gone()) * (float)fps_cnt;
667
668           if(!fps_timer.check())
669             {
670               fps_timer.start(1000);
671               fps_cnt = 0;
672             }
673         }
674     }
675   
676   return exit_status;
677 }
678
679 void
680 GameSession::respawn(const std::string& sector, const std::string& spawnpoint)
681 {
682   newsector = sector;
683   newspawnpoint = spawnpoint;
684 }
685
686 /* Bounce a brick: */
687 void bumpbrick(float x, float y)
688 {
689   Sector::current()->add_bouncy_brick(Vector(((int)(x + 1) / 32) * 32,
690                          (int)(y / 32) * 32));
691
692   sound_manager->play_sound(sounds[SND_BRICK], Vector(x, y));
693 }
694
695 /* (Status): */
696 void
697 GameSession::drawstatus(DrawingContext& context)
698 {
699   char str[60];
700   
701   snprintf(str, 60, " %d", player_status.score);
702   context.draw_text(white_text, _("SCORE"), Vector(0, 0), LAYER_FOREGROUND1);
703   context.draw_text(gold_text, str, Vector(96, 0), LAYER_FOREGROUND1);
704
705   if(st_gl_mode == ST_GL_TEST)
706     {
707       context.draw_text(white_text, _("Press ESC To Return"), Vector(0,20),
708           LAYER_FOREGROUND1);
709     }
710
711   if(!time_left.check()) {
712     context.draw_text_center(white_text, _("TIME's UP"), Vector(0, 0),
713         LAYER_FOREGROUND1);
714   } else if (time_left.get_left() > TIME_WARNING || (global_frame_counter % 10) < 5) {
715     sprintf(str, " %d", time_left.get_left() / 1000 );
716     context.draw_text_center(white_text, _("TIME"),
717         Vector(0, 0), LAYER_FOREGROUND1);
718     context.draw_text_center(gold_text, str,
719         Vector(4*16, 0), LAYER_FOREGROUND1);
720   }
721
722   sprintf(str, " %d", player_status.distros);
723   context.draw_text(white_text, _("COINS"),
724       Vector(screen->w - white_text->get_text_width(_("COINS"))-white_text->get_text_width(str), 0),
725         LAYER_FOREGROUND1);
726   context.draw_text(gold_text, str,
727       Vector(screen->w - gold_text->get_text_width(" 99"), 0),LAYER_FOREGROUND1);
728
729   if (player_status.lives >= 5)
730     {
731       sprintf(str, "%dx", player_status.lives);
732       float x = screen->w - gold_text->get_text_width(str) - tux_life->w;
733       context.draw_text(gold_text, str, Vector(x, 20), LAYER_FOREGROUND1);
734       context.draw_surface(tux_life, Vector(screen->w - 16, 20),
735           LAYER_FOREGROUND1);
736     }
737   else
738     {
739       for(int i= 0; i < player_status.lives; ++i)
740         context.draw_surface(tux_life, 
741             Vector(screen->w - tux_life->w*4 +(tux_life->w*i), 20),
742             LAYER_FOREGROUND1);
743     }
744
745   context.draw_text(white_text, _("LIVES"),
746       Vector(screen->w - white_text->get_text_width(_("LIVES")) - white_text->get_text_width("   99"), 20),
747       LAYER_FOREGROUND1);
748
749   if(show_fps)
750     {
751       sprintf(str, "%2.1f", fps_fps);
752       context.draw_text(white_text, "FPS", 
753           Vector(screen->w - white_text->get_text_width("FPS      "), 40),
754           LAYER_FOREGROUND1);
755       context.draw_text(gold_text, str,
756           Vector(screen->w-4*16, 40), LAYER_FOREGROUND1);
757     }
758 }
759
760 void
761 GameSession::drawresultscreen(void)
762 {
763   char str[80];
764
765   DrawingContext context;
766   currentsector->background->draw(context);  
767
768   context.draw_text_center(blue_text, _("Result:"), Vector(0, 200),
769       LAYER_FOREGROUND1);
770
771   sprintf(str, _("SCORE: %d"), player_status.score);
772   context.draw_text_center(gold_text, str, Vector(0, 224), LAYER_FOREGROUND1);
773
774   sprintf(str, _("COINS: %d"), player_status.distros);
775   context.draw_text_center(gold_text, str, Vector(0, 256), LAYER_FOREGROUND1);
776
777   context.do_drawing();
778   
779   SDL_Event event;
780   wait_for_event(event,2000,5000,true);
781 }
782
783 std::string slotinfo(int slot)
784 {
785   char tmp[1024];
786   char slotfile[1024];
787   std::string title;
788   sprintf(slotfile,"%s/slot%d.stsg",st_save_dir,slot);
789
790   lisp_object_t* savegame = lisp_read_from_file(slotfile);
791   if (savegame)
792     {
793       LispReader reader(lisp_cdr(savegame));
794       reader.read_string("title", title);
795       lisp_free(savegame);
796     }
797
798   if (access(slotfile, F_OK) == 0)
799     {
800       if (!title.empty())
801         snprintf(tmp,1024,"Slot %d - %s",slot, title.c_str());
802       else
803         snprintf(tmp, 1024,_("Slot %d - Savegame"),slot);
804     }
805   else
806     sprintf(tmp,_("Slot %d - Free"),slot);
807
808   return tmp;
809 }
810
811