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