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