b43a0a9f7d1ad8a80389e6301492f7cebcba8a82
[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(st_gl_mode == ST_GL_TEST)
177     {
178       exit_status = LEVEL_ABORT;
179     }
180   else if (!Menu::current())
181     {
182       Menu::set_current(game_menu);
183     }
184 }
185
186 void
187 GameSession::process_events()
188 {
189   if (end_sequenze)
190     {
191       Player& tux = *world->get_tux();
192           
193       tux.input.left  = UP;
194       tux.input.right = DOWN; 
195       tux.input.down  = UP; 
196
197       if (int(last_x_pos) == int(tux.base.x))
198         tux.input.up    = DOWN; 
199       else
200         tux.input.up    = UP; 
201
202       last_x_pos = tux.base.x;
203     }
204   else
205     {
206       SDL_Event event;
207       while (SDL_PollEvent(&event))
208         {
209           /* Check for menu-events, if the menu is shown */
210           if (Menu::current())
211             {
212               Menu::current()->event(event);
213               st_pause_ticks_start();
214             }
215           else
216             {
217               Player& tux = *world->get_tux();
218   
219               st_pause_ticks_stop();
220
221               switch(event.type)
222                 {
223                 case SDL_QUIT:        /* Quit event - quit: */
224                   st_abort("Received window close", "");
225                   break;
226
227                 case SDL_KEYDOWN:     /* A keypress! */
228                   {
229                     SDLKey key = event.key.keysym.sym;
230             
231                     if(tux.key_event(key,DOWN))
232                       break;
233
234                     switch(key)
235                       {
236                       case SDLK_ESCAPE:    /* Escape: Open/Close the menu: */
237                         on_escape_press();
238                         break;
239                       default:
240                         break;
241                       }
242                   }
243                   break;
244                 case SDL_KEYUP:      /* A keyrelease! */
245                   {
246                     SDLKey key = event.key.keysym.sym;
247
248                     if(tux.key_event(key, UP))
249                       break;
250
251                     switch(key)
252                       {
253                       case SDLK_p:
254                         if(!Menu::current())
255                           {
256                             if(game_pause)
257                               {
258                                 game_pause = false;
259                                 st_pause_ticks_stop();
260                               }
261                             else
262                               {
263                                 game_pause = true;
264                                 st_pause_ticks_start();
265                               }
266                           }
267                         break;
268                       case SDLK_TAB:
269                         if(debug_mode)
270                           {
271                             tux.size = !tux.size;
272                             if(tux.size == BIG)
273                               {
274                                 tux.base.height = 64;
275                               }
276                             else
277                               tux.base.height = 32;
278                           }
279                         break;
280                       case SDLK_END:
281                         if(debug_mode)
282                           player_status.distros += 50;
283                         break;
284                       case SDLK_DELETE:
285                         if(debug_mode)
286                           tux.got_coffee = 1;
287                         break;
288                       case SDLK_INSERT:
289                         if(debug_mode)
290                           tux.invincible_timer.start(TUX_INVINCIBLE_TIME);
291                         break;
292                       case SDLK_l:
293                         if(debug_mode)
294                           --player_status.lives;
295                         break;
296                       case SDLK_s:
297                         if(debug_mode)
298                           player_status.score += 1000;
299                       case SDLK_f:
300                         if(debug_fps)
301                           debug_fps = false;
302                         else
303                           debug_fps = true;
304                         break;
305                       default:
306                         break;
307                       }
308                   }
309                   break;
310
311                 case SDL_JOYAXISMOTION:
312                   if (event.jaxis.axis == joystick_keymap.x_axis)
313                     {
314                       if (event.jaxis.value < -joystick_keymap.dead_zone)
315                         {
316                           tux.input.left  = DOWN;
317                           tux.input.right = UP;
318                         }
319                       else if (event.jaxis.value > joystick_keymap.dead_zone)
320                         {
321                           tux.input.left  = UP;
322                           tux.input.right = DOWN;
323                         }
324                       else
325                         {
326                           tux.input.left  = DOWN;
327                           tux.input.right = DOWN;
328                         }
329                     }
330                   else if (event.jaxis.axis == joystick_keymap.y_axis)
331                     {
332                       if (event.jaxis.value > joystick_keymap.dead_zone)
333                         tux.input.down = DOWN;
334                       else if (event.jaxis.value < -joystick_keymap.dead_zone)
335                         tux.input.down = UP;
336                       else
337                         tux.input.down = UP;
338                     }
339                   break;
340             
341                 case SDL_JOYBUTTONDOWN:
342                   if (event.jbutton.button == joystick_keymap.a_button)
343                     tux.input.up = DOWN;
344                   else if (event.jbutton.button == joystick_keymap.b_button)
345                     tux.input.fire = DOWN;
346                   else if (event.jbutton.button == joystick_keymap.start_button)
347                     on_escape_press();
348                   break;
349                 case SDL_JOYBUTTONUP:
350                   if (event.jbutton.button == joystick_keymap.a_button)
351                     tux.input.up = UP;
352                   else if (event.jbutton.button == joystick_keymap.b_button)
353                     tux.input.fire = UP;
354                   break;
355
356                 default:
357                   break;
358                 }  /* switch */
359             }
360         } /* while */
361     }
362 }
363
364
365 void
366 GameSession::check_end_conditions()
367 {
368   Player* tux = world->get_tux();
369
370   /* End of level? */
371   if (tux->base.x >= World::current()->get_level()->endpos + 320)
372     {
373       exit_status = LEVEL_FINISHED;
374     }
375   else if (tux->base.x >= World::current()->get_level()->endpos && !end_sequenze)
376     {
377       end_sequenze = true;
378       last_x_pos = -1;
379       halt_music();
380     }
381   else
382     {
383       // Check End conditions
384       if (tux->is_dead())
385         {
386           player_status.lives -= 1;             
387     
388           if (player_status.lives < 0)
389             { // No more lives!?
390               if(st_gl_mode != ST_GL_TEST)
391                 drawendscreen();
392               
393               exit_status = GAME_OVER;
394             }
395           else
396             { // Still has lives, so reset Tux to the levelstart
397               restart_level();
398             }
399         }
400     } 
401 }
402
403 void
404 GameSession::action(double frame_ratio)
405 {
406   check_end_conditions();
407   
408   if (exit_status == NONE)
409     {
410       // Update Tux and the World
411       world->action(frame_ratio);
412     }
413 }
414
415 void 
416 GameSession::draw()
417 {
418   world->draw();
419   drawstatus();
420
421   if(game_pause)
422     {
423       int x = screen->h / 20;
424       for(int i = 0; i < x; ++i)
425         {
426           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);
427         }
428       fillrect(0,0,screen->w,screen->h,rand() % 50, rand() % 50, rand() % 50, 128);
429       blue_text->drawf("PAUSE - Press 'P' To Play", 0, 230, A_HMIDDLE, A_TOP, 1);
430     }
431
432   if(Menu::current())
433     {
434       Menu::current()->draw();
435       mouse_cursor->draw();
436     }
437
438   updatescreen();
439 }
440
441 void
442 GameSession::process_menu()
443 {
444   Menu* menu = Menu::current();
445   if(menu)
446     {
447       menu->action();
448
449       if(menu == game_menu)
450         {
451           switch (game_menu->check())
452             {
453             case 2:
454               st_pause_ticks_stop();
455               break;
456             case 5:
457               st_pause_ticks_stop();
458               exit_status = LEVEL_ABORT;
459               break;
460             }
461         }
462       else if(menu == options_menu)
463         {
464           process_options_menu();
465         }
466       else if(menu == load_game_menu )
467         {
468           process_load_game_menu();
469         }
470     }
471 }
472
473 GameSession::ExitStatus
474 GameSession::run()
475 {
476   Menu::set_current(0);
477   current_ = this;
478   
479   int fps_cnt = 0;
480
481   update_time = last_update_time = st_get_ticks();
482
483   /* Clear screen: */
484   clearscreen(0, 0, 0);
485   updatescreen();
486
487   // Eat unneeded events
488   SDL_Event event;
489   while (SDL_PollEvent(&event)) {}
490
491   draw();
492
493   float overlap = 0.0f;
494   while (exit_status == NONE)
495     {
496       /* Calculate the movement-factor */
497       double frame_ratio = ((double)(update_time-last_update_time))/((double)FRAME_RATE);
498
499       if(!frame_timer.check())
500         {
501           frame_timer.start(25);
502           ++global_frame_counter;
503         }
504
505       /* Handle events: */
506       world->get_tux()->input.old_fire = world->get_tux()->input.fire;
507
508       process_events();
509       process_menu();
510
511       // Update the world state and all objects in the world
512       // Do that with a constante time-delta so that the game will run
513       // determistic and not different on different machines
514       if(!game_pause && !Menu::current())
515         {
516           frame_ratio *= game_speed;
517           frame_ratio += overlap;
518           while (frame_ratio > 0)
519             {
520               // Update the world
521               if (end_sequenze)
522                 action(.5f);
523               else
524                 action(1.0f);
525               frame_ratio -= 1.0f;
526             }
527           overlap = frame_ratio;
528         }
529       else
530         {
531           ++pause_menu_frame;
532           SDL_Delay(50);
533         }
534
535       draw();
536
537       /* Time stops in pause mode */
538       if(game_pause || Menu::current())
539         {
540           continue;
541         }
542
543       /* Set the time of the last update and the time of the current update */
544       last_update_time = update_time;
545       update_time      = st_get_ticks();
546
547       /* Pause till next frame, if the machine running the game is too fast: */
548       /* FIXME: Works great for in OpenGl mode, where the CPU doesn't have to do that much. But
549          the results in SDL mode aren't perfect (thought the 100 FPS are reached), even on an AMD2500+. */
550       if(last_update_time >= update_time - 12) 
551         {
552           SDL_Delay(10);
553           update_time = st_get_ticks();
554         }
555
556       /* Handle time: */
557       if (!time_left.check() && world->get_tux()->dying == DYING_NOT)
558         world->get_tux()->kill(KILL);
559
560       /* Handle music: */
561       if(world->get_tux()->invincible_timer.check())
562         {
563           if(world->get_music_type() != HERRING_MUSIC)
564             world->play_music(HERRING_MUSIC);
565         }
566       /* are we low on time ? */
567       else if (time_left.get_left() < TIME_WARNING
568          && (world->get_music_type() == LEVEL_MUSIC))
569         {
570           world->play_music(HURRYUP_MUSIC);
571         }
572       /* or just normal music? */
573       else if(world->get_music_type() != LEVEL_MUSIC)
574         {
575           world->play_music(LEVEL_MUSIC);
576         }
577
578       /* Calculate frames per second */
579       if(show_fps)
580         {
581           ++fps_cnt;
582           fps_fps = (1000.0 / (float)fps_timer.get_gone()) * (float)fps_cnt;
583
584           if(!fps_timer.check())
585             {
586               fps_timer.start(1000);
587               fps_cnt = 0;
588             }
589         }
590     }
591   
592   delete world;
593   world = 0;
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