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