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