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