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