Page MenuHomePhabricator (Chris)

No OneTemporary

Authored By
Unknown
Size
74 KB
Referenced Files
None
Subscribers
None
diff --git a/AUTHORS b/AUTHORS
index ad7f6b8..489a8b1 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,57 +1,59 @@
Active developers
acme_pjz
Edward Lii
MCMic
odamite
oyvindln (SDL2 port)
Tedium (Cloudscape theme)
Former developers
Luka Horvat
O. Bahri Gordebak
Contributors
AapoRantalainen
ctdabomb (Testing, levelmaking)
davy
emarshall85
+ GunChleoc
hasufell
Sauer2
squarecross
TermiT
worldcitizen
+ Wuzzy
Yann Soubeyrand
Ports/Packaging
AapoRantalainen - Maemo port
acme_pjz - Windows version
amdmi3 - FreeBSD port
Artur_J - AmigaOS port
Edward Lii - linux binary, openSUSE packaging
hasufell - gentoo packaging
hcf - Xbox port
kirpken - Web port
Knitter - MacOS X port
mcobit - Pandora port
MCMic - Arch Linux packaging
odamite - Ubuntu packaging, Windows installer
petos - Mageia packaging
Translators
acme_pjz - Simplified Chinese
Akien, Poussinou - French
BioHazardX - Italian
eugeneloza - Ukrainian
GunChleoc - Scottish Gaelic
KroArtem, mesnevi, eugeneloza - Russian
mdtrooper - Spanish
ming.yan2 - Traditional Chinese
odamite - Finnish
Petter Reinholdtsen, comradekingu - Norwegian Bokmål
SanskritFritz - Hungarian
Tedium - Dutch
Wuzzy (with help of Sauer2) - German
For an up to date list and contact information see:
http://meandmyshadow.sourceforge.net/wiki/index.php/Authors
diff --git a/ChangeLog b/ChangeLog
index 9a0290d..b3598f4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,326 +1,339 @@
-Me and My Shadow V0.5 (RC)
+Me and My Shadow V0.5
------------------------
+
+Brief list of changes:
+
* Switch to SDL2.
* Menu theme.
* Achievement and statistics system.
* Scripting with lua.
* Pushable block.
* Improved addon dialog.
* Improved level editor user interface.
* Sizable blocks.
* Scenery blocks.
* Undo/redo in level editor originally written by squarecross.
* Added 'visible' property to blocks.
+* Custom appearance of blocks.
+* Various other bug fixes and minor improvements.
Translations:
-* Updated Simplified Chinese, German, and Scottish Gaelic translations.
+* Updated Simplified Chinese, German, Russian, French and Scottish Gaelic translations.
* Added Hungarian translation by SanskritFritz.
+* Added Norwegian translation by Petter Reinholdtsen and Allan Nordhøy.
+* Added Ukrainian translation by eugeneloza.
Known bugs and limitations:
* No proper IME support for text box, etc.
* OSX support is broken.
+* The button specification in theme file is changed, old theme may render button incorrectly in new version.
+* Some achievements are not realistic, and there is a typo in achievements.
+* Invisible collectible is counted in total number of collectibles.
+* The scenery layer naming convention is confusing.
+* The target time for tutorial level 10 seems unbeatable. Needs further investigation.
+* There seems to be a random crash bug when exiting the game.
Me and My Shadow V0.4
------------------------
* Fixed the .desktop file.
* Created a separate docs/ folder to contain the documentation files like Controls.txt, ThemeDescription.txt, etc...
* Implemented an OpenGL renderer as alternative for the SDLRenderer.
* Fixed a bug where escape in the options menu would exit both the options menu and the main menu.
* Updated all GUIs and menus to support different resolutions, making them dynamic.
* Extended the Game rendering to support different resolutions.
* Extended the CMakeLists.txt file to make paths configurable.
* Added the library tinygettext to the project to support localisation.
* Added the library tinyformat to allow easy string formatting.
* Made the internal string literals translatable.
* Added the library findlocale to detect the preferred localization.
* Updated the CMakeLists.txt to compile the bundled libraries.
* Added the font Droid Sans for languages that contain non-latin characters.
* Implemented a levelpack manager for preloading the levelpacks at the start.
* Made levelpacks translatable as well.
* Fixed a bug where the game would crash on translating the tooltip for levels of the 'Custom Levels' levelpack.
* Changed the number of levels per row in the LevelSelect screens to support multiple resolutions.
* Fixed a bug where levelpack translations weren't detected properly.
* Extended the LevelEditor to support different window sizes.
* Added language and resolution options in the Options menu.
* Made 800x600 the minimum resolution supported.
* Fixed a bug where the camera would move in the leveleditor even if the mouse was on top of the toolbar.
* Added support for different die animations, one for dying while looking right and one for looking left.
* Also changed the way the level is aligned when the screen is larger than the level, the bottom of the level will stay the bottom of the window/screen.
* Fixed a bug where the camera didn't scroll smooth to the left.
* Updated the Cloudscape theme to V2.1.
* Fixed some in-game tooltip memory leak.
* Warnings thrown by tinygettext are now suppressed.
* Fixed a bug in the the screen surface where the alpha mask wasn't configured properly when using the gl renderer.
* Added a python script for generating a .pot file by extracting the translatable strings out of a levelpack.
* Updated the time and recordings icons, they are now black and white.
* Fixed a bug where after restarting the inter level gui would appear.
* Fixed a bug where the player or the shadow got displaced when being squashed.
* Levels get centred in the leveleditor when smaller than the screen size.
* Updated the Cloudscape theme to V2.2.
* Fixed some bugs regarding the new levelpackmanager and installing/removing addons.
* Also applied a fix for TreeStorageNode.cpp to prevent some compile errors.
* Disabled the death animation when falling of the level.
* Fixed a bug where the player or the shadow would die when jumping on a block that has a spike behind it.
* The block configuration screens in the leveleditor are now centred.
* Added toolbox for easy selecting block types in the leveleditor.
* Level selection screen can now be controlled with only the keyboard or a gamepad.
* Added command line arguments for configuring resolution and window or fullscreen mode.
* Added collectables to the game.
* Made the sound and music options in the options menu a value instead of an on/off toggle.
* Added a new GUIObject for selecting a value inside a given range, GUISlider.
* Changing the sound or music in the options menu is now applied directly.
* Added command line options for configuring sound and music volume.
* Changed the addons menu to use a GUISingleLineListBox instead of three separate GUI?ObjectButtons.
* Fixed a bug where the music volume wasn't updated while adjusting it.
* Added caching support to GUIObjects, text is only rendered when changed or needed.
* Changing resolution or language doesn't require a restart any more.
* Fixed a bug where the chaching didn't update in GUISingleLineListBox.
* Fixed a bug where the player or shadow became immortal when standing on top of a moving block that moved through spikes.
* Added gravity and automatic width to the GUIObjectButton.
* Added resolution enumeration using SDL_ListModes(), filtering out resolutions smaller than the minimum (800x600).
* Fixed some bugs regarding the gravity parameter in the GUIObject which broke the GUISlider and GUIObjectCheckbox.
* Fixed some memory leaks when changing resolution.
* Added scaling support for themes to rescale instead of reloading the whole theme.
* The game window is now sizable.
* Fixed a bug in the leveleditor where the pressed mouse button was checked using event.type while not in handleEvent() but in logic().
* Removed some old copy code that could cause deletion of all levels in a levelpack.
* Fixed a memory bug when using openGL mode and resizing the window.
* Fixed the font size of single line list box and a memory leak in GUIObject.
* Fixed a memory leak in the MusicManager.
* Fixed a compile warning in the Main.cpp file regarding a translatable string.
* Updated the tutorial levelpack to include the new collectable.
* Fixed another memory leak in the MusicManager.
* Implemented a proper method for limiting the resizing of the window below 800x600 for Linux (X11) systems.
* Implemented a method for rearranging GUI elements upon resizing the window.
* Added a shell script to add the key names to the .pot file.
* Fixed a memory leak in font loading and window resizing.
* Added Compiling.txt file containing compiling instructions for Linux systems.
* Buttons in the options menu use a smaller font when there's not enough space.
* Fixed the constant invocation of onVideoResize() bug.
* Added a minimum window size limit (800x600) for Windows systems.
* Fixed a bug where the currentID would be incorrect after postLoad when there was a teleporter in the level which wasn't the last in the levelObjects vector with an id.
* Applied patch by worldcitizen, which fixes some compile issues when using gcc 4.7.
* Fixed a bug that Windows doesn't have stdint.h but source file tried to included it.
* Fixed a bug where key names weren't translated.
* Changed the notification block's message dialog size.
* Fixed Cloudscape as default theme.
* Fixed some issues with the rendering of the movingspeed text in the [[LevelEditor}leveleditor]].
* Cleaned up the Main.cpp, moving some initialisation stuff in the appropriate init method.
* Fixed an issue regarding arbitrary fullscreen resolutions.
* Made the help message for the command line untranslatable since the dictionary manager isn't and can't be loaded before showing it.
* Added a shortcut for toggling fullscreen (Alt+Enter).
* Fixed a bug where the user could restart the level while playing a recording.
* Cursor is now invisible during game-play, both in the game state and the play mode of the leveleditor.
* Changed the draw order of the player and the shadow, the player is now drawn last.
* Fixed translated time and recordings labels in the level select screen.
* Fixed a clipping issue with the knewave font.
* Added more music by Juho-Petteri Yliuntinen.
* Left clicking objects in the leveleditor with the configure tool will now show the properties dialog of that block, if any.
* Fixed some bugs with levelpack translations.
* Camera changes focus back to the player when the shadow dies.
* Fixed some issues with long strings in the level editor.
* Fixed some issues regarding resizing and GUIGravityCenter with GUILabels.
* Fixed a bug where the game stopped responding or gave a black screen when resizing the window with a dialog on top.
* Updated the Cloudscape theme with the new collectable made by Tedium.
* Added a sound for picking up collectables.
* Exit now has an open and a closed state.
* Updated collectable GUI to match Tedium's mockup
* Implemented GUIOverlays to solve the black background when resizing the window with a dialog on top.
* Fixed a bug with resizing in the leveleditor where the placement surface wasn't recreated.
* Fixed the enterLoop method of the GUIOverlay to also call the resize method of the parentState.
* Fixed the name convention of the GUIObjectRoot when using a GUIOverlay to improve readability of the code.
* The number of collectables collected in the HUD is now hidden in the leveleditor.
* Message boxes can now be closed by pressing escape, return or backspace.
* Fixed a bug where the player could shift in front of a moving block instead of getting squashed when standing on top of his shadow.
* Made the error messages in the Addons menu translatable.
* Fixed a bug where the configure dialog of switches and buttons didn't show the configured behaviour when using any language other than English.
* Fixed the .desktop file by removing a duplicate category, thanks to hasufell for pointing this out.
* Only the fonts are reloaded now when changing the language instead of reloading everything.
* Fixed the copyright notice at the top of each source file.
* Added the Credits file for the classic theme.
* Added an AUTHORS file, basically a copy of the wiki page Authors.
* Replaced the hit.wav and jump.wav files with sounds we know are free, made by odamite under CC0.
* Updated the credits file, there's now one central Credits.txt that contains all the licenses of the art used in meandmyshadow or pointers to that information.
* Added a license header to the source files in the tools folder.
* Removed the misc folder with the Empty.map.
* A new (empty) map is now created internally instead of loaded from an empty file.
* Changed the Name value in the .desktop file to match the name of the game with correct capitalisation.
* Changed the location the addons file is fetched from, the addons git repository instead of the project web.
* Implemented a Credits screen.
* Fixed bug where picked up collectables didn't save.
* The Credits menu is now filled with text from the files AUTHORS and Credits.txt.
* Added music credits to the credits screen.
* Fixed the Name field of the music files, there were no quites around the name which contained a space.
* Added horizontal scrollbar in the credits screen
* Fixed the graphics on a horizontal GUIScrollBar.
* Removed the Credits menu entry and added an icon to the lower right corner.
* Tried to fix a segfault in the LevelSelect screens when navigating with the keyboard.
* Added the translatable string credits and updated the translations by looking up the translation from other open source projects.
* Corrected the translation files' headers.
* Updated the credits icon.
* Updated the headers of the levelpack translations.
* Added a ChangeLog file.
Translations added:
* Russian translations for the game, default, tutorial and classic levelpack by KroArtem.
* Italian translations for the game, default, tutorial and classic levelpack by BioHazardX.
* Finnish translations for the game, default, tutorial and classic levelpack by odamite.
* Simplified Chinese for the game, default, tutorial and classic levelpack by acme_pjz.
* Traditional Chines for the game by ming.yan2.
* German translations for the game and tutorial levelpack by Wuzzy.
* Dutch translation for the game by Tedium.
Me and My Shadow V0.3
------------------------
* An input manager was added to allow the configuration of key bindings.
* The format of the progress files of levelpacks was changed to the POA format.
* Added a teleport option in the level editor to make testing easier, default key binding is F5.
* Fixed a bug where non-latin file and path names caused unexpected behaviour under Windows.
* The menu and GUI theme was changed to fit with the new default theme Cloudscape matching the mock-ups by odamite. (link)
* The name of the 14th level of the classic levelpack changed from 'Damn' to 'Headache'.
* Extension were automatically added to file names in save dialogs if not present.
* Separate levels can be played again through a special levelpack named Levels
* The shortcut Ctrl+s was removed which toggled the sound and music on and off.
* Save dialogs can now start with an empty filename field.
* Two level statistics where added: time and recordings.
* Medals can be earned by beating a set target time and recordings.
* Level names of levels inside a levelpack are now retrieved from the level file itself instead of the levelpack file.
* The game now recorded user input to be able to replay it later on.
* A carrot was added to the GUITextBox to allow easier editing.
* Joystick support has been added to the InputManager.
* MD5sum are used to link replays and statistics to levels.
* The CMakeLists.txt was updated to include openssl and crypto.
* Fixed a bug where the level statistics were always updated, now the best stays.
* The best time and best number of recordings replays are auto-saved.
* Level selection blocks updated to match the Cloudscape theme.
* Added animation for the arrows of the GUISingleLineListBox.
* All key bindings can now have a primary and alternative key.
* GUITextBox and GUITextArea now handle delete and backspace properly.
* A section at the bottom of the LevelSelect screen was added to show level and level statistic information.
* A method was made for drawing so named GUIBoxes.
* Target time and recordings can be configured in the leveleditor.
* Separate the up and jump key and the down and action keys in the input manager.
* Removed the non-free music that was there from the initial release.
* Replays are shown after completing a level.
* The help menu got removed and the entry in the main menu was replaced with the addons menu.
* The clear progress option was moved to the options menu.
* Added a music manager to add support for multiple music tracks.
* Sound and music is now separated in the options menu.
* A message box shows instead of a label to when a certain changes requires the user to restart the game.
* A bug was fixed in the md5 calculation of the TreeStorageNode.
* Changed the drawGUIBox to not use rounded rectangles to support older versions of SDL_gfx.
* Fixed a bug where an extension was added to a file dialog that was used for folders.
* Fixed a bug regarding the player holding the shadow whilst on a moving block.
* Added a LevelEditSelect screen to replace the old levelpackeditor.
* Target times and recordings added for the classic levelpack thanks to Tedium.
* Fixed a bug where the player could continue recording after he died.
* Menu theme music added, made by vaev (Juho-Petteri Yliuntinen).
* Extended the MusicManager to support a separate loop file as alternative to a loop start time.
* Added icons to the tooltips in the LevelSelect screen.
* Changing block type in the level editor is now a separate key binding.
* Added an interlevel popup to show the target time and recordings also shows the achieved medal.
* Made Me and My Shadow as XDG-compliant as possible by saving user data in ~/.local/share/ and config files in ~/.config/.
* Notifications aren't shown when the inter level popup is up.
* Fixed a bug where an empty levelpack could crash the game.
* Fixed the bug where the replay button of last level doesn't show up.
* Changed the way notification blocks are displayed, there's no popup any more, but a GUIBox at the bottom of the screen.
* Fixed a bug regarding the leveleditor crashing because of dangling pointer objNotificationBlock.
* Updated the Cloudscape theme made by Tedium.
* Changed colour of the '&'-sign in logo to match the updated background image.
* Fixed selection overlapping in the GUIListBox.
* Both the player and shadow can now get squashed when between a solid block and a moving block.
* Updated the Controls.txt to include all the (new) keybindings.
* Made it possible to configure the starting state of fragile blocks.
* Added check to prevent overwriting levels using the LevelEditSelect screen.
* Added notification for when the shadow dies.
* Updated the icons to match the latest Cloudscape version.
Me and My Shadow V0.2
------------------------
* The GUIScrollBar was added and used in the LevelSelect screen when there are too many levels to fit on the screen.
* The GUIListBox control was added and used in filedialogs.
* Support for levelpacks was added.
* An option was added to the LevelSelect screen to play custom levels made in the editor.
* Added a levelpack editor to the leveleditor.
* Added an options menu, with this came the settings file.
* A theme manager was added to support theming.
* The screen gets dimmed when a GUI is opened.
* Some improvements to the POAParser.
* The help menu graphics where updated by removing the dots in the background.
* Added an addon manager to allow downloadable themes, levelpacks and levels
* The data folder was structured more by separating levels from levelpacks and by adding a separate folder for themes.
* Libarchive became a dependency of Me and My Shadow because it is used in the Addons menu.
* The code underwent massive refactoring and documenting to match a set of code conventions.
* Bug fixed where the player or shadow could teleport by restarting a level whilst touching a moving block.
* The notification block was added to the game.
* Fixed a bug where the player could jump on a fragile block that was destroyed if the jump was timed correctly.
* An internet proxy option was added.
* The |leveleditor got a massive overhaul, basically being built up from scratch.
* Primitive drawing methods where added to the Functions.cpp file.
* Focus support was added to the GUIObjectTextBox.
* A [[GUITextArea] was added to support multiline text input.
* User created content was separated from main and addon content.
* A congratulation text was added when finishing a levelpack.
* Fixed a swap bug, causing the shadow the sink in the floor.
* The tutorial levelpack was added.
* The original levelpack was renamed to classic.
* The drawing of primitives was handed over to SDL_gfx
* A help screen system was introduced that consisted out of multiple slides explaining the certain aspects of the game.
* An icon indicating the recording status was added to the upper left corner of the screen.
* The tab key allows the player to switch the camera focus between the player and the shadow.
* Both the player and the shadow can be themed using the same system as blocks.
* Resetting doesn't reset the saved state any more, allowing the player to reload a checkpoint after resetting (by accident).
* A new default levelpack was added containing levels ranging from easy to medium difficulty.
* Jump and fall animation support was added.
* The Theme:Cloudscape became the default theme, the default theme is renamed to classic just like the levelpack.
* Tooltips where added to the toolbar in the leveleditor.
* An icon for the Windows build was added.
* CMake modules where bundled to make packaging easier for systems missing these modules.
* The order in which the blocks appear in the leveleditor have been changed, instead of using the order they appear internally.
* Some compiler warnings fixed, adding newlines to the end of each source file for example.
* Fixed a problem with older version of libarchive.
* Added a separate update button to the addons menu so that a user can uninstall an addon without being forced to update it first.
* Fixed a bug where the program wouldn't quit when in the leveleditor.
* The up and down arrow keys can now be used to navigate through the main menu.
Me and My Shadow V0.1.2
------------------------
* The POAParser was updated to handle non-existing files better.
* Some fixes were made to prevent a crash under Linux.
* Command line arguments added to change config and data paths.
* Automatic data path detection was added.
* Missing background music changed from an error to a warning.
* .desktop file and icon added.
* Tooltip added to levelblocks in the LevelSelect screen to show the level's name.
Me and My Shadow V0.1.1
------------------------
* The TitleMenu has been removed.
* The transition between states has been changed to a fade transition.
* The code was refactored (source files in src/ folder and header files per class)
* The background of both the menu and the game have been changed to remove the spots.
* The movement system has changed, in V0.1 the player would always record.
* Data files rearranged.
* Improved the blocks graphics using the Gem Jewel Diamond Glass set by Ville Seppanen <http://opengameart.org/content/gem-jewel-diamond-glass>.
* The leveleditor had been integrated in the game instead of being a separate program.
* Saving/loading has been implemented (used for checkpoints).
* CMake is now used.
* Transparency support for gfx.
* Checkpoints have been added.
* Menu for the leveleditor was made.
* The ImageManager has been added to prevent loading the same image twice.
* Swap block has been added.
* Fragile block has been added.
* New levelformat.
* Moving blocks and spikes have been added.
* Portal, Switch and Button added.
* Custom image support for blocks (never used and remove in MeAndMyShadow 0.3).
* Conveyor Belt added.
\ No newline at end of file
diff --git a/src/Addons.cpp b/src/Addons.cpp
index a84606a..2fa5270 100644
--- a/src/Addons.cpp
+++ b/src/Addons.cpp
@@ -1,1082 +1,1098 @@
/*
* Copyright (C) 2011-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Addons.h"
#include "GameState.h"
#include "Functions.h"
#include "FileManager.h"
#include "GUIObject.h"
#include "GUIOverlay.h"
#include "GUIScrollBar.h"
#include "GUITextArea.h"
#include "GUIListBox.h"
#include "POASerializer.h"
#include "LevelPackManager.h"
#include "InputManager.h"
#include "ThemeManager.h"
#include <string>
#include <sstream>
#include <iostream>
#include "libs/tinyformat/tinyformat.h"
#include <SDL.h>
#include <SDL_ttf.h>
using namespace std;
static const char* predefinedCategories[] = {
"levels", __("Levels"), __("Single level which usually contain demanding puzzles"),
"levelpacks", __("Levelpacks"), __("Collection of levels with the same author or style"),
"themes", __("Themes"), __("Give every block and background a new look and feel"),
NULL,
};
static std::map<std::string, std::string> categoryNameMap;
static std::map<std::string, std::string> categoryDescriptionMap;
-Addons::Addons(SDL_Renderer &renderer, ImageManager &imageManager):selected(NULL){
+Addons::Addons(SDL_Renderer &renderer, ImageManager &imageManager)
+ : selected(NULL), categoryList(NULL), categoryDescription(NULL), list(NULL)
+{
//Render the title.
title = titleTextureFromText(renderer, _("Addons"), objThemes.getTextColor(false), SCREEN_WIDTH);
//Load placeholder addon icons and screenshot.
addonIcon["levels"] = imageManager.loadImage(getDataPath() + "/gfx/addon1.png");
addonIcon["levelpacks"] = imageManager.loadImage(getDataPath() + "/gfx/addon2.png");
addonIcon["themes"] = imageManager.loadImage(getDataPath() + "/gfx/addon3.png");
addonIcon[std::string()] = imageManager.loadImage(getDataPath() + "/gfx/addon0.png");
//Load predefined categories.
if (categoryNameMap.empty()) {
for (int i = 0; predefinedCategories[i]; i += 3) {
categoryNameMap[predefinedCategories[i]] = predefinedCategories[i + 1];
categoryDescriptionMap[predefinedCategories[i]] = predefinedCategories[i + 2];
}
}
screenshot=imageManager.loadTexture(getDataPath()+"/gfx/screenshot.png", renderer);
-
- //Open the addons file in the user cache path for writing (downloading) to.
- FILE* addon=fopen((getUserPath(USER_CACHE)+"addons").c_str(),"wb");
//Clear the GUI if any.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Show a loading screen
{
int w = 0, h = 0;
SDL_GetRendererOutputSize(&renderer, &w, &h);
SDL_Color fg = { 255, 255, 255, 0 };
TexturePtr loadingTexture = titleTextureFromText(renderer, _("Loading..."), fg, w);
SDL_Rect loadingRect = rectFromTexture(*loadingTexture);
loadingRect.x = (w - loadingRect.w) / 2;
loadingRect.y = (h - loadingRect.h) / 2;
SDL_SetRenderDrawColor(&renderer, 0, 0, 0, 255);
SDL_RenderClear(&renderer);
SDL_RenderCopy(&renderer, loadingTexture.get(), NULL, &loadingRect);
SDL_RenderPresent(&renderer);
SDL_RenderClear(&renderer);
}
//Try to get(download) the addonsList.
- if(getAddonsList(addon, renderer, imageManager)==false){
+ bool ret = getAddonsList(renderer, imageManager);
+
+ if(!ret) {
+ if (error.empty()) error = " ";
+
//It failed so we show the error message.
GUIObjectRoot=new GUIObject(imageManager,renderer,0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
GUIObject* obj=new GUILabel(imageManager,renderer,90,96,200,32,_("Unable to initialize addon menu:"));
obj->name="lbl";
GUIObjectRoot->addChild(obj);
obj=new GUILabel(imageManager,renderer,120,130,200,32,error.c_str());
obj->name="lbl";
GUIObjectRoot->addChild(obj);
obj=new GUIButton(imageManager,renderer,90,550,200,32,_("Back"));
obj->name="cmdBack";
obj->eventCallback=this;
GUIObjectRoot->addChild(obj);
return;
}
//Now create the GUI.
createGUI(renderer, imageManager);
}
Addons::~Addons(){
//If the GUIObjectRoot exist delete it.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
void Addons::createGUI(SDL_Renderer& renderer, ImageManager& imageManager){
//Downloaded the addons file now we can create the GUI.
GUIObjectRoot=new GUIObject(imageManager,renderer,0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
//Create list of categories
categoryList = new GUISingleLineListBox(imageManager, renderer, (SCREEN_WIDTH - 500) / 2, 100, 500, 32);
categoryList->name="lstTabs";
//Loop through the categories and add them to the list.
//FIXME: Hack for easy detecting which categories there are.
{
set<string> categories;
for (const auto& a : addons) {
categories.insert(a.type);
}
for (const auto& c : categories) {
auto it = categoryNameMap.find(c);
categoryList->addItem(c, it == categoryNameMap.end() ? c.c_str() : _(it->second));
}
}
categoryList->value=0;
categoryList->eventCallback=this;
GUIObjectRoot->addChild(categoryList);
//category description
categoryDescription = new GUILabel(imageManager, renderer, 0, 136, SCREEN_WIDTH, 32, "", 0, true, true, GUIGravityCenter);
if (categoryList->value >= 0 && categoryList->value < (int)categoryList->item.size()) {
auto it = categoryDescriptionMap.find(categoryList->item[categoryList->value].first);
if (it != categoryDescriptionMap.end()) categoryDescription->caption = _(it->second);
}
GUIObjectRoot->addChild(categoryDescription);
//Create the list for the addons.
//By default levels will be selected.
list=new GUIListBox(imageManager,renderer,SCREEN_WIDTH*0.1,176,SCREEN_WIDTH*0.8,SCREEN_HEIGHT-228);
addonsToList(categoryList->getName(), renderer, imageManager);
list->name="lstAddons";
list->clickEvents=true;
list->eventCallback=this;
list->value=-1;
GUIObjectRoot->addChild(list);
type="levels";
//The back button.
GUIObject* obj=new GUIButton(imageManager,renderer,20,20,-1,32,_("Back"));
obj->name="cmdBack";
obj->eventCallback=this;
GUIObjectRoot->addChild(obj);
}
-bool Addons::getAddonsList(FILE* file, SDL_Renderer& renderer, ImageManager& imageManager){
+bool Addons::getAddonsList(SDL_Renderer& renderer, ImageManager& imageManager){
//First we download the file.
- if(downloadFile(getSettings()->getValue("addon_url"),file)==false){
+ FILE* file = fopen((getUserPath(USER_CACHE) + "addons").c_str(), "wb");
+ bool downloadStatus = downloadFile(getSettings()->getValue("addon_url"), file);
+ fclose(file);
+
+ if (!downloadStatus){
//NOTE: We keep the console output English so we put the string literal here twice.
cerr<<"ERROR: unable to download addons file!"<<endl;
error=_("ERROR: unable to download addons file!");
return false;
}
- fclose(file);
-
+
//Load the downloaded file.
ifstream addonFile;
addonFile.open((getUserPath(USER_CACHE)+"addons").c_str());
if(!addonFile.good()) {
//NOTE: We keep the console output English so we put the string literal here twice.
cerr<<"ERROR: unable to load addon_list file!"<<endl;
/// TRANSLATORS: addon_list is the name of a file and should not be translated.
error=_("ERROR: unable to load addon_list file!");
return false;
}
//Parse the addonsfile.
TreeStorageNode obj;
{
POASerializer objSerializer;
if(!objSerializer.readNode(addonFile,&obj,true)){
//NOTE: We keep the console output English so we put the string literal here twice.
cerr<<"ERROR: Invalid file format of addons file!"<<endl;
error=_("ERROR: Invalid file format of addons file!");
return false;
}
}
//Check the addon version in the addons list.
int version=0;
if(!obj.attributes["version"].empty())
version=atoi(obj.attributes["version"][0].c_str());
if(version<MIN_VERSION || version>MAX_VERSION){
//NOTE: We keep the console output English so we put the string literal here twice.
cerr<<"ERROR: Addon list version is unsupported! (received: "<<version<<" supported:"<<MIN_VERSION<<"-"<<MAX_VERSION<<")"<<endl;
error=_("ERROR: Addon list version is unsupported!");
return false;
}
//Also load the installed_addons file.
ifstream iaddonFile;
iaddonFile.open((getUserPath(USER_CONFIG)+"installed_addons").c_str());
if(!iaddonFile) {
//The installed_addons file doesn't exist, so we create it.
ofstream iaddons;
iaddons.open((getUserPath(USER_CONFIG)+"installed_addons").c_str());
iaddons<<" "<<endl;
iaddons.close();
//Also load the installed_addons file.
iaddonFile.open((getUserPath(USER_CONFIG)+"installed_addons").c_str());
if(!iaddonFile) {
//NOTE: We keep the console output English so we put the string literal here twice.
cerr<<"ERROR: Unable to create the installed_addons file."<<endl;
/// TRANSLATORS: installed_addons is the name of a file and should not be translated.
error=_("ERROR: Unable to create the installed_addons file.");
return false;
}
}
//And parse the installed_addons file.
TreeStorageNode obj1;
- {
- POASerializer objSerializer;
- if(!objSerializer.readNode(iaddonFile,&obj1,true)){
- //NOTE: We keep the console output English so we put the string literal here twice.
- cerr<<"ERROR: Invalid file format of the installed_addons!"<<endl;
- error=_("ERROR: Invalid file format of the installed_addons!");
- return false;
- }
+ if (!POASerializer().readNode(iaddonFile, &obj1, true)){
+ iaddonFile.close();
+
+ //In this case we should erase installed_addons so that the user is possible to enter addon screen next time.
+ ofstream iaddons;
+ iaddons.open((getUserPath(USER_CONFIG) + "installed_addons").c_str());
+ iaddons << " " << endl;
+ iaddons.close();
+
+ //NOTE: We keep the console output English so we put the string literal here twice.
+ cerr<<"ERROR: Invalid file format of the installed_addons!"<<endl;
+ error=_("ERROR: Invalid file format of the installed_addons!");
+ return false;
}
-
-
+
+
//Fill the vector.
fillAddonList(obj,obj1, renderer, imageManager);
//Close the files.
iaddonFile.close();
addonFile.close();
return true;
}
void Addons::fillAddonList(TreeStorageNode &objAddons, TreeStorageNode &objInstalledAddons,
SDL_Renderer& renderer, ImageManager& imageManager){
//Loop through the blocks of the addons file.
//These should contain the types levels, levelpacks, themes.
for(unsigned int i=0;i<objAddons.subNodes.size();i++){
TreeStorageNode* block=objAddons.subNodes[i];
if(block==NULL) continue;
//Check what kind of block it is, only category at the moment.
if(block->name=="category" && block->value.size()>0){
string type=block->value[0];
//Now loop the entries(subNodes) of the block.
for(unsigned int i=0;i<block->subNodes.size();i++){
TreeStorageNode* entry=block->subNodes[i];
if(entry==NULL) continue;
if(entry->name=="entry" && entry->value.size()==1){
//The entry is valid so create a new Addon.
Addon addon;
addon.icon=nullptr;
addon.screenshot=nullptr;
addon.type=type;
addon.name=entry->value[0];
addon.version = 0;
addon.installedVersion = 0;
if(!entry->attributes["file"].empty())
addon.file=entry->attributes["file"][0];
if(!entry->attributes["author"].empty())
addon.author=entry->attributes["author"][0];
if(!entry->attributes["description"].empty())
addon.description=entry->attributes["description"][0];
if(!entry->attributes["license"].empty())
addon.license=entry->attributes["license"][0];
if(!entry->attributes["website"].empty())
addon.website=entry->attributes["website"][0];
if(entry->attributes["icon"].size()>1){
//There are (at least) two values, the url to the icon and its md5sum used for caching.
addon.icon=loadCachedImage(
entry->attributes["icon"][0].c_str(),
entry->attributes["icon"][1].c_str(),
imageManager
);
}
if(entry->attributes["screenshot"].size()>1){
//There are (at least) two values, the url to the screenshot and its md5sum used for caching.
addon.screenshot=loadCachedTexture(
entry->attributes["screenshot"][0].c_str(),
entry->attributes["screenshot"][1].c_str(),
renderer,
imageManager
);
}
if(!entry->attributes["version"].empty())
addon.version=atoi(entry->attributes["version"][0].c_str());
addon.upToDate=false;
addon.installed=false;
//Check if the addon is already installed.
for(unsigned int i=0;i<objInstalledAddons.subNodes.size();i++){
TreeStorageNode* installed=objInstalledAddons.subNodes[i];
if(installed==NULL) continue;
if(installed->name=="entry" && installed->value.size()==3){
if(addon.type.compare(installed->value[0])==0 && addon.name.compare(installed->value[1])==0) {
addon.installed=true;
addon.installedVersion=atoi(installed->value[2].c_str());
if(addon.installedVersion>=addon.version) {
addon.upToDate=true;
}
//Read the dependencies and content from the file.
for(unsigned int j=0;j<installed->subNodes.size();j++){
if(installed->subNodes[j]->name=="content"){
TreeStorageNode* obj=installed->subNodes[j];
for(unsigned int k=0;k<obj->subNodes.size();k++){
if(obj->subNodes[k]->value.size()==1)
addon.content.push_back(pair<string,string>(obj->subNodes[k]->name,obj->subNodes[k]->value[0]));
}
}else if(installed->subNodes[j]->name=="dependencies"){
TreeStorageNode* obj=installed->subNodes[j];
for(unsigned int k=0;k<obj->subNodes.size();k++){
if(obj->subNodes[k]->value.size()==1)
addon.dependencies.push_back(pair<string,string>(obj->subNodes[k]->name,obj->subNodes[k]->value[0]));
}
}
}
}
}
}
//Finally put him in the list.
addons.push_back(addon);
}
}
}
}
}
void Addons::addonsToList(const std::string &type, SDL_Renderer& renderer, ImageManager&){
//Clear the list.
list->clearItems();
//Loop through the addons.
for(unsigned int i=0;i<addons.size();i++) {
//Make sure the addon is of the requested type.
if(addons[i].type!=type)
continue;
const Addon& addon=addons[i];
string entry=addon.name+" by "+addon.author;
if(addon.installed){
if(addon.upToDate){
entry+=" *";
}else{
entry+=" +";
}
}
SurfacePtr surf = createSurface(list->width,74);
//Check if there's an icon for the addon.
if(addon.icon){
applySurface(5, 5, addon.icon, surf.get(), NULL);
}else{
auto it = addonIcon.find(type);
if (it == addonIcon.end()) it = addonIcon.find(std::string());
assert(it != addonIcon.end());
applySurface(5, 5, it->second, surf.get(), NULL);
}
SDL_Surface* nameSurf=TTF_RenderUTF8_Blended(fontGUI,addon.name.c_str(),objThemes.getTextColor(true));
SDL_SetSurfaceAlphaMod(nameSurf,0xFF);
applySurface(74,-1,nameSurf,surf.get(),NULL);
SDL_FreeSurface(nameSurf);
/// TRANSLATORS: indicates the author of an addon.
string authorLine = tfm::format(_("by %s"),addon.author);
SDL_Surface* authorSurf=TTF_RenderUTF8_Blended(fontText,authorLine.c_str(),objThemes.getTextColor(true));
SDL_SetSurfaceAlphaMod(authorSurf,0xFF);
applySurface(74,43,authorSurf,surf.get(),NULL);
SDL_FreeSurface(authorSurf);
if(addon.installed){
if(addon.upToDate){
SDL_Surface* infoSurf=TTF_RenderUTF8_Blended(fontText,_("Installed"),objThemes.getTextColor(true));
SDL_SetSurfaceAlphaMod(infoSurf,0xFF);
applySurface(surf->w-infoSurf->w-32,(surf->h-infoSurf->h)/2,infoSurf,surf.get(),NULL);
SDL_FreeSurface(infoSurf);
}else{
SDL_Surface* infoSurf=TTF_RenderUTF8_Blended(fontText,_("Updatable"),objThemes.getTextColor(true));
SDL_SetSurfaceAlphaMod(infoSurf,0xFF);
applySurface(surf->w-infoSurf->w-32,(surf->h-infoSurf->h)/2,infoSurf,surf.get(),NULL);
SDL_FreeSurface(infoSurf);
}
}else{
SDL_Color c = objThemes.getTextColor(true);
c.r = c.r / 2 + 128;
c.g = c.g / 2 + 128;
c.b = c.b / 2 + 128;
SDL_Surface* infoSurf = TTF_RenderUTF8_Blended(fontText, _("Not installed"), c);
SDL_SetSurfaceAlphaMod(infoSurf,0xFF);
applySurface(surf->w-infoSurf->w-32,(surf->h-infoSurf->h)/2,infoSurf,surf.get(),NULL);
SDL_FreeSurface(infoSurf);
}
list->addItem(renderer,entry,textureFromSurface(renderer,std::move(surf)));
}
}
bool Addons::saveInstalledAddons(){
+ //If there is error loading addons file we doesn't save installed_addons at all.
+ if (!error.empty()) return false;
+
//Open the file.
ofstream iaddons;
iaddons.open((getUserPath(USER_CONFIG)+"installed_addons").c_str());
if(!iaddons) return false;
TreeStorageNode installed;
//Loop through all the addons.
vector<Addon>::iterator it;
for(it=addons.begin();it!=addons.end();++it){
//Check if the level is installed or not.
if(it->installed) {
TreeStorageNode *entry=new TreeStorageNode;
entry->name="entry";
entry->value.push_back(it->type);
entry->value.push_back(it->name);
char version[64];
sprintf(version,"%d",it->installedVersion);
entry->value.push_back(version);
//Now add a subNode for each content.
TreeStorageNode* content=new TreeStorageNode;
content->name="content";
for(unsigned int i=0;i<it->content.size();i++){
TreeStorageNode* contentEntry=new TreeStorageNode;
contentEntry->name=it->content[i].first;
contentEntry->value.push_back(it->content[i].second);
//Add the content node to the entry node.
content->subNodes.push_back(contentEntry);
}
entry->subNodes.push_back(content);
//Now add a sub node for the dependencies.
TreeStorageNode* deps=new TreeStorageNode;
deps->name="dependencies";
for(unsigned int i=0;i<it->dependencies.size();i++){
TreeStorageNode* depsEntry=new TreeStorageNode;
depsEntry->name=it->dependencies[i].first;
depsEntry->value.push_back(it->dependencies[i].second);
//Add the content node to the entry node.
deps->subNodes.push_back(depsEntry);
}
entry->subNodes.push_back(deps);
//And add the entry to the top node.
installed.subNodes.push_back(entry);
}
}
//And write away the file.
POASerializer objSerializer;
objSerializer.writeNode(&installed,iaddons,true,true);
return true;
}
SharedTexture Addons::loadCachedTexture(const char* url,const char* md5sum,
SDL_Renderer& renderer, ImageManager& imageManager){
//Check if the image is cached.
string imageFile=getUserPath(USER_CACHE)+"images/"+md5sum;
if(fileExists(imageFile.c_str())){
//It is, so load the image.
return imageManager.loadTexture(imageFile, renderer);
}else{
//Download the image.
FILE* file=fopen(imageFile.c_str(),"wb");
//Downloading failed.
if(!downloadFile(url,file)){
cerr<<"ERROR: Unable to download image from "<<url<<endl;
fclose(file);
return NULL;
}
fclose(file);
//Load the image.
return imageManager.loadTexture(imageFile, renderer);
}
}
SDL_Surface* Addons::loadCachedImage(const char* url, const char* md5sum,
ImageManager& imageManager){
//Check if the image is cached.
string imageFile = getUserPath(USER_CACHE) + "images/" + md5sum;
if (fileExists(imageFile.c_str())){
//It is, so load the image.
return imageManager.loadImage(imageFile);
} else{
//Download the image.
FILE* file = fopen(imageFile.c_str(), "wb");
//Downloading failed.
if (!downloadFile(url, file)){
cerr << "ERROR: Unable to download image from " << url << endl;
fclose(file);
return NULL;
}
fclose(file);
//Load the image.
return imageManager.loadImage(imageFile);
}
}
void Addons::handleEvents(ImageManager& imageManager, SDL_Renderer& renderer){
//Check if we should quit.
if(event.type==SDL_QUIT){
//Save the installed addons before exiting.
saveInstalledAddons();
setNextState(STATE_EXIT);
}
//Check if escape is pressed, if so return to the main menu.
if(inputMgr.isKeyDownEvent(INPUTMGR_ESCAPE)){
setNextState(STATE_MENU);
}
+ if (categoryList == NULL) return;
+
//Check horizontal movement
int value = categoryList->value;
if (inputMgr.isKeyDownEvent(INPUTMGR_RIGHT)){
isKeyboardOnly = true;
value++;
if (value >= (int)categoryList->item.size()) value = 0;
} else if (inputMgr.isKeyDownEvent(INPUTMGR_LEFT)){
isKeyboardOnly = true;
value--;
if (value < 0) value = categoryList->item.size() - 1;
}
if (value >= 0 && value < (int)categoryList->item.size()) {
if (categoryList->value != value) {
categoryList->value = value;
GUIEventCallback_OnEvent(imageManager, renderer, categoryList->name, categoryList, GUIEventChange);
return;
}
//Check vertical movement
if (inputMgr.isKeyDownEvent(INPUTMGR_UP)){
isKeyboardOnly = true;
list->value--;
if (list->value < 0) list->value = 0;
//FIXME: ad-hoc stupid code
list->scrollScrollbar(0xC0000000);
list->scrollScrollbar(list->value);
} else if (inputMgr.isKeyDownEvent(INPUTMGR_DOWN)){
isKeyboardOnly = true;
list->value++;
if (list->value >= (int)list->item.size()) list->value = list->item.size() - 1;
//FIXME: ad-hoc stupid code
list->scrollScrollbar(0xC0000000);
list->scrollScrollbar(list->value);
}
if (isKeyboardOnly && inputMgr.isKeyDownEvent(INPUTMGR_SELECT) && list->value >= 0 && list->value<(int)list->item.size()) {
GUIEventCallback_OnEvent(imageManager, renderer, list->name, list, GUIEventChange); // ???
GUIEventCallback_OnEvent(imageManager, renderer, list->name, list, GUIEventClick);
return;
}
}
}
void Addons::logic(ImageManager&, SDL_Renderer&){}
void Addons::render(ImageManager&, SDL_Renderer& renderer){
//Draw background.
objThemes.getBackground(true)->draw(renderer);
//Draw the title.
drawTitleTexture(SCREEN_WIDTH, *title, renderer);
}
void Addons::resize(ImageManager& imageManager, SDL_Renderer& renderer){
//Delete the gui (if any).
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Now create a new one.
createGUI(renderer, imageManager);
}
void Addons::showAddon(ImageManager& imageManager, SDL_Renderer& renderer){
//Make sure an addon is selected.
if(!selected)
return;
//Skip next mouse up event since we're clicking a list box and showing a new window.
GUISkipNextMouseUpEvent = true;
//Create a root object.
GUIObject* root=new GUIFrame(imageManager,renderer,(SCREEN_WIDTH-760)/2,(SCREEN_HEIGHT-560)/2,760,560,selected->name.c_str());
//Create the 'by creator' label.
GUIObject* obj=new GUILabel(imageManager,renderer,0,45,760,50,tfm::format(_("by %s"),selected->author).c_str(),0,true,true,GUIGravityCenter);
root->addChild(obj);
//Create the description text.
std::string s = tfm::format(_("Version: %d\n"), selected->version);
if (selected->installed) {
s += tfm::format(_("Installed version: %d\n"), selected->installedVersion);
}
if (!selected->license.empty()) {
s += tfm::format(_("License: %s\n"), appendURLToLicense(selected->license));
}
if (!selected->website.empty()) {
s += tfm::format(_("Website: %s\n"), selected->website);
}
s += '\n';
if (selected->description.empty()) {
s += _("(No descriptions provided)");
} else {
s += selected->description;
}
GUITextArea* description=new GUITextArea(imageManager,renderer,10,90,530,390);
description->setString(renderer, s, true);
description->editable=false;
description->onResize();
description->extractHyperlinks();
root->addChild(description);
//Create the screenshot image. (If a screenshot is missing, we use the default screenshot.)
GUIImage* img=new GUIImage(imageManager,renderer,550,90,200,150,selected->screenshot?selected->screenshot:screenshot);
root->addChild(img);
GUIButton *cancelButton;
//Add buttons depending on the installed/update status.
if(selected->installed && !selected->upToDate){
GUIObject* bRemove=new GUIButton(imageManager,renderer,root->width*0.95,510,-1,32,_("Remove"),0,true,true,GUIGravityRight);
bRemove->name="cmdRemove";
bRemove->eventCallback=this;
root->addChild(bRemove);
//Create a back button.
cancelButton = new GUIButton(imageManager, renderer, root->width*0.05, 510, -1, 32, _("Back"), 0, true, true, GUIGravityLeft);
cancelButton->name = "cmdCloseOverlay";
cancelButton->eventCallback = this;
root->addChild(cancelButton);
//Update widget sizes.
root->render(renderer, 0,0,false);
//Create a nicely centered button.
obj = new GUIButton(imageManager, renderer,
(int)floor((cancelButton->left + cancelButton->width + bRemove->left - bRemove->width)*0.5), 510,
-1, 32, _("Update"), 0, true, true, GUIGravityCenter);
obj->name="cmdUpdate";
obj->eventCallback=this;
root->addChild(obj);
}else{
if(!selected->installed){
obj=new GUIButton(imageManager,renderer,root->width*0.9,510,-1,32,_("Install"),0,true,true,GUIGravityRight);
obj->name="cmdInstall";
obj->eventCallback=this;
root->addChild(obj);
}else if(selected->upToDate){
obj=new GUIButton(imageManager,renderer,root->width*0.9,510,-1,32,_("Remove"),0,true,true,GUIGravityRight);
obj->name="cmdRemove";
obj->eventCallback=this;
root->addChild(obj);
}
//Create a back button.
cancelButton = new GUIButton(imageManager, renderer, root->width*0.1, 510, -1, 32, _("Back"), 0, true, true, GUIGravityLeft);
cancelButton->name = "cmdCloseOverlay";
cancelButton->eventCallback = this;
root->addChild(cancelButton);
}
new AddonOverlay(renderer, root, cancelButton, description, TabFocus | ReturnControls);
}
void Addons::GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name,GUIObject* obj,int eventType){
if(name=="lstTabs"){
//Get the category type.
type=categoryList->getName();
//Get the description of current category.
auto it = categoryDescriptionMap.find(type);
if (it != categoryDescriptionMap.end()) categoryDescription->caption = _(it->second);
else categoryDescription->caption.clear();
//Get the list corresponding with the category and select the first entry.
addonsToList(type, renderer, imageManager);
list->value=-1;
//Call an event as if an entry in the addons listbox was clicked.
GUIEventCallback_OnEvent(imageManager, renderer, "lstAddons",list,GUIEventChange);
}else if(name=="lstAddons"){
//Check which type of event.
if(eventType==GUIEventChange){
//Get the addon struct that belongs to it.
Addon* addon=NULL;
//Make sure the addon list on screen isn't empty.
if (list->value >= 0 && list->value < (int)list->item.size()){
//Get the name of the (newly) selected entry.
string entry=list->getItem(list->value);
//Loop through the addons of the selected category.
for(unsigned int i=0;i<addons.size();i++){
//Make sure the addons are of the same type.
if(addons[i].type!=categoryList->getName())
continue;
string prefix=addons[i].name;
if(!entry.compare(0,prefix.size(),prefix)){
addon=&addons[i];
}
}
}
//Set the new addon as selected and unselect the list.
selected=addon;
if (!isKeyboardOnly) list->value = -1;
}else if(eventType==GUIEventClick){
//Make sure an addon is selected.
if(selected){
showAddon(imageManager,renderer);
}
}
}else if(name=="cmdBack"){
saveInstalledAddons();
setNextState(STATE_MENU);
}else if(name=="cmdCloseOverlay"){
//We can safely delete the GUIObjectRoot, since it's handled by the GUIOverlay.
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}else if(name=="cmdUpdate"){
//NOTE: This simply removes the addon and reinstalls it.
//The complete addon is downloaded either way so no need for checking what has been changed/added/removed/etc...
if(selected){
removeAddon(imageManager,renderer,selected);
installAddon(imageManager,renderer,selected);
}
addonsToList(categoryList->getName(), renderer, imageManager);
}else if(name=="cmdInstall"){
if(selected)
installAddon(imageManager,renderer,selected);
addonsToList(categoryList->getName(), renderer, imageManager);
}else if(name=="cmdRemove"){
//TODO: Check for dependencies.
//Loop through the addons to check if this addon is a dependency of another addon.
vector<Addon>::iterator it;
for(it=addons.begin();it!=addons.end();++it){
//Check if the addon has dependencies.
if(!it->dependencies.empty()){
vector<pair<string,string> >::iterator depIt;
for(depIt=it->dependencies.begin();depIt!=it->dependencies.end();++depIt){
if(depIt->first=="addon" && depIt->second==selected->name){
msgBox(imageManager,renderer,tfm::format(_("This addon can't be removed because it's needed by %s."),it->name),MsgBoxOKOnly,_("Dependency"));
return;
}
}
}
}
if(selected)
removeAddon(imageManager,renderer,selected);
addonsToList(categoryList->getName(), renderer, imageManager);
}
//NOTE: In case of install/remove/update we can delete the GUIObjectRoot, since it's managed by the GUIOverlay.
if(name=="cmdUpdate" || name=="cmdInstall" || name=="cmdRemove"){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
void Addons::removeAddon(ImageManager& imageManager,SDL_Renderer& renderer, Addon* addon){
//To remove an addon we loop over the content vector in the structure.
//NOTE: This should contain all INSTALLED content, if something failed during installation it isn't added.
for(unsigned int i=0;i<addon->content.size();i++){
//Check the type of content.
if(addon->content[i].first=="file"){
string file=getUserPath(USER_DATA)+addon->content[i].second;
//Check if the file exists.
if(!fileExists(file.c_str())){
cerr<<"WARNING: File '"<<file<<"' appears to have been removed already."<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: File '%s' appears to have been removed already."),file),MsgBoxOKOnly,_("Addon error"));
continue;
}
//Remove the file.
if(!removeFile(file.c_str())){
cerr<<"ERROR: Unable to remove file '"<<file<<"'!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("ERROR: Unable to remove file '%s'!"),file),MsgBoxOKOnly,_("Addon error"));
continue;
}
}else if(addon->content[i].first=="folder"){
string dir=getUserPath(USER_DATA)+addon->content[i].second;
//Check if the directory exists.
if(!dirExists(dir.c_str())){
cerr<<"WARNING: Directory '"<<dir<<"' appears to have been removed already."<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Directory '%s' appears to have been removed already."),dir),MsgBoxOKOnly,_("Addon error"));
continue;
}
//Remove the directory.
if(!removeDirectory(dir.c_str())){
cerr<<"ERROR: Unable to remove directory '"<<dir<<"'!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("ERROR: Unable to remove directory '%s'!"),dir),MsgBoxOKOnly,_("Addon error"));
continue;
}
}else if(addon->content[i].first=="level"){
string file=getUserPath(USER_DATA)+"levels/"+addon->content[i].second;
//Check if the level file exists.
if(!fileExists(file.c_str())){
cerr<<"WARNING: Level '"<<file<<"' appears to have been removed already."<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Level '%s' appears to have been removed already."),file),MsgBoxOKOnly,_("Addon error"));
continue;
}
//Remove the level file.
if(!removeFile(file.c_str())){
cerr<<"ERROR: Unable to remove level '"<<file<<"'!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("ERROR: Unable to remove level '%s'!"),file),MsgBoxOKOnly,_("Addon error"));
continue;
}
//Also remove the level from the Levels levelpack.
LevelPack* levelsPack=getLevelPackManager()->getLevelPack(LEVELS_PATH);
for(int i=0;i<levelsPack->getLevelCount();i++){
if(levelsPack->getLevelFile(i)==file){
//Remove the level and break out of the loop.
levelsPack->removeLevel(i);
break;
}
}
}else if(addon->content[i].first=="levelpack"){
//FIXME: We assume no trailing slash since there mustn't be one for installing, bad :(
string dir=getUserPath(USER_DATA)+"levelpacks/"+addon->content[i].second+"/";
//Check if the directory exists.
if(!dirExists(dir.c_str())){
cerr<<"WARNING: Levelpack directory '"<<dir<<"' appears to have been removed already."<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Levelpack directory '%s' appears to have been removed already."),dir),MsgBoxOKOnly,_("Addon error"));
continue;
}
//Remove the directory.
if(!removeDirectory(dir.c_str())){
cerr<<"ERROR: Unable to remove levelpack directory '"<<dir<<"'!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("ERROR: Unable to remove levelpack directory '%s'!"),dir),MsgBoxOKOnly,_("Addon error"));
continue;
}
//Also remove the levelpack from the levelpackManager.
getLevelPackManager()->removeLevelPack(dir, true);
}
}
//Now that the content has been removed clear the content list itself.
addon->content.clear();
//And finally set the addon to not installed.
addon->installed=false;
addon->installedVersion=0;
//Also clear the 'offline' information.
addon->content.clear();
addon->dependencies.clear();
}
void Addons::installAddon(ImageManager& imageManager,SDL_Renderer& renderer, Addon* addon){
string tmpDir=getUserPath(USER_CACHE)+"tmp/";
string fileName=fileNameFromPath(addon->file,true);
//Download the selected addon to the tmp folder.
if(!downloadFile(addon->file,tmpDir)){
cerr<<"ERROR: Unable to download addon file "<<addon->file<<endl;
msgBox(imageManager,renderer,tfm::format(_("ERROR: Unable to download addon file %s."),addon->file),MsgBoxOKOnly,_("Addon error"));
return;
}
//Now extract the addon.
if(!extractFile(tmpDir+fileName,tmpDir+"/addon/")){
cerr<<"ERROR: Unable to extract addon file "<<addon->file<<endl;
msgBox(imageManager,renderer,tfm::format(_("ERROR: Unable to extract addon file %s."),addon->file),MsgBoxOKOnly,_("Addon error"));
return;
}
ifstream metadata((tmpDir+"/addon/metadata").c_str());
if(!metadata){
cerr<<"ERROR: Addon is missing metadata!"<<endl;
msgBox(imageManager,renderer,_("ERROR: Addon is missing metadata!"),MsgBoxOKOnly,_("Addon error"));
return;
}
//Read the metadata from the addon.
TreeStorageNode obj;
{
POASerializer objSerializer;
if(!objSerializer.readNode(metadata,&obj,true)){
//NOTE: We keep the console output English so we put the string literal here twice.
cerr<<"ERROR: Invalid file format for metadata file!"<<endl;
msgBox(imageManager,renderer,_("ERROR: Invalid file format for metadata file!"),MsgBoxOKOnly,_("Addon error"));
return;
}
}
//Loop through the subNodes.
for(unsigned int i=0;i<obj.subNodes.size();i++){
//Check for the content subNode (there should only be one).
if(obj.subNodes[i]->name=="content"){
TreeStorageNode* obj1=obj.subNodes[i];
//Loop through the subNodes of that.
for(unsigned int j=0;j<obj1->subNodes.size();j++){
TreeStorageNode* obj2=obj1->subNodes[j];
//This code happens for all types of content.
string source=tmpDir+"addon/content/";
if(obj2->value.size()>0)
source+=obj2->value[0];
//The destination MUST be in the user data path.
string dest=getUserPath(USER_DATA);
if(obj2->value.size()>1)
dest+=obj2->value[1];
//Check what the content type is.
if(obj2->name=="file" && obj2->value.size()==2){
//Now copy the file.
if(fileExists(dest.c_str())){
cerr<<"WARNING: File '"<<dest<<"' already exists, addon may be broken or not working!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: File '%s' already exists, addon may be broken or not working!"),dest),MsgBoxOKOnly,_("Addon error"));
continue;
}
if(!copyFile(source.c_str(),dest.c_str())){
cerr<<"WARNING: Unable to copy file '"<<source<<"' to '"<<dest<<"', addon may be broken or not working!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Unable to copy file '%s' to '%s', addon may be broken or not working!"),source,dest),MsgBoxOKOnly,_("Addon error"));
continue;
}
//Add it to the content vector.
addon->content.push_back(pair<string,string>("file",obj2->value[1]));
}else if(obj2->name=="folder" && obj2->value.size()==2){
//The dest must NOT exist, otherwise it will fail.
if(dirExists(dest.c_str())){
cerr<<"WARNING: Destination directory '"<<dest<<"' already exists, addon may be broken or not working!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Destination directory '%s' already exists, addon may be broken or not working!"),dest),MsgBoxOKOnly,_("Addon error"));
continue;
}
//FIXME: Copy the directory instead of renaming it, in case the same folder/parts of the folder are needed in different places.
if(!renameDirectory(source.c_str(),dest.c_str())){
cerr<<"WARNING: Unable to move directory '"<<source<<"' to '"<<dest<<"', addon may be broken or not working!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Unable to move directory '%s' to '%s', addon may be broken or not working!"),source,dest),MsgBoxOKOnly,_("Addon error"));
continue;
}
//Add it to the content vector.
addon->content.push_back(pair<string,string>("folder",obj2->value[1]));
}else if(obj2->name=="level" && obj2->value.size()==1){
//The destination MUST be in the levels folder in the user data path.
dest+="levels/"+fileNameFromPath(source);
//Now copy the file.
if(fileExists(dest.c_str())){
cerr<<"WARNING: Level '"<<dest<<"' already exists, addon may be broken or not working!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Level '%s' already exists, addon may be broken or not working!"),dest),MsgBoxOKOnly,_("Addon error"));
continue;
}
if(!copyFile(source.c_str(),dest.c_str())){
cerr<<"WARNING: Unable to copy level '"<<source<<"' to '"<<dest<<"', addon may be broken or not working!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Unable to copy level '%s' to '%s', addon may be broken or not working!"),source,dest),MsgBoxOKOnly,_("Addon error"));
continue;
}
//It's a level so add it to the Levels levelpack.
LevelPack* levelsPack=getLevelPackManager()->getLevelPack(LEVELS_PATH);
if(levelsPack){
levelsPack->addLevel(dest);
levelsPack->setLocked(levelsPack->getLevelCount()-1);
}else{
cerr<<"ERROR: Unable to add level to Levels levelpack"<<endl;
}
addon->content.push_back(pair<string,string>("level",fileNameFromPath(source)));
}else if(obj2->name=="levelpack" && obj2->value.size()==1){
//TODO: Check if the source contains a trailing slash.
//The destination MUST be in the user data path.
dest+="levelpacks/"+fileNameFromPath(source);
//The dest must NOT exist, otherwise it will fail.
if(dirExists(dest.c_str())){
cerr<<"WARNING: Levelpack directory '"<<dest<<"' already exists, addon may be broken or not working!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Levelpack directory '%s' already exists, addon may be broken or not working!"),dest),MsgBoxOKOnly,_("Addon error"));
continue;
}
//FIXME: Copy the directory instead of renaming it, in case the same folder/parts of the folder are needed in different places.
if(!renameDirectory(source.c_str(),dest.c_str())){
cerr<<"WARNING: Unable to move directory '"<<source<<"' to '"<<dest<<"', addon may be broken or not working!"<<endl;
msgBox(imageManager,renderer,tfm::format(_("WARNING: Unable to move directory '%s' to '%s', addon may be broken or not working!"),source,dest),MsgBoxOKOnly,_("Addon error"));
continue;
}
//It's a levelpack so add it to the levelpack manager.
getLevelPackManager()->loadLevelPack(dest);
addon->content.push_back(pair<string,string>("levelpack",fileNameFromPath(source)));
}
}
}else if(obj.subNodes[i]->name=="dependencies"){
TreeStorageNode* obj1=obj.subNodes[i];
//Loop through the subNodes of that.
for(unsigned int j=0;j<obj1->subNodes.size();j++){
TreeStorageNode* obj2=obj1->subNodes[j];
if(obj2->name=="addon" && obj2->value.size()>0){
Addon* dep=NULL;
//Check if the requested addon can be found.
vector<Addon>::iterator it;
for(it=addons.begin();it!=addons.end();++it){
if(it->name==obj2->value[0]){
dep=&(*it);
break;
}
}
if(!dep){
cerr<<"ERROR: Addon requires another addon ("<<obj2->value[0]<<") which can't be found!"<<endl;
msgBox(imageManager, renderer, tfm::format(_("ERROR: Addon requires another addon (%s) which can't be found!"), obj2->value[0]), MsgBoxOKOnly, _("Addon error"));
continue;
}
//The addon has been found, try to install it.
//FIXME: Somehow prevent recursion, maybe max depth (??)
if(!dep->installed){
msgBox(imageManager, renderer, tfm::format(_("The addon %s is needed and will be installed now."), dep->name), MsgBoxOKOnly, _("Dependency"));
installAddon(imageManager,renderer, dep);
}
//Add the dependency to the addon.
addon->dependencies.push_back(pair<string,string>("addon",dep->name));
}
}
}
}
//The addon is installed and up to date, but not necessarily flawless.
addon->installed=true;
addon->upToDate=true;
addon->installedVersion=addon->version;
}
diff --git a/src/Addons.h b/src/Addons.h
index ab38c1b..86ac8c6 100644
--- a/src/Addons.h
+++ b/src/Addons.h
@@ -1,165 +1,161 @@
/*
* Copyright (C) 2011-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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 Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ADDONS_H
#define ADDONS_H
#include "GameState.h"
#include "GameObjects.h"
#include "GUIObject.h"
#include "GUIListBox.h"
#include <array>
#include <vector>
#include <string>
//The addons menu.
class Addons: public GameState,public GUIEventCallback{
private:
//The minimum addon version that is supported.
static const int MIN_VERSION=2;
//The maximum addon version that is supported.
static const int MAX_VERSION=2;
//An addon entry.
struct Addon{
//The name of the addon.
std::string name;
//The type of addon. (Level, Levelpack, Theme)
std::string type;
//The link to the addon file.
std::string file;
//The name of the author.
std::string author;
//The description of the addon.
std::string description;
//The license of the addon.
std::string license;
//The website of the addon.
std::string website;
//Icon for the addon.
SDL_Surface* icon;
//Screenshot for the addon.
SharedTexture screenshot;
//The latest version of the addon.
int version;
//The version that the user has installed, if installed.
int installedVersion;
//Boolean if the addon is installed.
bool installed;
//Boolean if the addon is upToDate. (installedVersion==version)
bool upToDate;
//Map that contains the content of the addon.
//NOTE: This is only filled if the addon is installed.
std::vector<std::pair<std::string,std::string> > content;
//Array that holds the name of the addons it's dependent on.
//NOTE: This is only filled if the addon is installed.
std::vector<std::pair<std::string,std::string> > dependencies;
};
//The title.
TexturePtr title;
//Placeholder icons for addons in case they don't provide custom icons.
std::map<std::string, SDL_Surface*> addonIcon;
//Placeholder screenshot for addons in case they don't provide one.
SharedTexture screenshot;
//Map containing a vector of Addons for each addon category.
std::vector<Addon> addons;
- //File pointing to the addon file in the userpath.
- FILE* addon;
-
//String that should contain the error when something fails.
std::string error;
//The type of addon that is currently selected.
std::string type;
//Pointer to the addon that is selected.
Addon* selected;
//The list used for the selecting of the category.
GUISingleLineListBox* categoryList;
//Pointer to the description.
GUIObject* categoryDescription;
//The list used for listing the addons.
GUIListBox* list;
public:
//Constructor.
Addons(SDL_Renderer& renderer, ImageManager& imageManager);
//Destructor.
~Addons();
//Method that will create the GUI.
void createGUI(SDL_Renderer &renderer, ImageManager &imageManager);
//Method that loads that downloads the addons list.
- //file: Pointer to the file to download the list to.
//Returns: True if the file is downloaded successfuly.
- bool getAddonsList(FILE* file, SDL_Renderer& renderer, ImageManager& imageManager);
- //
- void fillAddonList(TreeStorageNode &objAddons,TreeStorageNode &objInstalledAddons,SDL_Renderer& renderer, ImageManager& imageManager);
+ bool getAddonsList(SDL_Renderer& renderer, ImageManager& imageManager);
+
+ void fillAddonList(TreeStorageNode &objAddons,TreeStorageNode &objInstalledAddons,SDL_Renderer& renderer, ImageManager& imageManager);
//Put all the addons of a given type in a vector.
//type: The type the addons must be.
//Returns: Vector containing the addons.
void addonsToList(const std::string &type, SDL_Renderer &renderer, ImageManager &);
//Method that will save the installed addons to the installed_addons file.
//Returns: True if the file is saved successfuly.
bool saveInstalledAddons();
//Method for loading a cached image and downloading if it isn't cached.
//url: The url to the image.
//md5sum: The md5sum used for caching.
//Returns: Shared pointer to the loaded image.
SDL_Surface* loadCachedImage(const char* url,const char* md5sum, ImageManager& imageManager);
SharedTexture loadCachedTexture(const char* url, const char* md5sum, SDL_Renderer& renderer, ImageManager& imageManager);
//Method that will open a GUIOverlay with the an overview of the selected addon.
void showAddon(ImageManager& imageManager,SDL_Renderer& renderer);
//Inherited from GameState.
void handleEvents(ImageManager&, SDL_Renderer&) override;
void logic(ImageManager&, SDL_Renderer&) override;
void render(ImageManager&, SDL_Renderer& renderer) override;
void resize(ImageManager &imageManager, SDL_Renderer& renderer) override;
//Method used for GUI event handling.
//name: The name of the callback.
//obj: Pointer to the GUIObject that caused the event.
//eventType: The type of event: click, change, etc..
void GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name,GUIObject* obj,int eventType);
//This method will remove the addon based on the content vector.
//NOTE It doesn't check if the addon is installed or not.
//addon: The addon to remove.
void removeAddon(ImageManager& imageManager,SDL_Renderer &renderer, Addon* addon);
//This method will install the addon by downloading,extracting and reading.
//NOTE It doesn't check if the addon is installed or not.
//addon: The addon to install.
void installAddon(ImageManager& imageManager, SDL_Renderer &renderer, Addon* addon);
};
#endif

File Metadata

Mime Type
text/x-diff
Expires
Sat, May 16, 7:25 PM (1 d, 6 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
63204
Default Alt Text
(74 KB)

Event Timeline