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