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