Fix for #453 (Menu frame lingers)
[supertux.git] / src / title.cpp
1 //  $Id$
2 //
3 //  SuperTux
4 //  Copyright (C) 2004 Tobias Glaesser <tobi.web@gmx.de>
5 //  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
6 //
7 //  This program is free software; you can redistribute it and/or
8 //  modify it under the terms of the GNU General Public License
9 //  as published by the Free Software Foundation; either version 2
10 //  of the License, or (at your option) any later version.
11 //
12 //  This program is distributed in the hope that it will be useful,
13 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
14 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 //  GNU General Public License for more details.
16 //
17 //  You should have received a copy of the GNU General Public License
18 //  along with this program; if not, write to the Free Software
19 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20 //  02111-1307, USA.
21
22 #include <config.h>
23 #include <version.h>
24
25 #include "title.hpp"
26
27 #include "addon/addon_manager.hpp"
28 #include "audio/sound_manager.hpp"
29 #include "fadeout.hpp"
30 #include "file_system.hpp"
31 #include "gameconfig.hpp"
32 #include "gettext.hpp"
33 #include "gui/menu.hpp"
34 #include "lisp/parser.hpp"
35 #include "log.hpp"
36 #include "main.hpp"
37 #include "mainloop.hpp"
38 #include "object/camera.hpp"
39 #include "object/player.hpp"
40 #include "options_menu.hpp"
41 #include "resources.hpp"
42 #include "sector.hpp"
43 #include "textscroller.hpp"
44 #include "video/drawing_context.hpp"
45 #include "world.hpp"
46
47 #include <algorithm>
48 #include <physfs.h>
49
50 enum MainMenuIDs {
51   MNID_STARTGAME,
52   MNID_LEVELS_CONTRIB,
53   MNID_ADDONS,
54   MNID_OPTIONMENU,
55   MNID_LEVELEDITOR,
56   MNID_CREDITS,
57   MNID_QUITMAINMENU
58 };
59
60 void
61 TitleScreen::update_load_game_menu()
62 {
63 }
64
65 void
66 TitleScreen::free_contrib_menu()
67 {
68   for(std::vector<World*>::iterator i = contrib_worlds.begin();
69       i != contrib_worlds.end(); ++i)
70     delete *i;
71
72   contrib_worlds.clear();
73 }
74
75 void
76 TitleScreen::generate_contrib_menu()
77 {
78   /** Generating contrib levels list by making use of Level Subset  */
79   std::vector<std::string> level_worlds;
80   char** files = PHYSFS_enumerateFiles("levels/");
81   for(const char* const* filename = files; *filename != 0; ++filename) {
82     std::string filepath = std::string("levels/") + *filename;
83     if(PHYSFS_isDirectory(filepath.c_str()))
84       level_worlds.push_back(filepath);
85   }
86   PHYSFS_freeList(files);
87
88   free_contrib_menu();
89   contrib_menu.reset(new Menu());
90
91   contrib_menu->add_label(_("Contrib Levels"));
92   contrib_menu->add_hl();
93
94   int i = 0;
95   for (std::vector<std::string>::iterator it = level_worlds.begin();
96       it != level_worlds.end(); ++it) {
97     try {
98       std::auto_ptr<World> world (new World());
99       world->load(*it + "/info");
100       if(world->hide_from_contribs) {
101         continue;
102       }
103       contrib_menu->add_entry(i++, world->title);
104       contrib_worlds.push_back(world.release());
105     } catch(std::exception& e) {
106 #ifdef DEBUG
107       log_warning << "Couldn't parse levelset info for '" << *it << "': " << e.what() << std::endl;
108 #endif
109     }
110   }
111
112   contrib_menu->add_hl();
113   contrib_menu->add_back(_("Back"));
114 }
115
116 std::string
117 TitleScreen::get_level_name(const std::string& filename)
118 {
119   try {
120     lisp::Parser parser;
121     const lisp::Lisp* root = parser.parse(filename);
122
123     const lisp::Lisp* level = root->get_lisp("supertux-level");
124     if(!level)
125       return "";
126
127     std::string name;
128     level->get("name", name);
129     return name;
130   } catch(std::exception& e) {
131       log_warning << "Problem getting name of '" << filename << "': "
132                   << e.what() << std::endl;
133     return "";
134   }
135 }
136
137 void
138 TitleScreen::check_levels_contrib_menu()
139 {
140   int index = contrib_menu->check();
141   if (index == -1)
142     return;
143
144   current_world = contrib_worlds[index];
145
146   if(!current_world->is_levelset) {
147     start_game();
148   } else {
149     contrib_world_menu.reset(new Menu());
150
151     contrib_world_menu->add_label(current_world->title);
152     contrib_world_menu->add_hl();
153
154     for (unsigned int i = 0; i < current_world->get_num_levels(); ++i)
155     {
156       /** get level's title */
157       std::string filename = current_world->get_level_filename(i);
158       std::string title = get_level_name(filename);
159       contrib_world_menu->add_entry(i, title);
160     }
161
162     contrib_world_menu->add_hl();
163     contrib_world_menu->add_back(_("Back"));
164
165     Menu::push_current(contrib_world_menu.get());
166   }
167 }
168
169 void
170 TitleScreen::check_contrib_world_menu()
171 {
172   int index = contrib_world_menu->check();
173   if (index != -1) {
174     if (contrib_world_menu->get_item_by_id(index).kind == MN_ACTION) {
175       sound_manager->stop_music();
176       GameSession* session =
177         new GameSession(current_world->get_level_filename(index));
178       main_loop->push_screen(session);
179     }
180   }
181 }
182
183 namespace {
184   bool generate_addons_menu_sorter(const Addon* a1, const Addon* a2)
185   {
186     return a1->title < a2->title;
187   }
188
189   const int ADDON_LIST_START_ID = 10;
190 }
191
192 void
193 TitleScreen::generate_addons_menu()
194 {
195   AddonManager& adm = AddonManager::get_instance();
196
197   // refresh list of addons
198   addons = adm.get_addons();
199   
200   // sort list
201   std::sort(addons.begin(), addons.end(), generate_addons_menu_sorter);
202
203   // (re)generate menu
204   free_addons_menu();
205   addons_menu.reset(new Menu());
206
207   addons_menu->add_label(_("Add-ons"));
208   addons_menu->add_hl();
209   
210 #ifdef HAVE_LIBCURL
211   addons_menu->add_entry(0, std::string(_("Check Online")));
212 #else
213   addons_menu->add_inactive(0, std::string(_("Check Online (disabled)")));
214 #endif
215
216   //addons_menu->add_hl();
217
218   for (unsigned int i = 0; i < addons.size(); i++) {
219     const Addon& addon = *addons[i];
220     std::string text = "";
221     if (addon.kind != "") text += addon.kind + " ";
222     text += std::string("\"") + addon.title + "\"";
223     if (addon.author != "") text += " by \"" + addon.author + "\"";
224     addons_menu->add_toggle(ADDON_LIST_START_ID + i, text, addon.loaded);
225   }
226
227   addons_menu->add_hl();
228   addons_menu->add_back(_("Back"));
229 }
230
231 void
232 TitleScreen::check_addons_menu()
233 {
234   int index = addons_menu->check();
235   if (index == -1) return;
236
237   // check if "Check Online" was chosen
238   if (index == 0) {
239     try {
240       AddonManager::get_instance().check_online();
241       generate_addons_menu();
242       Menu::set_current(addons_menu.get());
243       addons_menu->set_active_item(index);
244     } 
245     catch (std::runtime_error e) {
246       log_warning << "Check for available Add-ons failed: " << e.what() << std::endl;
247     }
248     return;
249   }
250
251   // if one of the Addons listed was chosen, take appropriate action
252   if ((index >= ADDON_LIST_START_ID) && (index < ADDON_LIST_START_ID) + addons.size()) {
253     Addon& addon = *addons[index - ADDON_LIST_START_ID];
254     if (!addon.installed) {
255       try {
256         AddonManager::get_instance().install(&addon);
257       } 
258       catch (std::runtime_error e) {
259         log_warning << "Installing Add-on failed: " << e.what() << std::endl;
260       }
261       addons_menu->set_toggled(index, addon.loaded);
262     } else if (!addon.loaded) {
263       try {
264         AddonManager::get_instance().enable(&addon);
265       } 
266       catch (std::runtime_error e) {
267         log_warning << "Enabling Add-on failed: " << e.what() << std::endl;
268       }
269       addons_menu->set_toggled(index, addon.loaded);
270     } else {
271       try {
272         AddonManager::get_instance().disable(&addon);
273       } 
274       catch (std::runtime_error e) {
275         log_warning << "Disabling Add-on failed: " << e.what() << std::endl;
276       }
277       addons_menu->set_toggled(index, addon.loaded);
278     }
279   }
280 }
281
282 void
283 TitleScreen::free_addons_menu()
284 {
285 }
286
287 void
288 TitleScreen::make_tux_jump()
289 {
290   static bool jumpWasReleased = true;
291   Sector* sector  = titlesession->get_current_sector();
292   Player* tux = sector->player;
293
294   controller->update();
295   controller->press(Controller::RIGHT);
296
297   // Check if we should press the jump button
298   Rect lookahead = tux->get_bbox();
299   lookahead.p2.x += 96;
300   bool pathBlocked = !sector->is_free_of_statics(lookahead);
301   if ((pathBlocked && jumpWasReleased) || !tux->on_ground()) {
302     controller->press(Controller::JUMP);
303     jumpWasReleased = false;
304   } else {
305     jumpWasReleased = true;
306   }
307
308   // Wrap around at the end of the level back to the beginning
309   if(sector->get_width() - 320 < tux->get_pos().x) {
310     sector->activate("main");
311     sector->camera->reset(tux->get_pos());
312   }
313 }
314
315 TitleScreen::TitleScreen()
316 {
317   controller.reset(new CodeController());
318   titlesession.reset(new GameSession("levels/misc/menu.stl"));
319
320   Player* player = titlesession->get_current_sector()->player;
321   player->set_controller(controller.get());
322   player->set_speedlimit(230); //MAX_WALK_XM
323
324   generate_main_menu();
325
326   frame = std::auto_ptr<Surface>(new Surface("images/engine/menu/frame.png"));
327 }
328
329 void
330 TitleScreen::generate_main_menu()
331 {
332   main_menu.reset(new Menu());
333   main_menu->set_pos(SCREEN_WIDTH/2, SCREEN_HEIGHT/2 + 35);
334   main_menu->add_entry(MNID_STARTGAME, _("Start Game"));
335   main_menu->add_entry(MNID_LEVELS_CONTRIB, _("Contrib Levels"));
336   main_menu->add_entry(MNID_ADDONS, _("Add-ons"));
337   main_menu->add_submenu(_("Options"), get_options_menu());
338   main_menu->add_entry(MNID_CREDITS, _("Credits"));
339   main_menu->add_entry(MNID_QUITMAINMENU, _("Quit"));
340 }
341
342 TitleScreen::~TitleScreen()
343 {
344 }
345
346 void
347 TitleScreen::setup()
348 {
349   player_status->reset();
350
351   Sector* sector = titlesession->get_current_sector();
352   if(Sector::current() != sector) {
353     sector->play_music(LEVEL_MUSIC);
354     sector->activate(sector->player->get_pos());
355   }
356
357   Menu::set_current(main_menu.get());
358 }
359
360 void
361 TitleScreen::leave()
362 {
363   Sector* sector = titlesession->get_current_sector();
364   sector->deactivate();
365   Menu::set_current(NULL);
366 }
367
368 void
369 TitleScreen::draw(DrawingContext& context)
370 {
371   Sector* sector  = titlesession->get_current_sector();
372   sector->draw(context);
373
374   // FIXME: Add something to scale the frame to the resolution of the screen
375   context.draw_surface(frame.get(), Vector(0,0),LAYER_FOREGROUND1);
376
377   context.draw_text(small_font, "SuperTux " PACKAGE_VERSION "\n",
378       Vector(5, SCREEN_HEIGHT - 50), ALIGN_LEFT, LAYER_FOREGROUND1);
379   context.draw_text(small_font,
380       _(
381 "Copyright (c) 2007 SuperTux Devel Team\n"
382 "This game comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to\n"
383 "redistribute it under certain conditions; see the file COPYING for details.\n"
384 ),
385       Vector(5, SCREEN_HEIGHT - 50 + small_font->get_height() + 5),
386       ALIGN_LEFT, LAYER_FOREGROUND1);
387 }
388
389 void
390 TitleScreen::update(float elapsed_time)
391 {
392   main_loop->set_speed(0.6f);
393   Sector* sector  = titlesession->get_current_sector();
394   sector->update(elapsed_time);
395
396   make_tux_jump();
397
398   Menu* menu = Menu::current();
399   if(menu) {
400     if(menu == main_menu.get()) {
401       switch (main_menu->check()) {
402         case MNID_STARTGAME:
403           // Start Game, ie. goto the slots menu
404           if(main_world.get() == NULL) {
405             main_world.reset(new World());
406             main_world->load("levels/world1/info");
407           }
408           current_world = main_world.get();
409           start_game();
410           break;
411
412         case MNID_LEVELS_CONTRIB:
413           // Contrib Menu
414           generate_contrib_menu();
415           Menu::push_current(contrib_menu.get());
416           break;
417
418         case MNID_ADDONS:
419           // Add-ons Menu
420           generate_addons_menu();
421           Menu::push_current(addons_menu.get());
422           break;
423
424         case MNID_CREDITS:
425           Menu::set_current(NULL);
426           main_loop->push_screen(new TextScroller("credits.txt"),
427                                  new FadeOut(0.5));
428           break;
429
430         case MNID_QUITMAINMENU:
431           main_loop->quit(new FadeOut(0.25));
432           sound_manager->stop_music(0.25);
433           break;
434       }
435     } else if(menu == contrib_menu.get()) {
436       check_levels_contrib_menu();
437     } else if(menu == addons_menu.get()) {
438       check_addons_menu();
439     } else if (menu == contrib_world_menu.get()) {
440       check_contrib_world_menu();
441     }
442   }
443
444   // reopen menu if user closed it (so that the app doesn't close when user
445   // accidently hit ESC)
446   if(Menu::current() == 0 && main_loop->has_no_pending_fadeout()) {
447     generate_main_menu();
448     Menu::set_current(main_menu.get());
449   }
450 }
451
452 void
453 TitleScreen::start_game()
454 {
455   Menu::set_current(NULL);
456   std::string basename = current_world->get_basedir();
457   basename = basename.substr(0, basename.length()-1);
458   std::string worlddirname = FileSystem::basename(basename);
459   std::ostringstream stream;
460   stream << "profile" << config->profile << "/" << worlddirname << ".stsg";
461   std::string slotfile = stream.str();
462
463   try {
464     current_world->set_savegame_filename(slotfile);
465     current_world->run();
466   } catch(std::exception& e) {
467     log_fatal << "Couldn't start world: " << e.what() << std::endl;
468   }
469 }