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