Page MenuHomePhabricator (Chris)

No OneTemporary

Authored By
Unknown
Size
49 KB
Referenced Files
None
Subscribers
None
diff --git a/src/LevelPack.cpp b/src/LevelPack.cpp
index 87de811..cb62627 100644
--- a/src/LevelPack.cpp
+++ b/src/LevelPack.cpp
@@ -1,517 +1,522 @@
/****************************************************************************
** Copyright (C) 2011 Luka Horvat <redreaper132 at gmail.com>
** Copyright (C) 2011 Edward Lii <edward_iii at myway.com>
** Copyright (C) 2011 O. Bahri Gordebak <gordebak at gmail.com>
**
**
** This file may be used under the terms of the GNU General Public
** License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of
** this file.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
**
****************************************************************************/
#include "LevelPack.h"
#include "Functions.h"
#include "FileManager.h"
#include "TreeStorageNode.h"
#include "POASerializer.h"
#include "MD5.h"
#include <string>
#include <vector>
#include <fstream>
#include <algorithm>
#include <iostream>
using namespace std;
LevelPack::LevelPack():currentLevel(0),loaded(false),levels(){
//We need to set the pointer to the dictionaryManager to NULL.
dictionaryManager=NULL;
}
LevelPack::~LevelPack(){
//We call clear, since that already takes care of the deletion, including the dictionaryManager.
clear();
}
void LevelPack::clear(){
currentLevel=0;
loaded=false;
levels.clear();
levelpackDescription.clear();
levelpackPath.clear();
levelProgressFile.clear();
congratulationText.clear();
//Also delte the dictionaryManager if it isn't null.
if(dictionaryManager){
delete dictionaryManager;
dictionaryManager=NULL;
}
}
bool LevelPack::loadLevels(const std::string& levelListFile){
//We're going to load a new levellist so first clean any existing levels.
clear();
//If the levelListFile is empty we have nothing to load so we return false.
if(levelListFile.empty()){
cerr<<"ERROR: No levellist file given."<<endl;
return false;
}
//Process the levelListFile, create a new string since lecelListFile is constant.
string levelListNew=levelListFile;
levelpackPath=pathFromFileName(levelListNew);
//Create two input streams, one for the levellist file and one for the levelprogress.
ifstream level(levelListNew.c_str());
if(!level){
cerr<<"ERROR: Can't load level list "<<levelListNew<<endl;
return false;
}
//Load the level list file.
TreeStorageNode obj;
{
POASerializer objSerializer;
if(!objSerializer.readNode(level,&obj,true)){
cerr<<"ERROR: Invalid file format of level list "<<levelListNew<<endl;
return false;
}
}
//Check if there are translations for the levels.
{
//Try to open the locale folder in the levelpack to detect if there are translations for the levelpack.
vector<string> v;
v=enumAllDirs(pathFromFileName(levelListNew),false);
if(std::find(v.begin(),v.end(),"locale")!=v.end()){
//Folder is present so configure the levelDictionaryManager.
dictionaryManager=new tinygettext::DictionaryManager();
dictionaryManager->add_directory(pathFromFileName(levelListNew)+"locale/");
dictionaryManager->set_charset("UTF-8");
dictionaryManager->set_language(tinygettext::Language::from_name(language));
}else{
dictionaryManager=NULL;
}
}
//Look for the name.
{
vector<string> &v=obj.attributes["name"];
if(!v.empty()){
levelpackName=v[0];
}else{
//Name is not defined so take the folder name.
levelpackName=pathFromFileName(levelListFile);
//Remove the last character '/'
levelpackName=levelpackName.substr(0,levelpackName.size()-1);
levelpackName=fileNameFromPath(levelpackName);
}
}
//Look for the description.
{
vector<string> &v=obj.attributes["description"];
if(!v.empty())
levelpackDescription=v[0];
}
//Look for the congratulation text.
{
vector<string> &v=obj.attributes["congratulations"];
if(!v.empty())
congratulationText=v[0];
}
//Loop through the level list entries.
for(unsigned int i=0;i<obj.subNodes.size();i++){
TreeStorageNode* obj1=obj.subNodes[i];
if(obj1==NULL)
continue;
if(obj1->value.size()>=1 && obj1->name=="levelfile"){
Level level;
level.file=obj1->value[0];
level.targetTime=0;
level.targetRecordings=0;
//Open the level file to retrieve the name and target time/recordings.
TreeStorageNode obj;
POASerializer objSerializer;
if(objSerializer.loadNodeFromFile((levelpackPath+level.file).c_str(),&obj,true)){
//Calc the MD5 FIRST because query obj.attributes will modify internal structure.
obj.name.clear();
obj.calcMD5(level.md5Digest);
//Get the name of the level.
vector<string>& v=obj.attributes["name"];
if(!v.empty())
level.name=v[0];
//If the name is empty then we set it to the file name.
if(level.name.empty())
level.name=fileNameFromPath(level.file);
//Get the target time of the level.
v=obj.attributes["time"];
if(!v.empty())
level.targetTime=atoi(v[0].c_str());
else
level.targetTime=-1;
//Get the target recordings of the level.
v=obj.attributes["recordings"];
if(!v.empty())
level.targetRecordings=atoi(v[0].c_str());
else
level.targetRecordings=-1;
}
//The default for locked is true, unless it's the first one.
level.locked=!levels.empty();
level.won=false;
level.time=-1;
level.recordings=-1;
//Add the level to the levels.
levels.push_back(level);
}
}
loaded=true;
return true;
}
void LevelPack::loadProgress(const std::string& levelProgressFile){
//Open the levelProgress file.
ifstream levelProgress;
if(!levelProgressFile.empty()){
this->levelProgressFile=levelProgressFile;
levelProgress.open(processFileName(this->levelProgressFile).c_str());
}
//Check if the file exists.
if(levelProgress){
//Now load the progress/statistics.
TreeStorageNode obj;
{
POASerializer objSerializer;
if(!objSerializer.readNode(levelProgress,&obj,true)){
cerr<<"ERROR: Invalid file format of level progress file."<<endl;
}
}
//Loop through the entries.
for(unsigned int i=0;i<obj.subNodes.size();i++){
TreeStorageNode* obj1=obj.subNodes[i];
if(obj1==NULL)
continue;
if(obj1->value.size()>=1 && obj1->name=="level"){
//We've found an entry for a level, now search the correct level.
Level* level=NULL;
for(unsigned int o=0;o<levels.size();o++){
if(obj1->value[0]==levels[o].file){
level=&levels[o];
break;
}
}
//Check if we found the level.
if(!level)
continue;
//Get the progress/statistics.
for(map<string,vector<string> >::iterator i=obj1->attributes.begin();i!=obj1->attributes.end();++i){
if(i->first=="locked"){
level->locked=(i->second[0]=="1");
}
if(i->first=="won"){
level->won=(i->second[0]=="1");
}
if(i->first=="time"){
level->time=(atoi(i->second[0].c_str()));
}
if(i->first=="recordings"){
level->recordings=(atoi(i->second[0].c_str()));
}
}
}
}
}
}
void LevelPack::saveLevels(const std::string& levelListFile){
//Get the fileName.
string levelListNew=processFileName(levelListFile);
//Open an output stream.
ofstream level(levelListNew.c_str());
//Check if we can use the file.
if(!level){
cerr<<"ERROR: Can't save level list "<<levelListNew<<endl;
return;
}
//Storage node that will contain the data that should be written.
TreeStorageNode obj;
//Make sure that there's a description.
if(!levelpackDescription.empty())
obj.attributes["description"].push_back(levelpackDescription);
//Make sure that there's a congratulation text.
if(!congratulationText.empty())
obj.attributes["congratulations"].push_back(congratulationText);
//Add the levels to the file.
for(unsigned int i=0;i<levels.size();i++){
TreeStorageNode* obj1=new TreeStorageNode;
obj1->name="levelfile";
obj1->value.push_back(fileNameFromPath(levels[i].file));
obj1->value.push_back(levels[i].name);
obj.subNodes.push_back(obj1);
}
//Write the it away.
POASerializer objSerializer;
objSerializer.writeNode(&obj,level,false,true);
}
+void LevelPack::updateLanguage() {
+ if(dictionaryManager!=NULL)
+ dictionaryManager->set_language(tinygettext::Language::from_name(language));
+}
+
void LevelPack::addLevel(const string& levelFileName,int levelno){
//Fill in the details.
Level level;
if(!levelpackPath.empty() && levelFileName.compare(0,levelpackPath.length(),levelpackPath)==0){
level.file=fileNameFromPath(levelFileName);
}else{
level.file=levelFileName;
}
level.targetTime=0;
level.targetRecordings=0;
//Get the name of the level.
TreeStorageNode obj;
POASerializer objSerializer;
if(objSerializer.loadNodeFromFile(levelFileName.c_str(),&obj,true)){
//Calc the MD5 FIRST because query obj.attributes will modify internal structure.
obj.name.clear();
obj.calcMD5(level.md5Digest);
//Get the name of the level.
vector<string>& v=obj.attributes["name"];
if(!v.empty())
level.name=v[0];
//If the name is empty then we set it to the file name.
if(level.name.empty())
level.name=fileNameFromPath(levelFileName);
//Get the target time of the level.
v=obj.attributes["time"];
if(!v.empty())
level.targetTime=atoi(v[0].c_str());
else
level.targetTime=-1;
//Get the target recordings of the level.
v=obj.attributes["recordings"];
if(!v.empty())
level.targetRecordings=atoi(v[0].c_str());
else
level.targetRecordings=-1;
}
//Set if it should be locked or not.
level.won=false;
level.time=-1;
level.recordings=-1;
level.locked=levels.empty()?false:true;
//Check if the level should be at the end or somewhere in the middle.
if(levelno<0 || levelno>=int(levels.size())){
levels.push_back(level);
}else{
levels.insert(levels.begin()+levelno,level);
}
//NOTE: We set loaded to true.
loaded=true;
}
void LevelPack::moveLevel(unsigned int level1,unsigned int level2){
if(level1<0 || level1>=levels.size())
return;
if(level2<0 || level2>=levels.size())
return;
if(level1==level2)
return;
levels.insert(levels.begin()+level2,levels[level1]);
if(level2<=level1)
levels.erase(levels.begin()+level1+1);
else
levels.erase(levels.begin()+level1);
}
void LevelPack::saveLevelProgress(){
//Check if the levels are loaded and a progress file is given.
if(!loaded || levelProgressFile.empty())
return;
//Open the progress file.
ofstream levelProgress(processFileName(levelProgressFile).c_str());
if(!levelProgress)
return;
//Open an output stream.
TreeStorageNode node;
//Loop through the levels.
for(unsigned int o=0;o<levels.size();o++){
TreeStorageNode* obj=new TreeStorageNode;
node.subNodes.push_back(obj);
char s[64];
//Set the name of the node.
obj->name="level";
obj->value.push_back(levels[o].file);
//Set the values.
obj->attributes["locked"].push_back(levels[o].locked?"1":"0");
obj->attributes["won"].push_back(levels[o].won?"1":"0");
sprintf(s,"%d",levels[o].time);
obj->attributes["time"].push_back(s);
sprintf(s,"%d",levels[o].recordings);
obj->attributes["recordings"].push_back(s);
}
//Create a POASerializer and write away the leve node.
POASerializer objSerializer;
objSerializer.writeNode(&node,levelProgress,true,true);
}
const string& LevelPack::getLevelName(int level){
if(level<0)
level=currentLevel;
return levels[level].name;
}
const unsigned char* LevelPack::getLevelMD5(int level){
if(level<0)
level=currentLevel;
return levels[level].md5Digest;
}
void LevelPack::getLevelAutoSaveRecordPath(int level,std::string &bestTimeFilePath,std::string &bestRecordingFilePath,bool createPath){
if(level<0)
level=currentLevel;
bestTimeFilePath.clear();
bestRecordingFilePath.clear();
//get level pack path.
string levelpackPath=LevelPack::levelpackPath;
string s=levels[level].file;
//process level pack name
for(;;){
string::size_type lps=levelpackPath.find_last_of("/\\");
if(lps==string::npos){
break;
}else if(lps==levelpackPath.size()-1){
levelpackPath.resize(lps);
}else{
levelpackPath=levelpackPath.substr(lps+1);
break;
}
}
//profess file name
{
string::size_type lps=s.find_last_of("/\\");
if(lps!=string::npos) s=s.substr(lps+1);
}
//check if it's custom level
{
string path="%USER%/records/autosave/";
if(!levelpackPath.empty()){
path+=levelpackPath;
path+='/';
}
path=processFileName(path);
if(createPath) createDirectory(path.c_str());
s=path+s;
}
//calculate MD5
s+='-';
s+=Md5::toString(levels[level].md5Digest);
//over
bestTimeFilePath=s+"-best-time.mnmsrec";
bestRecordingFilePath=s+"-best-recordings.mnmsrec";
}
void LevelPack::setLevelName(unsigned int level,const std::string& name){
if(level<levels.size())
levels[level].name=name;
}
const string& LevelPack::getLevelFile(int level){
if(level<0)
level=currentLevel;
return levels[level].file;
}
const string& LevelPack::getLevelpackPath(){
return levelpackPath;
}
struct LevelPack::Level* LevelPack::getLevel(int level){
if(level<0)
return &levels[currentLevel];
return &levels[level];
}
void LevelPack::resetLevel(int level){
if(level<0)
level=currentLevel;
//Set back to default.
levels[level].locked=(level!=0);
levels[level].won=false;
levels[level].time=-1;
levels[level].recordings=-1;
}
void LevelPack::nextLevel(){
currentLevel++;
}
bool LevelPack::getLocked(unsigned int level){
return levels[level].locked;
}
void LevelPack::setCurrentLevel(unsigned int level){
currentLevel=level;
}
void LevelPack::setLocked(unsigned int level,bool locked){
levels[level].locked=locked;
}
void LevelPack::swapLevel(unsigned int level1,unsigned int level2){
if(level1<levels.size()&&level2<levels.size()){
swap(levels[level1],levels[level2]);
}
}
void LevelPack::removeLevel(unsigned int level){
if(level<levels.size()){
levels.erase(levels.begin()+level);
}
}
diff --git a/src/LevelPack.h b/src/LevelPack.h
index e4c347d..ee077c5 100644
--- a/src/LevelPack.h
+++ b/src/LevelPack.h
@@ -1,179 +1,183 @@
/****************************************************************************
** Copyright (C) 2011 Luka Horvat <redreaper132 at gmail.com>
** Copyright (C) 2011 Edward Lii <edward_iii at myway.com>
** Copyright (C) 2011 O. Bahri Gordebak <gordebak at gmail.com>
**
**
** This file may be used under the terms of the GNU General Public
** License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of
** this file.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
**
****************************************************************************/
#ifndef LEVELPACK_H
#define LEVELPACK_H
#include <SDL/SDL.h>
#include <SDL/SDL_mixer.h>
#include <SDL/SDL_ttf.h>
#include <vector>
#include <string>
#include "GameObjects.h"
#include "Player.h"
#include "libs/tinygettext/tinygettext.hpp"
class LevelPack{
public:
//A level entry structure.
struct Level{
//The name of the level.
string name;
//The filename of the level.
string file;
//Boolean if the level is locked.
bool locked;
//Boolean if the level is won.
bool won;
//Integer containing the number of ticks (40 = 1s) it took to finish the level.
//If there's no time the value will be -1.
int time;
//Integer containing the target time to get a medal.
int targetTime;
//Integer containing the number of recordings used to finish the level.
//When not won the value is -1.
int recordings;
//Integer containing the target recordings to get a medal.
int targetRecordings;
//MD5 of level node. :/
unsigned char md5Digest[16];
};
private:
//Index of the current level.
int currentLevel;
//Boolean if the levels are loaded.
bool loaded;
//Vector containing the filenames of the levels.
std::vector<Level> levels;
//The file name of the level progress.
std::string levelProgressFile;
public:
//The name of the levelpack.
std::string levelpackName;
//The location the levelpack is stored.
std::string levelpackPath;
//A description of the levelpack.
std::string levelpackDescription;
//The text that will be displayed when the levels are finished.
std::string congratulationText;
//The dictionaryManager of the levelpack, used to translate strings.
tinygettext::DictionaryManager* dictionaryManager;
//Constructor.
LevelPack();
//Destructor.
~LevelPack();
//gettext function
inline tinygettext::DictionaryManager* getDictionaryManager() const{
return dictionaryManager;
}
+ //Method for updating the language to the configured one.
+ //NOTE: This is called when changing the translation in the Options menu.
+ void updateLanguage();
+
//Adds a level to the levels.
//levelFileName: The filename of the level to add.
//level: The index of the level to add.
void addLevel(const std::string& levelFileName,int levelno=-1);
//Removes a level from the levels.
//level: The index of the level to remove.
void removeLevel(unsigned int level);
//Moves the level to a given index.
//level1: The level to move.
//level2: The destination.
void moveLevel(unsigned int level1,unsigned int level2);
//Swaps two level.
//level1: The first level to swap.
//level2: The second level to swap.
void swapLevel(unsigned int level1,unsigned int level2);
//Get the levelFile for a given level.
//level: The level index to get the levelFileName from.
//Returns: String containing the levelFileName.
const std::string& getLevelFile(int level=-1);
//Get the levelpackPath of the levels.
//Returns: String containing the levelpackPath.
const std::string& getLevelpackPath();
//Get the levelName for a given level.
//level: The level index to get the levelName from.
//Returns: String containing the levelName.
const std::string& getLevelName(int level=-1);
//Sets the levelName for a given level.
//level: The level index to get the levelName from.
//name: The new name of the level.
void setLevelName(unsigned int level,const std::string& name);
//Get the MD5 for a given level.
//level: The level index.
- //Returns: const unsigned char[16] contains the digest.
+ //Returns: const unsigned char[16] containing the digest.
const unsigned char* getLevelMD5(int level=-1);
//get level's auto-save record path,
//using level's MD5, file name and other information.
void getLevelAutoSaveRecordPath(int level,std::string &bestTimeFilePath,std::string &bestRecordingFilePath,bool createPath);
//Set the currentLevel.
//level: The new current level.
void setCurrentLevel(unsigned int level);
//Get the currentLevel.
//Returns: The currentLevel.
inline int getCurrentLevel(){return currentLevel;}
//Get the levelCount.
//Returns: The level count.
inline int getLevelCount(){return levels.size();}
//Method that will return the requested level.
//level: The index of the level, default is the current level.
//Returns: Pointer to the requested level structure.
struct Level* getLevel(int level=-1);
//Method that will reset any progress/statistics for a given level.
//level: The index of the level to reset, default is currentLevel.
void resetLevel(int level=-1);
//Check if a certain level is locked.
//level: The index of the level to check.
//Returns: True if the level is locked.
bool getLocked(unsigned int level);
//Set a level locked or not.
//level: The level to (un)lock.
//locked: The new status of the level, default is unlocked (false).
void setLocked(unsigned int level,bool locked=false);
//Empties the levels.
void clear();
bool loadLevels(const std::string& levelListFile);
void loadProgress(const std::string& levelProgressFile);
void saveLevels(const std::string& levelListFile);
void saveLevelProgress();
void nextLevel();
};
#endif
diff --git a/src/LevelPackManager.cpp b/src/LevelPackManager.cpp
index 31aa462..77ac8dc 100644
--- a/src/LevelPackManager.cpp
+++ b/src/LevelPackManager.cpp
@@ -1,111 +1,118 @@
/****************************************************************************
** Copyright (C) 2011 Luka Horvat <redreaper132 at gmail.com>
** Copyright (C) 2011 Edward Lii <edward_iii at myway.com>
** Copyright (C) 2011 O. Bahri Gordebak <gordebak at gmail.com>
**
**
** This file may be used under the terms of the GNU General Public
** License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of
** this file.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
**
****************************************************************************/
#include "LevelPackManager.h"
#include "LevelPack.h"
#include "FileManager.h"
#include <stdio.h>
void LevelPackManager::loadLevelPack(std::string path){
//Load the levelpack.
LevelPack* levelpack=new LevelPack();
levelpack->loadLevels(path+"/levels.lst");
//Check if the entry doesn't already exist.
if(levelpacks.find(levelpack->levelpackName)!=levelpacks.end()){
cerr<<"WARNING: Levelpack entry \""+levelpack->levelpackName+"\" already exist."<<endl;
return;
}
//It doesn't exist so add it.
levelpacks[levelpack->levelpackName]=levelpack;
}
void LevelPackManager::addLevelPack(LevelPack* levelpack){
//Check if the entry doesn't already exist.
if(levelpacks.find(levelpack->levelpackName)!=levelpacks.end()){
cerr<<"WARNING: Levelpack entry \""+levelpack->levelpackName+"\" already exist."<<endl;
return;
}
//It doesn't exist so add it.
levelpacks[levelpack->levelpackName]=levelpack;
}
void LevelPackManager::removeLevelPack(std::string name){
std::map<std::string,LevelPack*>::iterator it=levelpacks.find(name);
//Check if the entry exists.
if(it!=levelpacks.end()){
levelpacks.erase(it);
}else{
cerr<<"WARNING: Levelpack entry \""+name+"\" doesn't exist."<<endl;
}
}
LevelPack* LevelPackManager::getLevelPack(std::string name){
return levelpacks[name];
}
vector<string> LevelPackManager::enumLevelPacks(int type){
//The vector that will be returned.
vector<string> v;
//Now do the type dependent adding.
switch(type){
case ALL_PACKS:
{
std::map<std::string,LevelPack*>::iterator i;
for(i=levelpacks.begin();i!=levelpacks.end();++i){
//We add everything except the "Custom Levels" pack since that's also in "Levels".
if(i->first!="Custom Levels")
v.push_back(i->first);
}
break;
}
case CUSTOM_PACKS:
{
std::map<std::string,LevelPack*>::iterator i;
for(i=levelpacks.begin();i!=levelpacks.end();++i){
//Only add levelpacks that are under the custom folder OR if it's the "Custom Levels" levelpack.
if(i->second->levelpackPath.find(getUserPath(USER_DATA)+"custom/")==0 || i->first=="Custom Levels"){
v.push_back(i->first);
}
}
break;
}
}
//And return the vector.
return v;
}
+void LevelPackManager::updateLanguage(){
+ std::map<std::string,LevelPack*>::iterator i;
+ for(i=levelpacks.begin();i!=levelpacks.end();++i){
+ i->second->updateLanguage();
+ }
+}
+
LevelPackManager::~LevelPackManager(){
//We call destroy().
destroy();
}
void LevelPackManager::destroy(){
//Loop through the levelpacks and delete them.
std::map<std::string,LevelPack*>::iterator i;
for(i=levelpacks.begin();i!=levelpacks.end();++i){
delete i->second;
}
levelpacks.clear();
}
diff --git a/src/LevelPackManager.h b/src/LevelPackManager.h
index ad08454..a744915 100644
--- a/src/LevelPackManager.h
+++ b/src/LevelPackManager.h
@@ -1,76 +1,80 @@
/****************************************************************************
** Copyright (C) 2011 Luka Horvat <redreaper132 at gmail.com>
** Copyright (C) 2011 Edward Lii <edward_iii at myway.com>
** Copyright (C) 2011 O. Bahri Gordebak <gordebak at gmail.com>
**
**
** This file may be used under the terms of the GNU General Public
** License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of
** this file.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
**
****************************************************************************/
#ifndef LEVELPACKMANAGER_H
#define LEVELPACKMANAGER_H
#include "LevelPack.h"
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <string>
#include <map>
#include <vector>
//Class for loading and managing levelpacks.
class LevelPackManager{
public:
//Constructor.
LevelPackManager(){}
//Destructor.
~LevelPackManager();
//Load a levelpack and add it to the map.
//name: The name of the levelpack files.
void loadLevelPack(std::string name);
//Insert a levelpack in the LevelPackManager.
//levelpack: Pointer to the levelpack to add.
void addLevelPack(LevelPack* levelpack);
//Removes a levelpack from the LevelPackManager.
//name: The name of the levelpack to remove.
void removeLevelPack(std::string name);
//Method that will return a levelpack.
//name: The name of the levelpack.
//Returns: Pointer to the requested levelpack.
LevelPack* getLevelPack(std::string name);
//Method that will return a vector containing all (or a subset) of the levelpacks.
//type: The list type, default is ALL_PACKS.
std::vector<std::string> enumLevelPacks(int type=ALL_PACKS);
+
+ //Method that will update the translation of the levelpacks.
+ //NOTE: This is called when changing the language in the Options menu.
+ void updateLanguage();
//Destroys the levelpacks.
void destroy();
//Enumeration containing the different types of levelpack lists.
enum LevelPackLists
{
//This list contains every levelpack.
ALL_PACKS,
//This list contains all the custom levelpacks (and Levels).
CUSTOM_PACKS
};
private:
//Map containing the levelpacks.
//The key is the name of the levelpack and the value is a pointer to the levelpack.
std::map<std::string,LevelPack*> levelpacks;
};
#endif
diff --git a/src/TitleMenu.cpp b/src/TitleMenu.cpp
index 80d2225..4a6ff15 100644
--- a/src/TitleMenu.cpp
+++ b/src/TitleMenu.cpp
@@ -1,654 +1,655 @@
/****************************************************************************
** Copyright (C) 2011 Luka Horvat <redreaper132 at gmail.com>
** Copyright (C) 2011 Edward Lii <edward_iii at myway.com>
** Copyright (C) 2011 O. Bahri Gordebak <gordebak at gmail.com>
**
**
** This file may be used under the terms of the GNU General Public
** License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of
** this file.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
**
****************************************************************************/
#include "Functions.h"
#include "GameState.h"
#include "Globals.h"
#include "TitleMenu.h"
#include "GUIListBox.h"
#include "InputManager.h"
#include <iostream>
#include <algorithm>
#include <sstream>
#include "libs/tinygettext/tinygettext.hpp"
using namespace std;
/////////////////////////MAIN_MENU//////////////////////////////////
//Integer containing the highlighted/selected menu option.
static int highlight=0;
Menu::Menu(){
highlight=0;
animation=0;
//Load the title image.
title=loadImage(getDataPath()+"gfx/menu/title.png");
//Now render the five entries.
SDL_Color black={0,0,0};
entries[0]=TTF_RenderUTF8_Blended(fontTitle,_("Play"),black);
entries[1]=TTF_RenderUTF8_Blended(fontTitle,_("Options"),black);
entries[2]=TTF_RenderUTF8_Blended(fontTitle,_("Map Editor"),black);
entries[3]=TTF_RenderUTF8_Blended(fontTitle,_("Addons"),black);
entries[4]=TTF_RenderUTF8_Blended(fontTitle,_("Quit"),black);
entries[5]=TTF_RenderUTF8_Blended(fontTitle,">",black);
entries[6]=TTF_RenderUTF8_Blended(fontTitle,"<",black);
}
Menu::~Menu(){
//We need to free the five text surfaceses.
for(unsigned int i=0;i<7;i++)
SDL_FreeSurface(entries[i]);
}
void Menu::handleEvents(){
//Get the x and y location of the mouse.
int x,y;
SDL_GetMouseState(&x,&y);
//Calculate which option is highlighted using the location of the mouse.
//Only if mouse is 'doing something'
if(event.type==SDL_MOUSEMOTION || event.type==SDL_MOUSEBUTTONDOWN){
if(x>=200&&x<SCREEN_WIDTH-200&&y>=(SCREEN_HEIGHT-200)/2&&y<(SCREEN_HEIGHT-200)/2+320){
highlight=(y-((SCREEN_HEIGHT-200)/2-64))/64;
}
}
//Down/Up -arrows move highlight
if(inputMgr.isKeyDownEvent(INPUTMGR_DOWN)){
highlight++;
if(highlight>=6)
highlight=5;
}
if(inputMgr.isKeyDownEvent(INPUTMGR_UP)){
highlight--;
if(highlight<1)
highlight=1;
}
//Check if there's a press event.
if((event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT) ||
(inputMgr.isKeyUpEvent(INPUTMGR_SELECT))){
//We have one so check which selected/highlighted option needs to be done.
switch(highlight){
case 1:
//Enter the levelSelect state.
setNextState(STATE_LEVEL_SELECT);
break;
case 2:
//Enter the options state.
setNextState(STATE_OPTIONS);
break;
case 3:
//Enter the levelEditor, but first set the level to a default leveledit map.
levelName="";
setNextState(STATE_LEVEL_EDIT_SELECT);
break;
case 4:
//Check if internet is enabled.
if(!getSettings()->getBoolValue("internet")){
msgBox(_("Enable internet in order to install addons."),MsgBoxOKOnly,_("Internet disabled"));
break;
}
//Enter the help state.
setNextState(STATE_ADDONS);
break;
case 5:
//We quit, so we enter the exit state.
setNextState(STATE_EXIT);
break;
}
}
//We also need to quit the menu when escape is pressed.
if(inputMgr.isKeyUpEvent(INPUTMGR_ESCAPE)){
setNextState(STATE_EXIT);
}
//Check if we need to quit, if so we enter the exit state.
if(event.type==SDL_QUIT){
setNextState(STATE_EXIT);
}
}
//Nothing to do here
void Menu::logic(){
animation++;
if(animation>10)
animation=-10;
}
void Menu::render(){
applySurface(0,0,menuBackground,screen,NULL);
//Draw the title.
applySurface((SCREEN_WIDTH-title->w)/2,40,title,screen,NULL);
//Draw the menu entries.
for(unsigned int i=0;i<5;i++){
applySurface((SCREEN_WIDTH-entries[i]->w)/2,(SCREEN_HEIGHT-200)/2+64*i+(64-entries[i]->h)/2,entries[i],screen,NULL);
}
//Check if an option is selected/highlighted.
if(highlight>0){
//Draw the '>' sign, which is entry 5.
int x=(SCREEN_WIDTH-entries[highlight-1]->w)/2-(25-abs(animation)/2)-entries[5]->w;
int y=(SCREEN_HEIGHT-200)/2-64+64*highlight+(64-entries[5]->h)/2;
applySurface(x,y,entries[5],screen,NULL);
//Draw the '<' sign, which is entry 6.
x=(SCREEN_WIDTH-entries[highlight-1]->w)/2+entries[highlight-1]->w+(25-abs(animation)/2);
y=(SCREEN_HEIGHT-200)/2-64+64*highlight+(64-entries[6]->h)/2;
applySurface(x,y,entries[6],screen,NULL);
}
}
void Menu::resize(){}
/////////////////////////OPTIONS_MENU//////////////////////////////////
//Some varables for the options.
static bool fullscreen,leveltheme,internet;
static string themeName,languageName;
static int lastLang,lastRes;
static bool useProxy;
static string internetProxy;
static bool restartFlag;
static _res currentRes;
static vector<_res> resolutionList;
Options::Options(){
//Render the title.
SDL_Color black={0,0,0};
title=TTF_RenderUTF8_Blended(fontTitle,_("Settings"),black);
//Load the jump sound, used for sound volume configuration.
jumpSound=Mix_LoadWAV((getDataPath()+"sfx/jump.wav").c_str());
lastJumpSound=0;
//Set some default settings.
fullscreen=getSettings()->getBoolValue("fullscreen");
languageName=getSettings()->getValue("lang");
themeName=processFileName(getSettings()->getValue("theme"));
leveltheme=getSettings()->getBoolValue("leveltheme");
internet=getSettings()->getBoolValue("internet");
internetProxy=getSettings()->getValue("internet-proxy");
useProxy=!internetProxy.empty();
//Set the restartFlag false.
restartFlag=false;
//Now create the gui.
createGUI();
}
Options::~Options(){
//Delete the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Free the title image.
SDL_FreeSurface(title);
//And free the jump sound.
Mix_FreeChunk(jumpSound);
}
void Options::createGUI(){
//Variables for positioning
int x = (SCREEN_WIDTH-540)/2;
int liftY=40; //TODO: This is variable for laziness of maths...
//Create the root element of the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
GUIObjectRoot=new GUIObject(0,0,SCREEN_WIDTH,SCREEN_HEIGHT,GUIObjectNone);
//Now we create GUIObjects for every option.
GUIObject* obj=new GUIObject(x,150-liftY,240,36,GUIObjectLabel,_("Music"));
GUIObjectRoot->childControls.push_back(obj);
musicSlider=new GUISlider(x+220,150-liftY,256,36,atoi(getSettings()->getValue("music").c_str()),0,128,15);
musicSlider->name="sldMusic";
musicSlider->eventCallback=this;
GUIObjectRoot->childControls.push_back(musicSlider);
obj=new GUIObject(x,190-liftY,240,36,GUIObjectLabel,_("Sound"));
GUIObjectRoot->childControls.push_back(obj);
soundSlider=new GUISlider(x+220,190-liftY,256,36,atoi(getSettings()->getValue("sound").c_str()),0,128,15);
soundSlider->name="sldSound";
soundSlider->eventCallback=this;
GUIObjectRoot->childControls.push_back(soundSlider);
obj=new GUIObject(x,230-liftY,240,36,GUIObjectCheckBox,_("Fullscreen"),fullscreen?1:0);
obj->name="chkFullscreen";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(x,270-liftY,240,36,GUIObjectLabel,_("Resolution"));
obj->name="lstResolution";
GUIObjectRoot->childControls.push_back(obj);
//Create list with many different resolutions
resolutions = new GUISingleLineListBox(x+220,270-liftY,300,36);
resolutions->value=-1;
//Enumerate avaliable resolutions using SDL_ListModes()
//Note: we enumerate fullscreen resolutions because
// windowed resolutions always can be arbitrary
if(resolutionList.empty()){
SDL_Rect **modes=SDL_ListModes(NULL,SDL_FULLSCREEN|SDL_HWSURFACE);
if(modes==NULL || ((intptr_t)modes) == -1){
cout<<"Error: Can't enumerate avaliable screen resolutions."
" Use predefined screen resolutions list instead."<<endl;
static const _res predefinedResolutionList[] = {
{800,600},
{1024,600},
{1024,768},
{1152,864},
{1280,720},
{1280,768},
{1280,800},
{1280,960},
{1280,1024},
{1360,768},
{1366,768},
{1440,900},
{1600,900},
{1600,1200},
{1680,1080},
{1920,1080},
{1920,1200},
{2560,1440},
{3840,2160}
};
for(unsigned int i=0;i<sizeof(predefinedResolutionList)/sizeof(_res);i++){
resolutionList.push_back(predefinedResolutionList[i]);
}
}else{
for(unsigned int i=0;modes[i]!=NULL;i++){
//Check if the resolution is big enough
if(modes[i]->w>=800 && modes[i]->h>=600){
_res res={modes[i]->w, modes[i]->h};
resolutionList.push_back(res);
}
}
reverse(resolutionList.begin(),resolutionList.end());
}
}
//Get current resolution from config file. Thus it can be user defined
currentRes.w=atoi(getSettings()->getValue("width").c_str());
currentRes.h=atoi(getSettings()->getValue("height").c_str());
for (int i=0; i<(int)resolutionList.size();i++){
//Create a string from width and height and then add it to list
ostringstream out;
out << resolutionList[i].w << "x" << resolutionList[i].h;
resolutions->item.push_back(out.str());
//Check if current resolution matches, select it
if (resolutionList[i].w==currentRes.w && resolutionList[i].h==currentRes.h){
resolutions->value=i;
}
}
//Add current resolution if it isn't already in the list
if(resolutions->value==-1){
ostringstream out;
out << currentRes.w << "x" << currentRes.h;
resolutions->item.push_back(out.str());
resolutions->value=resolutions->item.size()-1;
}
lastRes=resolutions->value;
GUIObjectRoot->childControls.push_back(resolutions);
obj=new GUIObject(x,310-liftY,240,36,GUIObjectLabel,_("Language"));
obj->name="lstResolution";
GUIObjectRoot->childControls.push_back(obj);
//Create GUI list with available languages
langs = new GUISingleLineListBox(x+220,310-liftY,300,36);
langs->name="lstLanguages";
/// TRANSLATORS: as detect user's language automatically
langs->item.push_back(_("Auto-Detect"));
langValues.push_back("");
langs->item.push_back("English");
langValues.push_back("en");
//Get a list of every available language
set<tinygettext::Language> languages = dictionaryManager->get_languages();
for (set<tinygettext::Language>::iterator s0 = languages.begin(); s0 != languages.end(); ++s0){
//If language in loop is the same in config file, then select it
- if (getSettings()->getValue("lang")==s0->str()){
+ if(getSettings()->getValue("lang")==s0->str()){
lastLang=distance(languages.begin(),s0)+2;
}
//Add language in loop to list and listbox
langs->item.push_back(s0->get_name());
langValues.push_back(s0->str());
}
//If Auto or English are selected
if(getSettings()->getValue("lang")==""){
lastLang=0;
}else if(getSettings()->getValue("lang")=="en"){
lastLang=1;
}
langs->value=lastLang;
GUIObjectRoot->childControls.push_back(langs);
obj=new GUIObject(x,350-liftY,240,36,GUIObjectLabel,_("Theme"));
obj->name="theme";
GUIObjectRoot->childControls.push_back(obj);
//Create the theme option gui element.
theme=new GUISingleLineListBox(x+220,350-liftY,300,36);
theme->name="lstTheme";
vector<string> v=enumAllDirs(getUserPath(USER_DATA)+"themes/");
for(vector<string>::iterator i = v.begin(); i != v.end(); ++i){
themeLocations[*i]=getUserPath(USER_DATA)+"themes/"+*i;
}
vector<string> v2=enumAllDirs(getDataPath()+"themes/");
for(vector<string>::iterator i = v2.begin(); i != v2.end(); ++i){
themeLocations[*i]=getDataPath()+"themes/"+*i;
}
v.insert(v.end(), v2.begin(), v2.end());
//Try to find the configured theme so we can display it.
int value=-1;
for(vector<string>::iterator i = v.begin(); i != v.end(); ++i){
if(themeLocations[*i]==themeName) {
value=i-v.begin();
}
}
theme->item=v;
if(value==-1)
value=theme->item.size()-1;
theme->value=value;
//NOTE: We call the event handling method to correctly set the themename.
GUIEventCallback_OnEvent("lstTheme",theme,GUIEventChange);
theme->eventCallback=this;
GUIObjectRoot->childControls.push_back(theme);
obj=new GUIObject(x,390-liftY,240,36,GUIObjectCheckBox,_("Level themes"),leveltheme?1:0);
obj->name="chkLeveltheme";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(x,430-liftY,240,36,GUIObjectCheckBox,_("Internet"),internet?1:0);
obj->name="chkInternet";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
//new: proxy settings
obj=new GUIObject(x,470-liftY,240,36,GUIObjectLabel,_("Internet proxy"));
obj->name="chkProxy";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(x+220,470-liftY,300,36,GUIObjectTextBox,internetProxy.c_str());
obj->name="txtProxy";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
//new: key settings
GUIObject* b1=new GUIObject(SCREEN_WIDTH*0.3,SCREEN_HEIGHT-120,-1,36,GUIObjectButton,_("Config Keys"),0,true,true,GUIGravityCenter);
b1->name="cmdKeys";
b1->eventCallback=this;
GUIObjectRoot->childControls.push_back(b1);
//Reset progress settings.
/// TRANSLATORS: Used for button which clear any level progress like unlocked levels and highscores.
GUIObject* b2=new GUIObject(SCREEN_WIDTH*0.7,SCREEN_HEIGHT-120,-1,36,GUIObjectButton,_("Clear Progress"),0,true,true,GUIGravityCenter);
b2->name="cmdReset";
b2->eventCallback=this;
GUIObjectRoot->childControls.push_back(b2);
b1->render(0,0,false);
b2->render(0,0,false);
if(b2->left-b2->gravityX < b1->left+b1->width-b1->gravityX){
b1->smallFont=true;
b1->width=-1;
b2->smallFont=true;
b2->width=-1;
}
b1=new GUIObject(SCREEN_WIDTH*0.3,SCREEN_HEIGHT-60,-1,36,GUIObjectButton,_("Cancel"),0,true,true,GUIGravityCenter);
b1->name="cmdBack";
b1->eventCallback=this;
GUIObjectRoot->childControls.push_back(b1);
b2=new GUIObject(SCREEN_WIDTH*0.7,SCREEN_HEIGHT-60,-1,36,GUIObjectButton,_("Save Changes"),0,true,true,GUIGravityCenter);
b2->name="cmdSave";
b2->eventCallback=this;
GUIObjectRoot->childControls.push_back(b2);
b1->render(0,0,false);
b2->render(0,0,false);
if(b2->left-b2->gravityX < b1->left+b1->width-b1->gravityX){
b1->smallFont=true;
b1->width=-1;
b2->smallFont=true;
b2->width=-1;
}
}
static string convertInt(int i){
stringstream ss;
ss << i;
return ss.str();
}
void Options::GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType){
//Check what type of event it was.
if(eventType==GUIEventClick){
if(name=="cmdBack"){
//TODO: Reset the key changes.
//Reset the music volume.
getMusicManager()->setVolume(atoi(getSettings()->getValue("music").c_str()));
Mix_Volume(-1,atoi(getSettings()->getValue("sound").c_str()));
//And goto the main menu.
setNextState(STATE_MENU);
}else if(name=="cmdSave"){
//Save is pressed thus save
char s[64];
sprintf(s,"%d",soundSlider->value);
getSettings()->setValue("sound",s);
sprintf(s,"%d",musicSlider->value);
getSettings()->setValue("music",s);
getMusicManager()->setEnabled(musicSlider->value>0);
Mix_Volume(-1,soundSlider->value);
getSettings()->setValue("fullscreen",fullscreen?"1":"0");
getSettings()->setValue("leveltheme",leveltheme?"1":"0");
getSettings()->setValue("internet",internet?"1":"0");
getSettings()->setValue("theme",themeName);
if(!useProxy)
internetProxy.clear();
getSettings()->setValue("internet-proxy",internetProxy);
getSettings()->setValue("lang",langValues.at(langs->value));
//Is resolution from the list or is it user defined in config file
if(resolutions->value<(int)resolutionList.size()){
getSettings()->setValue("width",convertInt(resolutionList[resolutions->value].w));
getSettings()->setValue("height",convertInt(resolutionList[resolutions->value].h));
}else{
getSettings()->setValue("width",convertInt(currentRes.w));
getSettings()->setValue("height",convertInt(currentRes.h));
}
//Save the key configuration.
inputMgr.saveConfig();
//Save the settings.
saveSettings();
//Before we return check if some .
if(restartFlag || resolutions->value!=lastRes){
//The resolution changed so we need to recreate the screen.
if(!createScreen()){
//Screen creation failed so set to safe settings.
getSettings()->setValue("fullscreen","0");
getSettings()->setValue("width",convertInt(resolutionList[lastRes].w));
getSettings()->setValue("height",convertInt(resolutionList[lastRes].h));
if(!createScreen()){
//Everything fails so quit.
setNextState(STATE_EXIT);
return;
}
}
//The screen is created, now load the (menu) theme.
if(!loadTheme()){
//Loading the theme failed so quit.
setNextState(STATE_EXIT);
return;
}
}
if(langs->value!=lastLang){
//We set the language.
language=langValues.at(langs->value);
dictionaryManager->set_language(tinygettext::Language::from_name(langValues.at(langs->value)));
+ getLevelPackManager()->updateLanguage();
- //And reload everything (fonts, levelpack translations, music...) FIXME: lots of useless things reloaded
- if(!loadFiles()){
+ //And reload the font.
+ if(!loadFonts()){
//Loading failed so quit.
setNextState(STATE_EXIT);
return;
}
}
//Now return to the main menu.
setNextState(STATE_MENU);
}else if(name=="cmdKeys"){
inputMgr.showConfig();
}else if(name=="cmdReset"){
if(msgBox(_("Do you really want to reset level progress?"),MsgBoxYesNo,_("Warning"))==MsgBoxYes){
//We delete the progress folder.
#ifdef WIN32
removeDirectory((getUserPath()+"progress").c_str());
createDirectory((getUserPath()+"progress").c_str());
#else
removeDirectory((getUserPath(USER_DATA)+"/progress").c_str());
createDirectory((getUserPath(USER_DATA)+"/progress").c_str());
#endif
}
return;
}else if(name=="chkFullscreen"){
fullscreen=obj->value?true:false;
//Check if fullscreen changed.
if(fullscreen==getSettings()->getBoolValue("fullscreen")){
//We disable the restart message flag.
restartFlag=false;
}else{
//We set the restart message flag.
restartFlag=true;
}
}else if(name=="chkLeveltheme"){
leveltheme=obj->value?true:false;
}else if(name=="chkInternet"){
internet=obj->value?true:false;
}else if(name=="chkProxy"){
useProxy=obj->value?true:false;
}
}
if(name=="lstTheme"){
if(theme!=NULL && theme->value>=0 && theme->value<(int)theme->item.size()){
//Check if the theme is installed in the data path.
if(themeLocations[theme->item[theme->value]].find(getDataPath())!=string::npos){
themeName="%DATA%/themes/"+fileNameFromPath(themeLocations[theme->item[theme->value]]);
}else if(themeLocations[theme->item[theme->value]].find(getUserPath(USER_DATA))!=string::npos){
themeName="%USER%/themes/"+fileNameFromPath(themeLocations[theme->item[theme->value]]);
}else{
themeName=themeLocations[theme->item[theme->value]];
}
}
}else if(name=="txtProxy"){
internetProxy=obj->caption;
//Check if the internetProxy field is empty.
useProxy=!internetProxy.empty();
}else if(name=="sldMusic"){
getMusicManager()->setEnabled(musicSlider->value>0);
getMusicManager()->setVolume(musicSlider->value);
}else if(name=="sldSound"){
Mix_Volume(-1,soundSlider->value);
if(lastJumpSound==0){
Mix_PlayChannel(-1,jumpSound,0);
lastJumpSound=15;
}
}
}
void Options::handleEvents(){
//Check if we need to quit, if so enter the exit state.
if(event.type==SDL_QUIT){
setNextState(STATE_EXIT);
}
//Check if the escape button is pressed, if so go back to the main menu.
if(inputMgr.isKeyUpEvent(INPUTMGR_ESCAPE)){
setNextState(STATE_MENU);
}
}
void Options::logic(){
//Increase the lastJumpSound variable if needed.
if(lastJumpSound!=0){
lastJumpSound--;
}
}
void Options::render(){
//Render the menu background image.
applySurface(0,0,menuBackground,screen,NULL);
//Now render the title.
applySurface((SCREEN_WIDTH-title->w)/2,40-TITLE_FONT_RAISE,title,screen,NULL);
//NOTE: The rendering of the GUI is done in Main.
}
void Options::resize(){
//Recreate the gui to fit the new resolution.
createGUI();
}

File Metadata

Mime Type
text/x-diff
Expires
Tue, Jun 16, 12:16 AM (2 w, 2 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
70725
Default Alt Text
(49 KB)

Event Timeline