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