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