Random stuff that I should have committed ages ago.
[supertux.git] / src / mainloop.cpp
1 //  $Id$
2 //
3 //  SuperTux
4 //  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
5 //
6 //  This program is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU General Public License
8 //  as published by the Free Software Foundation; either version 2
9 //  of the License, or (at your option) any later version.
10 //
11 //  This program is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //  GNU General Public License for more details.
15 //
16 //  You should have received a copy of the GNU General Public License
17 //  along with this program; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19 #include <config.h>
20
21 #include "mainloop.hpp"
22
23 #include <stdlib.h>
24 #include <SDL.h>
25 #include "video/drawing_context.hpp"
26 #include "control/joystickkeyboardcontroller.hpp"
27 #include "gui/menu.hpp"
28 #include "audio/sound_manager.hpp"
29 #include "scripting/time_scheduler.hpp"
30 #include "scripting/squirrel_util.hpp"
31 #include "gameconfig.hpp"
32 #include "constants.hpp"
33 #include "main.hpp"
34 #include "resources.hpp"
35 #include "screen.hpp"
36 #include "screen_fade.hpp"
37 #include "timer.hpp"
38 #include "player_status.hpp"
39 #include "video/renderer.hpp"
40 #include "random_generator.hpp"
41
42 /** ticks (as returned from SDL_GetTicks) per frame */
43 static const Uint32 TICKS_PER_FRAME = (Uint32) (1000.0 / LOGICAL_FPS);
44 /** don't skip more than every 2nd frame */
45 static const int MAX_FRAME_SKIP = 2;
46
47 float game_speed = 1.0f;
48
49 MainLoop* main_loop = NULL;
50
51 MainLoop::MainLoop()
52   : speed(1.0), nextpop(false), nextpush(false), fps(0), screenshot_requested(false)
53 {
54   using namespace Scripting;
55   TimeScheduler::instance = new TimeScheduler();
56 }
57
58 MainLoop::~MainLoop()
59 {
60   using namespace Scripting;
61   delete TimeScheduler::instance;
62   TimeScheduler::instance = NULL;
63
64   for(std::vector<Screen*>::iterator i = screen_stack.begin();
65       i != screen_stack.end(); ++i) {
66     delete *i;
67   }
68 }
69
70 void
71 MainLoop::push_screen(Screen* screen, ScreenFade* screen_fade)
72 {
73   this->next_screen.reset(screen);
74   this->screen_fade.reset(screen_fade);
75   nextpush = !nextpop;
76   nextpop = false;
77   speed = 1.0f;
78 }
79
80 void
81 MainLoop::exit_screen(ScreenFade* screen_fade)
82 {
83   next_screen.reset(NULL);
84   this->screen_fade.reset(screen_fade);
85   nextpop = true;
86   nextpush = false;
87 }
88
89 void
90 MainLoop::set_screen_fade(ScreenFade* screen_fade)
91 {
92   this->screen_fade.reset(screen_fade);
93 }
94
95 void
96 MainLoop::quit(ScreenFade* screen_fade)
97 {
98   for(std::vector<Screen*>::iterator i = screen_stack.begin();
99           i != screen_stack.end(); ++i)
100     delete *i;
101   screen_stack.clear();
102
103   exit_screen(screen_fade);
104 }
105
106 void
107 MainLoop::set_speed(float speed)
108 {
109   this->speed = speed;
110 }
111
112 float
113 MainLoop::get_speed() const
114 {
115   return speed;
116 }
117
118 void
119 MainLoop::draw_fps(DrawingContext& context, float fps_fps)
120 {
121   char str[60];
122   snprintf(str, sizeof(str), "%3.1f", fps_fps);
123   const char* fpstext = "FPS";
124   context.draw_text(small_font, fpstext, Vector(SCREEN_WIDTH - small_font->get_text_width(fpstext) - small_font->get_text_width(" 99999") - BORDER_X, BORDER_Y + 20), ALIGN_LEFT, LAYER_HUD);
125   context.draw_text(small_font, str, Vector(SCREEN_WIDTH - BORDER_X, BORDER_Y + 20), ALIGN_RIGHT, LAYER_HUD);
126 }
127
128 void
129 MainLoop::draw(DrawingContext& context)
130 {
131   static Uint32 fps_ticks = SDL_GetTicks();
132   static int frame_count = 0;
133
134   current_screen->draw(context);
135   if(Menu::current() != NULL)
136     Menu::current()->draw(context);
137   if(screen_fade.get() != NULL)
138     screen_fade->draw(context);
139   Console::instance->draw(context);
140
141   if(config->show_fps)
142     draw_fps(context, fps);
143
144   // if a screenshot was requested, pass request on to drawing_context
145   if (screenshot_requested) {
146     context.take_screenshot();
147     screenshot_requested = false;
148   }
149   context.do_drawing();
150
151   /* Calculate frames per second */
152   if(config->show_fps)
153   {
154     ++frame_count;
155
156     if(SDL_GetTicks() - fps_ticks >= 500)
157     {
158       fps = (float) frame_count / .5;
159       frame_count = 0;
160       fps_ticks = SDL_GetTicks();
161     }
162   }
163 }
164
165 void
166 MainLoop::update_gamelogic(float elapsed_time)
167 {
168   Scripting::update_debugger();
169   Scripting::TimeScheduler::instance->update(game_time);
170   current_screen->update(elapsed_time);
171   if (Menu::current() != NULL)
172         Menu::current()->update();
173   if(screen_fade.get() != NULL)
174     screen_fade->update(elapsed_time);
175   Console::instance->update(elapsed_time);
176 }
177
178 void
179 MainLoop::process_events()
180 {
181   main_controller->update();
182   Uint8* keystate = SDL_GetKeyState(NULL);
183   SDL_Event event;
184   while(SDL_PollEvent(&event)) 
185     {
186       main_controller->process_event(event);
187
188       if(Menu::current() != NULL)
189         Menu::current()->event(event);
190
191       switch(event.type)
192         {
193           case SDL_QUIT:
194             quit();
195             break;
196               
197           case SDL_VIDEORESIZE:
198             Renderer::instance()->resize(event.resize.w, event.resize.h);
199             Menu::recalc_pos();
200             break;
201             
202           case SDL_KEYDOWN:
203             if (event.key.keysym.sym == SDLK_F10)
204               {
205                 config->show_fps = !config->show_fps;
206               }
207             if (event.key.keysym.sym == SDLK_F11) 
208               {
209                 config->use_fullscreen = !config->use_fullscreen;
210                 init_video();
211                 Menu::recalc_pos();
212               }
213             else if (event.key.keysym.sym == SDLK_PRINT ||
214                      event.key.keysym.sym == SDLK_F12)
215               {
216                 take_screenshot();
217               }
218             else if (event.key.keysym.sym == SDLK_F1 &&
219                      (keystate[SDLK_LCTRL] || keystate[SDLK_RCTRL]) &&
220                      keystate[SDLK_c])
221               {
222                 Console::instance->toggle();
223                 config->console_enabled = true;
224                 config->save();
225               }
226             break;
227         }
228     }
229 }
230
231 void
232 MainLoop::handle_screen_switch()
233 {
234   while( (next_screen.get() != NULL || nextpop) &&
235       (screen_fade.get() == NULL || screen_fade->done())) {
236     if(current_screen.get() != NULL) {
237       current_screen->leave();
238     }
239
240     if(nextpop) {
241       if(screen_stack.empty()) {
242         running = false;
243         break;
244       }
245       next_screen.reset(screen_stack.back());
246       screen_stack.pop_back();
247     }
248     if(nextpush && current_screen.get() != NULL) {
249       screen_stack.push_back(current_screen.release());
250     }
251
252     nextpush = false;
253     nextpop = false;
254     speed = 1.0;
255     Screen* next_screen_ptr = next_screen.release();
256     next_screen.reset(0);
257     if(next_screen_ptr)
258       next_screen_ptr->setup();
259     current_screen.reset(next_screen_ptr);
260     screen_fade.reset(NULL);
261
262     waiting_threads.wakeup();
263   }
264 }
265
266 void
267 MainLoop::run(DrawingContext &context)
268 {
269   Uint32 last_ticks = 0;
270   Uint32 elapsed_ticks = 0;
271
272   running = true;
273   while(running) {
274
275     handle_screen_switch();
276     if(!running || current_screen.get() == NULL)
277       break;
278
279     Uint32 ticks = SDL_GetTicks();
280     elapsed_ticks += ticks - last_ticks;
281     last_ticks = ticks;
282
283     Uint32 ticks_per_frame = (Uint32) (TICKS_PER_FRAME * game_speed);
284
285     if (elapsed_ticks > ticks_per_frame*4) {
286       // when the game loads up or levels are switched the
287       // elapsed_ticks grows extremely large, so we just ignore those
288       // large time jumps
289       elapsed_ticks = 0;
290     }
291
292     int frames = 0;
293
294     if (elapsed_ticks > ticks_per_frame) 
295       {
296         while(elapsed_ticks > ticks_per_frame && frames < MAX_FRAME_SKIP) 
297           {
298             elapsed_ticks -= ticks_per_frame;
299             float timestep = 1.0 / LOGICAL_FPS;
300             real_time += timestep;
301             timestep *= speed;
302             game_time += timestep;
303
304             process_events();
305             update_gamelogic(timestep);
306             frames += 1;
307           }
308
309         draw(context);
310       }
311
312     sound_manager->update();
313
314     SDL_Delay(0);
315   }
316 }
317
318 void 
319 MainLoop::take_screenshot()
320 {
321   screenshot_requested = true;
322 }
323