- removed st_abort() from lisp reader, client code should check the return value...
[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
411 void
412 GameSession::check_end_conditions()
413 {
414   Player* tux = world->get_tux();
415
416   /* End of level? */
417   if (tux->base.x >= World::current()->get_level()->endpos + 32 * (get_level()->use_endsequence ? 22 : 10))
418     {
419       exit_status = LEVEL_FINISHED;
420     }
421   else if (tux->base.x >= World::current()->get_level()->endpos && !end_sequence)
422     {
423       end_sequence = true;
424       last_x_pos = -1;
425       music_manager->halt_music();
426     }
427   else
428     {
429       // Check End conditions
430       if (tux->is_dead())
431         {
432           player_status.lives -= 1;             
433     
434           if (player_status.lives < 0)
435             { // No more lives!?
436               if(st_gl_mode != ST_GL_TEST)
437                 drawendscreen();
438               
439               exit_status = GAME_OVER;
440             }
441           else
442             { // Still has lives, so reset Tux to the levelstart
443               restart_level();
444             }
445         }
446     } 
447 }
448
449 void
450 GameSession::action(double frame_ratio)
451 {
452   check_end_conditions();
453   
454   if (exit_status == NONE)
455     {
456       // Update Tux and the World
457       world->action(frame_ratio);
458     }
459 }
460
461 void 
462 GameSession::draw()
463 {
464   world->draw();
465   drawstatus();
466
467   if(game_pause)
468     {
469       int x = screen->h / 20;
470       for(int i = 0; i < x; ++i)
471         {
472           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);
473         }
474       fillrect(0,0,screen->w,screen->h,rand() % 50, rand() % 50, rand() % 50, 128);
475       blue_text->drawf("PAUSE - Press 'P' To Play", 0, 230, A_HMIDDLE, A_TOP, 1);
476     }
477
478   if(Menu::current())
479     {
480       Menu::current()->draw();
481       mouse_cursor->draw();
482     }
483
484   updatescreen();
485 }
486
487 void
488 GameSession::process_menu()
489 {
490   Menu* menu = Menu::current();
491   if(menu)
492     {
493       menu->action();
494
495       if(menu == game_menu)
496         {
497           switch (game_menu->check())
498             {
499             case MNID_CONTINUE:
500               st_pause_ticks_stop();
501               break;
502             case MNID_ABORTLEVEL:
503               st_pause_ticks_stop();
504               exit_status = LEVEL_ABORT;
505               break;
506             }
507         }
508       else if(menu == options_menu)
509         {
510           process_options_menu();
511         }
512       else if(menu == load_game_menu )
513         {
514           process_load_game_menu();
515         }
516     }
517 }
518
519 GameSession::ExitStatus
520 GameSession::run()
521 {
522   Menu::set_current(0);
523   current_ = this;
524   
525   int fps_cnt = 0;
526
527   update_time = last_update_time = st_get_ticks();
528
529   /* Clear screen: */
530 //  clearscreen(0, 0, 0);
531 //  updatescreen();
532
533   // Eat unneeded events
534   SDL_Event event;
535   while (SDL_PollEvent(&event)) {}
536
537   draw();
538
539   float overlap = 0.0f;
540   while (exit_status == NONE)
541     {
542       /* Calculate the movement-factor */
543       double frame_ratio = ((double)(update_time-last_update_time))/((double)FRAME_RATE);
544
545       if(!frame_timer.check())
546         {
547           frame_timer.start(25);
548           ++global_frame_counter;
549         }
550
551       /* Handle events: */
552       world->get_tux()->input.old_fire = world->get_tux()->input.fire;
553
554       process_events();
555       process_menu();
556
557       // Update the world state and all objects in the world
558       // Do that with a constante time-delta so that the game will run
559       // determistic and not different on different machines
560       if(!game_pause && !Menu::current())
561         {
562           frame_ratio *= game_speed;
563           frame_ratio += overlap;
564           while (frame_ratio > 0)
565             {
566               // Update the world
567               if (end_sequence)
568                 action(.5f);
569               else
570                 action(1.0f);
571               frame_ratio -= 1.0f;
572             }
573           overlap = frame_ratio;
574         }
575       else
576         {
577           ++pause_menu_frame;
578           SDL_Delay(50);
579         }
580
581       draw();
582
583       /* Time stops in pause mode */
584       if(game_pause || Menu::current())
585         {
586           continue;
587         }
588
589       /* Set the time of the last update and the time of the current update */
590       last_update_time = update_time;
591       update_time      = st_get_ticks();
592
593       /* Pause till next frame, if the machine running the game is too fast: */
594       /* FIXME: Works great for in OpenGl mode, where the CPU doesn't have to do that much. But
595          the results in SDL mode aren't perfect (thought the 100 FPS are reached), even on an AMD2500+. */
596       if(last_update_time >= update_time - 12) 
597         {
598           SDL_Delay(10);
599           update_time = st_get_ticks();
600         }
601
602       /* Handle time: */
603       if (!time_left.check() && world->get_tux()->dying == DYING_NOT)
604         world->get_tux()->kill(Player::KILL);
605
606       /* Handle music: */
607       if(world->get_tux()->invincible_timer.check())
608         {
609           if(world->get_music_type() != HERRING_MUSIC)
610             world->play_music(HERRING_MUSIC);
611         }
612       /* are we low on time ? */
613       else if (time_left.get_left() < TIME_WARNING
614          && (world->get_music_type() == LEVEL_MUSIC))
615         {
616           world->play_music(HURRYUP_MUSIC);
617         }
618       /* or just normal music? */
619       else if(world->get_music_type() != LEVEL_MUSIC)
620         {
621           world->play_music(LEVEL_MUSIC);
622         }
623
624       /* Calculate frames per second */
625       if(show_fps)
626         {
627           ++fps_cnt;
628           fps_fps = (1000.0 / (float)fps_timer.get_gone()) * (float)fps_cnt;
629
630           if(!fps_timer.check())
631             {
632               fps_timer.start(1000);
633               fps_cnt = 0;
634             }
635         }
636     }
637   
638   return exit_status;
639 }
640
641 /* Bounce a brick: */
642 void bumpbrick(float x, float y)
643 {
644   World::current()->add_bouncy_brick(((int)(x + 1) / 32) * 32,
645                          (int)(y / 32) * 32);
646
647   play_sound(sounds[SND_BRICK], SOUND_CENTER_SPEAKER);
648 }
649
650 /* (Status): */
651 void
652 GameSession::drawstatus()
653 {
654   char str[60];
655
656   sprintf(str, "%d", player_status.score);
657   white_text->draw("SCORE", 0, 0, 1);
658   gold_text->draw(str, 96, 0, 1);
659
660   if(st_gl_mode == ST_GL_TEST)
661     {
662       white_text->draw("Press ESC To Return",0,20,1);
663     }
664
665   if (time_left.get_left() > TIME_WARNING || (global_frame_counter % 10) < 5)
666     {
667       sprintf(str, "%d", time_left.get_left() / 1000 );
668       white_text->draw("TIME", 224, 0, 1);
669       gold_text->draw(str, 304, 0, 1);
670     }
671
672   sprintf(str, "%d", player_status.distros);
673   white_text->draw("COINS", screen->h, 0, 1);
674   gold_text->draw(str, 608, 0, 1);
675
676   white_text->draw("LIVES", 480, 20);
677   if (player_status.lives >= 5)
678     {
679       sprintf(str, "%dx", player_status.lives);
680       gold_text->draw(str, 585, 20);
681       tux_life->draw(565+(18*3), 20);
682     }
683   else
684     {
685       for(int i= 0; i < player_status.lives; ++i)
686         tux_life->draw(565+(18*i),20);
687     }
688
689   if(show_fps)
690     {
691       sprintf(str, "%2.1f", fps_fps);
692       white_text->draw("FPS", screen->h, 40, 1);
693       gold_text->draw(str, screen->h + 60, 40, 1);
694     }
695 }
696
697 void
698 GameSession::drawendscreen()
699 {
700   char str[80];
701
702   if (get_level()->img_bkgd)
703     get_level()->img_bkgd->draw(0, 0);
704   else
705     drawgradient(get_level()->bkgd_top, get_level()->bkgd_bottom);
706
707   blue_text->drawf("GAMEOVER", 0, 200, A_HMIDDLE, A_TOP, 1);
708
709   sprintf(str, "SCORE: %d", player_status.score);
710   gold_text->drawf(str, 0, 224, A_HMIDDLE, A_TOP, 1);
711
712   sprintf(str, "COINS: %d", player_status.distros);
713   gold_text->drawf(str, 0, 256, A_HMIDDLE, A_TOP, 1);
714
715   flipscreen();
716   
717   SDL_Event event;
718   wait_for_event(event,2000,5000,true);
719 }
720
721 void
722 GameSession::drawresultscreen(void)
723 {
724   char str[80];
725
726   if (get_level()->img_bkgd)
727     get_level()->img_bkgd->draw(0, 0);
728   else
729     drawgradient(get_level()->bkgd_top, get_level()->bkgd_bottom);
730
731   blue_text->drawf("Result:", 0, 200, A_HMIDDLE, A_TOP, 1);
732
733   sprintf(str, "SCORE: %d", player_status.score);
734   gold_text->drawf(str, 0, 224, A_HMIDDLE, A_TOP, 1);
735
736   sprintf(str, "COINS: %d", player_status.distros);
737   gold_text->drawf(str, 0, 256, A_HMIDDLE, A_TOP, 1);
738
739   flipscreen();
740   
741   SDL_Event event;
742   wait_for_event(event,2000,5000,true);
743 }
744
745 std::string slotinfo(int slot)
746 {
747   char tmp[1024];
748   char slotfile[1024];
749   std::string title;
750   sprintf(slotfile,"%s/slot%d.stsg",st_save_dir,slot);
751
752   lisp_object_t* savegame = lisp_read_from_file(slotfile);
753   if (savegame)
754     {
755       LispReader reader(lisp_cdr(savegame));
756       reader.read_string("title", &title);
757       lisp_free(savegame);
758     }
759
760   if (access(slotfile, F_OK) == 0)
761     {
762       if (!title.empty())
763         snprintf(tmp,1024,"Slot %d - %s",slot, title.c_str());
764       else
765         snprintf(tmp, 1024,"Slot %d - Savegame",slot);
766     }
767   else
768     sprintf(tmp,"Slot %d - Free",slot);
769
770   return tmp;
771 }
772
773