started to convert timer into a class
[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 void
50 GameSession::init()
51 {
52   game_pause = false;
53 }
54
55 GameSession::GameSession()
56 {
57   current_ = this;
58   assert(0);
59 }
60
61 GameSession::GameSession(const std::string& filename)
62 {
63   init();
64
65   //assert(!"Don't call me");
66   current_ = this;
67
68   world = new World; // &::global_world;
69
70   fps_timer.init(true);
71   frame_timer.init(true);
72
73   world->load(filename);
74 }
75
76 GameSession::GameSession(const std::string& subset_, int levelnb_, int mode)
77   : subset(subset_),
78     levelnb(levelnb_)
79 {
80   init();
81
82   current_ = this;
83
84   world = new World; // &::global_world;
85
86   fps_timer.init(true);
87   frame_timer.init(true);
88
89   st_gl_mode = mode;
90   
91   /* Init the game: */
92   world->arrays_free();
93   world->set_defaults();
94
95   if (st_gl_mode == ST_GL_LOAD_LEVEL_FILE)
96     {
97       if (world->load(subset))
98         exit(1);
99     }
100   else
101     {
102       if(world->load(subset, levelnb) != 0)
103         exit(1);
104     }
105
106   world->get_level()->load_gfx();
107   loadshared();
108   
109   world->activate_bad_guys();
110   world->activate_particle_systems();
111   world->get_level()->load_song();
112
113   if(st_gl_mode != ST_GL_TEST)
114     load_hs();
115
116   if(st_gl_mode == ST_GL_PLAY || st_gl_mode == ST_GL_LOAD_LEVEL_FILE)
117     levelintro();
118
119   time_left.init(true);
120   start_timers();
121
122   if(st_gl_mode == ST_GL_LOAD_GAME)
123     loadgame(levelnb);
124 }
125
126 GameSession::~GameSession()
127 {
128   delete world;
129 }
130
131 void
132 GameSession::levelintro(void)
133 {
134   Player& tux = *world->get_tux();
135
136   char str[60];
137   /* Level Intro: */
138   clearscreen(0, 0, 0);
139
140   sprintf(str, "LEVEL %d", levelnb);
141   text_drawf(&blue_text, str, 0, 200, A_HMIDDLE, A_TOP, 1);
142
143   sprintf(str, "%s", world->get_level()->name.c_str());
144   text_drawf(&gold_text, str, 0, 224, A_HMIDDLE, A_TOP, 1);
145
146   sprintf(str, "TUX x %d", tux.lives);
147   text_drawf(&white_text, str, 0, 256, A_HMIDDLE, A_TOP, 1);
148
149   flipscreen();
150
151   SDL_Event event;
152   wait_for_event(event,1000,3000,true);
153 }
154
155 /* Reset Timers */
156 void
157 GameSession::start_timers()
158 {
159   time_left.start(world->get_level()->time_left*1000);
160   st_pause_ticks_init();
161   update_time = st_get_ticks();
162 }
163
164 void
165 GameSession::process_events()
166 {
167   Player& tux = *world->get_tux();
168
169   SDL_Event event;
170   while (SDL_PollEvent(&event))
171     {
172       /* Check for menu-events, if the menu is shown */
173       if(show_menu)
174         menu_event(event);
175
176       switch(event.type)
177         {
178         case SDL_QUIT:        /* Quit event - quit: */
179           quit = true;
180           break;
181         case SDL_KEYDOWN:     /* A keypress! */
182           {
183             SDLKey key = event.key.keysym.sym;
184             
185             if(tux.key_event(key,DOWN))
186               break;
187
188             switch(key)
189               {
190               case SDLK_ESCAPE:    /* Escape: Open/Close the menu: */
191                 if(!game_pause)
192                   {
193                     if(st_gl_mode == ST_GL_TEST)
194                       quit = true;
195                     else if(show_menu)
196                       {
197                         Menu::set_current(game_menu);
198                         show_menu = 0;
199                         st_pause_ticks_stop();
200                       }
201                     else
202                       {
203                         Menu::set_current(game_menu);
204                         show_menu = 1;
205                         st_pause_ticks_start();
206                       }
207                   }
208                 break;
209               default:
210                 break;
211               }
212           }
213           break;
214         case SDL_KEYUP:      /* A keyrelease! */
215           {
216             SDLKey key = event.key.keysym.sym;
217
218             if(tux.key_event(key, UP))
219               break;
220
221             switch(key)
222               {
223               case SDLK_p:
224                 if(!show_menu)
225                   {
226                     if(game_pause)
227                       {
228                         game_pause = false;
229                         st_pause_ticks_stop();
230                       }
231                     else
232                       {
233                         game_pause = true;
234                         st_pause_ticks_start();
235                       }
236                   }
237                 break;
238               case SDLK_TAB:
239                 if(debug_mode)
240                   {
241                     tux.size = !tux.size;
242                     if(tux.size == BIG)
243                       {
244                         tux.base.height = 64;
245                       }
246                     else
247                       tux.base.height = 32;
248                   }
249                 break;
250               case SDLK_END:
251                 if(debug_mode)
252                   player_status.distros += 50;
253                 break;
254               case SDLK_SPACE:
255                 if(debug_mode)
256                   player_status.next_level = 1;
257                 break;
258               case SDLK_DELETE:
259                 if(debug_mode)
260                   tux.got_coffee = 1;
261                 break;
262               case SDLK_INSERT:
263                 if(debug_mode)
264                   tux.invincible_timer.start(TUX_INVINCIBLE_TIME);
265                 break;
266               case SDLK_l:
267                 if(debug_mode)
268                   --tux.lives;
269                 break;
270               case SDLK_s:
271                 if(debug_mode)
272                   player_status.score += 1000;
273               case SDLK_f:
274                 if(debug_fps)
275                   debug_fps = false;
276                 else
277                   debug_fps = true;
278                 break;
279               default:
280                 break;
281               }
282           }
283           break;
284
285         case SDL_JOYAXISMOTION:
286           switch(event.jaxis.axis)
287             {
288             case JOY_X:
289               if (event.jaxis.value < -JOYSTICK_DEAD_ZONE)
290                 {
291                   tux.input.left  = DOWN;
292                   tux.input.right = UP;
293                 }
294               else if (event.jaxis.value > JOYSTICK_DEAD_ZONE)
295                 {
296                   tux.input.left  = UP;
297                   tux.input.right = DOWN;
298                 }
299               else
300                 {
301                   tux.input.left  = DOWN;
302                   tux.input.right = DOWN;
303                 }
304               break;
305             case JOY_Y:
306               if (event.jaxis.value > JOYSTICK_DEAD_ZONE)
307                 tux.input.down = DOWN;
308               else if (event.jaxis.value < -JOYSTICK_DEAD_ZONE)
309                 tux.input.down = UP;
310               else
311                 tux.input.down = UP;
312               
313               break;
314             default:
315               break;
316             }
317           break;
318         case SDL_JOYBUTTONDOWN:
319           if (event.jbutton.button == JOY_A)
320             tux.input.up = DOWN;
321           else if (event.jbutton.button == JOY_B)
322             tux.input.fire = DOWN;
323           break;
324         case SDL_JOYBUTTONUP:
325           if (event.jbutton.button == JOY_A)
326             tux.input.up = UP;
327           else if (event.jbutton.button == JOY_B)
328             tux.input.fire = UP;
329             
330           break;
331
332         default:
333           break;
334
335         }  /* switch */
336
337     } /* while */
338 }
339
340 int
341 GameSession::action(double frame_ratio)
342 {
343   Player& tux = *world->get_tux();
344
345   if (tux.is_dead() || player_status.next_level)
346     {
347       /* Tux either died, or reached the end of a level! */
348       halt_music();
349       
350       if (player_status.next_level)
351         {
352           /* End of a level! */
353           levelnb++;
354           player_status.next_level = 0;
355           if(st_gl_mode != ST_GL_TEST)
356             {
357               drawresultscreen();
358             }
359           else
360             {
361               world->get_level()->free_gfx();
362               world->get_level()->cleanup();
363               world->get_level()->free_song();
364               world->arrays_free();
365
366               unloadshared();
367               return(0);
368             }
369           tux.level_begin();
370         }
371       else
372         {
373           tux.is_dying();
374
375           /* No more lives!? */
376
377           if (tux.lives < 0)
378             {
379               if(st_gl_mode != ST_GL_TEST)
380                 drawendscreen();
381
382               if(st_gl_mode != ST_GL_TEST)
383                 {
384                   if (player_status.score > hs_score)
385                     save_hs(player_status.score);
386                 }
387
388               world->get_level()->free_gfx();
389               world->get_level()->cleanup();
390               world->get_level()->free_song();
391               world->arrays_free();
392
393               unloadshared();
394               return(0);
395             } /* if (lives < 0) */
396         }
397
398       /* Either way, (re-)load the (next) level... */
399       tux.level_begin();
400       world->set_defaults();
401       
402       world->get_level()->cleanup();
403
404       if (st_gl_mode == ST_GL_LOAD_LEVEL_FILE)
405         {
406           if(world->get_level()->load(subset) != 0)
407             return 0;
408         }
409       else
410         {
411           if(world->get_level()->load(subset, levelnb) != 0)
412             return 0;
413         }
414
415       world->arrays_free();
416       world->activate_bad_guys();
417       world->activate_particle_systems();
418
419       world->get_level()->free_gfx();
420       world->get_level()->load_gfx();
421       world->get_level()->free_song();
422       world->get_level()->load_song();
423
424       if(st_gl_mode != ST_GL_TEST)
425         levelintro();
426       start_timers();
427       /* Play music: */
428       play_current_music();
429     }
430
431   tux.action(frame_ratio);
432
433   world->action(frame_ratio);
434
435   return -1;
436 }
437
438 void 
439 GameSession::draw()
440 {
441   world->draw();
442   drawstatus();
443
444   if(game_pause)
445     {
446       int x = screen->h / 20;
447       for(int i = 0; i < x; ++i)
448         {
449           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);
450         }
451       fillrect(0,0,screen->w,screen->h,rand() % 50, rand() % 50, rand() % 50, 128);
452       text_drawf(&blue_text, "PAUSE - Press 'P' To Play", 0, 230, A_HMIDDLE, A_TOP, 1);
453     }
454
455   if(show_menu)
456     {
457       menu_process_current();
458       mouse_cursor->draw();
459     }
460
461   updatescreen();
462 }
463
464
465 int
466 GameSession::run()
467 {
468   Player& tux = *world->get_tux();
469   current_ = this;
470   
471   int  fps_cnt;
472   bool done;
473
474   global_frame_counter = 0;
475   game_pause = false;
476
477   fps_timer.init(true);
478   frame_timer.init(true);
479
480   last_update_time = st_get_ticks();
481   fps_cnt = 0;
482
483   /* Clear screen: */
484   clearscreen(0, 0, 0);
485   updatescreen();
486
487   /* Play music: */
488   play_current_music();
489
490   // Eat unneeded events
491   SDL_Event event;
492   while (SDL_PollEvent(&event)) {}
493
494   draw();
495
496   done = false;
497   quit = false;
498   while (!done && !quit)
499     {
500       /* Calculate the movement-factor */
501       double frame_ratio = ((double)(update_time-last_update_time))/((double)FRAME_RATE);
502       if(frame_ratio > 1.5) /* Quick hack to correct the unprecise CPU clocks a little bit. */
503         frame_ratio = 1.5 + (frame_ratio - 1.5) * 0.85;
504
505       if(!frame_timer.check())
506         {
507           frame_timer.start(25);
508           ++global_frame_counter;
509         }
510
511       /* Handle events: */
512       tux.input.old_fire = tux.input.fire;
513
514       process_events();
515
516       if(show_menu)
517         {
518           if(current_menu == game_menu)
519             {
520               switch (game_menu->check())
521                 {
522                 case 2:
523                   st_pause_ticks_stop();
524                   break;
525                 case 3:
526                   update_load_save_game_menu(save_game_menu, false);
527                   break;
528                 case 4:
529                   update_load_save_game_menu(load_game_menu, true);
530                   break;
531                 case 7:
532                   st_pause_ticks_stop();
533                   done = true;
534                   break;
535                 }
536             }
537           else if(current_menu == options_menu)
538             {
539               process_options_menu();
540             }
541           else if(current_menu == save_game_menu )
542             {
543               process_save_game_menu();
544             }
545           else if(current_menu == load_game_menu )
546             {
547               process_load_game_menu();
548             }
549         }
550
551
552       /* Handle actions: */
553
554       if(!game_pause && !show_menu)
555         {
556           /*float z = frame_ratio;
557             frame_ratio = 1;
558             while(z >= 1)
559             {*/
560           if (action(frame_ratio) == 0)
561             {
562               /* == 0: no more lives */
563               /* == -1: continues */
564               return 0;
565             }
566           /*  --z;
567                      }*/
568         }
569       else
570         {
571           ++pause_menu_frame;
572           SDL_Delay(50);
573         }
574
575       if(debug_mode && debug_fps)
576         SDL_Delay(60);
577
578       /*Draw the current scene to the screen */
579       /*If the machine running the game is too slow
580         skip the drawing of the frame (so the calculations are more precise and
581         the FPS aren't affected).*/
582       /*if( ! fps_fps < 50.0 )
583         game_draw();
584         else
585         jump = true;*/ /*FIXME: Implement this tweak right.*/
586       draw();
587
588       /* Time stops in pause mode */
589       if(game_pause || show_menu )
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         SDL_Delay(10);
603         update_time = st_get_ticks();
604       }
605       /*if((update_time - last_update_time) < 10)
606         SDL_Delay((11 - (update_time - last_update_time))/2);*/
607
608       /* Handle time: */
609       if (time_left.check())
610         {
611           /* are we low on time ? */
612           if (time_left.get_left() < TIME_WARNING
613               && (get_current_music() != HURRYUP_MUSIC))     /* play the fast music */
614             {
615               set_current_music(HURRYUP_MUSIC);
616               play_current_music();
617             }
618
619         }
620       else
621         tux.kill(KILL);
622
623       /* Calculate frames per second */
624       if(show_fps)
625         {
626           ++fps_cnt;
627           fps_fps = (1000.0 / (float)fps_timer.get_gone()) * (float)fps_cnt;
628
629           if(!fps_timer.check())
630             {
631               fps_timer.start(1000);
632               fps_cnt = 0;
633             }
634         }
635     }
636
637   halt_music();
638
639   world->get_level()->free_gfx();
640   world->get_level()->cleanup();
641   world->get_level()->free_song();
642
643   unloadshared();
644   world->arrays_free();
645
646   return quit;
647 }
648
649 /* Bounce a brick: */
650 void bumpbrick(float x, float y)
651 {
652   World::current()->add_bouncy_brick(((int)(x + 1) / 32) * 32,
653                          (int)(y / 32) * 32);
654
655   play_sound(sounds[SND_BRICK], SOUND_CENTER_SPEAKER);
656 }
657
658 /* (Status): */
659 void
660 GameSession::drawstatus()
661 {
662   Player& tux = *world->get_tux();
663   char str[60];
664
665   sprintf(str, "%d", player_status.score);
666   text_draw(&white_text, "SCORE", 0, 0, 1);
667   text_draw(&gold_text, str, 96, 0, 1);
668
669   if(st_gl_mode != ST_GL_TEST)
670     {
671       sprintf(str, "%d", hs_score);
672       text_draw(&white_text, "HIGH", 0, 20, 1);
673       text_draw(&gold_text, str, 96, 20, 1);
674     }
675   else
676     {
677       text_draw(&white_text,"Press ESC To Return",0,20,1);
678     }
679
680   if (time_left.get_left() > TIME_WARNING || (global_frame_counter % 10) < 5)
681     {
682       sprintf(str, "%d", time_left.get_left() / 1000 );
683       text_draw(&white_text, "TIME", 224, 0, 1);
684       text_draw(&gold_text, str, 304, 0, 1);
685     }
686
687   sprintf(str, "%d", player_status.distros);
688   text_draw(&white_text, "DISTROS", screen->h, 0, 1);
689   text_draw(&gold_text, str, 608, 0, 1);
690
691   text_draw(&white_text, "LIVES", screen->h, 20, 1);
692
693   if(show_fps)
694     {
695       sprintf(str, "%2.1f", fps_fps);
696       text_draw(&white_text, "FPS", screen->h, 40, 1);
697       text_draw(&gold_text, str, screen->h + 60, 40, 1);
698     }
699
700   for(int i= 0; i < tux.lives; ++i)
701     {
702       texture_draw(&tux_life,565+(18*i),20);
703     }
704 }
705
706 void
707 GameSession::drawendscreen()
708 {
709   char str[80];
710
711   clearscreen(0, 0, 0);
712
713   text_drawf(&blue_text, "GAMEOVER", 0, 200, A_HMIDDLE, A_TOP, 1);
714
715   sprintf(str, "SCORE: %d", player_status.score);
716   text_drawf(&gold_text, str, 0, 224, A_HMIDDLE, A_TOP, 1);
717
718   sprintf(str, "DISTROS: %d", player_status.distros);
719   text_drawf(&gold_text, str, 0, 256, A_HMIDDLE, A_TOP, 1);
720
721   flipscreen();
722   
723   SDL_Event event;
724   wait_for_event(event,2000,5000,true);
725 }
726
727 void
728 GameSession::drawresultscreen(void)
729 {
730   char str[80];
731
732   clearscreen(0, 0, 0);
733
734   text_drawf(&blue_text, "Result:", 0, 200, A_HMIDDLE, A_TOP, 1);
735
736   sprintf(str, "SCORE: %d", player_status.score);
737   text_drawf(&gold_text, str, 0, 224, A_HMIDDLE, A_TOP, 1);
738
739   sprintf(str, "DISTROS: %d", player_status.distros);
740   text_drawf(&gold_text, str, 0, 256, A_HMIDDLE, A_TOP, 1);
741
742   flipscreen();
743   
744   SDL_Event event;
745   wait_for_event(event,2000,5000,true);
746 }
747
748 void
749 GameSession::savegame(int)
750 {
751 #if 0
752   char savefile[1024];
753   FILE* fi;
754   unsigned int ui;
755
756   sprintf(savefile,"%s/slot%d.save",st_save_dir,slot);
757
758   fi = fopen(savefile, "wb");
759
760   if (fi == NULL)
761     {
762       fprintf(stderr, "Warning: I could not open the slot file ");
763     }
764   else
765     {
766       fputs(level_subset, fi);
767       fputs("\n", fi);
768       fwrite(&level,sizeof(int),1,fi);
769       fwrite(&score,sizeof(int),1,fi);
770       fwrite(&distros,sizeof(int),1,fi);
771       fwrite(&scroll_x,sizeof(float),1,fi);
772       //FIXME:fwrite(&tux,sizeof(Player),1,fi);
773       //FIXME:timer_fwrite(&tux.invincible_timer,fi);
774       //FIXME:timer_fwrite(&tux.skidding_timer,fi);
775       //FIXME:timer_fwrite(&tux.safe_timer,fi);
776       //FIXME:timer_fwrite(&tux.frame_timer,fi);
777       timer_fwrite(&time_left,fi);
778       ui = st_get_ticks();
779       fwrite(&ui,sizeof(int),1,fi);
780     }
781   fclose(fi);
782 #endif 
783 }
784
785 void
786 GameSession::loadgame(int)
787 {
788 #if 0
789   char savefile[1024];
790   char str[100];
791   FILE* fi;
792   unsigned int ui;
793
794   sprintf(savefile,"%s/slot%d.save",st_save_dir,slot);
795
796   fi = fopen(savefile, "rb");
797
798   if (fi == NULL)
799     {
800       fprintf(stderr, "Warning: I could not open the slot file ");
801
802     }
803   else
804     {
805       fgets(str, 100, fi);
806       strcpy(level_subset, str);
807       level_subset[strlen(level_subset)-1] = '\0';
808       fread(&level,sizeof(int),1,fi);
809
810       world->set_defaults();
811       world->get_level()->cleanup();
812       world->arrays_free();
813       world->get_level()->free_gfx();
814       world->get_level()->free_song();
815
816       if(world->get_level()->load(level_subset,level) != 0)
817         exit(1);
818
819       world->activate_bad_guys();
820       world->activate_particle_systems();
821       world->get_level()->load_gfx();
822       world->get_level()->load_song();
823
824       levelintro();
825       update_time = st_get_ticks();
826
827       fread(&score,   sizeof(int),1,fi);
828       fread(&distros, sizeof(int),1,fi);
829       fread(&scroll_x,sizeof(float),1,fi);
830       //FIXME:fread(&tux,     sizeof(Player), 1, fi);
831       //FIXME:timer_fread(&tux.invincible_timer,fi);
832       //FIXME:timer_fread(&tux.skidding_timer,fi);
833       //FIXME:timer_fread(&tux.safe_timer,fi);
834       //FIXME:timer_fread(&tux.frame_timer,fi);
835       timer_fread(&time_left,fi);
836       fread(&ui,sizeof(int),1,fi);
837       fclose(fi);
838     }
839 #endif 
840 }
841
842 std::string slotinfo(int slot)
843 {
844   FILE* fi;
845   char slotfile[1024];
846   char tmp[200];
847   char str[5];
848   int slot_level;
849   sprintf(slotfile,"%s/slot%d.save",st_save_dir,slot);
850
851   fi = fopen(slotfile, "rb");
852
853   sprintf(tmp,"Slot %d - ",slot);
854
855   if (fi == NULL)
856     {
857       strcat(tmp,"Free");
858     }
859   else
860     {
861       fgets(str, 100, fi);
862       str[strlen(str)-1] = '\0';
863       strcat(tmp, str);
864       strcat(tmp, " / Level:");
865       fread(&slot_level,sizeof(int),1,fi);
866       sprintf(str,"%d",slot_level);
867       strcat(tmp,str);
868       fclose(fi);
869     }
870
871   return tmp;
872 }
873