Page MenuHomePhabricator (Chris)

No OneTemporary

Authored By
Unknown
Size
108 KB
Referenced Files
None
Subscribers
None
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

Mime Type
text/x-diff
Expires
Sat, May 16, 8:25 PM (1 d, 19 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
63822
Default Alt Text
(108 KB)

Event Timeline