- moved gameobjects into there own file
[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   /* 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 /* Bounce a brick: */
659 void bumpbrick(float x, float y)
660 {
661   world.add_bouncy_brick(((int)(x + 1) / 32) * 32,
662                          (int)(y / 32) * 32);
663
664   play_sound(sounds[SND_BRICK], SOUND_CENTER_SPEAKER);
665 }
666
667 /* (Status): */
668 void drawstatus(void)
669 {
670   char str[60];
671
672   sprintf(str, "%d", score);
673   text_draw(&white_text, "SCORE", 0, 0, 1);
674   text_draw(&gold_text, str, 96, 0, 1);
675
676   if(st_gl_mode != ST_GL_TEST)
677     {
678       sprintf(str, "%d", hs_score);
679       text_draw(&white_text, "HIGH", 0, 20, 1);
680       text_draw(&gold_text, str, 96, 20, 1);
681     }
682   else
683     {
684       text_draw(&white_text,"Press ESC To Return",0,20,1);
685     }
686
687   if (timer_get_left(&time_left) > TIME_WARNING || (global_frame_counter % 10) < 5)
688     {
689       sprintf(str, "%d", timer_get_left(&time_left) / 1000 );
690       text_draw(&white_text, "TIME", 224, 0, 1);
691       text_draw(&gold_text, str, 304, 0, 1);
692     }
693
694   sprintf(str, "%d", distros);
695   text_draw(&white_text, "DISTROS", screen->h, 0, 1);
696   text_draw(&gold_text, str, 608, 0, 1);
697
698   text_draw(&white_text, "LIVES", screen->h, 20, 1);
699
700   if(show_fps)
701     {
702       sprintf(str, "%2.1f", fps_fps);
703       text_draw(&white_text, "FPS", screen->h, 40, 1);
704       text_draw(&gold_text, str, screen->h + 60, 40, 1);
705     }
706
707   for(int i=0; i < tux.lives; ++i)
708     {
709       texture_draw(&tux_life,565+(18*i),20);
710     }
711 }
712
713
714 void drawendscreen(void)
715 {
716   char str[80];
717
718   clearscreen(0, 0, 0);
719
720   text_drawf(&blue_text, "GAMEOVER", 0, 200, A_HMIDDLE, A_TOP, 1);
721
722   sprintf(str, "SCORE: %d", score);
723   text_drawf(&gold_text, str, 0, 224, A_HMIDDLE, A_TOP, 1);
724
725   sprintf(str, "DISTROS: %d", distros);
726   text_drawf(&gold_text, str, 0, 256, A_HMIDDLE, A_TOP, 1);
727
728   flipscreen();
729   
730   SDL_Event event;
731   wait_for_event(event,2000,5000,true);
732 }
733
734 void drawresultscreen(void)
735 {
736   char str[80];
737
738   clearscreen(0, 0, 0);
739
740   text_drawf(&blue_text, "Result:", 0, 200, A_HMIDDLE, A_TOP, 1);
741
742   sprintf(str, "SCORE: %d", score);
743   text_drawf(&gold_text, str, 0, 224, A_HMIDDLE, A_TOP, 1);
744
745   sprintf(str, "DISTROS: %d", distros);
746   text_drawf(&gold_text, str, 0, 256, A_HMIDDLE, A_TOP, 1);
747
748   flipscreen();
749   
750   SDL_Event event;
751   wait_for_event(event,2000,5000,true);
752 }
753
754 void
755 GameSession::savegame(int slot)
756 {
757   char savefile[1024];
758   FILE* fi;
759   unsigned int ui;
760
761   sprintf(savefile,"%s/slot%d.save",st_save_dir,slot);
762
763   fi = fopen(savefile, "wb");
764
765   if (fi == NULL)
766     {
767       fprintf(stderr, "Warning: I could not open the slot file ");
768     }
769   else
770     {
771       fputs(level_subset, fi);
772       fputs("\n", fi);
773       fwrite(&level,sizeof(int),1,fi);
774       fwrite(&score,sizeof(int),1,fi);
775       fwrite(&distros,sizeof(int),1,fi);
776       fwrite(&scroll_x,sizeof(float),1,fi);
777       fwrite(&tux,sizeof(Player),1,fi);
778       timer_fwrite(&tux.invincible_timer,fi);
779       timer_fwrite(&tux.skidding_timer,fi);
780       timer_fwrite(&tux.safe_timer,fi);
781       timer_fwrite(&tux.frame_timer,fi);
782       timer_fwrite(&time_left,fi);
783       ui = st_get_ticks();
784       fwrite(&ui,sizeof(int),1,fi);
785     }
786   fclose(fi);
787
788 }
789
790 void
791 GameSession::loadgame(int slot)
792 {
793   char savefile[1024];
794   char str[100];
795   FILE* fi;
796   unsigned int ui;
797
798   sprintf(savefile,"%s/slot%d.save",st_save_dir,slot);
799
800   fi = fopen(savefile, "rb");
801
802   if (fi == NULL)
803     {
804       fprintf(stderr, "Warning: I could not open the slot file ");
805
806     }
807   else
808     {
809       fgets(str, 100, fi);
810       strcpy(level_subset, str);
811       level_subset[strlen(level_subset)-1] = '\0';
812       fread(&level,sizeof(int),1,fi);
813
814       world->set_defaults();
815       world->get_level()->cleanup();
816       world->arrays_free();
817       world->get_level()->free_gfx();
818       world->get_level()->free_song();
819
820       if(world->get_level()->load(level_subset,level) != 0)
821         exit(1);
822
823       world->activate_bad_guys();
824       world->activate_particle_systems();
825       world->get_level()->load_gfx();
826       world->get_level()->load_song();
827
828       levelintro();
829       update_time = st_get_ticks();
830
831       fread(&score,   sizeof(int),1,fi);
832       fread(&distros, sizeof(int),1,fi);
833       fread(&scroll_x,sizeof(float),1,fi);
834       fread(&tux,     sizeof(Player), 1, fi);
835       timer_fread(&tux.invincible_timer,fi);
836       timer_fread(&tux.skidding_timer,fi);
837       timer_fread(&tux.safe_timer,fi);
838       timer_fread(&tux.frame_timer,fi);
839       timer_fread(&time_left,fi);
840       fread(&ui,sizeof(int),1,fi);
841       fclose(fi);
842     }
843
844 }
845
846 std::string slotinfo(int slot)
847 {
848   FILE* fi;
849   char slotfile[1024];
850   char tmp[200];
851   char str[5];
852   int slot_level;
853   sprintf(slotfile,"%s/slot%d.save",st_save_dir,slot);
854
855   fi = fopen(slotfile, "rb");
856
857   sprintf(tmp,"Slot %d - ",slot);
858
859   if (fi == NULL)
860     {
861       strcat(tmp,"Free");
862     }
863   else
864     {
865       fgets(str, 100, fi);
866       str[strlen(str)-1] = '\0';
867       strcat(tmp, str);
868       strcat(tmp, " / Level:");
869       fread(&slot_level,sizeof(int),1,fi);
870       sprintf(str,"%d",slot_level);
871       strcat(tmp,str);
872       fclose(fi);
873     }
874
875   return tmp;
876 }
877