93c5aef961aab689654c6bcd9821b465962817f3
[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   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       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   /* update particle systems */
436   std::vector<ParticleSystem*>::iterator p;
437   for(p = world->particle_systems.begin(); p != world->particle_systems.end(); ++p)
438     {
439       (*p)->simulate(frame_ratio);
440     }
441
442   /* Handle all possible collisions. */
443   collision_handler();
444
445   return -1;
446 }
447
448 void 
449 GameSession::draw()
450 {
451   world->draw();
452   drawstatus();
453
454   if(game_pause)
455     {
456       int x = screen->h / 20;
457       for(int i = 0; i < x; ++i)
458         {
459           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);
460         }
461       fillrect(0,0,screen->w,screen->h,rand() % 50, rand() % 50, rand() % 50, 128);
462       text_drawf(&blue_text, "PAUSE - Press 'P' To Play", 0, 230, A_HMIDDLE, A_TOP, 1);
463     }
464
465   if(show_menu)
466     {
467       menu_process_current();
468       mouse_cursor->draw();
469     }
470
471   updatescreen();
472 }
473
474
475 int
476 GameSession::run()
477 {
478   current_ = this;
479   
480   int  fps_cnt;
481   bool done;
482
483   global_frame_counter = 0;
484   game_pause = 0;
485   timer_init(&fps_timer,true);
486   timer_init(&frame_timer,true);
487   last_update_time = st_get_ticks();
488   fps_cnt = 0;
489
490   /* Clear screen: */
491   clearscreen(0, 0, 0);
492   updatescreen();
493
494   /* Play music: */
495   play_current_music();
496
497   while (SDL_PollEvent(&event))
498   {}
499
500   draw();
501
502   done = false;
503   quit = false;
504   while (!done && !quit)
505     {
506       /* Calculate the movement-factor */
507       frame_ratio = ((double)(update_time-last_update_time))/((double)FRAME_RATE);
508       if(frame_ratio > 1.5) /* Quick hack to correct the unprecise CPU clocks a little bit. */
509         frame_ratio = 1.5 + (frame_ratio - 1.5) * 0.85;
510
511       if(!timer_check(&frame_timer))
512         {
513           timer_start(&frame_timer,25);
514           ++global_frame_counter;
515         }
516
517       /* Handle events: */
518
519       tux.input.old_fire = tux.input.fire;
520
521       process_events();
522
523       if(show_menu)
524         {
525           if(current_menu == game_menu)
526             {
527               switch (game_menu->check())
528                 {
529                 case 2:
530                   st_pause_ticks_stop();
531                   break;
532                 case 3:
533                   update_load_save_game_menu(save_game_menu, false);
534                   break;
535                 case 4:
536                   update_load_save_game_menu(load_game_menu, true);
537                   break;
538                 case 7:
539                   st_pause_ticks_stop();
540                   done = true;
541                   break;
542                 }
543             }
544           else if(current_menu == options_menu)
545             {
546               process_options_menu();
547             }
548           else if(current_menu == save_game_menu )
549             {
550               process_save_game_menu();
551             }
552           else if(current_menu == load_game_menu )
553             {
554               process_load_game_menu();
555             }
556         }
557
558
559       /* Handle actions: */
560
561       if(!game_pause && !show_menu)
562         {
563           /*float z = frame_ratio;
564             frame_ratio = 1;
565             while(z >= 1)
566             {*/
567           if (action() == 0)
568             {
569               /* == 0: no more lives */
570               /* == -1: continues */
571               return 0;
572             }
573           /*  --z;
574                      }*/
575         }
576       else
577         {
578           ++pause_menu_frame;
579           SDL_Delay(50);
580         }
581
582       if(debug_mode && debug_fps)
583         SDL_Delay(60);
584
585       /*Draw the current scene to the screen */
586       /*If the machine running the game is too slow
587         skip the drawing of the frame (so the calculations are more precise and
588         the FPS aren't affected).*/
589       /*if( ! fps_fps < 50.0 )
590         game_draw();
591         else
592         jump = true;*/ /*FIXME: Implement this tweak right.*/
593       draw();
594
595       /* Time stops in pause mode */
596       if(game_pause || show_menu )
597         {
598           continue;
599         }
600
601       /* Set the time of the last update and the time of the current update */
602       last_update_time = update_time;
603       update_time = st_get_ticks();
604
605       /* Pause till next frame, if the machine running the game is too fast: */
606       /* FIXME: Works great for in OpenGl mode, where the CPU doesn't have to do that much. But
607          the results in SDL mode aren't perfect (thought the 100 FPS are reached), even on an AMD2500+. */
608       if(last_update_time >= update_time - 12) {
609         SDL_Delay(10);
610         update_time = st_get_ticks();
611       }
612       /*if((update_time - last_update_time) < 10)
613         SDL_Delay((11 - (update_time - last_update_time))/2);*/
614
615       /* Handle time: */
616       if (timer_check(&time_left))
617         {
618           /* are we low on time ? */
619           if ((timer_get_left(&time_left) < TIME_WARNING)
620               && (get_current_music() != HURRYUP_MUSIC))     /* play the fast music */
621             {
622               set_current_music(HURRYUP_MUSIC);
623               play_current_music();
624             }
625
626         }
627       else
628         tux.kill(KILL);
629
630       /* Calculate frames per second */
631       if(show_fps)
632         {
633           ++fps_cnt;
634           fps_fps = (1000.0 / (float)timer_get_gone(&fps_timer)) * (float)fps_cnt;
635
636           if(!timer_check(&fps_timer))
637             {
638               timer_start(&fps_timer,1000);
639               fps_cnt = 0;
640             }
641         }
642     }
643
644   halt_music();
645
646   world->get_level()->free_gfx();
647   world->get_level()->cleanup();
648   world->get_level()->free_song();
649
650   unloadshared();
651   world->arrays_free();
652
653   game_started = false;
654
655   return(quit);
656 }
657
658 /* Draw a tile on the screen: */
659
660 void drawshape(float x, float y, unsigned int c, Uint8 alpha)
661 {
662   if (c != 0)
663     {
664       Tile* ptile = TileManager::instance()->get(c);
665       if(ptile)
666         {
667           if(ptile->images.size() > 1)
668             {
669               texture_draw(&ptile->images[( ((global_frame_counter*25) / ptile->anim_speed) % (ptile->images.size()))],x,y, alpha);
670             }
671           else if (ptile->images.size() == 1)
672             {
673               texture_draw(&ptile->images[0],x,y, alpha);
674             }
675           else
676             {
677               //printf("Tile not dravable %u\n", c);
678             }
679         }
680     }
681 }
682
683 /* Bounce a brick: */
684 void bumpbrick(float x, float y)
685 {
686   world.add_bouncy_brick(((int)(x + 1) / 32) * 32,
687                          (int)(y / 32) * 32);
688
689   play_sound(sounds[SND_BRICK], SOUND_CENTER_SPEAKER);
690 }
691
692 /* (Status): */
693 void drawstatus(void)
694 {
695   char str[60];
696
697   sprintf(str, "%d", score);
698   text_draw(&white_text, "SCORE", 0, 0, 1);
699   text_draw(&gold_text, str, 96, 0, 1);
700
701   if(st_gl_mode != ST_GL_TEST)
702     {
703       sprintf(str, "%d", hs_score);
704       text_draw(&white_text, "HIGH", 0, 20, 1);
705       text_draw(&gold_text, str, 96, 20, 1);
706     }
707   else
708     {
709       text_draw(&white_text,"Press ESC To Return",0,20,1);
710     }
711
712   if (timer_get_left(&time_left) > TIME_WARNING || (global_frame_counter % 10) < 5)
713     {
714       sprintf(str, "%d", timer_get_left(&time_left) / 1000 );
715       text_draw(&white_text, "TIME", 224, 0, 1);
716       text_draw(&gold_text, str, 304, 0, 1);
717     }
718
719   sprintf(str, "%d", distros);
720   text_draw(&white_text, "DISTROS", screen->h, 0, 1);
721   text_draw(&gold_text, str, 608, 0, 1);
722
723   text_draw(&white_text, "LIVES", screen->h, 20, 1);
724
725   if(show_fps)
726     {
727       sprintf(str, "%2.1f", fps_fps);
728       text_draw(&white_text, "FPS", screen->h, 40, 1);
729       text_draw(&gold_text, str, screen->h + 60, 40, 1);
730     }
731
732   for(int i=0; i < tux.lives; ++i)
733     {
734       texture_draw(&tux_life,565+(18*i),20);
735     }
736 }
737
738
739 void drawendscreen(void)
740 {
741   char str[80];
742
743   clearscreen(0, 0, 0);
744
745   text_drawf(&blue_text, "GAMEOVER", 0, 200, A_HMIDDLE, A_TOP, 1);
746
747   sprintf(str, "SCORE: %d", score);
748   text_drawf(&gold_text, str, 0, 224, A_HMIDDLE, A_TOP, 1);
749
750   sprintf(str, "DISTROS: %d", distros);
751   text_drawf(&gold_text, str, 0, 256, A_HMIDDLE, A_TOP, 1);
752
753   flipscreen();
754   
755   SDL_Event event;
756   wait_for_event(event,2000,5000,true);
757 }
758
759 void drawresultscreen(void)
760 {
761   char str[80];
762
763   clearscreen(0, 0, 0);
764
765   text_drawf(&blue_text, "Result:", 0, 200, A_HMIDDLE, A_TOP, 1);
766
767   sprintf(str, "SCORE: %d", score);
768   text_drawf(&gold_text, str, 0, 224, A_HMIDDLE, A_TOP, 1);
769
770   sprintf(str, "DISTROS: %d", distros);
771   text_drawf(&gold_text, str, 0, 256, A_HMIDDLE, A_TOP, 1);
772
773   flipscreen();
774   
775   SDL_Event event;
776   wait_for_event(event,2000,5000,true);
777 }
778
779 void
780 GameSession::savegame(int slot)
781 {
782   char savefile[1024];
783   FILE* fi;
784   unsigned int ui;
785
786   sprintf(savefile,"%s/slot%d.save",st_save_dir,slot);
787
788   fi = fopen(savefile, "wb");
789
790   if (fi == NULL)
791     {
792       fprintf(stderr, "Warning: I could not open the slot file ");
793     }
794   else
795     {
796       fputs(level_subset, fi);
797       fputs("\n", fi);
798       fwrite(&level,sizeof(int),1,fi);
799       fwrite(&score,sizeof(int),1,fi);
800       fwrite(&distros,sizeof(int),1,fi);
801       fwrite(&scroll_x,sizeof(float),1,fi);
802       fwrite(&tux,sizeof(Player),1,fi);
803       timer_fwrite(&tux.invincible_timer,fi);
804       timer_fwrite(&tux.skidding_timer,fi);
805       timer_fwrite(&tux.safe_timer,fi);
806       timer_fwrite(&tux.frame_timer,fi);
807       timer_fwrite(&time_left,fi);
808       ui = st_get_ticks();
809       fwrite(&ui,sizeof(int),1,fi);
810     }
811   fclose(fi);
812
813 }
814
815 void
816 GameSession::loadgame(int slot)
817 {
818   char savefile[1024];
819   char str[100];
820   FILE* fi;
821   unsigned int ui;
822
823   sprintf(savefile,"%s/slot%d.save",st_save_dir,slot);
824
825   fi = fopen(savefile, "rb");
826
827   if (fi == NULL)
828     {
829       fprintf(stderr, "Warning: I could not open the slot file ");
830
831     }
832   else
833     {
834       fgets(str, 100, fi);
835       strcpy(level_subset, str);
836       level_subset[strlen(level_subset)-1] = '\0';
837       fread(&level,sizeof(int),1,fi);
838
839       set_defaults();
840       world->get_level()->cleanup();
841       world->arrays_free();
842
843       if(world->get_level()->load(level_subset,level) != 0)
844         exit(1);
845       world->activate_bad_guys();
846       world->activate_particle_systems();
847       
848       world->get_level()->free_gfx();
849       world->get_level()->load_gfx();
850       world->get_level()->free_song();
851       world->get_level()->load_song();
852
853       levelintro();
854       update_time = st_get_ticks();
855
856       fread(&score,   sizeof(int),1,fi);
857       fread(&distros, sizeof(int),1,fi);
858       fread(&scroll_x,sizeof(float),1,fi);
859       fread(&tux,     sizeof(Player), 1, fi);
860       timer_fread(&tux.invincible_timer,fi);
861       timer_fread(&tux.skidding_timer,fi);
862       timer_fread(&tux.safe_timer,fi);
863       timer_fread(&tux.frame_timer,fi);
864       timer_fread(&time_left,fi);
865       fread(&ui,sizeof(int),1,fi);
866       fclose(fi);
867     }
868
869 }
870
871 std::string slotinfo(int slot)
872 {
873   FILE* fi;
874   char slotfile[1024];
875   char tmp[200];
876   char str[5];
877   int slot_level;
878   sprintf(slotfile,"%s/slot%d.save",st_save_dir,slot);
879
880   fi = fopen(slotfile, "rb");
881
882   sprintf(tmp,"Slot %d - ",slot);
883
884   if (fi == NULL)
885     {
886       strcat(tmp,"Free");
887     }
888   else
889     {
890       fgets(str, 100, fi);
891       str[strlen(str)-1] = '\0';
892       strcat(tmp, str);
893       strcat(tmp, " / Level:");
894       fread(&slot_level,sizeof(int),1,fi);
895       sprintf(str,"%d",slot_level);
896       strcat(tmp,str);
897       fclose(fi);
898     }
899
900   return tmp;
901 }
902