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