7b7afc9d4caa4695296e8c60ed90b3e2561ceb1e
[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 <assert.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <math.h>
27 #include <string.h>
28 #include <errno.h>
29 #include <unistd.h>
30 #include <math.h>
31 #include <time.h>
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 "music_manager.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   music_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_p:
320                         if(!Menu::current())
321                           {
322                             if(game_pause)
323                               {
324                                 game_pause = false;
325                                 st_pause_ticks_stop();
326                               }
327                             else
328                               {
329                                 game_pause = true;
330                                 st_pause_ticks_start();
331                               }
332                           }
333                         break;
334                       case SDLK_TAB:
335                         if(debug_mode)
336                           {
337                             tux.size = !tux.size;
338                             if(tux.size == BIG)
339                               {
340                                 tux.base.height = 64;
341                               }
342                             else
343                               tux.base.height = 32;
344                           }
345                         break;
346                       case SDLK_END:
347                         if(debug_mode)
348                           player_status.distros += 50;
349                         break;
350                       case SDLK_DELETE:
351                         if(debug_mode)
352                           tux.got_power = tux.FIRE_POWER;
353                         break;
354                       case SDLK_HOME:
355                         if(debug_mode)
356                           tux.got_power = tux.ICE_POWER;
357                         break;
358                       case SDLK_INSERT:
359                         if(debug_mode)
360                           tux.invincible_timer.start(TUX_INVINCIBLE_TIME);
361                         break;
362                       case SDLK_l:
363                         if(debug_mode)
364                           --player_status.lives;
365                         break;
366                       case SDLK_s:
367                         if(debug_mode)
368                           player_status.score += 1000;
369                       case SDLK_f:
370                         if(debug_fps)
371                           debug_fps = false;
372                         else
373                           debug_fps = true;
374                         break;
375                       default:
376                         break;
377                       }
378                   }
379                   break;
380
381                 case SDL_JOYAXISMOTION:
382                   if (event.jaxis.axis == joystick_keymap.x_axis)
383                     {
384                       if (event.jaxis.value < -joystick_keymap.dead_zone)
385                         {
386                           tux.input.left  = DOWN;
387                           tux.input.right = UP;
388                         }
389                       else if (event.jaxis.value > joystick_keymap.dead_zone)
390                         {
391                           tux.input.left  = UP;
392                           tux.input.right = DOWN;
393                         }
394                       else
395                         {
396                           tux.input.left  = DOWN;
397                           tux.input.right = DOWN;
398                         }
399                     }
400                   else if (event.jaxis.axis == joystick_keymap.y_axis)
401                     {
402                       if (event.jaxis.value > joystick_keymap.dead_zone)
403                         tux.input.down = DOWN;
404                       else if (event.jaxis.value < -joystick_keymap.dead_zone)
405                         tux.input.down = UP;
406                       else
407                         tux.input.down = UP;
408                     }
409                   break;
410             
411                 case SDL_JOYBUTTONDOWN:
412                   if (event.jbutton.button == joystick_keymap.a_button)
413                     tux.input.up = DOWN;
414                   else if (event.jbutton.button == joystick_keymap.b_button)
415                     tux.input.fire = DOWN;
416                   else if (event.jbutton.button == joystick_keymap.start_button)
417                     on_escape_press();
418                   break;
419                 case SDL_JOYBUTTONUP:
420                   if (event.jbutton.button == joystick_keymap.a_button)
421                     tux.input.up = UP;
422                   else if (event.jbutton.button == joystick_keymap.b_button)
423                     tux.input.fire = UP;
424                   break;
425
426                 default:
427                   break;
428                 }  /* switch */
429             }
430         } /* while */
431     }
432 }
433
434 void
435 GameSession::check_end_conditions()
436 {
437   Player* tux = currentsector->player;
438
439   /* End of level? */
440   int endpos = (currentsector->solids->get_width() - 5) * 32;
441   Tile* endtile = collision_goal(tux->base);
442
443   // fallback in case the other endpositions don't trigger
444   if (!end_sequence && tux->base.x >= endpos)
445     {
446       end_sequence = ENDSEQUENCE_WAITING;
447       last_x_pos = -1;
448       music_manager->play_music(level_end_song, 0);
449       endsequence_timer.start(7000);
450       tux->invincible_timer.start(7000); //FIXME: Implement a winning timer for the end sequence (with special winning animation etc.)
451     }
452   else if(end_sequence && !endsequence_timer.check())
453     {
454       exit_status = ES_LEVEL_FINISHED;
455       return;
456     }
457   else if(end_sequence == ENDSEQUENCE_RUNNING && endtile && endtile->data >= 1)
458     {
459       end_sequence = ENDSEQUENCE_WAITING;
460     }
461   else if(!end_sequence && endtile && endtile->data == 0)
462     {
463       end_sequence = ENDSEQUENCE_RUNNING;
464       last_x_pos = -1;
465       music_manager->play_music(level_end_song, 0);
466       endsequence_timer.start(7000); // 5 seconds until we finish the map
467       tux->invincible_timer.start(7000); //FIXME: Implement a winning timer for the end sequence (with special winning animation etc.)
468     }
469   else if (!end_sequence && tux->is_dead())
470     {
471       player_status.bonus = PlayerStatus::NO_BONUS;
472
473       if (player_status.lives < 0)
474         { // No more lives!?
475           exit_status = ES_GAME_OVER;
476         }
477       else
478         { // Still has lives, so reset Tux to the levelstart
479           restart_level();
480         }
481
482       return;
483     }
484 }
485
486 void
487 GameSession::action(double frame_ratio)
488 {
489   if (exit_status == ES_NONE && !currentsector->player->growing_timer.check())
490     {
491       // Update Tux and the World
492       currentsector->action(frame_ratio);
493     }
494 }
495
496 void 
497 GameSession::draw()
498 {
499   currentsector->draw(*context);
500   drawstatus(*context);
501
502   if(game_pause)
503     {
504       int x = screen->h / 20;
505       for(int i = 0; i < x; ++i)
506         {
507           context->draw_filled_rect(
508               Vector(i % 2 ? (pause_menu_frame * i)%screen->w :
509                 -((pause_menu_frame * i)%screen->w)
510                 ,(i*20+pause_menu_frame)%screen->h),
511               Vector(screen->w,10),
512               Color(20,20,20, rand() % 20 + 1), LAYER_FOREGROUND1+1);
513         }
514       context->draw_filled_rect(
515           Vector(0,0), Vector(screen->w, screen->h),
516           Color(rand() % 50, rand() % 50, rand() % 50, 128), LAYER_FOREGROUND1);
517       context->draw_text_center(blue_text, "PAUSE - Press 'P' To Play",
518           Vector(0, 230), LAYER_FOREGROUND1+2);
519     }
520
521   if(Menu::current())
522     {
523       Menu::current()->draw(*context);
524       mouse_cursor->draw(*context);
525     }
526
527   context->do_drawing();
528 }
529
530 void
531 GameSession::process_menu()
532 {
533   Menu* menu = Menu::current();
534   if(menu)
535     {
536       menu->action();
537
538       if(menu == game_menu)
539         {
540           switch (game_menu->check())
541             {
542             case MNID_CONTINUE:
543               st_pause_ticks_stop();
544               break;
545             case MNID_ABORTLEVEL:
546               st_pause_ticks_stop();
547               exit_status = ES_LEVEL_ABORT;
548               break;
549             }
550         }
551       else if(menu == options_menu)
552         {
553           process_options_menu();
554         }
555       else if(menu == load_game_menu )
556         {
557           process_load_game_menu();
558         }
559     }
560 }
561
562 GameSession::ExitStatus
563 GameSession::run()
564 {
565   Menu::set_current(0);
566   current_ = this;
567   
568   int fps_cnt = 0;
569
570   update_time = last_update_time = st_get_ticks();
571
572   // Eat unneeded events
573   SDL_Event event;
574   while (SDL_PollEvent(&event)) {}
575
576   draw();
577
578   while (exit_status == ES_NONE)
579     {
580       /* Calculate the movement-factor */
581       double frame_ratio = ((double)(update_time-last_update_time))/((double)FRAME_RATE);
582
583       if(!frame_timer.check())
584         {
585           frame_timer.start(25);
586           ++global_frame_counter;
587         }
588
589       /* Handle events: */
590       currentsector->player->input.old_fire 
591         = currentsector->player->input.fire;
592
593       process_events();
594       process_menu();
595
596       // Update the world state and all objects in the world
597       // Do that with a constante time-delta so that the game will run
598       // determistic and not different on different machines
599       if(!game_pause && !Menu::current())
600         {
601           // Update the world
602           check_end_conditions();
603           if (end_sequence == ENDSEQUENCE_RUNNING)
604              action(frame_ratio/2);
605           else if(end_sequence == NO_ENDSEQUENCE)
606              action(frame_ratio);
607         }
608       else
609         {
610           ++pause_menu_frame;
611           SDL_Delay(50);
612         }
613
614       draw();
615
616       /* Time stops in pause mode */
617       if(game_pause || Menu::current())
618         {
619           continue;
620         }
621
622       /* Set the time of the last update and the time of the current update */
623       last_update_time = update_time;
624       update_time      = st_get_ticks();
625
626       /* Pause till next frame, if the machine running the game is too fast: */
627       /* FIXME: Works great for in OpenGl mode, where the CPU doesn't have to do that much. But
628          the results in SDL mode aren't perfect (thought the 100 FPS are reached), even on an AMD2500+. */
629       if(last_update_time >= update_time - 12) 
630         {
631           SDL_Delay(10);
632           update_time = st_get_ticks();
633         }
634
635       /* Handle time: */
636       if (!time_left.check() && currentsector->player->dying == DYING_NOT
637               && !end_sequence)
638         currentsector->player->kill(Player::KILL);
639
640       /* Handle music: */
641       if(currentsector->player->invincible_timer.check() && !end_sequence)
642         {
643           currentsector->play_music(HERRING_MUSIC);
644         }
645       /* are we low on time ? */
646       else if (time_left.get_left() < TIME_WARNING && !end_sequence)
647         {
648           currentsector->play_music(HURRYUP_MUSIC);
649         }
650       /* or just normal music? */
651       else if(currentsector->get_music_type() != LEVEL_MUSIC && !end_sequence)
652         {
653           currentsector->play_music(LEVEL_MUSIC);
654         }
655
656       /* Calculate frames per second */
657       if(show_fps)
658         {
659           ++fps_cnt;
660           fps_fps = (1000.0 / (float)fps_timer.get_gone()) * (float)fps_cnt;
661
662           if(!fps_timer.check())
663             {
664               fps_timer.start(1000);
665               fps_cnt = 0;
666             }
667         }
668     }
669   
670   return exit_status;
671 }
672
673 /* Bounce a brick: */
674 void bumpbrick(float x, float y)
675 {
676   Sector::current()->add_bouncy_brick(Vector(((int)(x + 1) / 32) * 32,
677                          (int)(y / 32) * 32));
678
679   play_sound(sounds[SND_BRICK], SOUND_CENTER_SPEAKER);
680 }
681
682 /* (Status): */
683 void
684 GameSession::drawstatus(DrawingContext& context)
685 {
686   char str[60];
687   
688   snprintf(str, 60, "%d", player_status.score);
689   context.draw_text(white_text, "SCORE", Vector(0, 0), LAYER_FOREGROUND1);
690   context.draw_text(gold_text, str, Vector(96, 0), LAYER_FOREGROUND1);
691
692   if(st_gl_mode == ST_GL_TEST)
693     {
694       context.draw_text(white_text, "Press ESC To Return", Vector(0,20),
695           LAYER_FOREGROUND1);
696     }
697
698   if(!time_left.check()) {
699     context.draw_text_center(white_text, "TIME's UP", Vector(0, 0),
700         LAYER_FOREGROUND1);
701   } else if (time_left.get_left() > TIME_WARNING || (global_frame_counter % 10) < 5) {
702     sprintf(str, "%d", time_left.get_left() / 1000 );
703     context.draw_text_center(white_text, "TIME",
704         Vector(0, 0), LAYER_FOREGROUND1);
705     context.draw_text_center(gold_text, str,
706         Vector(4*16, 0), LAYER_FOREGROUND1);
707   }
708
709   sprintf(str, "%d", player_status.distros);
710   context.draw_text(white_text, "COINS",
711       Vector(screen->w - white_text->w*9, 0), LAYER_FOREGROUND1);
712   context.draw_text(gold_text, str,
713       Vector(screen->w - gold_text->w*2, 0), LAYER_FOREGROUND1);
714
715   context.draw_text(white_text, "LIVES",
716       Vector(screen->w - white_text->w*9, 20), LAYER_FOREGROUND1);
717   if (player_status.lives >= 5)
718     {
719       sprintf(str, "%dx", player_status.lives);
720       float x = screen->w - gold_text->get_text_width(str) - tux_life->w;
721       context.draw_text(gold_text, str, Vector(x, 20), LAYER_FOREGROUND1);
722       context.draw_surface(tux_life, Vector(screen->w - 16, 20),
723           LAYER_FOREGROUND1);
724     }
725   else
726     {
727       for(int i= 0; i < player_status.lives; ++i)
728         context.draw_surface(tux_life, 
729             Vector(screen->w - tux_life->w*4 +(tux_life->w*i), 20),
730             LAYER_FOREGROUND1);
731     }
732
733   if(show_fps)
734     {
735       sprintf(str, "%2.1f", fps_fps);
736       context.draw_text(white_text, "FPS", 
737           Vector(screen->w - white_text->w*9, 40), LAYER_FOREGROUND1);
738       context.draw_text(gold_text, str,
739           Vector(screen->w-4*16, 40), LAYER_FOREGROUND1);
740     }
741 }
742
743 void
744 GameSession::drawresultscreen(void)
745 {
746   char str[80];
747
748   DrawingContext context;
749   currentsector->background->draw(context);  
750
751   context.draw_text_center(blue_text, "Result:", Vector(0, 200),
752       LAYER_FOREGROUND1);
753
754   sprintf(str, "SCORE: %d", player_status.score);
755   context.draw_text_center(gold_text, str, Vector(0, 224), LAYER_FOREGROUND1);
756
757   sprintf(str, "COINS: %d", player_status.distros);
758   context.draw_text_center(gold_text, str, Vector(0, 256), LAYER_FOREGROUND1);
759
760   context.do_drawing();
761   
762   SDL_Event event;
763   wait_for_event(event,2000,5000,true);
764 }
765
766 std::string slotinfo(int slot)
767 {
768   char tmp[1024];
769   char slotfile[1024];
770   std::string title;
771   sprintf(slotfile,"%s/slot%d.stsg",st_save_dir,slot);
772
773   lisp_object_t* savegame = lisp_read_from_file(slotfile);
774   if (savegame)
775     {
776       LispReader reader(lisp_cdr(savegame));
777       reader.read_string("title", title);
778       lisp_free(savegame);
779     }
780
781   if (access(slotfile, F_OK) == 0)
782     {
783       if (!title.empty())
784         snprintf(tmp,1024,"Slot %d - %s",slot, title.c_str());
785       else
786         snprintf(tmp, 1024,"Slot %d - Savegame",slot);
787     }
788   else
789     sprintf(tmp,"Slot %d - Free",slot);
790
791   return tmp;
792 }
793
794