- changed/clean up level end detection a bit, still not really as it should be
[supertux.git] / src / gameloop.cpp
1 /*
2   gameloop.c
3   
4   Super Tux - Game Loop!
5   
6   by Bill Kendrick & Tobias Glaesser
7   bill@newbreedsoftware.com
8   http://www.newbreedsoftware.com/supertux/
9   
10   April 11, 2000 - March 15, 2004
11 */
12
13 #include <assert.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <math.h>
17 #include <string.h>
18 #include <errno.h>
19 #include <unistd.h>
20 #include <math.h>
21 #include <time.h>
22 #include <SDL.h>
23
24 #ifndef WIN32
25 #include <sys/types.h>
26 #include <ctype.h>
27 #endif
28
29 #include "defines.h"
30 #include "globals.h"
31 #include "gameloop.h"
32 #include "screen.h"
33 #include "setup.h"
34 #include "high_scores.h"
35 #include "menu.h"
36 #include "badguy.h"
37 #include "world.h"
38 #include "special.h"
39 #include "player.h"
40 #include "level.h"
41 #include "scene.h"
42 #include "collision.h"
43 #include "tile.h"
44 #include "particlesystem.h"
45 #include "resources.h"
46
47 GameSession* GameSession::current_ = 0;
48
49 GameSession::GameSession(const std::string& subset_, int levelnb_, int mode)
50   : world(0), st_gl_mode(mode), levelnb(levelnb_), subset(subset_)
51 {
52   current_ = this;
53   restart_level();
54 }
55
56 void
57 GameSession::restart_level()
58 {
59   game_pause = false;
60   exit_status = NONE;
61
62   fps_timer.init(true);
63   frame_timer.init(true);
64
65   if (st_gl_mode == ST_GL_LOAD_LEVEL_FILE)
66     {
67       world = new World(subset);
68     }
69   else if (st_gl_mode == ST_GL_DEMO_GAME)
70     {
71       world = new World(subset);
72     }
73   else
74     {
75       world = new World(subset, levelnb);
76     }
77     
78   if (st_gl_mode != ST_GL_DEMO_GAME)
79     {
80       if(st_gl_mode != ST_GL_TEST)
81         load_hs();
82
83       if(st_gl_mode == ST_GL_PLAY || st_gl_mode == ST_GL_LOAD_LEVEL_FILE)
84         levelintro();
85     }
86
87   time_left.init(true);
88   start_timers(); 
89 }
90
91 GameSession::~GameSession()
92 {
93   delete world;
94 }
95
96 void
97 GameSession::levelintro(void)
98 {
99   char str[60];
100   /* Level Intro: */
101   clearscreen(0, 0, 0);
102
103   sprintf(str, "%s", world->get_level()->name.c_str());
104   gold_text->drawf(str, 0, 200, A_HMIDDLE, A_TOP, 1);
105
106   sprintf(str, "TUX x %d", player_status.lives);
107   white_text->drawf(str, 0, 224, A_HMIDDLE, A_TOP, 1);
108   
109   sprintf(str, "by %s", world->get_level()->author.c_str());
110   white_small_text->drawf(str, 0, 400, A_HMIDDLE, A_TOP, 1);
111   
112
113   flipscreen();
114
115   SDL_Event event;
116   wait_for_event(event,1000,3000,true);
117 }
118
119 /* Reset Timers */
120 void
121 GameSession::start_timers()
122 {
123   time_left.start(world->get_level()->time_left*1000);
124   st_pause_ticks_init();
125   update_time = st_get_ticks();
126 }
127
128 void
129 GameSession::process_events()
130 {
131   Player& tux = *world->get_tux();
132
133   SDL_Event event;
134   while (SDL_PollEvent(&event))
135     {
136       /* Check for menu-events, if the menu is shown */
137       if(show_menu)
138         current_menu->event(event);
139
140       switch(event.type)
141         {
142         case SDL_QUIT:        /* Quit event - quit: */
143           st_abort("Received window close", "");
144           break;
145
146         case SDL_KEYDOWN:     /* A keypress! */
147           {
148             SDLKey key = event.key.keysym.sym;
149             
150             if(tux.key_event(key,DOWN))
151               break;
152
153             switch(key)
154               {
155               case SDLK_ESCAPE:    /* Escape: Open/Close the menu: */
156                 if(!game_pause)
157                   {
158                     if(st_gl_mode == ST_GL_TEST)
159                       {
160                         exit_status = LEVEL_ABORT;
161                       }
162                     else if(show_menu)
163                       {
164                         Menu::set_current(game_menu);
165                         show_menu = 0;
166                         st_pause_ticks_stop();
167                       }
168                     else
169                       {
170                         Menu::set_current(game_menu);
171                         show_menu = 1;
172                         st_pause_ticks_start();
173                       }
174                   }
175                 break;
176               default:
177                 break;
178               }
179           }
180           break;
181         case SDL_KEYUP:      /* A keyrelease! */
182           {
183             SDLKey key = event.key.keysym.sym;
184
185             if(tux.key_event(key, UP))
186               break;
187
188             switch(key)
189               {
190               case SDLK_p:
191                 if(!show_menu)
192                   {
193                     if(game_pause)
194                       {
195                         game_pause = false;
196                         st_pause_ticks_stop();
197                       }
198                     else
199                       {
200                         game_pause = true;
201                         st_pause_ticks_start();
202                       }
203                   }
204                 break;
205               case SDLK_TAB:
206                 if(debug_mode)
207                   {
208                     tux.size = !tux.size;
209                     if(tux.size == BIG)
210                       {
211                         tux.base.height = 64;
212                       }
213                     else
214                       tux.base.height = 32;
215                   }
216                 break;
217               case SDLK_END:
218                 if(debug_mode)
219                   player_status.distros += 50;
220                 break;
221               case SDLK_DELETE:
222                 if(debug_mode)
223                   tux.got_coffee = 1;
224                 break;
225               case SDLK_INSERT:
226                 if(debug_mode)
227                   tux.invincible_timer.start(TUX_INVINCIBLE_TIME);
228                 break;
229               case SDLK_l:
230                 if(debug_mode)
231                   --player_status.lives;
232                 break;
233               case SDLK_s:
234                 if(debug_mode)
235                   player_status.score += 1000;
236               case SDLK_f:
237                 if(debug_fps)
238                   debug_fps = false;
239                 else
240                   debug_fps = true;
241                 break;
242               default:
243                 break;
244               }
245           }
246           break;
247
248         case SDL_JOYAXISMOTION:
249           switch(event.jaxis.axis)
250             {
251             case JOY_X:
252               if (event.jaxis.value < -JOYSTICK_DEAD_ZONE)
253                 {
254                   tux.input.left  = DOWN;
255                   tux.input.right = UP;
256                 }
257               else if (event.jaxis.value > JOYSTICK_DEAD_ZONE)
258                 {
259                   tux.input.left  = UP;
260                   tux.input.right = DOWN;
261                 }
262               else
263                 {
264                   tux.input.left  = DOWN;
265                   tux.input.right = DOWN;
266                 }
267               break;
268             case JOY_Y:
269               if (event.jaxis.value > JOYSTICK_DEAD_ZONE)
270                 tux.input.down = DOWN;
271               else if (event.jaxis.value < -JOYSTICK_DEAD_ZONE)
272                 tux.input.down = UP;
273               else
274                 tux.input.down = UP;
275               
276               break;
277             default:
278               break;
279             }
280           break;
281         case SDL_JOYBUTTONDOWN:
282           if (event.jbutton.button == JOY_A)
283             tux.input.up = DOWN;
284           else if (event.jbutton.button == JOY_B)
285             tux.input.fire = DOWN;
286           break;
287         case SDL_JOYBUTTONUP:
288           if (event.jbutton.button == JOY_A)
289             tux.input.up = UP;
290           else if (event.jbutton.button == JOY_B)
291             tux.input.fire = UP;
292             
293           break;
294
295         default:
296           break;
297
298         }  /* switch */
299
300     } /* while */
301 }
302
303
304 void
305 GameSession::check_end_conditions()
306 {
307   Player* tux = world->get_tux();
308
309   /* End of level? */
310   if (tux->base.x >= World::current()->get_level()->endpos
311       && World::current()->get_level()->endpos != 0)
312     {
313       exit_status = LEVEL_FINISHED;
314     }
315   else
316     {
317       // Check End conditions
318       if (tux->is_dead())
319         {
320           
321           if (player_status.lives < 0)
322             { // No more lives!?
323               if(st_gl_mode != ST_GL_TEST)
324                 drawendscreen();
325           
326               if(st_gl_mode != ST_GL_TEST)
327                 {
328                   if (player_status.score > hs_score)
329                     save_hs(player_status.score);
330                 }
331               
332               exit_status = GAME_OVER;
333             }
334           else
335             { // Still has lives, so reset Tux to the levelstart
336               restart_level();
337             }
338         }
339     } 
340 }
341
342 void
343 GameSession::action(double frame_ratio)
344 {
345   check_end_conditions();
346   
347   if (exit_status == NONE)
348     {
349       Player* tux = world->get_tux();
350       
351       // Update Tux and the World
352       tux->action(frame_ratio);
353       world->action(frame_ratio);
354     }
355 }
356
357 void 
358 GameSession::draw()
359 {
360   world->draw();
361   drawstatus();
362
363   if(game_pause)
364     {
365       int x = screen->h / 20;
366       for(int i = 0; i < x; ++i)
367         {
368           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);
369         }
370       fillrect(0,0,screen->w,screen->h,rand() % 50, rand() % 50, rand() % 50, 128);
371       blue_text->drawf("PAUSE - Press 'P' To Play", 0, 230, A_HMIDDLE, A_TOP, 1);
372     }
373
374   if(show_menu)
375     {
376       menu_process_current();
377       mouse_cursor->draw();
378     }
379
380   updatescreen();
381 }
382
383
384 GameSession::ExitStatus
385 GameSession::run()
386 {
387   Player* tux = world->get_tux();
388   current_ = this;
389   
390   int  fps_cnt;
391
392   global_frame_counter = 0;
393   game_pause = false;
394
395   fps_timer.init(true);
396   frame_timer.init(true);
397
398   last_update_time = st_get_ticks();
399   fps_cnt = 0;
400
401   /* Clear screen: */
402   clearscreen(0, 0, 0);
403   updatescreen();
404
405   /* Play music: */
406   play_current_music();
407
408   // Eat unneeded events
409   SDL_Event event;
410   while (SDL_PollEvent(&event)) {}
411
412   draw();
413
414   while (exit_status == NONE)
415     {
416       /* Calculate the movement-factor */
417       double frame_ratio = ((double)(update_time-last_update_time))/((double)FRAME_RATE);
418       if(frame_ratio > 1.5) /* Quick hack to correct the unprecise CPU clocks a little bit. */
419         frame_ratio = 1.5 + (frame_ratio - 1.5) * 0.85;
420
421       if(!frame_timer.check())
422         {
423           frame_timer.start(25);
424           ++global_frame_counter;
425         }
426
427       /* Handle events: */
428       tux->input.old_fire = tux->input.fire;
429
430       process_events();
431
432       if(show_menu)
433         {
434           if(current_menu == game_menu)
435             {
436               switch (game_menu->check())
437                 {
438                 case 2:
439                   st_pause_ticks_stop();
440                   break;
441                 case 3:
442                   // FIXME:
443                   //update_load_save_game_menu(save_game_menu);
444                   break;
445                 case 4:
446                   update_load_save_game_menu(load_game_menu);
447                   break;
448                 case 7:
449                   st_pause_ticks_stop();
450                   exit_status = LEVEL_ABORT;
451                   break;
452                 }
453             }
454           else if(current_menu == options_menu)
455             {
456               process_options_menu();
457             }
458           else if(current_menu == load_game_menu )
459             {
460               process_load_game_menu();
461             }
462         }
463       
464       // Handle actions:
465       if(!game_pause && !show_menu)
466         {
467           action(frame_ratio);
468           if (exit_status != NONE)
469             return exit_status;
470         }
471       else
472         {
473           ++pause_menu_frame;
474           SDL_Delay(50);
475         }
476
477       if(debug_mode && debug_fps)
478         SDL_Delay(60);
479
480       /*Draw the current scene to the screen */
481       /*If the machine running the game is too slow
482         skip the drawing of the frame (so the calculations are more precise and
483         the FPS aren't affected).*/
484       /*if( ! fps_fps < 50.0 )
485         game_draw();
486         else
487         jump = true;*/ /*FIXME: Implement this tweak right.*/
488       draw();
489
490       /* Time stops in pause mode */
491       if(game_pause || show_menu )
492         {
493           continue;
494         }
495
496       /* Set the time of the last update and the time of the current update */
497       last_update_time = update_time;
498       update_time = st_get_ticks();
499
500       /* Pause till next frame, if the machine running the game is too fast: */
501       /* FIXME: Works great for in OpenGl mode, where the CPU doesn't have to do that much. But
502          the results in SDL mode aren't perfect (thought the 100 FPS are reached), even on an AMD2500+. */
503       if(last_update_time >= update_time - 12) {
504         SDL_Delay(10);
505         update_time = st_get_ticks();
506       }
507       /*if((update_time - last_update_time) < 10)
508         SDL_Delay((11 - (update_time - last_update_time))/2);*/
509
510       /* Handle time: */
511       if (time_left.check())
512         {
513           /* are we low on time ? */
514           if (time_left.get_left() < TIME_WARNING
515               && (get_current_music() != HURRYUP_MUSIC)) /* play the fast music */
516             {
517               set_current_music(HURRYUP_MUSIC);
518               play_current_music();
519             }
520         }
521       else if(tux->dying == DYING_NOT)
522         tux->kill(KILL);
523
524       /* Calculate frames per second */
525       if(show_fps)
526         {
527           ++fps_cnt;
528           fps_fps = (1000.0 / (float)fps_timer.get_gone()) * (float)fps_cnt;
529
530           if(!fps_timer.check())
531             {
532               fps_timer.start(1000);
533               fps_cnt = 0;
534             }
535         }
536     }
537
538   halt_music();
539
540   world->get_level()->free_gfx();
541   world->get_level()->cleanup();
542   world->get_level()->free_song();
543
544   return exit_status;
545 }
546
547 /* Bounce a brick: */
548 void bumpbrick(float x, float y)
549 {
550   World::current()->add_bouncy_brick(((int)(x + 1) / 32) * 32,
551                          (int)(y / 32) * 32);
552
553   play_sound(sounds[SND_BRICK], SOUND_CENTER_SPEAKER);
554 }
555
556 /* (Status): */
557 void
558 GameSession::drawstatus()
559 {
560   char str[60];
561
562   sprintf(str, "%d", player_status.score);
563   white_text->draw("SCORE", 0, 0, 1);
564   gold_text->draw(str, 96, 0, 1);
565
566   if(st_gl_mode != ST_GL_TEST)
567     {
568       sprintf(str, "%d", hs_score);
569       white_text->draw("HIGH", 0, 20, 1);
570       gold_text->draw(str, 96, 20, 1);
571     }
572   else
573     {
574       white_text->draw("Press ESC To Return",0,20,1);
575     }
576
577   if (time_left.get_left() > TIME_WARNING || (global_frame_counter % 10) < 5)
578     {
579       sprintf(str, "%d", time_left.get_left() / 1000 );
580       white_text->draw("TIME", 224, 0, 1);
581       gold_text->draw(str, 304, 0, 1);
582     }
583
584   sprintf(str, "%d", player_status.distros);
585   white_text->draw("DISTROS", screen->h, 0, 1);
586   gold_text->draw(str, 608, 0, 1);
587
588   white_text->draw("LIVES", screen->h, 20, 1);
589
590   if(show_fps)
591     {
592       sprintf(str, "%2.1f", fps_fps);
593       white_text->draw("FPS", screen->h, 40, 1);
594       gold_text->draw(str, screen->h + 60, 40, 1);
595     }
596
597   for(int i= 0; i < player_status.lives; ++i)
598     {
599       tux_life->draw(565+(18*i),20);
600     }
601 }
602
603 void
604 GameSession::drawendscreen()
605 {
606   char str[80];
607
608   clearscreen(0, 0, 0);
609
610   blue_text->drawf("GAMEOVER", 0, 200, A_HMIDDLE, A_TOP, 1);
611
612   sprintf(str, "SCORE: %d", player_status.score);
613   gold_text->drawf(str, 0, 224, A_HMIDDLE, A_TOP, 1);
614
615   sprintf(str, "COINS: %d", player_status.distros);
616   gold_text->drawf(str, 0, 256, A_HMIDDLE, A_TOP, 1);
617
618   flipscreen();
619   
620   SDL_Event event;
621   wait_for_event(event,2000,5000,true);
622 }
623
624 void
625 GameSession::drawresultscreen(void)
626 {
627   char str[80];
628
629   clearscreen(0, 0, 0);
630
631   blue_text->drawf("Result:", 0, 200, A_HMIDDLE, A_TOP, 1);
632
633   sprintf(str, "SCORE: %d", player_status.score);
634   gold_text->drawf(str, 0, 224, A_HMIDDLE, A_TOP, 1);
635
636   sprintf(str, "DISTROS: %d", player_status.distros);
637   gold_text->drawf(str, 0, 256, A_HMIDDLE, A_TOP, 1);
638
639   flipscreen();
640   
641   SDL_Event event;
642   wait_for_event(event,2000,5000,true);
643 }
644
645 std::string slotinfo(int slot)
646 {
647   char tmp[1024];
648   char slotfile[1024];
649   sprintf(slotfile,"%s/slot%d.stsg",st_save_dir,slot);
650
651   if (access(slotfile, F_OK) == 0)
652     sprintf(tmp,"Slot %d - Savegame",slot);
653   else
654     sprintf(tmp,"Slot %d - Free",slot);
655
656   return tmp;
657 }
658
659