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