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