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