Page Menu
Home
Phabricator (Chris)
Search
Configure Global Search
Log In
Files
F118960
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
108 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/Addons.cpp b/src/Addons.cpp
index 7042410..53cce91 100644
--- a/src/Addons.cpp
+++ b/src/Addons.cpp
@@ -1,696 +1,864 @@
/*
* Copyright (C) 2011-2012 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 "Globals.h"
#include "Objects.h"
#include "GUIObject.h"
#include "GUIOverlay.h"
#include "GUIScrollBar.h"
#include "GUITextArea.h"
#include "GUIListBox.h"
#include "POASerializer.h"
#include "InputManager.h"
#include <string>
#include <sstream>
#include <iostream>
#include "libs/tinyformat/tinyformat.h"
#include <SDL/SDL.h>
#ifdef __APPLE__
#include <SDL_ttf/SDL_ttf.h>
#else
#include <SDL/SDL_ttf.h>
#endif
using namespace std;
-Addons::Addons(){
+Addons::Addons():selected(NULL){
//Render the title.
title=TTF_RenderUTF8_Blended(fontTitle,_("Addons"),themeTextColor);
//Load placeholder addon icons and screenshot.
addonIcon[0]=loadImage(getDataPath()+"/gfx/addon1.png");
SDL_SetAlpha(addonIcon[0],0,0);
-
addonIcon[1]=loadImage(getDataPath()+"/gfx/addon2.png");
SDL_SetAlpha(addonIcon[1],0,0);
-
addonIcon[2]=loadImage(getDataPath()+"/gfx/addon3.png");
SDL_SetAlpha(addonIcon[2],0,0);
-
screenshot=loadImage(getDataPath()+"/gfx/screenshot.png");
-
- FILE* addon=fopen((getUserPath(USER_CACHE)+"addons").c_str(),"wb");
- addons=NULL;
- selected=NULL;
+ //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;
}
//Try to get(download) the addonsList.
- if(getAddonsList(addon)==false) {
+ if(getAddonsList(addon)==false){
//It failed so we show the error message.
GUIObjectRoot=new GUIObject(0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
GUIObject* obj=new GUILabel(90,96,200,32,_("Unable to initialize addon menu:"));
obj->name="lbl";
GUIObjectRoot->addChild(obj);
obj=new GUILabel(120,130,200,32,error.c_str());
obj->name="lbl";
GUIObjectRoot->addChild(obj);
obj=new GUIButton(90,550,200,32,_("Back"));
obj->name="cmdBack";
obj->eventCallback=this;
GUIObjectRoot->addChild(obj);
return;
}
//Now create the GUI.
createGUI();
}
Addons::~Addons(){
- delete addons;
-
//Free the title surface.
SDL_FreeSurface(title);
//If the GUIObjectRoot exist delete it.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
void Addons::createGUI(){
//Downloaded the addons file now we can create the GUI.
GUIObjectRoot=new GUIObject(0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
//Create list of categories
- GUISingleLineListBox *listTabs=new GUISingleLineListBox((SCREEN_WIDTH-360)/2,100,360,36);
- listTabs->name="lstTabs";
- listTabs->addItem(_("Levels"));
- listTabs->addItem(_("Level Packs"));
- listTabs->addItem(_("Themes"));
- listTabs->value=0;
- listTabs->eventCallback=this;
- GUIObjectRoot->addChild(listTabs);
+ categoryList=new GUISingleLineListBox((SCREEN_WIDTH-360)/2,100,360,36);
+ categoryList->name="lstTabs";
+ //Loop through the categories and add them to the list.
+
+ //FIXME: Hack for easy detecting which categories there are.
+ {
+ map<string,bool> categories;
+ map<string,bool>::iterator mapIt;
+ vector<Addon>::iterator it;
+ for(it=addons.begin();it!=addons.end();++it)
+ categories[it->type]=true;
+ for(mapIt=categories.begin();mapIt!=categories.end();++mapIt)
+ categoryList->addItem(mapIt->first,_(mapIt->first));
+ }
+ categoryList->value=0;
+ categoryList->eventCallback=this;
+ GUIObjectRoot->addChild(categoryList);
//Create the list for the addons.
//By default levels will be selected.
list=new GUIListBox(SCREEN_WIDTH*0.1,160,SCREEN_WIDTH*0.8,SCREEN_HEIGHT-210);
- addonsToList("levels");
+ addonsToList(categoryList->getName());
list->name="lstAddons";
list->clickEvents=true;
list->eventCallback=this;
list->value=-1;
GUIObjectRoot->addChild(list);
type="levels";
//The back button.
GUIObject* obj=new GUIButton(20,20,-1,32,_("Back"));
obj->name="cmdBack";
obj->eventCallback=this;
GUIObjectRoot->addChild(obj);
}
bool Addons::getAddonsList(FILE* file){
//First we download the file.
if(downloadFile(getSettings()->getValue("addon_url"),file)==false){
//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;
}
}
//Fill the vector.
- addons = new std::vector<Addon>;
- fillAddonList(*addons,obj,obj1);
+ fillAddonList(obj,obj1);
//Close the files.
iaddonFile.close();
addonFile.close();
return true;
}
-void Addons::fillAddonList(std::vector<Addons::Addon> &list, TreeStorageNode &addons, TreeStorageNode &installed_addons){
+void Addons::fillAddonList(TreeStorageNode &objAddons, TreeStorageNode &objInstalledAddons){
//Loop through the blocks of the addons file.
//These should contain the types levels, levelpacks, themes.
- for(unsigned int i=0;i<addons.subNodes.size();i++){
- TreeStorageNode* block=addons.subNodes[i];
+ for(unsigned int i=0;i<objAddons.subNodes.size();i++){
+ TreeStorageNode* block=objAddons.subNodes[i];
if(block==NULL) continue;
-
- string type;
- type=block->name;
- //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=addon.screenshot=NULL;
- addon.type=type;
- addon.name=entry->value[0];
-
- if(!entry->attributes["file"].empty())
- addon.file=entry->attributes["file"][0];
- if(!entry->attributes["folder"].empty())
- addon.folder=entry->attributes["folder"][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["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());
- if(addon.icon)
- SDL_SetAlpha(addon.icon,0,0);
- }
- 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=loadCachedImage(entry->attributes["screenshot"][0].c_str(),entry->attributes["screenshot"][1].c_str());
- if(addon.screenshot)
- SDL_SetAlpha(addon.screenshot,0,0);
- }
- 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<installed_addons.subNodes.size();i++){
- TreeStorageNode* installed=installed_addons.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;
- }
+ //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=addon.screenshot=NULL;
+ addon.type=type;
+ addon.name=entry->value[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["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());
+ if(addon.icon)
+ SDL_SetAlpha(addon.icon,0,0);
+ }
+ 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=loadCachedImage(entry->attributes["screenshot"][0].c_str(),entry->attributes["screenshot"][1].c_str());
+ if(addon.screenshot)
+ SDL_SetAlpha(addon.screenshot,0,0);
+ }
+ 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;
+ }
+
+ //Also read the content vector.
+ for(unsigned int j=0;j<installed->subNodes.size();j++){
+ if(installed->subNodes[j]->value.size()==1)
+ addon.content.push_back(pair<string,string>(installed->subNodes[j]->name,installed->subNodes[j]->value[0]));
+ }
+ }
}
}
+
+ //Finally put him in the list.
+ addons.push_back(addon);
}
-
- //Finally put him in the list.
- list.push_back(addon);
}
}
}
}
void Addons::addonsToList(const std::string &type){
+ //Clear the list.
list->clearItems();
- for(unsigned int i=0;i<addons->size();i++) {
- //Check if the addon is from the right type.
- if((*addons)[i].type==type) {
- string entry = (*addons)[i].name + " by " + (*addons)[i].author;
- if((*addons)[i].installed) {
- if((*addons)[i].upToDate) {
- entry += " *";
- } else {
- entry += " +";
- }
- }
-
- SDL_Surface* surf=SDL_CreateRGBSurface(SDL_SWSURFACE,list->width,74,32,RMASK,GMASK,BMASK,AMASK);
-
- //Check if there's an icon for the addon.
- if((*addons)[i].icon){
- applySurface(5,5,(*addons)[i].icon,surf,NULL);
+ //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;
+
+ Addon addon=addons[i];
+
+ string entry=addon.name+" by "+addon.author;
+ if(addon.installed){
+ if(addon.upToDate){
+ entry+=" *";
}else{
- if(type=="levels")
- applySurface(5,5,addonIcon[0],surf,NULL);
- else if(type=="levelpacks")
- applySurface(5,5,addonIcon[1],surf,NULL);
- else
- applySurface(5,5,addonIcon[2],surf,NULL);
+ entry+=" +";
}
+ }
+
+ SDL_Surface* surf=SDL_CreateRGBSurface(SDL_SWSURFACE,list->width,74,32,RMASK,GMASK,BMASK,AMASK);
+
+ //Check if there's an icon for the addon.
+ if(addon.icon){
+ applySurface(5,5,addon.icon,surf,NULL);
+ }else{
+ if(type=="levels")
+ applySurface(5,5,addonIcon[0],surf,NULL);
+ else if(type=="levelpacks")
+ applySurface(5,5,addonIcon[1],surf,NULL);
+ else
+ applySurface(5,5,addonIcon[2],surf,NULL);
+ }
- SDL_Color black={0,0,0,0};
- SDL_Surface* nameSurf=TTF_RenderUTF8_Blended(fontGUI,(*addons)[i].name.c_str(),black);
- SDL_SetAlpha(nameSurf,0,0xFF);
- applySurface(74,-1,nameSurf,surf,NULL);
- SDL_FreeSurface(nameSurf);
-
- /// TRANSLATORS: indicates the author of an addon.
- string authorLine = tfm::format(_("by %s"),(*addons)[i].author);
- SDL_Surface* authorSurf=TTF_RenderUTF8_Blended(fontText,authorLine.c_str(),black);
- SDL_SetAlpha(authorSurf,0,0xFF);
- applySurface(74,43,authorSurf,surf,NULL);
- SDL_FreeSurface(authorSurf);
-
- if((*addons)[i].installed){
- if((*addons)[i].upToDate){
- SDL_Surface* infoSurf=TTF_RenderUTF8_Blended(fontText,_("Installed"),black);
- SDL_SetAlpha(infoSurf,0,0xFF);
- applySurface(surf->w-infoSurf->w-32,(surf->h-infoSurf->h)/2,infoSurf,surf,NULL);
- SDL_FreeSurface(infoSurf);
- }else{
- SDL_Surface* infoSurf=TTF_RenderUTF8_Blended(fontText,_("Updatable"),black);
- SDL_SetAlpha(infoSurf,0,0xFF);
- applySurface(surf->w-infoSurf->w-32,(surf->h-infoSurf->h)/2,infoSurf,surf,NULL);
- SDL_FreeSurface(infoSurf);
- }
+ SDL_Color black={0,0,0,0};
+ SDL_Surface* nameSurf=TTF_RenderUTF8_Blended(fontGUI,addon.name.c_str(),black);
+ SDL_SetAlpha(nameSurf,0,0xFF);
+ applySurface(74,-1,nameSurf,surf,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(),black);
+ SDL_SetAlpha(authorSurf,0,0xFF);
+ applySurface(74,43,authorSurf,surf,NULL);
+ SDL_FreeSurface(authorSurf);
+
+ if(addon.installed){
+ if(addon.upToDate){
+ SDL_Surface* infoSurf=TTF_RenderUTF8_Blended(fontText,_("Installed"),black);
+ SDL_SetAlpha(infoSurf,0,0xFF);
+ applySurface(surf->w-infoSurf->w-32,(surf->h-infoSurf->h)/2,infoSurf,surf,NULL);
+ SDL_FreeSurface(infoSurf);
}else{
- SDL_Color grey={127,127,127};
- SDL_Surface* infoSurf=TTF_RenderUTF8_Blended(fontText,_("Not installed"),grey);
+ SDL_Surface* infoSurf=TTF_RenderUTF8_Blended(fontText,_("Updatable"),black);
SDL_SetAlpha(infoSurf,0,0xFF);
applySurface(surf->w-infoSurf->w-32,(surf->h-infoSurf->h)/2,infoSurf,surf,NULL);
SDL_FreeSurface(infoSurf);
}
-
- list->addItem(entry,surf);
+ }else{
+ SDL_Color grey={127,127,127};
+ SDL_Surface* infoSurf=TTF_RenderUTF8_Blended(fontText,_("Not installed"),grey);
+ SDL_SetAlpha(infoSurf,0,0xFF);
+ applySurface(surf->w-infoSurf->w-32,(surf->h-infoSurf->h)/2,infoSurf,surf,NULL);
+ SDL_FreeSurface(infoSurf);
}
+
+ list->addItem(entry,surf);
}
}
bool Addons::saveInstalledAddons(){
- if(!addons) return false;
-
//Open the file.
ofstream iaddons;
iaddons.open((getUserPath(USER_CONFIG)+"installed_addons").c_str());
if(!iaddons) return false;
-
- //Loop all the levels.
+
TreeStorageNode installed;
- for(unsigned int i=0;i<addons->size();i++) {
+
+ //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((*addons)[i].installed) {
+ if(it->installed) {
TreeStorageNode *entry=new TreeStorageNode;
entry->name="entry";
- entry->value.push_back((*addons)[i].type);
- entry->value.push_back((*addons)[i].name);
+ entry->value.push_back(it->type);
+ entry->value.push_back(it->name);
char version[64];
- sprintf(version,"%d",(*addons)[i].installedVersion);
+ sprintf(version,"%d",it->installedVersion);
entry->value.push_back(version);
-
+
+ //Now add a subNode for each content.
+ for(int i=0;i<it->content.size();i++){
+ TreeStorageNode* content=new TreeStorageNode;
+ content->name=it->content[i].first;
+ content->value.push_back(it->content[i].second);
+
+ //Add the content node to the entry node.
+ entry->subNodes.push_back(content);
+ }
+
installed.subNodes.push_back(entry);
}
}
-
//And write away the file.
POASerializer objSerializer;
objSerializer.writeNode(&installed,iaddons,true,true);
return true;
}
SDL_Surface* Addons::loadCachedImage(const char* url,const char* md5sum){
//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 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 loadImage(imageFile);
}
}
void Addons::handleEvents(){
//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.isKeyUpEvent(INPUTMGR_ESCAPE)){
setNextState(STATE_MENU);
}
}
void Addons::logic(){}
void Addons::render(){
//Draw background.
objThemes.getBackground(true)->draw(screen);
//Draw the title.
applySurface((SCREEN_WIDTH-title->w)/2,40-TITLE_FONT_RAISE,title,screen,NULL);
}
void Addons::resize(){
//Delete the gui (if any).
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Now create a new one.
createGUI();
}
void Addons::showAddon(){
//Make sure an addon is selected.
if(!selected)
return;
//Create a root object.
GUIObject* root=new GUIFrame((SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-400)/2,600,400,selected->name.c_str());
//Create the 'by creator' label.
GUIObject* obj=new GUILabel(0,50,600,50,tfm::format(_("by %s"),selected->author).c_str(),0,true,true,GUIGravityCenter);
root->addChild(obj);
//Create the description text.
GUITextArea* description=new GUITextArea(10,100,370,200);
description->setString(selected->description.c_str());
description->editable=false;
description->resize();
root->addChild(description);
//Create the screenshot image.
GUIImage* img=new GUIImage(390,100,200,150);
img->setImage(selected->screenshot?selected->screenshot:screenshot);
root->addChild(img);
//Add buttons depending on the installed/update status.
if(selected->installed && !selected->upToDate){
GUIObject* bRemove=new GUIButton(root->width*0.97,350,-1,32,_("Remove"),0,true,true,GUIGravityRight);
bRemove->name="cmdRemove";
bRemove->eventCallback=this;
root->addChild(bRemove);
//Create a back button.
GUIObject* bBack=new GUIButton(root->width*0.03,350,-1,32,_("Back"),0,true,true,GUIGravityLeft);
bBack->name="cmdCloseOverlay";
bBack->eventCallback=this;
root->addChild(bBack);
//Update widget sizes.
root->render(0,0,false);
//Create a nicely centered button.
obj=new GUIButton((int)floor((bBack->left+bBack->width+bRemove->left-bRemove->width)*0.5),350,-1,32,_("Update"),0,true,true,GUIGravityCenter);
obj->name="cmdUpdate";
obj->eventCallback=this;
root->addChild(obj);
}else{
if(!selected->installed){
obj=new GUIButton(root->width*0.9,350,-1,32,_("Install"),0,true,true,GUIGravityRight);
obj->name="cmdInstall";
obj->eventCallback=this;
root->addChild(obj);
}else if(selected->upToDate){
obj=new GUIButton(root->width*0.9,350,-1,32,_("Remove"),0,true,true,GUIGravityRight);
obj->name="cmdRemove";
obj->eventCallback=this;
root->addChild(obj);
}
//Create a back button.
obj=new GUIButton(root->width*0.1,350,-1,32,_("Back"),0,true,true,GUIGravityLeft);
obj->name="cmdCloseOverlay";
obj->eventCallback=this;
root->addChild(obj);
}
new GUIOverlay(root);
}
void Addons::GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType){
if(name=="lstTabs"){
- if(obj->value==0){
- addonsToList("levels");
- type="levels";
- }else if(obj->value==1){
- addonsToList("levelpacks");
- type="levelpacks";
- }else{
- addonsToList("themes");
- type="themes";
- }
+ //Get the category type.
+ type=categoryList->getName();
+ //Get the list corresponding with the category and select the first entry.
+ addonsToList(type);
list->value=0;
+ //Call an event as if an entry in the addons listbox was clicked.
GUIEventCallback_OnEvent("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;
- if(!list->item.empty()) {
- string entry = list->getItem(list->value);
- for(unsigned int i=0;i<addons->size();i++) {
- std::string prefix=(*addons)[i].name;
- if(!entry.compare(0, prefix.size(), prefix)) {
- addon=&(*addons)[i];
+
+ //Make sure the addon list on screen isn't empty.
+ if(!list->item.empty()){
+ //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;
list->value=-1;
}else if(eventType==GUIEventClick){
//Make sure an addon is selected.
if(selected){
showAddon();
}
}
}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"){
- //First remove the addon and then install it again.
- if(type.compare("levels")==0) {
- if(downloadFile(selected->file,(getUserPath(USER_DATA)+"/levels/"))!=false){
- selected->upToDate=true;
- selected->installedVersion=selected->version;
- addonsToList("levels");
- }else{
- cerr<<"ERROR: Unable to download addon!"<<endl;
- msgBox(_("ERROR: Unable to download addon!"),MsgBoxOKOnly,_("ERROR:"));
- return;
- }
- }else if(type.compare("levelpacks")==0) {
- if(!removeDirectory((getUserPath(USER_DATA)+"levelpacks/"+selected->folder+"/").c_str())){
- cerr<<"ERROR: Unable to remove the directory "<<(getUserPath(USER_DATA)+"levelpacks/"+selected->folder+"/")<<"."<<endl;
- return;
- }
- if(downloadFile(selected->file,(getUserPath(USER_CACHE)+"/tmp/"))!=false){
- extractFile(getUserPath(USER_CACHE)+"/tmp/"+fileNameFromPath(selected->file,true),getUserPath(USER_DATA)+"/levelpacks/"+selected->folder+"/");
- selected->upToDate=true;
- selected->installedVersion=selected->version;
- addonsToList("levelpacks");
- }else{
- cerr<<"ERROR: Unable to download addon!"<<endl;
- msgBox(_("ERROR: Unable to download addon!"),MsgBoxOKOnly,_("ERROR:"));
- return;
- }
- }else if(type.compare("themes")==0) {
- if(!removeDirectory((getUserPath(USER_DATA)+"themes/"+selected->folder+"/").c_str())){
- cerr<<"ERROR: Unable to remove the directory "<<(getUserPath(USER_DATA)+"themes/"+selected->folder+"/")<<"."<<endl;
- return;
- }
- if(downloadFile(selected->file,(getUserPath(USER_CACHE)+"/tmp/"))!=false){
- extractFile((getUserPath(USER_CACHE)+"/tmp/"+fileNameFromPath(selected->file,true)),(getUserPath(USER_DATA)+"/themes/"+selected->folder+"/"));
- selected->upToDate=true;
- selected->installedVersion=selected->version;
- addonsToList("themes");
- }else{
- cerr<<"ERROR: Unable to download addon!"<<endl;
- msgBox(_("ERROR: Unable to download addon!"),MsgBoxOKOnly,_("ERROR:"));
- return;
- }
+ //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(selected);
+ installAddon(selected);
}
+ addonsToList(categoryList->getName());
}else if(name=="cmdInstall"){
- //Download the addon.
- if(type.compare("levels")==0) {
- if(downloadFile(selected->file,getUserPath(USER_DATA)+"/levels/")!=false){
- selected->upToDate=true;
- selected->installed=true;
- selected->installedVersion=selected->version;
- addonsToList("levels");
-
- //And add the level to the levels levelpack.
- LevelPack* levelsPack=getLevelPackManager()->getLevelPack("Levels");
- levelsPack->addLevel(getUserPath(USER_DATA)+"/levels/"+fileNameFromPath(selected->file));
- levelsPack->setLocked(levelsPack->getLevelCount()-1);
- }else{
- cerr<<"ERROR: Unable to download addon!"<<endl;
- msgBox(_("ERROR: Unable to download addon!"),MsgBoxOKOnly,_("ERROR:"));
- return;
- }
- }else if(type.compare("levelpacks")==0) {
- if(downloadFile(selected->file,getUserPath(USER_CACHE)+"/tmp/")!=false){
- extractFile(getUserPath(USER_CACHE)+"/tmp/"+fileNameFromPath(selected->file,true),getUserPath(USER_DATA)+"/levelpacks/"+selected->folder+"/");
- selected->upToDate=true;
- selected->installed=true;
- selected->installedVersion=selected->version;
- addonsToList("levelpacks");
-
- //And add the levelpack to the levelpackManager.
- getLevelPackManager()->loadLevelPack(getUserPath(USER_DATA)+"/levelpacks/"+selected->folder);
- }else{
- cerr<<"ERROR: Unable to download addon!"<<endl;
- msgBox(_("ERROR: Unable to download addon!"),MsgBoxOKOnly,_("ERROR:"));
- return;
- }
- }else if(type.compare("themes")==0) {
- if(downloadFile(selected->file,getUserPath(USER_CACHE)+"/tmp/")!=false){
- extractFile(getUserPath(USER_CACHE)+"/tmp/"+fileNameFromPath(selected->file,true),getUserPath(USER_DATA)+"/themes/"+selected->folder+"/");
- selected->upToDate=true;
- selected->installed=true;
- selected->installedVersion=selected->version;
- addonsToList("themes");
- }else{
- cerr<<"ERROR: Unable to download addon!"<<endl;
- msgBox(_("ERROR: Unable to download addon!"),MsgBoxOKOnly,_("ERROR:"));
- return;
- }
- }
+ if(selected)
+ installAddon(selected);
+ addonsToList(categoryList->getName());
}else if(name=="cmdRemove"){
- //Uninstall the addon.
- if(type.compare("levels")==0) {
- if(remove((getUserPath(USER_DATA)+"levels/"+fileNameFromPath(selected->file)).c_str())){
- cerr<<"ERROR: Unable to remove the file "<<(getUserPath(USER_DATA) + "levels/" + fileNameFromPath(selected->file))<<"."<<endl;
- return;
+ //TODO: Check for dependencies.
+ if(selected)
+ removeAddon(selected);
+ addonsToList(categoryList->getName());
+ }
+
+ //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(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(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("WARNING: File '"+file+"' appears to have been removed already.",MsgBoxOKOnly,"Addon error");
+ continue;
}
- selected->upToDate=false;
- selected->installed=false;
- addonsToList("levels");
+ //Remove the file.
+ if(!removeFile(file.c_str())){
+ cerr<<"ERROR: Unable to remove file '"<<file<<"'!"<<endl;
+ msgBox("ERROR: Unable to remove file '"+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("WARNING: Directory '"+dir+"' appears to have been removed already.",MsgBoxOKOnly,"Addon error");
+ continue;
+ }
- //And remove the level from the levels levelpack.
- LevelPack* levelsPack=getLevelPackManager()->getLevelPack("Levels");
+ //Remove the directory.
+ if(!removeDirectory(dir.c_str())){
+ cerr<<"ERROR: Unable to remove directory '"<<dir<<"'!"<<endl;
+ msgBox("ERROR: Unable to remove directory '"+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("WARNING: Level '"+file+"' appears to have been removed already.",MsgBoxOKOnly,"Addon error");
+ continue;
+ }
+ //Remove the level file.
+ if(!removeFile(file.c_str())){
+ cerr<<"ERROR: Unable to remove level '"<<file<<"'!"<<endl;
+ msgBox("ERROR: Unable to remove level '"+file+"'!",MsgBoxOKOnly,"Addon error");
+ continue;
+ }
+ //Also remove the level from the Levels levelpack.
+ LevelPack* levelsPack=getLevelPackManager()->getLevelPack("Levels/");
+
for(int i=0;i<levelsPack->getLevelCount();i++){
- if(levelsPack->getLevelFile(i)==(getUserPath(USER_DATA)+"levels/"+fileNameFromPath(selected->file))){
+ if(levelsPack->getLevelFile(i)==file){
//Remove the level and break out of the loop.
levelsPack->removeLevel(i);
break;
}
}
- }else if(type.compare("levelpacks")==0) {
- if(!removeDirectory((getUserPath(USER_DATA)+"levelpacks/"+selected->folder+"/").c_str())){
- cerr<<"ERROR: Unable to remove the directory "<<(getUserPath(USER_DATA)+"levelpacks/"+selected->folder+"/")<<"."<<endl;
- return;
+ }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("WARNING: Levelpack directory '"+dir+"' appears to have been removed already.",MsgBoxOKOnly,"Addon error");
+ continue;
}
-
- selected->upToDate=false;
- selected->installed=false;
- addonsToList("levelpacks");
-
- //And remove the levelpack from the levelpack manager.
- getLevelPackManager()->removeLevelPack(selected->folder);
- }else if(type.compare("themes")==0) {
- if(!removeDirectory((getUserPath(USER_DATA)+"themes/"+selected->folder+"/").c_str())){
- cerr<<"ERROR: Unable to remove the directory "<<(getUserPath(USER_DATA)+"themes/"+selected->folder+"/")<<"."<<endl;
- return;
+
+ //Remove the directory.
+ if(!removeDirectory(dir.c_str())){
+ cerr<<"ERROR: Unable to remove levelpack directory '"<<dir<<"'!"<<endl;
+ msgBox("ERROR: Unable to remove levelpack directory '"+dir+"'!",MsgBoxOKOnly,"Addon error");
+ continue;
}
- selected->upToDate=false;
- selected->installed=false;
- addonsToList("themes");
+ //Also remove the levelpack from the levelpackManager.
+ getLevelPackManager()->removeLevelPack(dir);
}
}
- //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;
+ //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;
+}
+
+void Addons::installAddon(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("ERROR: Unable to download addon file "+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("ERROR: Unable to extract addon file "+addon->file,MsgBoxOKOnly,"Addon error");
+ return;
}
+
+ ifstream metadata((tmpDir+"/addon/metadata").c_str());
+ if(!metadata){
+ cerr<<"ERROR: Addon is missing metadata!"<<endl;
+ msgBox("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("ERROR: Invalid file format for metadata file!",MsgBoxOKOnly,"Addon error");
+ return;
+ }
+ }
+
+ //Loop through the subNodes.
+ for(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(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("WARNING: File '"+dest+"' already exists, addon may be broken or not working!",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("WARNING: Unable to copy file '"+source+"' to '"+dest+"', addon may be broken or not working!",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("WARNING: Destination directory '"+dest+"' already exists, addon may be broken or not working!",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("WARNING: Unable to move directory '"+source+"' to '"+dest+"', addon may be broken or not working!",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("WARNING: Level '"+dest+"' already exists, addon may be broken or not working!",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("WARNING: Unable to copy level '"+source+"' to '"+dest+"', addon may be broken or not working!",MsgBoxOKOnly,"Addon error");
+ continue;
+ }
+
+ //It's a level so add it to the Levels levelpack.
+ LevelPack* levelsPack=getLevelPackManager()->getLevelPack("Levels/");
+ 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("WARNING: Levelpack directory '"+dest+"' already exists, addon may be broken or not working!",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("WARNING: Unable to move directory '"+source+"' to '"+dest+"', addon may be broken or not working!",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(int j=0;j<obj1->subNodes.size();j++){
+ TreeStorageNode* obj2=obj1->subNodes[j];
+
+ if(obj2->name=="addon"){
+ //TODO: Dependencies
+ }
+ }
+ }
+ }
+
+ //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 53e110e..bc8e193 100644
--- a/src/Addons.h
+++ b/src/Addons.h
@@ -1,142 +1,161 @@
/*
* Copyright (C) 2011-2012 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 <vector>
#include <string>
#ifdef __APPLE__
#include <SDL_mixer/SDL_mixer.h>
#include <SDL_ttf/SDL_ttf.h>
#else
#include <SDL/SDL_mixer.h>
#include <SDL/SDL_ttf.h>
#endif
//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.
string name;
//The type of addon. (Level, Levelpack, Theme)
string type;
//The link to the addon file.
string file;
- //The folder to place the addon in, only for themes and levelpacks.
- string folder;
//The name of the author.
string author;
//The description of the addon.
string description;
//Icon for the addon.
SDL_Surface* icon;
//Screenshot for the addon.
SDL_Surface* 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;
};
//The title.
SDL_Surface* title;
//Placeholder icons for addons in case they don't provide custom icons.
SDL_Surface* addonIcon[3];
//Placeholder screenshot for addons in case they don't provide one.
SDL_Surface* screenshot;
- //Vector containing all the addons.
- std::vector<Addon>* addons;
+ //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.
string error;
//The type of addon that is currently selected.
string type;
//Pointer to the addon that is selected.
Addon* selected;
-
+
+ //The list used for the selecting of the category.
+ GUISingleLineListBox* categoryList;
//The list used for listing the addons.
GUIListBox* list;
public:
//Constructor.
Addons();
//Destructor.
~Addons();
//Method that will create the GUI.
void createGUI();
//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);
//
- void fillAddonList(std::vector<Addons::Addon> &list,TreeStorageNode &addons,TreeStorageNode &installed);
+ void fillAddonList(TreeStorageNode &objAddons,TreeStorageNode &objInstalledAddons);
//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 string &type);
//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: Pointer to the SDL_Surface.
SDL_Surface* loadCachedImage(const char* url,const char* md5sum);
//Method that will open a GUIOverlay with the an overview of the selected addon.
void showAddon();
//Inherited from GameState.
void handleEvents();
void logic();
void render();
void resize();
//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(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(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(Addon* addon);
+
};
#endif
diff --git a/src/FileManager.cpp b/src/FileManager.cpp
index f2fb831..6874c15 100644
--- a/src/FileManager.cpp
+++ b/src/FileManager.cpp
@@ -1,811 +1,822 @@
/*
* Copyright (C) 2011-2012 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 <stdio.h>
#include <iostream>
#include <string>
#include <vector>
#include "Globals.h"
#include "FileManager.h"
#include "Functions.h"
#ifdef __APPLE__
#include "archive.h"
#include "archive_entry.h"
#else
#include <archive.h>
#include <archive_entry.h>
#endif
using namespace std;
#ifdef WIN32
#include <windows.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <direct.h>
#pragma comment(lib,"shlwapi.lib")
#else
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>
#endif
//Under Windows there's just one userpath.
#ifdef WIN32
string userPath,dataPath,appPath,exeName;
#else
//But on other platforms we make a difference between the userPath (config files) and the userDataPath (data files).
//Finally there's the path for cache data userCachePath.
string userPath,userDataPath,userCachePath,dataPath,appPath,exeName;
#endif
bool configurePaths() {
//Get the appPath and the exeName.
{
char s[4096];
int i,m;
#ifdef WIN32
m=GetModuleFileNameA(NULL,s,sizeof(s));
#elif defined(ANDROID)
//FIXME: Oops. There are no any executable files in Android.
strcpy(s,"./meandmyshadow");
m=strlen(s);
#else
m=readlink("/proc/self/exe",s,sizeof(s));
#endif
s[m]=0;
for(i=m-1;i>=0;i--){
if(s[i]=='/'||s[i]=='\\'){
s[i]=0;
break;
}
}
appPath=s;
exeName=s+i+1;
}
//TODO: Check if the userpath is empty before setting userPath???
//Check if the userPath is empty.
if(getUserPath().empty()){
#ifdef WIN32
//Get the userPath.
char s[1024];
SHGetSpecialFolderPathA(NULL,s,CSIDL_PERSONAL,1);
userPath=s;
userPath+="\\My Games\\meandmyshadow\\";
#elif defined(ANDROID)
//FIXME: These paths are relative to SDL Android data path.
userPath="./";
userDataPath="./";
userCachePath="./";
#else
//Temp variable that is used to prevent NULL assignement.
char* env;
//First get the $XDG_CONFIG_HOME env var.
env=getenv("XDG_CONFIG_HOME");
//If it's null set userPath to $HOME/.config/.
if(env!=NULL){
userPath=env;
}else{
userPath=getenv("HOME");
userPath+="/.config";
}
//And add meandmyshadow to it.
userPath+="/meandmyshadow/";
//Now get the $XDG_DATA_HOME env var.
env=getenv("XDG_DATA_HOME");
//If it's null set userDataPath to $HOME/.local/share.
if(env!=NULL){
userDataPath=env;
}else{
userDataPath=getenv("HOME");
userDataPath+="/.local/share";
}
//And add meandmyshadow to it.
userDataPath+="/meandmyshadow/";
//Now get the $XDG_CACHE_HOME env var.
env=getenv("XDG_CACHE_HOME");
//If it's null set userCachePath to $HOME/.cache.
if(env!=NULL){
userCachePath=env;
}else{
userCachePath=getenv("HOME");
userCachePath+="/.cache";
}
//And add meandmyshadow to it.
userCachePath+="/meandmyshadow/";
//Set env null.
env=NULL;
#endif
//Print the userPath.
cout<<"User preferences will be fetched from: "<<userPath<<endl;
#ifndef WIN32
//In case of a non-Windows computer show the user data path.
cout<<"User data will be fetched from: "<<userDataPath<<endl;
#endif
}
#ifdef WIN32
//Create the userPath folder and other subfolders.
createDirectory(userPath.c_str());
createDirectory((userPath+"levels").c_str());
createDirectory((userPath+"levelpacks").c_str());
createDirectory((userPath+"themes").c_str());
createDirectory((userPath+"music").c_str());
//The progress folder and subfolders.
createDirectory((userPath+"progress").c_str());
createDirectory((userPath+"progress\\main").c_str());
createDirectory((userPath+"progress\\addon").c_str());
createDirectory((userPath+"progress\\custom").c_str());
createDirectory((userPath+"tmp").c_str());
createDirectory((userPath+"images").c_str());
//The records folder for recordings.
createDirectory((userPath+"records").c_str());
createDirectory((userPath+"records\\autosave").c_str());
//And the custom folder inside the userpath.
createDirectory((userPath+"custom").c_str());
createDirectory((userPath+"custom\\levels").c_str());
createDirectory((userPath+"custom\\levelpacks").c_str());
#else
//Create the userPath.
createDirectory(userPath.c_str());
createDirectory(userDataPath.c_str());
createDirectory(userCachePath.c_str());
//Also create other folders in the userpath.
createDirectory((userDataPath+"/levels").c_str());
createDirectory((userDataPath+"/levelpacks").c_str());
createDirectory((userDataPath+"/themes").c_str());
createDirectory((userDataPath+"/music").c_str());
//The progress folder and subfolders.
createDirectory((userDataPath+"/progress").c_str());
createDirectory((userDataPath+"/progress/main").c_str());
createDirectory((userDataPath+"/progress/addon").c_str());
createDirectory((userDataPath+"/progress/custom").c_str());
createDirectory((userCachePath+"/tmp").c_str());
createDirectory((userCachePath+"/images").c_str());
//The records folder for recordings.
createDirectory((userDataPath+"/records").c_str());
createDirectory((userDataPath+"/records/autosave").c_str());
//And the custom folder inside the userpath.
createDirectory((userDataPath+"/custom").c_str());
createDirectory((userDataPath+"/custom/levels").c_str());
createDirectory((userDataPath+"/custom/levelpacks").c_str());
#endif
//Get the dataPath by trying multiple relative locations.
{
FILE *f;
string s;
while(true){
//try existing one
if(!dataPath.empty()){
s=dataPath+"font/knewave.ttf";
if((f=fopen(s.c_str(),"rb"))!=NULL){
fclose(f);
break;
}
}
//try "./"
dataPath="./data/";
s=dataPath+"font/knewave.ttf";
if((f=fopen(s.c_str(),"rb"))!=NULL){
fclose(f);
break;
}
//try "../"
dataPath="../data/";
s=dataPath+"font/knewave.ttf";
if((f=fopen(s.c_str(),"rb"))!=NULL){
fclose(f);
break;
}
//try App.Path
dataPath=getAppPath()+"/data/";
s=dataPath+"font/knewave.ttf";
if((f=fopen(s.c_str(),"rb"))!=NULL){
fclose(f);
break;
}
//try App.Path+"/../"
dataPath=getAppPath()+"/../data/";
s=dataPath+"font/knewave.ttf";
if((f=fopen(s.c_str(),"rb"))!=NULL){
fclose(f);
break;
}
//try DATA_PATH
#ifdef DATA_PATH
dataPath=DATA_PATH;
s=dataPath+"font/knewave.ttf";
if((f=fopen(s.c_str(),"rb"))!=NULL){
fclose(f);
break;
}
#endif
#ifdef __APPLE__
extern std::string get_data_path();
dataPath = get_data_path();
dataPath=get_data_path();
s=dataPath+"font/knewave.ttf";
if((f=fopen(s.c_str(),"rb"))!=NULL){
fclose(f);
break;
}
#endif
//error: can't find file
return false;
}
//Print the dataPath.
cout<<"Data files will be fetched from: "<<dataPath<<endl;
}
return true;
}
std::vector<std::string> enumAllFiles(std::string path,const char* extension,bool containsPath){
vector<string> v;
#ifdef WIN32
string s1;
WIN32_FIND_DATAA f;
if(!path.empty()){
char c=path[path.size()-1];
if(c!='/'&&c!='\\') path+="\\";
}
s1=path;
if(extension!=NULL && *extension){
s1+="*.";
s1+=extension;
}else{
s1+="*";
}
HANDLE h=FindFirstFileA(s1.c_str(),&f);
if(h==NULL||h==INVALID_HANDLE_VALUE) return v;
do{
if(!(f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)){
if(containsPath){
v.push_back(path+f.cFileName);
}else{
v.push_back(f.cFileName);
}
}
}while(FindNextFileA(h,&f));
FindClose(h);
return v;
#else
int len=0;
if(extension!=NULL && *extension) len=strlen(extension);
if(!path.empty()){
char c=path[path.size()-1];
if(c!='/'&&c!='\\') path+="/";
}
DIR *pDir;
struct dirent *pDirent;
pDir=opendir(path.c_str());
if(pDir==NULL) return v;
while((pDirent=readdir(pDir))!=NULL){
if(pDirent->d_name[0]=='.'){
if(pDirent->d_name[1]==0||
(pDirent->d_name[1]=='.'&&pDirent->d_name[2]==0)) continue;
}
string s1=path+pDirent->d_name;
struct stat S_stat;
lstat(s1.c_str(),&S_stat);
if(!S_ISDIR(S_stat.st_mode)){
if(len>0){
if((int)s1.size()<len+1) continue;
if(s1[s1.size()-len-1]!='.') continue;
if(strcasecmp(&s1[s1.size()-len],extension)) continue;
}
if(containsPath){
v.push_back(s1);
}else{
v.push_back(string(pDirent->d_name));
}
}
}
closedir(pDir);
return v;
#endif
}
std::vector<std::string> enumAllDirs(std::string path,bool containsPath){
vector<string> v;
#ifdef WIN32
string s1;
WIN32_FIND_DATAA f;
if(!path.empty()){
char c=path[path.size()-1];
if(c!='/'&&c!='\\') path+="\\";
}
s1=path+"*";
HANDLE h=FindFirstFileA(s1.c_str(),&f);
if(h==NULL||h==INVALID_HANDLE_VALUE) return v;
do{
// skip '.' and '..' and hidden folders
if(f.cFileName[0]=='.'){
/*if(f.cFileName[1]==0||
(f.cFileName[1]=='.'&&f.cFileName[2]==0))*/ continue;
}
if(f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){
if(containsPath){
v.push_back(path+f.cFileName);
}else{
v.push_back(f.cFileName);
}
}
}while(FindNextFileA(h,&f));
FindClose(h);
return v;
#else
if(!path.empty()){
char c=path[path.size()-1];
if(c!='/'&&c!='\\') path+="/";
}
DIR *pDir;
struct dirent *pDirent;
pDir=opendir(path.c_str());
if(pDir==NULL) return v;
while((pDirent=readdir(pDir))!=NULL){
if(pDirent->d_name[0]=='.'){
if(pDirent->d_name[1]==0||
(pDirent->d_name[1]=='.'&&pDirent->d_name[2]==0)) continue;
}
string s1=path+pDirent->d_name;
struct stat S_stat;
lstat(s1.c_str(),&S_stat);
if(S_ISDIR(S_stat.st_mode)){
//Skip hidden folders.
s1=string(pDirent->d_name);
if(s1.find('.')==0) continue;
//Add result to vector.
if(containsPath){
v.push_back(path+pDirent->d_name);
}else{
v.push_back(s1);
}
}
}
closedir(pDir);
return v;
#endif
}
std::string processFileName(const std::string& s){
string prefix=dataPath;
//FIXME: Do we still need those last three?
//REMARK: maybe 'return prefix+s;' is not needed (?)
// it causes some bugs such as can't save level progress
if(s.compare(0,6,"%DATA%")==0){
if(s.size()>6 && (s[6]=='/' || s[6]=='\\')){
return dataPath+s.substr(7);
}else{
return dataPath+s.substr(6);
}
}else if(s.compare(0,6,"%USER%")==0){
if(s.size()>6 && (s[6]=='/' || s[6]=='\\')){
return getUserPath(USER_DATA)+s.substr(7);
}else{
return getUserPath(USER_DATA)+s.substr(6);
}
}else if(s.compare(0,9,"%LVLPACK%")==0){
if(s.size()>9 && (s[9]=='/' || s[9]=='\\')){
return prefix+"levelpacks/"+s.substr(10);
}else{
return prefix+"levelpacks/"+s.substr(9);
}
}else if(s.compare(0,5,"%LVL%")==0){
if(s.size()>5 && (s[5]=='/' || s[5]=='\\')){
return prefix+"levels/"+s.substr(6);
}else{
return prefix+"levels/"+s.substr(5);
}
}else if(s.compare(0,8,"%THEMES%")==0){
if(s.size()>8 && (s[8]=='/' || s[8]=='\\')){
return prefix+"themes/"+s.substr(9);
}else{
return prefix+"themes/"+s.substr(8);
}
}else if(s.size()>0 && (s[0]=='/' || s[0]=='\\')){
return s;
#ifdef WIN32
// Another fix for Windows :(
}else if(s.size()>1 && (s[1]==':')){
return s;
#endif
}else{
#if defined(ANDROID)
//REMARK: maybe 'return prefix+s;' is not needed (?)
// it causes some bugs such as can't save level progress in Android.
return s;
#else
return prefix+s;
#endif
}
}
std::string compressFileName(const std::string& s){
//FIXME: Do we need the other ones from processFileName?
//Check if the data path is at the start.
size_t pos=s.find(getDataPath());
if(pos!=string::npos){
pos+=getDataPath().size();
return "%DATA%/"+s.substr(pos);
}
pos=s.find(getUserPath(USER_DATA));
if(pos!=string::npos){
pos+=getUserPath(USER_DATA).size();
return "%USER%/"+s.substr(pos);
}
return s;
}
std::string fileNameFromPath(const std::string &path, const bool webURL){
std::string filename;
size_t pos;
#ifdef WIN32
// NOTE: '/' in string should be '/' not '\/',
// we don't need to escape it
if(webURL){
pos = path.find_last_of("/");
}else{
// NOTE: sometimes path separator in Windows can be '/',
// so we must check botn '\' and '/'
pos = path.find_last_of("\\/");
}
#else
// NOTE: '/' in string should be '/' not '\/',
// we don't need to escape it
pos = path.find_last_of("/");
#endif
if(pos != std::string::npos)
filename.assign(path.begin() + pos + 1, path.end());
else
filename=path;
return filename;
}
std::string pathFromFileName(const std::string &filename){
std::string path;
// NOTE: '/' in string should be '/' not '\/',
// we don't need to escape it
#ifdef WIN32
// NOTE: sometimes path separator in Windows can be '/',
// so we must check botn '\' and '/'
size_t pos = filename.find_last_of("\\/");
#else
size_t pos = filename.find_last_of("/");
#endif
if(pos != std::string::npos)
path.assign(filename.begin(), filename.begin() + pos +1);
else
path=filename;
return path;
}
bool downloadFile(const string &path, const string &destination) {
string filename=fileNameFromPath(path,true);
FILE* file = fopen((destination+filename).c_str(), "wb");
bool status=downloadFile(path,file);
fclose(file);
//And return the status.
return status;
}
bool downloadFile(const string &path, FILE* destination) {
CURL* curl=curl_easy_init();
// proxy test (test only)
string internetProxy = getSettings()->getValue("internet-proxy");
size_t pos = internetProxy.find_first_of(":");
if(pos!=string::npos){
curl_easy_setopt(curl,CURLOPT_PROXYPORT,atoi(internetProxy.substr(pos+1).c_str()));
internetProxy = internetProxy.substr(0,pos);
curl_easy_setopt(curl,CURLOPT_PROXY,internetProxy.c_str());
}
curl_easy_setopt(curl,CURLOPT_URL,path.c_str());
curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,writeData);
curl_easy_setopt(curl,CURLOPT_WRITEDATA,destination);
CURLcode res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
return (res==0);
}
size_t writeData(void *ptr, size_t size, size_t nmemb, void *stream){
return fwrite(ptr, size, nmemb, (FILE *)stream);
}
bool extractFile(const string &fileName, const string &destination) {
//Create the archive we're going to extract.
archive* file;
//Create the destination we're going to extract to.
archive* dest;
file=archive_read_new();
dest=archive_write_disk_new();
archive_write_disk_set_options(dest, ARCHIVE_EXTRACT_TIME);
archive_read_support_format_zip(file);
//Now read the archive.
if(archive_read_open_file(file,fileName.c_str(),10240)) {
cerr<<"Error while reading archive "+fileName<<endl;
return false;
}
//Now write every entry to disk.
int status;
archive_entry* entry=NULL;
while(true) {
status=archive_read_next_header(file,&entry);
if(status==ARCHIVE_EOF){
break;
}
if(status!=ARCHIVE_OK){
cerr<<"Error while reading archive "+fileName<<endl;
return false;
}
archive_entry_copy_pathname(entry,(destination+archive_entry_pathname(entry)).c_str());
status=archive_write_header(dest,entry);
if(status!=ARCHIVE_OK){
cerr<<"Error while extracting archive "+fileName<<endl;
return false;
}else{
copyData(file, dest);
status=archive_write_finish_entry(dest);
if(status!=ARCHIVE_OK){
cerr<<"Error while extracting archive "+fileName<<endl;
return false;
}
}
}
//Finally close the archive.
archive_read_close(file);
archive_read_finish(file);
return true;
}
+bool dirExists(const char* dir){
+#ifdef __linux__
+ struct stat sb;
+ if(stat(dir,&sb) == 0 && S_ISDIR(sb.st_mode)){
+ return true;
+ }
+#endif
+ //TODO: Add Windows code.
+ return false;
+}
+
bool createDirectory(const char* path){
#ifdef WIN32
char s0[1024],s[1024];
GetCurrentDirectoryA(sizeof(s0),s0);
PathCombineA(s,s0,path);
for(unsigned int i=0;i<sizeof(s);i++){
if(s[i]=='\0') break;
else if(s[i]=='/') s[i]='\\';
}
//printf("createDirectory:%s\n",s);
return SHCreateDirectoryExA(NULL,s,NULL)!=0;
#else
return mkdir(path,0777)==0;
#endif
}
bool removeDirectory(const char *path){
#ifdef WIN32
WIN32_FIND_DATAA f;
HANDLE h = FindFirstFileA((string(path)+"\\*").c_str(),&f);
#else
//Open the directory that needs to be removed.
DIR* d=opendir(path);
#endif
//Get the path length
size_t path_len = strlen(path);
//Boolean if the directory is emptied.
//True: succes False: failure
//Default is true because if the directory is empty it will never enter the while loop, but we still have success.
bool r = true;
#ifdef WIN32
if(h!=NULL && h!=INVALID_HANDLE_VALUE) {
#else
//Check if the directory exists.
if(d) {
//Pointer to an entry of the directory.
struct dirent* p;
#endif
#ifdef WIN32
do{
#else
//Loop the entries of the directory that needs to be removed as long as there's no error.
while(r && (p=readdir(d))) {
#endif
/* Skip the names "." and ".." as we don't want to recurse on them. */
#ifdef WIN32
if (!strcmp(f.cFileName, ".") || !strcmp(f.cFileName, "..")) {
#else
if (!strcmp(p->d_name, ".") || !strcmp(p->d_name, "..")) {
#endif
//The filename is . or .. so we continue to the next entry.
continue;
} else {
//r2 tells if the entry is deleted.
//True: succes False: failure
//Default is false.
bool r2 = false;
char* buf;
size_t len;
#ifdef WIN32
//Get the length of the path + the directory entry name.
len = path_len + strlen(f.cFileName) + 2;
#else
//Get the length of the path + the directory entry name.
len = path_len + strlen(p->d_name) + 2;
#endif
buf = (char*) malloc(len);
if(buf) {
#ifdef WIN32
_snprintf(buf, len, "%s\\%s", path, f.cFileName);
if(f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){
r2 = removeDirectory(buf);
}else{
r2 = unlink(buf)==0;
}
#else
struct stat statbuf;
snprintf(buf, len, "%s/%s", path, p->d_name);
if(!stat(buf, &statbuf)){
//Check if the entry is a directory or a file.
if (S_ISDIR(statbuf.st_mode)){
//We call ourself(removeDirectory) recursively.
//We return true on success.
r2 = removeDirectory(buf);
}else{
//unlink() returns zero on succes so we set r2 to the unlink(buf)==0.
r2 = unlink(buf)==0;
}
}
#endif
//Free the buf.
free(buf);
}
//We set r to r2 since r2 contains the status of the latest deletion.
r = r2;
}
#ifdef WIN32
}while(r && FindNextFileA(h,&f));
FindClose(h);
#else
}
//Close the directory.
closedir(d);
#endif
}
//The while loop has ended, meaning we (tried) cleared the directory.
//If r is true, meaning no errors we can delete the directory.
if(r){
//The return value of rmdir is 0 when it succeeded.
r = rmdir(path)==0;
}
//Return the status.
return r;
}
bool renameDirectory(const char* oldPath,const char* newPath){
return rename(oldPath,newPath)==0;
}
void copyData(archive* file, archive* dest) {
int status;
const void* buff;
size_t size;
#if ARCHIVE_VERSION_NUMBER < 3000000
off_t offset;
#else
int64_t offset;
#endif
while(true) {
status=archive_read_data_block(file, &buff, &size, &offset);
if(status==ARCHIVE_EOF){
return;
}
if(status!=ARCHIVE_OK){
cerr<<"Error while writing data to disk."<<endl;
return;
}
status=archive_write_data_block(dest, buff, size, offset);
if(status!=ARCHIVE_OK) {
cerr<<"Error while writing data to disk."<<endl;
return;
}
}
}
bool copyFile(const char* source,const char* dest){
//Open the source file.
ifstream fin(source,fstream::binary);
if(!fin)
return false;
//Open the dest file.
ofstream fout(dest,fstream::trunc|fstream::binary);
if(!fout)
return false;
//Copy.
fout<<fin.rdbuf();
return true;
}
bool removeFile(const char* file){
return remove(file)==0;
}
bool fileExists(const char* file){
#ifdef WIN32
bool ret=false;
HANDLE h=CreateFileA(file,0,FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,NULL,OPEN_EXISTING,0,NULL);
if(h!=INVALID_HANDLE_VALUE){
ret=true;
CloseHandle(h);
}
return ret;
#else
return (access(file,F_OK)==0);
#endif
}
bool createFile(const char* file){
//Open the file with write permission.
FILE* f=fopen(file,"wb");
//Check if there are no problems.
if(f){
//Close the file.
fclose(f);
return true;
}else{
return false;
}
}
diff --git a/src/FileManager.h b/src/FileManager.h
index 852af70..d823ec3 100644
--- a/src/FileManager.h
+++ b/src/FileManager.h
@@ -1,201 +1,205 @@
/*
* Copyright (C) 2011-2012 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 FILE_MANAGER_H
#define FILE_MANAGER_H
#include <string>
#include <vector>
#include <iostream>
//Included for the extractFile method.
#ifdef __APPLE__
#include "archive.h"
#else
#include <archive.h>
#endif
//Included for the downloadFile method.
#include <curl/curl.h>
//NOTE: All the methods work with processed pathnames.
//So %DATA%, %USER%, etc. can't be used.
//With exception of processFileName().
//A few strings that all have to do with file locations.
//userPath = The path the user files will be stored (addons, settings).
//exeName = The name of the executable.
//dataPath = The path the data files are located.
//appPath = The path where the executable is located.
extern std::string userPath,exeName,dataPath,appPath;
//The following two paths are for non-Windows systems only.
//userDataPath = The path for user created content and user downloaded content (addons).
//userCachePath = The path where temporary stuff will be stored.
#ifndef WIN32
extern std::string userDataPath,userCachePath;
#endif
//Enum containing the different userPath types.
//NOTE: They are only needed for the non-Windows platform..
enum UserPaths{
//The userpath containing the config files.
//Default $HOME/.config/meandmyshadow/
USER_CONFIG,
//The userpath containing the user data.
//Default $HOME/.local/share/meandmyshadow/
USER_DATA,
//The userpath containing the temporary files.
//Default $HOME/.cache/meandmyshadow/
USER_CACHE
};
//Method for retrieving the userPath.
//type: The type of userpath to return, only used on non-Windows platforms.
//Returns: The userPath.
inline const std::string& getUserPath(int type=0){
#ifdef WIN32
return userPath;
#else
switch(type){
case USER_CONFIG:
return userPath;
break;
case USER_DATA:
return userDataPath;
break;
case USER_CACHE:
return userCachePath;
break;
default:
std::cerr<<"WARNING: Illegal userpath type, returning user config path."<<std::endl;
return userPath;
break;
}
#endif
}
//Method for retrieving the exeName.
//Returns: The exeName.
inline const std::string& getEXEName(){
return exeName;
}
//Method for retrieving the dataPath.
//Returns: The dataPath.
inline const std::string& getDataPath(){
return dataPath;
}
//Method for retrieving the appPath.
//Returns: The appPath.
inline const std::string& getAppPath(){
return appPath;
}
//This method will try to find paths for the userPath, dataPath, appPath and exeName.
//Returns: True if nothing went wrong.
bool configurePaths();
//Method that returns a list of all the files in a given directory.
//path: The path to list the files of.
//extension: The extension the files must have.
//containsPath: Specifies if the return file name should contains path.
//Returns: A vector containing the names of the files.
std::vector<std::string> enumAllFiles(std::string path,const char* extension=NULL,bool containsPath=false);
//Method that returns a list of all the directories in a given directory.
//path: The path to list the directory of.
//containsPath: Specifies if the return file name should contains path.
//Returns: A vector containing the names of the directories.
std::vector<std::string> enumAllDirs(std::string path,bool containsPath=false);
//Method that will parse the string.
//It will convert %USER%, %DATA%, etc. to their according path.
//s: The string that needs to be processed.
//Returns: The processed string.
std::string processFileName(const std::string& s);
//Method that will parse the string.
//FIXME: The method name isn't misleading.
//It will convert the path to the proper domain %USER%, %DATA%, etc. if detected.
//s: The string that needs to be processed.
//Returns: The processed string.
std::string compressFileName(const std::string& s);
//Method used to retrieve the fileName from a full path.
//path: The path with the filename.
//webURL: Boolean if the path is a weburl.
//Returns: String containing the fileName.
std::string fileNameFromPath(const std::string &path, const bool webURL=false);
//Method used to retrieve the path without the fileName from a full path.
//filename: The path with the filename.
//Returns: String containing the path.
std::string pathFromFileName(const std::string &filename);
//Method that will download a file.
//path: The file to download.
//destination: The destination path where the file will be downloaded to.
//Returns: True if it succeeds without errors.
bool downloadFile(const std::string &path, const std::string &destination);
//Method that will download a file.
//path: The file to download.
//destination: A destination file where the downloaded file will be written to.
//Returns: True if it succeeds without errors.
bool downloadFile(const std::string &path, FILE* destination);
//Method used by curl to copy blocks of data.
size_t writeData(void* ptr,size_t size,size_t nmemb,void* stream);
//Method that will extract an archive and places it in the destination folder.
//fileName: The name of the archive.
//destination: The destination location where the extracted files will come.
//Returns: True if it succeeds without errors.
bool extractFile(const std::string &fileName, const std::string &destination);
//Method used to read a data blcok from an archive and write it to an archive.
//file: The archive to read from.
//dest: The archive to write to.
void copyData(archive* file, archive* dest);
+//Method that will check if a directory exists.
+//dir: The directory to check.
+//Returns: True if the directory exists.
+bool dirExists(const char* dir);
//Method that will create a directory.
//path: The directory to create.
//Returns: True if it succeeds.
bool createDirectory(const char* path);
//Method that will remove a directory.
//path: The directory to remove.
//Returns: True if it succeeds.
bool removeDirectory(const char* path);
//Method that will rename a directory.
//oldPath: The folder path.
//newPath: The destination folder name.
//Returns: True if it succeeds.
bool renameDirectory(const char* oldPath,const char* newPath);
//Method that will check if a file exists.
//file: The filename of the file to check.
//Returns: True if the files exists.
bool fileExists(const char* file);
//Method that will create a file.
//file: The filename of the file to create.
//Returns: True if it succeeds.
bool createFile(const char* file);
//Method that will copy a file.
//source: The input file.
//dest: The output file.
//Returns: True if it succeeds.
bool copyFile(const char* source,const char* dest);
//Method that will remove a file.
//file: The file to remove.
//Returns: True if it succeeds.
bool removeFile(const char* file);
#endif
diff --git a/src/LevelEditSelect.cpp b/src/LevelEditSelect.cpp
index 75cf4bf..15f5039 100644
--- a/src/LevelEditSelect.cpp
+++ b/src/LevelEditSelect.cpp
@@ -1,744 +1,744 @@
/*
* Copyright (C) 2012 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 "LevelEditSelect.h"
#include "GameState.h"
#include "Functions.h"
#include "FileManager.h"
#include "Globals.h"
#include "Objects.h"
#include "GUIObject.h"
#include "GUIListBox.h"
#include "GUIScrollBar.h"
#include "InputManager.h"
#include "StatisticsManager.h"
#include "Game.h"
#include "GUIOverlay.h"
#ifdef __APPLE__
#include <SDL_ttf/SDL_ttf.h>
#else
#include <SDL/SDL_ttf.h>
#endif
#include <algorithm>
#include <string>
#include <iostream>
#include "libs/tinyformat/tinyformat.h"
using namespace std;
LevelEditSelect::LevelEditSelect():LevelSelect(_("Map Editor"),LevelPackManager::CUSTOM_PACKS){
//Create the gui.
createGUI(true);
//Set the levelEditGUIObjectRoot.
levelEditGUIObjectRoot=GUIObjectRoot;
//show level list
changePack();
refresh();
}
LevelEditSelect::~LevelEditSelect(){
selectedNumber=NULL;
}
void LevelEditSelect::createGUI(bool initial){
if(initial){
//The levelpack name text field.
levelpackName=new GUITextBox(280,104,240,32);
levelpackName->eventCallback=this;
levelpackName->visible=false;
GUIObjectRoot->addChild(levelpackName);
}
if(!initial){
//Remove the previous buttons.
//TODO: better way to do this?
for(int i=0;i<(int)GUIObjectRoot->childControls.size();i++){
if(GUIObjectRoot->childControls[i]->caption!=_("Back")){
delete GUIObjectRoot->childControls[i];
GUIObjectRoot->childControls.erase(GUIObjectRoot->childControls.begin()+i);
i--;
}
}
}
//Create the six buttons at the bottom of the screen.
GUIButton* obj=new GUIButton(SCREEN_WIDTH*0.02,SCREEN_HEIGHT-120,-1,32,_("New Levelpack"));
obj->name="cmdNewLvlpack";
obj->eventCallback=this;
GUIObjectRoot->addChild(obj);
propertiesPack=new GUIButton(SCREEN_WIDTH*0.5,SCREEN_HEIGHT-120,-1,32,_("Pack Properties"),0,true,true,GUIGravityCenter);
propertiesPack->name="cmdLvlpackProp";
propertiesPack->eventCallback=this;
GUIObjectRoot->addChild(propertiesPack);
removePack=new GUIButton(SCREEN_WIDTH*0.98,SCREEN_HEIGHT-120,-1,32,_("Remove Pack"),0,true,true,GUIGravityRight);
removePack->name="cmdRmLvlpack";
removePack->eventCallback=this;
GUIObjectRoot->addChild(removePack);
move=new GUIButton(SCREEN_WIDTH*0.02,SCREEN_HEIGHT-60,-1,32,_("Move Map"));
move->name="cmdMoveMap";
move->eventCallback=this;
//NOTE: Set enabled equal to the inverse of initial.
//When resizing the window initial will be false and therefor the move button can stay enabled.
move->enabled=false;
GUIObjectRoot->addChild(move);
remove=new GUIButton(SCREEN_WIDTH*0.5,SCREEN_HEIGHT-60,-1,32,_("Remove Map"),0,false,true,GUIGravityCenter);
remove->name="cmdRmMap";
remove->eventCallback=this;
GUIObjectRoot->addChild(remove);
edit=new GUIButton(SCREEN_WIDTH*0.98,SCREEN_HEIGHT-60,-1,32,_("Edit Map"),0,false,true,GUIGravityRight);
edit->name="cmdEdit";
edit->eventCallback=this;
GUIObjectRoot->addChild(edit);
//Now update widgets and then check if they overlap
GUIObjectRoot->render(0,0,false);
if(propertiesPack->left-propertiesPack->gravityX < obj->left+obj->width ||
propertiesPack->left-propertiesPack->gravityX+propertiesPack->width > removePack->left-removePack->gravityX){
obj->smallFont=true;
obj->width=-1;
propertiesPack->smallFont=true;
propertiesPack->width=-1;
removePack->smallFont=true;
removePack->width=-1;
move->smallFont=true;
move->width=-1;
remove->smallFont=true;
remove->width=-1;
edit->smallFont=true;
edit->width=-1;
}
//Check again
GUIObjectRoot->render(0,0,false);
if(propertiesPack->left-propertiesPack->gravityX < obj->left+obj->width ||
propertiesPack->left-propertiesPack->gravityX+propertiesPack->width > removePack->left-removePack->gravityX){
obj->left = SCREEN_WIDTH*0.02;
obj->top = SCREEN_HEIGHT-140;
obj->smallFont=false;
obj->width=-1;
obj->gravity = GUIGravityLeft;
propertiesPack->left = SCREEN_WIDTH*0.02;
propertiesPack->top = SCREEN_HEIGHT-100;
propertiesPack->smallFont=false;
propertiesPack->width=-1;
propertiesPack->gravity = GUIGravityLeft;
removePack->left = SCREEN_WIDTH*0.02;
removePack->top = SCREEN_HEIGHT-60;
removePack->smallFont=false;
removePack->width=-1;
removePack->gravity = GUIGravityLeft;
move->left = SCREEN_WIDTH*0.98;
move->top = SCREEN_HEIGHT-140;
move->smallFont=false;
move->width=-1;
move->gravity = GUIGravityRight;
remove->left = SCREEN_WIDTH*0.98;
remove->top = SCREEN_HEIGHT-100;
remove->smallFont=false;
remove->width=-1;
remove->gravity = GUIGravityRight;
edit->left = SCREEN_WIDTH*0.98;
edit->top = SCREEN_HEIGHT-60;
edit->smallFont=false;
edit->width=-1;
edit->gravity = GUIGravityRight;
}
}
void LevelEditSelect::changePack(){
packName=levelpacks->item[levelpacks->value].second;
if(packName=="Custom Levels"){
//Disable some levelpack buttons.
propertiesPack->enabled=false;
removePack->enabled=false;
}else{
//Enable some levelpack buttons.
propertiesPack->enabled=true;
removePack->enabled=true;
}
//Set last levelpack.
getSettings()->setValue("lastlevelpack",levelpacks->getName());
//Now let levels point to the right pack.
levels=getLevelPackManager()->getLevelPack(levelpacks->getName());
}
void LevelEditSelect::packProperties(bool newPack){
//Open a message popup.
GUIObject* root=new GUIFrame((SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-320)/2,600,320,_("Properties"));
GUIObject* obj;
obj=new GUILabel(40,50,240,36,_("Name:"));
root->addChild(obj);
obj=new GUITextBox(60,80,480,36,packName.c_str());
if(newPack)
obj->caption="";
obj->name="LvlpackName";
root->addChild(obj);
obj=new GUILabel(40,120,240,36,_("Description:"));
root->addChild(obj);
obj=new GUITextBox(60,150,480,36,levels->levelpackDescription.c_str());
if(newPack)
obj->caption="";
obj->name="LvlpackDescription";
root->addChild(obj);
obj=new GUILabel(40,190,240,36,_("Congratulation text:"));
root->addChild(obj);
obj=new GUITextBox(60,220,480,36,levels->congratulationText.c_str());
if(newPack)
obj->caption="";
obj->name="LvlpackCongratulation";
root->addChild(obj);
obj=new GUIButton(root->width*0.3,320-44,-1,36,_("OK"),0,true,true,GUIGravityCenter);
obj->name="cfgOK";
obj->eventCallback=this;
root->addChild(obj);
obj=new GUIButton(root->width*0.7,320-44,-1,36,_("Cancel"),0,true,true,GUIGravityCenter);
obj->name="cfgCancel";
obj->eventCallback=this;
root->addChild(obj);
//Create the gui overlay.
//NOTE: We don't need to store a pointer since it will auto cleanup itself.
new GUIOverlay(root);
if(newPack){
packName.clear();
}
}
void LevelEditSelect::addLevel(){
//Open a message popup.
GUIObject* root=new GUIFrame((SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-200)/2,600,200,_("Add level"));
GUIObject* obj;
obj=new GUILabel(40,80,240,36,_("File name:"));
root->addChild(obj);
char s[64];
sprintf(s,"map%02d.map",levels->getLevelCount()+1);
obj=new GUITextBox(300,80,240,36,s);
obj->name="LvlFile";
root->addChild(obj);
obj=new GUIButton(root->width*0.3,200-44,-1,36,_("OK"),0,true,true,GUIGravityCenter);
obj->name="cfgAddOK";
obj->eventCallback=this;
root->addChild(obj);
obj=new GUIButton(root->width*0.7,200-44,-1,36,_("Cancel"),0,true,true,GUIGravityCenter);
obj->name="cfgAddCancel";
obj->eventCallback=this;
root->addChild(obj);
//Dim the screen using the tempSurface.\
//NOTE: We don't need to store a pointer since it will auto cleanup itself.
new GUIOverlay(root);
}
void LevelEditSelect::moveLevel(){
//Open a message popup.
GUIObject* root=new GUIFrame((SCREEN_WIDTH-600)/2,(SCREEN_HEIGHT-200)/2,600,200,_("Move level"));
GUIObject* obj;
obj=new GUILabel(40,60,240,36,_("Level: "));
root->addChild(obj);
obj=new GUITextBox(300,60,240,36,"1");
obj->name="MoveLevel";
root->addChild(obj);
obj=new GUISingleLineListBox(root->width*0.5,110,240,36,true,true,GUIGravityCenter);
obj->name="lstPlacement";
vector<string> v;
v.push_back(_("Before"));
v.push_back(_("After"));
v.push_back(_("Swap"));
(dynamic_cast<GUISingleLineListBox*>(obj))->addItems(v);
obj->value=0;
root->addChild(obj);
obj=new GUIButton(root->width*0.3,200-44,-1,36,_("OK"),0,true,true,GUIGravityCenter);
obj->name="cfgMoveOK";
obj->eventCallback=this;
root->addChild(obj);
obj=new GUIButton(root->width*0.7,200-44,-1,36,_("Cancel"),0,true,true,GUIGravityCenter);
obj->name="cfgMoveCancel";
obj->eventCallback=this;
root->addChild(obj);
//Create the gui overlay.
//NOTE: We don't need to store a pointer since it will auto cleanup itself.
new GUIOverlay(root);
}
void LevelEditSelect::refresh(bool change){
int m=levels->getLevelCount();
if(change){
numbers.clear();
//clear the selected level
if(selectedNumber!=NULL){
selectedNumber=NULL;
}
//Disable the level specific buttons.
move->enabled=false;
remove->enabled=false;
edit->enabled=false;
for(int n=0;n<=m;n++){
numbers.push_back(Number());
}
}
for(int n=0;n<m;n++){
SDL_Rect box={(n%LEVELS_PER_ROW)*64+80,(n/LEVELS_PER_ROW)*64+184,0,0};
numbers[n].init(n,box);
}
SDL_Rect box={(m%LEVELS_PER_ROW)*64+80,(m/LEVELS_PER_ROW)*64+184,0,0};
numbers[m].init("+",box);
m++; //including the "+" button
if(m>LEVELS_DISPLAYED_IN_SCREEN){
levelScrollBar->maxValue=(m-LEVELS_DISPLAYED_IN_SCREEN+LEVELS_PER_ROW-1)/LEVELS_PER_ROW;
levelScrollBar->visible=true;
}else{
levelScrollBar->maxValue=0;
levelScrollBar->visible=false;
}
if(!levels->levelpackDescription.empty())
levelpackDescription->caption=_CC(levels->getDictionaryManager(),levels->levelpackDescription);
else
levelpackDescription->caption="";
}
void LevelEditSelect::selectNumber(unsigned int number,bool selected){
if(selected){
levels->setCurrentLevel(number);
setNextState(STATE_LEVEL_EDITOR);
}else{
if(number==numbers.size()-1){
addLevel();
}else if(number<numbers.size()){
selectedNumber=&numbers[number];
//Enable the level specific buttons.
//NOTE: We check if 'remove levelpack' is enabled, if not then it's the Levels levelpack.
if(removePack->enabled)
move->enabled=true;
remove->enabled=true;
edit->enabled=true;
}
}
}
void LevelEditSelect::render(){
//Let the levelselect render.
LevelSelect::render();
}
void LevelEditSelect::resize(){
//Let the levelselect resize.
LevelSelect::resize();
//Create the GUI.
createGUI(false);
//NOTE: This is a workaround for buttons failing when resizing.
if(packName=="Custom Levels"){
removePack->enabled=false;
propertiesPack->enabled=false;
}
if(selectedNumber)
selectNumber(selectedNumber->getNumber(),false);
}
void LevelEditSelect::renderTooltip(unsigned int number,int dy){
SDL_Color fg={0,0,0};
SDL_Surface* name;
if(number==(unsigned)levels->getLevelCount()){
//Render the name of the level.
name=TTF_RenderUTF8_Blended(fontText,_("Add level"),fg);
}else{
//Render the name of the level.
name=TTF_RenderUTF8_Blended(fontText,_CC(levels->getDictionaryManager(),levels->getLevelName(number)),fg);
}
//Check if name isn't null.
if(name==NULL)
return;
//Now draw a square the size of the three texts combined.
SDL_Rect r=numbers[number].box;
r.y-=dy*64;
r.w=name->w;
r.h=name->h;
//Make sure the tooltip doesn't go outside the window.
if(r.y>SCREEN_HEIGHT-200){
r.y-=name->h+4;
}else{
r.y+=numbers[number].box.h+2;
}
if(r.x+r.w>SCREEN_WIDTH-50)
r.x=SCREEN_WIDTH-50-r.w;
//Draw a rectange
Uint32 color=0xFFFFFFFF;
drawGUIBox(r.x-5,r.y-5,r.w+10,r.h+10,screen,color);
//Calc the position to draw.
SDL_Rect r2=r;
//Now we render the name if the surface isn't null.
if(name!=NULL){
//Draw the name.
SDL_BlitSurface(name,NULL,screen,&r2);
}
//And free the surfaces.
SDL_FreeSurface(name);
}
void LevelEditSelect::GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType){
//NOTE: We check for the levelpack change to enable/disable some levelpack buttons.
if(name=="cmdLvlPack"){
//We call changepack and return to prevent the LevelSelect to undo what we did.
changePack();
refresh();
return;
}
//Let the level select handle his GUI events.
LevelSelect::GUIEventCallback_OnEvent(name,obj,eventType);
//Check for the edit button.
if(name=="cmdNewLvlpack"){
//Create a new pack.
packProperties(true);
}else if(name=="cmdLvlpackProp"){
//Show the pack properties.
packProperties(false);
}else if(name=="cmdRmLvlpack"){
//Show an "are you sure" message.
if(msgBox(_("Are you sure?"),MsgBoxYesNo,_("Remove prompt"))==MsgBoxYes){
//Remove the directory.
if(!removeDirectory(levels->levelpackPath.c_str())){
cerr<<"ERROR: Unable to remove levelpack directory "<<levels->levelpackPath<<endl;
}
//Remove it from the vector (levelpack list).
vector<pair<string,string> >::iterator it;
for(it=levelpacks->item.begin();it!=levelpacks->item.end();++it){
if(it->second==packName)
levelpacks->item.erase(it);
}
//Remove it from the levelpackManager.
getLevelPackManager()->removeLevelPack(levels->levelpackPath);
//And call changePack.
levelpacks->value=levelpacks->item.size()-1;
changePack();
refresh();
}
}else if(name=="cmdMoveMap"){
if(selectedNumber!=NULL){
moveLevel();
}
}else if(name=="cmdRmMap"){
if(selectedNumber!=NULL){
if(packName!="Custom Levels"){
if(!removeFile((levels->levelpackPath+"/"+levels->getLevel(selectedNumber->getNumber())->file).c_str())){
cerr<<"ERROR: Unable to remove level "<<(levels->levelpackPath+"/"+levels->getLevel(selectedNumber->getNumber())->file).c_str()<<endl;
}
levels->removeLevel(selectedNumber->getNumber());
levels->saveLevels(levels->levelpackPath+"/levels.lst");
}else{
//This is the levels levelpack so we just remove the file.
if(!removeFile(levels->getLevel(selectedNumber->getNumber())->file.c_str())){
cerr<<"ERROR: Unable to remove level "<<levels->getLevel(selectedNumber->getNumber())->file<<endl;
}
levels->removeLevel(selectedNumber->getNumber());
}
//And refresh the selection screen.
refresh();
}
}else if(name=="cmdEdit"){
if(selectedNumber!=NULL){
levels->setCurrentLevel(selectedNumber->getNumber());
setNextState(STATE_LEVEL_EDITOR);
}
}
//Check for levelpack properties events.
if(name=="cfgOK"){
//Now loop throught the children of the GUIObjectRoot in search of the fields.
for(unsigned int i=0;i<GUIObjectRoot->childControls.size();i++){
if(GUIObjectRoot->childControls[i]->name=="LvlpackName"){
//Check if the name changed.
if(packName!=GUIObjectRoot->childControls[i]->caption){
//Delete the old one.
if(!packName.empty()){
if(!renameDirectory((getUserPath(USER_DATA)+"custom/levelpacks/"+packName).c_str(),(getUserPath(USER_DATA)+"custom/levelpacks/"+GUIObjectRoot->childControls[i]->caption).c_str())){
cerr<<"ERROR: Unable to move levelpack directory "<<(getUserPath(USER_DATA)+"custom/levelpacks/"+packName)<<" to "<<(getUserPath(USER_DATA)+"custom/levelpacks/"+GUIObjectRoot->childControls[i]->caption)<<endl;
}
//Remove the old one from the levelpack manager.
getLevelPackManager()->removeLevelPack(levelpacks->getName());
//And the levelpack list.
vector<pair<string,string> >::iterator it1;
for(it1=levelpacks->item.begin();it1!=levelpacks->item.end();++it1){
if(it1!=levelpacks->item.end()){
levelpacks->item.erase(it1);
break;
}
}
}else{
//It's a new levelpack so we need to change the levels array.
LevelPack* pack=new LevelPack;
levels=pack;
//Now create the dirs.
if(!createDirectory((getUserPath(USER_DATA)+"custom/levelpacks/"+GUIObjectRoot->childControls[i]->caption).c_str())){
cerr<<"ERROR: Unable to create levelpack directory "<<(getUserPath(USER_DATA)+"custom/levelpacks/"+GUIObjectRoot->childControls[i]->caption)<<endl;
}
if(!createFile((getUserPath(USER_DATA)+"custom/levelpacks/"+GUIObjectRoot->childControls[i]->caption+"/levels.lst").c_str())){
cerr<<"ERROR: Unable to create levelpack file "<<(getUserPath(USER_DATA)+"custom/levelpacks/"+GUIObjectRoot->childControls[i]->caption+"/levels.lst")<<endl;
}
}
//And set the new name.
packName=GUIObjectRoot->childControls[i]->caption;
levels->levelpackName=packName;
levels->levelpackPath=(getUserPath(USER_DATA)+"custom/levelpacks/"+packName+"/");
//Also add the levelpack location
getLevelPackManager()->addLevelPack(levels);
levelpacks->addItem(levels->levelpackPath,GUIObjectRoot->childControls[i]->caption);
levelpacks->value=levelpacks->item.size()-1;
//And call changePack.
changePack();
}
}
if(GUIObjectRoot->childControls[i]->name=="LvlpackDescription"){
levels->levelpackDescription=GUIObjectRoot->childControls[i]->caption;
}
if(GUIObjectRoot->childControls[i]->name=="LvlpackCongratulation"){
levels->congratulationText=GUIObjectRoot->childControls[i]->caption;
}
}
//Refresh the leveleditselect to show the correct information.
refresh();
//Save the configuration.
levels->saveLevels(getUserPath(USER_DATA)+"custom/levelpacks/"+packName+"/levels.lst");
getSettings()->setValue("lastlevelpack",levels->levelpackPath);
//Clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}else if(name=="cfgCancel"){
//Check if packName is empty, if so it was a new levelpack and we need to revert to an existing one.
if(packName.empty()){
packName=levelpacks->item[levelpacks->value].second;
changePack();
}
//Clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
//Check for add level events.
if(name=="cfgAddOK"){
//Check if the file name isn't null.
//Now loop throught the children of the GUIObjectRoot in search of the fields.
for(unsigned int i=0;i<GUIObjectRoot->childControls.size();i++){
if(GUIObjectRoot->childControls[i]->name=="LvlFile"){
if(GUIObjectRoot->childControls[i]->caption.empty()){
msgBox(_("No file name given for the new level."),MsgBoxOKOnly,_("Missing file name"));
return;
}else{
string tmp_caption = GUIObjectRoot->childControls[i]->caption;
//Replace all spaces with a underline.
size_t j;
for(;(j=tmp_caption.find(" "))!=string::npos;){
tmp_caption.replace(j,1,"_");
}
//If there isn't ".map" extension add it.
size_t found=tmp_caption.find_first_of(".");
if(found!=string::npos)
tmp_caption.replace(tmp_caption.begin()+found+1,tmp_caption.end(),"map");
else if (tmp_caption.substr(found+1)!="map")
tmp_caption.append(".map");
/* Create path and file in it */
string path=(levels->levelpackPath+"/"+tmp_caption);
if(packName=="Custom Levels"){
path=(getUserPath(USER_DATA)+"/custom/levels/"+tmp_caption);
}
//First check if the file doesn't exist already.
FILE* f;
f=fopen(path.c_str(),"rb");
//Check if it exists.
if(f){
//Close the file.
fclose(f);
//Let the currentState render once to prevent multiple GUI overlapping and prevent the screen from going black.
currentState->render();
levelEditGUIObjectRoot->render();
//Notify the user.
msgBox(string("The file "+tmp_caption+" already exists."),MsgBoxOKOnly,"Error");
return;
}
if(!createFile(path.c_str())){
cerr<<"ERROR: Unable to create level file "<<path<<endl;
}else{
//Update statistics.
statsMgr.newAchievement("create1");
if((++statsMgr.createdLevels)>=50) statsMgr.newAchievement("create50");
}
levels->addLevel(path);
//NOTE: Also add the level to the levels levelpack in case of custom levels.
if(packName=="Custom Levels"){
- LevelPack* levelsPack=getLevelPackManager()->getLevelPack("Levels");
+ LevelPack* levelsPack=getLevelPackManager()->getLevelPack("Levels/");
if(levelsPack){
levelsPack->addLevel(path);
levelsPack->setLocked(levelsPack->getLevelCount()-1);
}else{
- cerr<<_("ERROR: Unable to add level to Levels levelpack")<<endl;
+ cerr<<"ERROR: Unable to add level to Levels levelpack"<<endl;
}
}
if(packName!="Custom Levels")
levels->saveLevels(getUserPath(USER_DATA)+"custom/levelpacks/"+packName+"/levels.lst");
refresh();
//Clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
return;
}
}
}
}
}else if(name=="cfgAddCancel"){
//Clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
//Check for move level events.
if(name=="cfgMoveOK"){
//Check if the entered level number is valid.
//Now loop throught the children of the GUIObjectRoot in search of the fields.
int level=0;
int placement=0;
for(unsigned int i=0;i<GUIObjectRoot->childControls.size();i++){
if(GUIObjectRoot->childControls[i]->name=="MoveLevel"){
level=atoi(GUIObjectRoot->childControls[i]->caption.c_str());
if(level<=0 || level>levels->getLevelCount()){
msgBox(_("The entered level number isn't valid!"),MsgBoxOKOnly,_("Illegal number"));
return;
}
}
if(GUIObjectRoot->childControls[i]->name=="lstPlacement"){
placement=GUIObjectRoot->childControls[i]->value;
}
}
//Now we execute the swap/move.
//Check for the place before.
if(placement==0){
//We place the selected level before the entered level.
levels->moveLevel(selectedNumber->getNumber(),level-1);
}else if(placement==1){
//We place the selected level after the entered level.
if(level<selectedNumber->getNumber())
levels->moveLevel(selectedNumber->getNumber(),level);
else
levels->moveLevel(selectedNumber->getNumber(),level+1);
}else if(placement==2){
//We swap the selected level with the entered level.
levels->swapLevel(selectedNumber->getNumber(),level-1);
}
//And save the change.
if(packName!="Custom Levels")
levels->saveLevels(getUserPath(USER_DATA)+"custom/levelpacks/"+packName+"/levels.lst");
refresh();
//Clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}else if(name=="cfgMoveCancel"){
//Clear the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
}
diff --git a/src/Settings.cpp b/src/Settings.cpp
index 6f4b281..81c40be 100644
--- a/src/Settings.cpp
+++ b/src/Settings.cpp
@@ -1,253 +1,253 @@
/*
* Copyright (C) 2011-2012 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 "Settings.h"
#include <SDL/SDL.h>
#include <string>
#include <stdio.h>
using namespace std;
Settings::Settings(const string fileName): fileName(fileName){
char s[32];
settings["sound"]="128";
settings["music"]="128";
settings["musiclist"]="default";
settings["fullscreen"]="0";
settings["width"]="800";
settings["height"]="600";
settings["resizable"]="1";
#ifdef HARDWARE_ACCELERATION
settings["gl"]="1";
#else
settings["gl"]="0";
#endif
settings["theme"]="%DATA%/themes/Cloudscape";
settings["leveltheme"]="1";
settings["levelmusic"]="1";
settings["internet"]="1";
settings["lastlevelpack"]="tutorial";
settings["internet-proxy"]="";
settings["lang"]="";
- settings["addon_url"]="http://sourceforge.net/p/meandmyshadow/addons/ci/HEAD/tree/addons04?format=raw";
+ settings["addon_url"]="http://sourceforge.net/p/meandmyshadow/addons/ci/HEAD/tree/addons05?format=raw";
//The record mode.
settings["quickrecord"]="0";
//Boolean if fading between states is enabled.
settings["fading"]="1";
//Key settings.
sprintf(s,"%d",(int)SDLK_UP);
settings["key_up"]=settings["key_jump"]=s;
sprintf(s,"%d",(int)SDLK_DOWN);
settings["key_down"]=settings["key_action"]=s; //SDLK_DOWN
sprintf(s,"%d",(int)SDLK_LEFT);
settings["key_left"]=s; //SDLK_LEFT
sprintf(s,"%d",(int)SDLK_RIGHT);
settings["key_right"]=s; //SDLK_RIGHT
sprintf(s,"%d",(int)SDLK_SPACE);
settings["key_space"]=s; //SDLK_SPACE
settings["key_cancelRecording"]="0"; //not set by default
sprintf(s,"%d",(int)SDLK_ESCAPE);
settings["key_escape"]=s; //SDLK_ESCAPE
sprintf(s,"%d",(int)SDLK_r);
settings["key_restart"]=s; //SDLK_r
sprintf(s,"%d",(int)SDLK_TAB);
settings["key_tab"]=s;
sprintf(s,"%d",(int)SDLK_F2);
settings["key_save"]=s; //SDLK_F2
sprintf(s,"%d",(int)SDLK_F3);
settings["key_load"]=s; //SDLK_F3
sprintf(s,"%d",(int)SDLK_F4);
settings["key_swap"]=s; //SDLK_F4
sprintf(s,"%d",(int)SDLK_F5);
settings["key_teleport"]=s; //SDLK_F5
sprintf(s,"%d",(int)SDLK_F12);
settings["key_suicide"]=s;
sprintf(s,"%d",(int)SDLK_RSHIFT);
settings["key_shift"]=s; //SDLK_RSHIFT
sprintf(s,"%d",(int)SDLK_PAGEUP);
settings["key_next"]=s; //SDLK_PAGEUP
sprintf(s,"%d",(int)SDLK_PAGEDOWN);
settings["key_previous"]=s; //SDLK_PAGEDOWN
sprintf(s,"%d",(int)SDLK_RETURN);
settings["key_select"]=s; //SDLK_RETURN
settings["key_up2"]=settings["key_down2"]=settings["key_left2"]=settings["key_right2"]=
settings["key_jump2"]=settings["key_action2"]=
settings["key_space2"]=settings["key_cancelRecording2"]=
settings["key_escape2"]=settings["key_restart2"]=settings["key_tab2"]=
settings["key_save2"]=settings["key_load2"]=settings["key_swap2"]=settings["key_teleport2"]=
settings["key_suicide2"]=settings["key_shift2"]=settings["key_next2"]=settings["key_previous2"]=
settings["key_select2"]="0";
}
void Settings::parseFile(){
//We open the settings file.
ifstream file;
file.open(fileName.c_str());
if(!file){
cout<<"Can't find config file!"<<endl;
createFile();
}
//Now we're going to walk throught the file line by line.
string line;
while(getline(file,line)){
string temp = line;
unComment(temp);
if(temp.empty() || empty(temp))
continue;
//The line is good so we parse it.
parseLine(temp);
}
//And close the file.
file.close();
}
void Settings::parseLine(const string &line){
if((line.find('=') == line.npos) || !validLine(line))
cout<<"Warning illegal line in config file!"<<endl;
string temp = line;
temp.erase(0, temp.find_first_not_of("\t "));
int seperator = temp.find('=');
//Get the key and trim it.
string key, value;
key = line.substr(0, seperator);
if(key.find('\t')!=line.npos || key.find(' ')!=line.npos)
key.erase(key.find_first_of("\t "));
//Get the value and trim it.
value = line.substr(seperator + 1);
value.erase(0, value.find_first_not_of("\t "));
value.erase(value.find_last_not_of("\t ") + 1);
//Add the setting to the settings map.
setValue(key,value);
}
bool Settings::validLine(const string &line){
string temp = line;
temp.erase(0, temp.find_first_not_of("\t "));
if(temp[0] == '=')
return false;
for(size_t i = temp.find('=') + 1; i < temp.length(); i++)
return true;
return false;
}
void Settings::unComment(string &line){
if (line.find('#') != line.npos)
line.erase(line.find('#'));
}
bool Settings::empty(const string &line){
return (line.find_first_not_of(' ')==line.npos);
}
string Settings::getValue(const string &key){
if(settings.find(key) == settings.end()){
cout<<"Key "<<key<<" couldn't be found!"<<endl;
return "";
}
return settings[key];
}
bool Settings::getBoolValue(const string &key){
if(settings.find(key) == settings.end()){
cout<<"Key "<<key<<" couldn't be found!"<<endl;
return false;
}
return (settings[key] != "0");
}
void Settings::setValue(const string &key, const string &value){
if(settings.find(key) == settings.end()){
cout<<"Key "<<key<<" couldn't be found!"<<endl;
return;
}
settings[key]=value;
}
void Settings::createFile(){
ofstream file;
file.open(fileName.c_str());
//Default Config file.
file<<"#MeAndMyShadow config file. Created on "<<endl;
map<string, string>::iterator iter;
for(iter = settings.begin(); iter != settings.end(); ++iter){
file << iter->first << " = " << iter->second << endl;
}
//And close the file.
file.close();
}
void Settings::save(){
ofstream file;
file.open(fileName.c_str());
//First get the date and time.
time_t rawtime;
struct tm* timedate;
char str_time[80];
time(&rawtime);
timedate=localtime(&rawtime);
//Note: Function asctime() is marked obsolete in POSIX. So we're using strftime() instead.
strftime(str_time,80,"%a %b %d %H:%M:%S %Y",timedate);
//Now write it to the first line of the config file.
file<<"#MeAndMyShadow config file. Created on "<<str_time<<endl;
//Loop through the settings and save them.
map<string,string>::const_iterator iter;
for(iter=settings.begin(); iter!=settings.end(); ++iter){
file<<iter->first<<" = "<<iter->second<<endl;
}
file.close();
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, May 16, 8:25 PM (1 d, 16 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
63822
Default Alt Text
(108 KB)
Attached To
Mode
R79 meandmyshadow
Attached
Detach File
Event Timeline