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