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