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