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