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