Console toggle now on caret key / Started work on Backspace, Autocomplete, Scroll...
[supertux.git] / src / game_session.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 #include <config.h>
22
23 #include <fstream>
24 #include <sstream>
25 #include <cassert>
26 #include <cstdio>
27 #include <cstdlib>
28 #include <cmath>
29 #include <cstring>
30 #include <cerrno>
31 #include <unistd.h>
32 #include <ctime>
33 #include <stdexcept>
34
35 #include <SDL.h>
36
37 #include "game_session.hpp"
38 #include "msg.hpp"
39 #include "worldmap.hpp"
40 #include "mainloop.hpp"
41 #include "video/screen.hpp"
42 #include "audio/sound_manager.hpp"
43 #include "gui/menu.hpp"
44 #include "sector.hpp"
45 #include "level.hpp"
46 #include "tile.hpp"
47 #include "player_status.hpp"
48 #include "object/particlesystem.hpp"
49 #include "object/background.hpp"
50 #include "object/gradient.hpp"
51 #include "object/tilemap.hpp"
52 #include "object/camera.hpp"
53 #include "object/player.hpp"
54 #include "object/level_time.hpp"
55 #include "lisp/lisp.hpp"
56 #include "lisp/parser.hpp"
57 #include "resources.hpp"
58 #include "worldmap.hpp"
59 #include "misc.hpp"
60 #include "statistics.hpp"
61 #include "timer.hpp"
62 #include "object/fireworks.hpp"
63 #include "textscroller.hpp"
64 #include "control/codecontroller.hpp"
65 #include "control/joystickkeyboardcontroller.hpp"
66 #include "main.hpp"
67 #include "file_system.hpp"
68 #include "gameconfig.hpp"
69 #include "gettext.hpp"
70 #include "exceptions.hpp"
71 #include "flip_level_transformer.hpp"
72
73 // the engine will be run with a logical framerate of 64fps.
74 // We chose 64fps here because it is a power of 2, so 1/64 gives an "even"
75 // binary fraction...
76 static const float LOGICAL_FPS = 64.0;
77
78 namespace {
79   const char* consoleCommands[] = {
80           "foo",
81           "whereami",
82           "camera",
83           "grow", 
84           "fire", 
85           "ice",
86           "lifeup",
87           "numberofthebeast",
88           "lifedown",
89           "grease",
90           "invincible",
91           "mortal",
92           "shrink", 
93           "kill",
94           "gotoend",
95           "flip",
96           "finish"
97   };
98 }
99
100 using namespace WorldMapNS;
101
102 GameSession* GameSession::current_ = 0;
103
104 GameSession::GameSession(const std::string& levelfile_, GameSessionMode mode,
105     Statistics* statistics)
106   : level(0), currentsector(0), mode(mode),
107     end_sequence(NO_ENDSEQUENCE), end_sequence_controller(0),
108     levelfile(levelfile_), best_level_statistics(statistics),
109     capture_demo_stream(0), playback_demo_stream(0), demo_controller(0)
110 {
111   current_ = this;
112   currentsector = 0;
113   
114   game_pause = false;
115   fps_fps = 0;
116
117   for (uint16_t i=0; i < sizeof(::consoleCommands)/sizeof(typeof(consoleCommands[0])); i++) {
118     Console::registerCommand(consoleCommands[i], this);
119   }
120
121   restart_level(true);
122 }
123
124 void
125 GameSession::restart_level(bool fromBeginning)
126 {
127   game_pause   = false;
128   end_sequence = NO_ENDSEQUENCE;
129
130   main_controller->reset();
131
132   delete level;
133   currentsector = 0;
134
135   level = new Level;
136   level->load(levelfile);
137
138   global_stats.reset();
139   global_stats.set_total_points(COINS_COLLECTED_STAT, level->get_total_coins());
140   global_stats.set_total_points(BADGUYS_KILLED_STAT, level->get_total_badguys());
141   
142   // get time
143   int time = 0;
144   for(std::vector<Sector*>::iterator i = level->sectors.begin(); i != level->sectors.end(); ++i)
145   {
146     Sector* sec = *i;
147
148     for(std::vector<GameObject*>::iterator j = sec->gameobjects.begin();
149         j != sec->gameobjects.end(); ++j)
150     {
151       GameObject* obj = *j;
152       
153       LevelTime* lt = dynamic_cast<LevelTime*> (obj);
154       if(lt)
155         time += int(lt->get_level_time());
156     }
157   }
158   global_stats.set_total_points(TIME_NEEDED_STAT, (time == 0) ? -1 : time);
159
160   if (fromBeginning) reset_sector="";
161   if(reset_sector != "") {
162     currentsector = level->get_sector(reset_sector);
163     if(!currentsector) {
164       std::stringstream msg;
165       msg << "Couldn't find sector '" << reset_sector << "' for resetting tux.";
166       throw std::runtime_error(msg.str());
167     }
168     currentsector->activate(reset_pos);
169   } else {
170     currentsector = level->get_sector("main");
171     if(!currentsector)
172       throw std::runtime_error("Couldn't find main sector");
173     currentsector->activate("main");
174   }
175   
176   if(mode == ST_GL_PLAY || mode == ST_GL_LOAD_LEVEL_FILE)
177     levelintro();
178
179   currentsector->play_music(LEVEL_MUSIC);
180
181   if(capture_file != "")
182     record_demo(capture_file);
183 }
184
185 GameSession::~GameSession()
186 {
187   delete capture_demo_stream;
188   delete playback_demo_stream;
189   delete demo_controller;
190
191   delete end_sequence_controller;
192   delete level;
193   for (uint16_t i=0; i < sizeof(::consoleCommands)/sizeof(typeof(consoleCommands[0])); i++) {
194     Console::unregisterCommand(consoleCommands[i], this);
195   }
196
197   current_ = NULL;
198 }
199
200 void
201 GameSession::record_demo(const std::string& filename)
202 {
203   delete capture_demo_stream;
204   
205   capture_demo_stream = new std::ofstream(filename.c_str()); 
206   if(!capture_demo_stream->good()) {
207     std::stringstream msg;
208     msg << "Couldn't open demo file '" << filename << "' for writing.";
209     throw std::runtime_error(msg.str());
210   }
211   capture_file = filename;
212 }
213
214 void
215 GameSession::play_demo(const std::string& filename)
216 {
217   delete playback_demo_stream;
218   delete demo_controller;
219   
220   playback_demo_stream = new std::ifstream(filename.c_str());
221   if(!playback_demo_stream->good()) {
222     std::stringstream msg;
223     msg << "Couldn't open demo file '" << filename << "' for reading.";
224     throw std::runtime_error(msg.str());
225   }
226
227   Player& tux = *currentsector->player;
228   demo_controller = new CodeController();
229   tux.set_controller(demo_controller);
230 }
231
232 void
233 GameSession::levelintro()
234 {
235   char str[60];
236
237   sound_manager->stop_music();
238
239   DrawingContext context;
240   for(Sector::GameObjects::iterator i = currentsector->gameobjects.begin();
241       i != currentsector->gameobjects.end(); ++i) {
242     Background* background = dynamic_cast<Background*> (*i);
243     if(background) {
244       background->draw(context);
245     }
246     Gradient* gradient = dynamic_cast<Gradient*> (*i);
247     if(gradient) {
248       gradient->draw(context);
249     }
250   }
251
252 //  context.draw_text(gold_text, level->get_name(), Vector(SCREEN_WIDTH/2, 160),
253 //      CENTER_ALLIGN, LAYER_FOREGROUND1);
254   context.draw_center_text(gold_text, level->get_name(), Vector(0, 160),
255       LAYER_FOREGROUND1);
256
257   sprintf(str, "Coins: %d", player_status->coins);
258   context.draw_text(white_text, str, Vector(SCREEN_WIDTH/2, 210),
259       CENTER_ALLIGN, LAYER_FOREGROUND1);
260
261   if((level->get_author().size()) && (level->get_author() != "SuperTux Team"))
262     //TODO make author check case/blank-insensitive
263     context.draw_text(white_small_text,
264       std::string(_("contributed by ")) + level->get_author(), 
265       Vector(SCREEN_WIDTH/2, 350), CENTER_ALLIGN, LAYER_FOREGROUND1);
266
267
268   if(best_level_statistics != NULL)
269     best_level_statistics->draw_message_info(context, _("Best Level Statistics"));
270
271   wait_for_event(1.0, 3.0);
272 }
273
274 void
275 GameSession::on_escape_press()
276 {
277   if(currentsector->player->is_dying() || end_sequence != NO_ENDSEQUENCE)
278     return;   // don't let the player open the menu, when he is dying
279   
280   if(mode == ST_GL_TEST) {
281     main_loop->exit_screen();
282   } else if (!Menu::current()) {
283     Menu::set_current(game_menu);
284     game_menu->set_active_item(MNID_CONTINUE);
285     game_pause = true;
286   } else {
287     game_pause = false;
288   }
289 }
290
291 void
292 GameSession::process_events()
293 {
294   Player& tux = *currentsector->player;
295
296   // end of pause mode?
297   if(!Menu::current() && game_pause) {
298     game_pause = false;
299   }
300
301   if (end_sequence != NO_ENDSEQUENCE) {
302     if(end_sequence_controller == 0) {
303       end_sequence_controller = new CodeController();
304       tux.set_controller(end_sequence_controller);
305     }
306
307     end_sequence_controller->press(Controller::RIGHT);
308     
309     if (int(last_x_pos) == int(tux.get_pos().x))
310       end_sequence_controller->press(Controller::JUMP);    
311     last_x_pos = tux.get_pos().x;
312   }
313
314   // playback a demo?
315   if(playback_demo_stream != 0) {
316     demo_controller->update();
317     char left = false;
318     char right = false;
319     char up = false;
320     char down = false;
321     char jump = false;
322     char action = false;
323     playback_demo_stream->get(left);
324     playback_demo_stream->get(right);
325     playback_demo_stream->get(up);
326     playback_demo_stream->get(down);
327     playback_demo_stream->get(jump);
328     playback_demo_stream->get(action);
329     demo_controller->press(Controller::LEFT, left);
330     demo_controller->press(Controller::RIGHT, right);
331     demo_controller->press(Controller::UP, up);
332     demo_controller->press(Controller::DOWN, down);
333     demo_controller->press(Controller::JUMP, jump);
334     demo_controller->press(Controller::ACTION, action);
335   }
336
337   // save input for demo?
338   if(capture_demo_stream != 0) {
339     capture_demo_stream ->put(main_controller->hold(Controller::LEFT));
340     capture_demo_stream ->put(main_controller->hold(Controller::RIGHT));
341     capture_demo_stream ->put(main_controller->hold(Controller::UP));
342     capture_demo_stream ->put(main_controller->hold(Controller::DOWN));
343     capture_demo_stream ->put(main_controller->hold(Controller::JUMP));   
344     capture_demo_stream ->put(main_controller->hold(Controller::ACTION));
345   }
346 }
347
348 bool
349 GameSession::consoleCommand(std::string command)
350 {
351   if (command == "foo") {
352     msg_info("bar");
353     return true;
354   }
355
356   if (currentsector == 0) return false;
357   Player& tux = *currentsector->player;
358   
359   // Cheating words (the goal of this is really for debugging,
360   // but could be used for some cheating, nothing wrong with that)
361   if (command == "grow") {
362     tux.set_bonus(GROWUP_BONUS, false);
363     return true;
364   }
365   if (command == "fire") {
366     tux.set_bonus(FIRE_BONUS, false);
367     return true;
368   }
369   if (command == "ice") {
370     tux.set_bonus(ICE_BONUS, false);
371     return true;
372   }
373   if (command == "lifeup") {
374     player_status->incLives();
375     return true;
376   }
377   if (command == "numberofthebeast") {
378     player_status->coins += 55;
379     return true;
380   }
381   if (command == "lifedown") {
382     player_status->coins = std::max(player_status->coins-25, 0);
383     return true;
384   }
385   if (command == "grease") {
386     tux.physic.set_velocity_x(tux.physic.get_velocity_x()*3);
387     return true;
388   }
389   if (command == "invincible") {
390     // be invincle for the rest of the level
391     tux.invincible_timer.start(10000);
392     return true;
393   }
394   if (command == "mortal") {
395     // give up invincibility
396     tux.invincible_timer.stop();
397     return true;
398   }
399   if (command == "shrink") {
400     // remove powerups
401     tux.kill(tux.SHRINK);
402     return true;
403   }
404   if (command == "kill") {
405     tux.kill(tux.KILL);
406     return true;
407   }
408   if (command == "whereami") {
409     msg_info("You are at x " << tux.get_pos().x << ", y " << tux.get_pos().y);
410     return true;
411   }
412   if (command == "gotoend") {
413     // goes to the end of the level
414     tux.move(Vector(
415           (currentsector->solids->get_width()*32) - (SCREEN_WIDTH*2), 0));
416     currentsector->camera->reset(
417         Vector(tux.get_pos().x, tux.get_pos().y));
418     return true;
419   }
420   if (command == "flip") {
421     FlipLevelTransformer flip_transformer;
422     flip_transformer.transform(GameSession::current()->get_current_level());
423     return true;
424   }
425   if (command == "finish") {
426     finish(true);
427     return true;
428   }
429   if (command == "camera") {
430     msg_info("Camera is at " 
431               << Sector::current()->camera->get_translation().x << "," 
432               << Sector::current()->camera->get_translation().y);
433     return true;
434   }
435
436   return false;
437 }
438
439 void
440 GameSession::check_end_conditions()
441 {
442   Player* tux = currentsector->player;
443
444   /* End of level? */
445   if(end_sequence && endsequence_timer.check()) {
446     finish(true);
447     
448     // add time spent to statistics
449     int tottime = 0, remtime = 0;
450     for(std::vector<Sector*>::iterator i = level->sectors.begin(); i != level->sectors.end(); ++i)
451     {
452       Sector* sec = *i;
453
454       for(std::vector<GameObject*>::iterator j = sec->gameobjects.begin();
455           j != sec->gameobjects.end(); ++j)
456       {
457         GameObject* obj = *j;
458
459         LevelTime* lt = dynamic_cast<LevelTime*> (obj);
460         if(lt)
461         {
462           tottime += int(lt->get_level_time());
463           remtime += int(lt->get_remaining_time());
464         }
465       }
466     }
467     global_stats.set_points(TIME_NEEDED_STAT, (tottime == 0 ? -1 : (tottime-remtime)));
468
469     return;
470   } else if (!end_sequence && tux->is_dead()) {
471     if (player_status->coins < 0) { 
472       // No more coins: restart level from beginning
473       player_status->coins = 0;
474       restart_level(true);
475     } else { 
476       // Still has coins: restart level from last reset point
477       restart_level(false);
478     }
479     
480     return;
481   }
482 }
483
484 void 
485 GameSession::draw(DrawingContext& context)
486 {
487   currentsector->draw(context);
488   drawstatus(context);
489
490   if(game_pause)
491     draw_pause(context);
492 }
493
494 void
495 GameSession::draw_pause(DrawingContext& context)
496 {
497   context.draw_filled_rect(
498       Vector(0,0), Vector(SCREEN_WIDTH, SCREEN_HEIGHT),
499       Color(.2, .2, .2, .5), LAYER_FOREGROUND1);
500 }
501   
502 void
503 GameSession::process_menu()
504 {
505   Menu* menu = Menu::current();
506   if(menu) {
507     menu->update();
508
509     if(menu == game_menu) {
510       switch (game_menu->check()) {
511         case MNID_CONTINUE:
512           Menu::set_current(0);
513           break;
514         case MNID_ABORTLEVEL:
515           Menu::set_current(0);
516           main_loop->exit_screen();
517           break;
518       }
519     } else if(menu == options_menu) {
520       process_options_menu();
521     }
522   }
523 }
524
525 void
526 GameSession::setup()
527 {
528   Menu::set_current(NULL);
529   current_ = this;
530
531   // Eat unneeded events
532   SDL_Event event;
533   while(SDL_PollEvent(&event))
534   {}
535 }
536
537 void
538 GameSession::update(float elapsed_time)
539 {
540   process_events();
541   process_menu();
542
543   check_end_conditions();
544
545   // handle controller
546   if(main_controller->pressed(Controller::PAUSE_MENU))
547     on_escape_press();
548   
549   // respawning in new sector?
550   if(newsector != "" && newspawnpoint != "") {
551     Sector* sector = level->get_sector(newsector);
552     if(sector == 0) {
553       msg_warning("Sector '" << newsector << "' not found");
554     }
555     sector->activate(newspawnpoint);
556     sector->play_music(LEVEL_MUSIC);
557     currentsector = sector;
558     newsector = "";
559     newspawnpoint = "";
560   }
561
562   // Update the world state and all objects in the world
563   if(!game_pause) {
564     // Update the world
565     if (end_sequence == ENDSEQUENCE_RUNNING) {
566       currentsector->update(elapsed_time/2);
567     } else if(end_sequence == NO_ENDSEQUENCE) {
568       if(!currentsector->player->growing_timer.started())
569         currentsector->update(elapsed_time);
570     } 
571   }
572
573   // update sounds
574   sound_manager->set_listener_position(currentsector->player->get_pos());
575
576   /* Handle music: */
577   if (currentsector->player->invincible_timer.started() && 
578       currentsector->player->invincible_timer.get_timeleft() 
579       > TUX_INVINCIBLE_TIME_WARNING && !end_sequence) {
580     currentsector->play_music(HERRING_MUSIC);
581   } else if(currentsector->get_music_type() != LEVEL_MUSIC && !end_sequence) {
582     currentsector->play_music(LEVEL_MUSIC);
583   }
584 }
585
586 #if 0
587 GameSession::ExitStatus
588 GameSession::run()
589 {
590   Menu::set_current(0);
591   current_ = this;
592   
593   int fps_cnt = 0;
594
595   // Eat unneeded events
596   SDL_Event event;
597   while(SDL_PollEvent(&event))
598   {}
599
600   draw();
601
602   Uint32 fps_ticks = SDL_GetTicks();
603   Uint32 fps_nextframe_ticks = SDL_GetTicks();
604   Uint32 ticks;
605   bool skipdraw = false;
606
607   while (exit_status == ES_NONE) {
608     // we run in a logical framerate so elapsed time is a constant
609     // This will make the game run determistic and not different on different
610     // machines
611     static const float elapsed_time = 1.0 / LOGICAL_FPS;
612     // old code... float elapsed_time = float(ticks - lastticks) / 1000.;
613     if(!game_pause)
614       game_time += elapsed_time;
615
616     // regulate fps
617     ticks = SDL_GetTicks();
618     if(ticks > fps_nextframe_ticks) {
619       if(skipdraw == true) {
620         // already skipped last frame? we have to slow down the game then...
621         skipdraw = false;
622         fps_nextframe_ticks -= (Uint32) (1000.0 / LOGICAL_FPS);
623       } else {
624         // don't draw all frames when we're getting too slow
625         skipdraw = true;
626       }
627     } else {
628       skipdraw = false;
629       while(fps_nextframe_ticks > ticks) {
630         /* just wait */
631         // If we really have to wait long, then do an imprecise SDL_Delay()
632         Uint32 diff = fps_nextframe_ticks - ticks;
633         if(diff > 15) {
634           SDL_Delay(diff - 10);
635         } 
636         ticks = SDL_GetTicks();
637       }
638     }
639     fps_nextframe_ticks = ticks + (Uint32) (1000.0 / LOGICAL_FPS);
640
641     process_events();
642     process_menu();
643
644     // Update the world state and all objects in the world
645     if(!game_pause)
646     {
647       // Update the world
648       check_end_conditions();
649       if (end_sequence == ENDSEQUENCE_RUNNING)
650         update(elapsed_time/2);
651       else if(end_sequence == NO_ENDSEQUENCE)
652         update(elapsed_time);
653     }
654
655     if(!skipdraw)
656       draw();
657
658     // update sounds
659     sound_manager->update();
660
661     /* Time stops in pause mode */
662     if(game_pause || Menu::current())
663     {
664       continue;
665     }
666
667     /* Handle music: */
668     if (currentsector->player->invincible_timer.started() && 
669             currentsector->player->invincible_timer.get_timeleft() 
670             > TUX_INVINCIBLE_TIME_WARNING && !end_sequence)
671     {
672       currentsector->play_music(HERRING_MUSIC);
673     }
674     /* or just normal music? */
675     else if(currentsector->get_music_type() != LEVEL_MUSIC && !end_sequence)
676     {
677       currentsector->play_music(LEVEL_MUSIC);
678     }
679
680     /* Calculate frames per second */
681     if(config->show_fps)
682     {
683       ++fps_cnt;
684       
685       if(SDL_GetTicks() - fps_ticks >= 500)
686       {
687         fps_fps = (float) fps_cnt / .5;
688         fps_cnt = 0;
689         fps_ticks = SDL_GetTicks();
690       }
691     }
692   }
693  
694   // just in case
695   currentsector = 0;
696   main_controller->reset();
697   return exit_status;
698 }
699 #endif
700
701 void
702 GameSession::finish(bool win)
703 {
704   if(win) {
705     if(WorldMap::current())
706       WorldMap::current()->finished_level(levelfile);
707   }
708   
709   main_loop->exit_screen();
710 }
711
712 void
713 GameSession::respawn(const std::string& sector, const std::string& spawnpoint)
714 {
715   newsector = sector;
716   newspawnpoint = spawnpoint;
717 }
718
719 void
720 GameSession::set_reset_point(const std::string& sector, const Vector& pos)
721 {
722   reset_sector = sector;
723   reset_pos = pos;
724 }
725
726 std::string
727 GameSession::get_working_directory()
728 {
729   return FileSystem::dirname(levelfile);
730 }
731
732 void
733 GameSession::display_info_box(const std::string& text)
734 {
735   InfoBox* box = new InfoBox(text);
736
737   bool running = true;
738   DrawingContext context;
739   
740   while(running)  {
741
742     main_controller->update();
743     SDL_Event event;
744     while (SDL_PollEvent(&event)) {
745       main_controller->process_event(event);
746       if(event.type == SDL_QUIT)
747         throw graceful_shutdown();
748     }
749
750     if(main_controller->pressed(Controller::JUMP)
751         || main_controller->pressed(Controller::ACTION)
752         || main_controller->pressed(Controller::PAUSE_MENU)
753         || main_controller->pressed(Controller::MENU_SELECT))
754       running = false;
755     else if(main_controller->pressed(Controller::DOWN))
756       box->scrolldown();
757     else if(main_controller->pressed(Controller::UP))
758       box->scrollup();
759     box->draw(context);
760     draw(context);
761     context.do_drawing();
762     sound_manager->update();
763   }
764
765   delete box;
766 }
767
768 void
769 GameSession::start_sequence(const std::string& sequencename)
770 {
771   if(sequencename == "endsequence" || sequencename == "fireworks") {
772     if(end_sequence)
773       return;
774
775     end_sequence = ENDSEQUENCE_RUNNING;
776     endsequence_timer.start(7.3);
777     last_x_pos = -1;
778     sound_manager->play_music("music/leveldone.ogg", false);
779     currentsector->player->invincible_timer.start(7.3);
780
781     // Stop all clocks.
782     for(std::vector<GameObject*>::iterator i = currentsector->gameobjects.begin();
783         i != currentsector->gameobjects.end(); ++i)
784     {
785       GameObject* obj = *i;
786
787       LevelTime* lt = dynamic_cast<LevelTime*> (obj);
788       if(lt)
789         lt->stop();
790     }
791
792     if(sequencename == "fireworks") {
793       currentsector->add_object(new Fireworks());
794     }
795   } else if(sequencename == "stoptux") {
796     if(!end_sequence) {
797       msg_warning("Final target reached without "
798         << "an active end sequence");
799       this->start_sequence("endsequence");
800     }
801     end_sequence =  ENDSEQUENCE_WAITING;
802   } else {
803     msg_warning("Unknown sequence '" << sequencename << "'");
804   }
805 }
806
807 /* (Status): */
808 void
809 GameSession::drawstatus(DrawingContext& context)
810 {
811   player_status->draw(context);
812
813   if(config->show_fps) {
814     char str[60];
815     snprintf(str, sizeof(str), "%3.1f", fps_fps);
816     const char* fpstext = "FPS";
817     context.draw_text(white_text, fpstext, Vector(SCREEN_WIDTH - white_text->get_text_width(fpstext) - gold_text->get_text_width(" 99999") - BORDER_X, BORDER_Y + 20), LEFT_ALLIGN, LAYER_FOREGROUND1);
818     context.draw_text(gold_text, str, Vector(SCREEN_WIDTH - BORDER_X, BORDER_Y + 20), RIGHT_ALLIGN, LAYER_FOREGROUND1);
819   }
820 }
821