AC_SUBST([ICONV_LIBS], [$LIBICONV])
dnl ===========================================================================
+dnl Check for libcurl
+
+LIBCURL_CHECK_CONFIG
+AC_SUBST([LIBCURL_LIBS], [$LIBCURL])
+
+dnl ===========================================================================
dnl Check for SDL
SDL_VERSION=1.2.4
AM_PATH_SDL($SDL_VERSION,
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-01-23 17:49+0100\n"
+"POT-Creation-Date: 2007-01-26 00:54+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
#: src/control/joystickkeyboardcontroller.cpp:603
#: src/control/joystickkeyboardcontroller.cpp:697 src/options_menu.cpp:62
-#: src/title.cpp:88 src/title.cpp:139 src/title.cpp:190
+#: src/title.cpp:90 src/title.cpp:141 src/title.cpp:192 src/title.cpp:267
msgid "Back"
msgstr ""
msgid "Continue"
msgstr ""
-#: src/game_session.cpp:104 src/options_menu.cpp:49 src/title.cpp:277
+#: src/game_session.cpp:104 src/options_menu.cpp:49 src/title.cpp:362
#: src/worldmap/worldmap.cpp:152
msgid "Options"
msgstr ""
msgid "Time"
msgstr ""
-#: src/title.cpp:82 src/title.cpp:275
+#: src/title.cpp:84 src/title.cpp:359
msgid "Start Game"
msgstr ""
-#: src/title.cpp:117 src/title.cpp:276
+#: src/title.cpp:119 src/title.cpp:360
msgid "Contrib Levels"
msgstr ""
-#: src/title.cpp:278
+#: src/title.cpp:253 src/title.cpp:361
+msgid "Add-ons"
+msgstr ""
+
+#: src/title.cpp:260
+msgid "Remove"
+msgstr ""
+
+#: src/title.cpp:262
+msgid "Install"
+msgstr ""
+
+#: src/title.cpp:363
msgid "Credits"
msgstr ""
-#: src/title.cpp:279
+#: src/title.cpp:364
msgid "Quit"
msgstr ""
-#: src/title.cpp:318
+#: src/title.cpp:403
msgid ""
"Copyright (c) 2006 SuperTux Devel Team\n"
"This game comes with ABSOLUTELY NO WARRANTY. This is free software, and you "
"redistribute it under certain conditions; see the file COPYING for details.\n"
msgstr ""
-#: src/title.cpp:420
+#: src/title.cpp:512
msgid "Free"
msgstr ""
-#: src/title.cpp:420 src/title.cpp:425
+#: src/title.cpp:512 src/title.cpp:517
msgid "Slot"
msgstr ""
Application supertux : $(sources) $(wrapper_objects) : linkerfile ;
C++Flags supertux : -DAPPDATADIR=\'\"$(appdatadir)\"\' ;
LinkWith supertux : squirrel ;
-ExternalLibs supertux : SDL SDLIMAGE GL OPENAL VORBIS VORBISFILE OGG ICONV PHYSFS BINRELOC ;
+ExternalLibs supertux : SDL SDLIMAGE GL OPENAL VORBIS VORBISFILE OGG ICONV PHYSFS BINRELOC LIBCURL ;
Help supertux : "Build the supertux executable" ;
IncludeDir supertux : squirrel/include squirrel ;
--- /dev/null
+// $Id$
+//
+// SuperTux - Add-on
+// Copyright (C) 2007 Christoph Sommer <christoph.sommer@2007.expires.deltadevelopment.de>
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+
+#include "addon.hpp"
+#include "addon_manager.hpp"
+
+void
+Addon::install()
+{
+ AddonManager& adm = AddonManager::get_instance();
+ adm.install(*this);
+}
+
+void
+Addon::remove()
+{
+ AddonManager& adm = AddonManager::get_instance();
+ adm.remove(*this);
+}
+
--- /dev/null
+// $Id$
+//
+// SuperTux - Add-on
+// Copyright (C) 2007 Christoph Sommer <christoph.sommer@2007.expires.deltadevelopment.de>
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+#ifndef ADDON_H
+#define ADDON_H
+
+#include <string>
+#include <vector>
+
+/**
+ * Represents an (available or installed) Add-on, e.g. a level set
+ */
+class Addon
+{
+public:
+ std::string title;
+ std::string url;
+ std::string fname;
+ bool isInstalled;
+
+ /**
+ * Download and install Add-on
+ */
+ void install();
+
+ /**
+ * Physically delete Add-on
+ */
+ void remove();
+
+};
+
+#endif
+
--- /dev/null
+// $Id$
+//
+// SuperTux - Add-on Manager
+// Copyright (C) 2007 Christoph Sommer <christoph.sommer@2007.expires.deltadevelopment.de>
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+
+#include <stdexcept>
+#include <list>
+#include <physfs.h>
+#include <sys/stat.h>
+#include "addon_manager.hpp"
+#include "config.h"
+#include "log.hpp"
+
+#ifdef HAVE_LIBCURL
+#include <curl/curl.h>
+#include <curl/types.h>
+#include <curl/easy.h>
+#endif
+
+#ifdef HAVE_LIBCURL
+namespace {
+
+ size_t my_curl_string_append(void *ptr, size_t size, size_t nmemb, void *string_ptr)
+ {
+ std::string& s = *static_cast<std::string*>(string_ptr);
+ std::string buf(static_cast<char*>(ptr), size * nmemb);
+ s += buf;
+ log_debug << "read " << size * nmemb << " bytes of data..." << std::endl;
+ return size * nmemb;
+ }
+
+ size_t my_curl_physfs_write(void *ptr, size_t size, size_t nmemb, void *f_p)
+ {
+ PHYSFS_file* f = static_cast<PHYSFS_file*>(f_p);
+ PHYSFS_sint64 written = PHYSFS_write(f, ptr, size, nmemb);
+ log_debug << "read " << size * nmemb << " bytes of data..." << std::endl;
+ return size * written;
+ }
+
+}
+#endif
+
+AddonManager&
+AddonManager::get_instance()
+{
+ static AddonManager instance;
+ return instance;
+}
+
+AddonManager::AddonManager()
+{
+#ifdef HAVE_LIBCURL
+ curl_global_init(CURL_GLOBAL_ALL);
+#endif
+}
+
+AddonManager::~AddonManager()
+{
+#ifdef HAVE_LIBCURL
+ curl_global_cleanup();
+#endif
+}
+
+std::vector<Addon>
+AddonManager::get_addons() const
+{
+ std::vector<Addon> addons;
+
+ // first step: search for installed addons
+
+ // iterate over complete search path (i.e. directories and archives)
+ char **i = PHYSFS_getSearchPath();
+ if (!i) throw std::runtime_error("Could not query physfs search path");
+ for (; *i != NULL; i++) {
+
+ // get filename of potential archive
+ std::string fileName = *i;
+
+ // make sure it's in the writeDir
+ static const std::string writeDir = PHYSFS_getWriteDir();
+ if (fileName.compare(0, writeDir.length(), writeDir) != 0) continue;
+
+ // make sure it looks like an archive
+ static const std::string archiveExt = ".zip";
+ if (fileName.compare(fileName.length()-archiveExt.length(), archiveExt.length(), archiveExt) != 0) continue;
+
+ // make sure it exists
+ struct stat stats;
+ if (stat(fileName.c_str(), &stats) != 0) continue;
+
+ // make sure it's an actual file
+ if (!S_ISREG(stats.st_mode)) continue;
+
+ // extract nice title
+ static const char* dirSep = PHYSFS_getDirSeparator();
+ std::string::size_type n = fileName.rfind(dirSep) + 1;
+ if (n == std::string::npos) n = 0;
+ std::string title = fileName.substr(n, fileName.length() - n - archiveExt.length());
+
+ Addon addon;
+ addon.title = title;
+ addon.fname = fileName;
+ addon.isInstalled = true;
+
+ addons.push_back(addon);
+ }
+
+#ifdef HAVE_LIBCURL
+ // second step: search for available addons
+
+ // FIXME: This URL is just for testing!
+ const char* baseUrl = "http://www.deltadevelopment.de/users/christoph/supertux/addons/";
+ std::string html = "";
+
+ CURL *curl_handle;
+ curl_handle = curl_easy_init();
+ curl_easy_setopt(curl_handle, CURLOPT_URL, baseUrl);
+ curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL");
+ curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, my_curl_string_append);
+ curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &html);
+ curl_easy_perform(curl_handle);
+ curl_easy_cleanup(curl_handle);
+
+ //std::string html = "Blubb<a href=\"http://www.deltadevelopment.de/users/christoph/supertux/addons/coconut_fortress.zip\">Coconut Fortress</a>\nFoobar<a href=\"http://www.deltadevelopment.de/users/christoph/supertux/addons/in_the_spring.zip\">Another</a>Baz";
+ static const std::string startToken = "href=\"";
+ static const std::string endToken = "\"";
+
+ // extract urls: for each startToken found...
+ std::string::size_type n = 0;
+ while ((n = html.find(startToken)) != std::string::npos) {
+
+ // strip everything up to and including token
+ html.erase(0, n + startToken.length());
+
+ // find end token
+ std::string::size_type n2 = html.find(endToken);
+ if (n2 == std::string::npos) break;
+
+ // extract url: it's the string inbetween
+ std::string url = html.substr(0, n2);
+
+ // strip everything up to and including endToken
+ html.erase(0, n2 + endToken.length());
+
+ // make absolute url
+ url = std::string(baseUrl) + url;
+
+ // make sure url looks like it points to an archive
+ static const std::string archiveExt = ".zip";
+ if (url.compare(url.length()-archiveExt.length(), archiveExt.length(), archiveExt) != 0) continue;
+
+ // extract nice title
+ std::string::size_type n = url.rfind('/') + 1;
+ if (n == std::string::npos) n = 0;
+ std::string title = url.substr(n, url.length() - n - archiveExt.length());
+
+ // construct file name
+ std::string fname = url.substr(n);
+
+ // make sure it does not contain weird characters
+ if (fname.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) continue;
+
+ Addon addon;
+ addon.title = title;
+ addon.fname = fname;
+ addon.url = url;
+ addon.isInstalled = false;
+
+ addons.push_back(addon);
+ }
+#endif
+
+ return addons;
+}
+
+
+void
+AddonManager::install(const Addon&
+ #ifdef HAVE_LIBCURL
+ addon
+ #endif
+ )
+{
+
+#ifdef HAVE_LIBCURL
+
+ char* url = (char*)malloc(addon.url.length() + 1);
+ strncpy(url, addon.url.c_str(), addon.url.length() + 1);
+
+ PHYSFS_file* f = PHYSFS_openWrite(addon.fname.c_str());
+
+ log_debug << "Downloading \"" << url << "\"" << std::endl;
+
+ CURL *curl_handle;
+ curl_handle = curl_easy_init();
+ curl_easy_setopt(curl_handle, CURLOPT_URL, url);
+ curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL");
+ curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, my_curl_physfs_write);
+ curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, f);
+ curl_easy_perform(curl_handle);
+ curl_easy_cleanup(curl_handle);
+
+ PHYSFS_close(f);
+
+ free(url);
+
+ static const std::string writeDir = PHYSFS_getWriteDir();
+ static const std::string dirSep = PHYSFS_getDirSeparator();
+ std::string fullFilename = writeDir + dirSep + addon.fname;
+ log_debug << "Finished downloading \"" << fullFilename << "\"" << std::endl;
+ PHYSFS_addToSearchPath(fullFilename.c_str(), 1);
+#endif
+
+}
+
+void
+AddonManager::remove(const Addon& addon)
+{
+ PHYSFS_removeFromSearchPath(addon.fname.c_str());
+ unlink(addon.fname.c_str());
+}
+
--- /dev/null
+// $Id$
+//
+// SuperTux - Add-on Manager
+// Copyright (C) 2007 Christoph Sommer <christoph.sommer@2007.expires.deltadevelopment.de>
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+#ifndef ADDON_MANAGER_H
+#define ADDON_MANAGER_H
+
+#include <string>
+#include <vector>
+#include "addon.hpp"
+
+/**
+ * Checks for, installs and removes Add-ons
+ */
+class AddonManager
+{
+public:
+ /**
+ * returns a list of (available or installed) Add-ons
+ */
+ std::vector<Addon> get_addons() const;
+
+ /**
+ * Download and install Add-on
+ */
+ void install(const Addon& addon);
+
+ /**
+ * Physically delete Add-on
+ */
+ void remove(const Addon& addon);
+
+ static AddonManager& get_instance();
+
+protected:
+ AddonManager();
+ ~AddonManager();
+};
+
+#endif
+
#include "options_menu.hpp"
#include "console.hpp"
#include "random_generator.hpp"
+#include "addon_manager.hpp"
enum MainMenuIDs {
MNID_STARTGAME,
MNID_LEVELS_CONTRIB,
+ MNID_ADDONS,
MNID_OPTIONMENU,
MNID_LEVELEDITOR,
MNID_CREDITS,
}
}
+namespace {
+ bool generate_addons_menu_sorter(const Addon& a1, const Addon& a2)
+ {
+ return a1.title < a2.title;
+ }
+}
+
+void
+TitleScreen::generate_addons_menu()
+{
+ AddonManager& adm = AddonManager::get_instance();
+ addons = adm.get_addons();
+
+ // sort addons
+ std::sort(addons.begin(), addons.end(), generate_addons_menu_sorter);
+
+ // hide installed addons from installation menu
+ std::vector<Addon>::iterator it2 = addons.begin();
+ while (it2 != addons.end()) {
+ Addon addon = *it2;
+ if (addon.isInstalled) {
+ bool restart = false;
+ for (std::vector<Addon>::iterator it = addons.begin(); it != addons.end(); ++it) {
+ Addon addon2 = *it;
+ if ((addon2.title == addon.title) && (!addon2.isInstalled)) {
+ addons.erase(it);
+ restart = true;
+ break;
+ }
+ }
+ if (restart) {
+ it2 = addons.begin();
+ continue;
+ }
+ }
+ it2++;
+ }
+
+ free_addons_menu();
+ addons_menu.reset(new Menu());
+
+ addons_menu->add_label(_("Add-ons"));
+ addons_menu->add_hl();
+
+ int i = 0;
+ for (std::vector<Addon>::iterator it = addons.begin(); it != addons.end(); ++it) {
+ Addon addon = *it;
+ if (addon.isInstalled) {
+ addons_menu->add_entry(i++, std::string(_("Remove")) + " \"" + addon.title + "\"");
+ } else {
+ addons_menu->add_entry(i++, std::string(_("Install")) + " \"" + addon.title + "\"");
+ }
+ }
+
+ addons_menu->add_hl();
+ addons_menu->add_back(_("Back"));
+}
+
+void
+TitleScreen::check_addons_menu()
+{
+ int index = addons_menu->check();
+ if (index == -1) return;
+
+ //AddonManager& adm = AddonManager::get_instance();
+ Addon addon = addons[index];
+ if (!addon.isInstalled) {
+ addon.install();
+ generate_addons_menu();
+ Menu::set_current(addons_menu.get());
+ } else {
+ addon.remove();
+ generate_addons_menu();
+ Menu::set_current(addons_menu.get());
+ }
+}
+
+void
+TitleScreen::free_addons_menu()
+{
+}
+
void
TitleScreen::make_tux_jump()
{
main_menu->set_pos(SCREEN_WIDTH/2, SCREEN_HEIGHT/2 + 35);
main_menu->add_entry(MNID_STARTGAME, _("Start Game"));
main_menu->add_entry(MNID_LEVELS_CONTRIB, _("Contrib Levels"));
+ main_menu->add_entry(MNID_ADDONS, _("Add-ons"));
main_menu->add_submenu(_("Options"), get_options_menu());
main_menu->add_entry(MNID_CREDITS, _("Credits"));
main_menu->add_entry(MNID_QUITMAINMENU, _("Quit"));
generate_contrib_menu();
Menu::push_current(contrib_menu.get());
break;
+ case MNID_ADDONS:
+ // Add-ons Menu
+ generate_addons_menu();
+ Menu::push_current(addons_menu.get());
+ break;
case MNID_CREDITS:
main_loop->push_screen(new TextScroller("credits.txt"),
new FadeOut(0.5));
process_load_game_menu();
} else if(menu == contrib_menu.get()) {
check_levels_contrib_menu();
+ } else if(menu == addons_menu.get()) {
+ check_addons_menu();
} else if (menu == contrib_world_menu.get()) {
check_contrib_world_menu();
}
#include <vector>
#include "screen.hpp"
#include "game_session.hpp"
+#include "addon.hpp"
class Menu;
class World;
void check_levels_contrib_menu();
void check_contrib_world_menu();
void free_contrib_menu();
+ void generate_addons_menu();
+ void check_addons_menu();
+ void free_addons_menu();
std::auto_ptr<Menu> main_menu;
std::auto_ptr<Menu> load_game_menu;
std::auto_ptr<Menu> contrib_world_menu;
std::auto_ptr<World> main_world;
std::vector<World*> contrib_worlds;
+ std::auto_ptr<Menu> addons_menu;
+ std::vector<Addon> addons;
World* current_world;
std::auto_ptr<CodeController> controller;