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