Page Menu
Home
Phabricator (Chris)
Search
Configure Global Search
Log In
Files
F134989
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
253 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/FileManager.cpp b/src/FileManager.cpp
index a0f9648..18947de 100644
--- a/src/FileManager.cpp
+++ b/src/FileManager.cpp
@@ -1,647 +1,669 @@
/****************************************************************************
** 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 <stdio.h>
#include <iostream>
#include <string>
#include <vector>
#include "Globals.h"
#include "FileManager.h"
#include "Functions.h"
#include <archive.h>
#include <archive_entry.h>
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
string userPath,dataPath,appPath,exeName;
bool configurePaths() {
//Get the appPath and the exeName.
{
char s[4096];
int i,m;
#ifdef WIN32
m=GetModuleFileNameA(NULL,s,sizeof(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\\";
#else
//Get the userPath.
userPath=getenv("HOME");
userPath+="/.meandmyshadow/";
#endif
//Print the userPath.
cout<<"User preferences will be fetched from: "<<userPath<<endl;
}
#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+"progress").c_str());
createDirectory((userPath+"tmp").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());
//Also create other folders in the userpath.
createDirectory((userPath+"/levels").c_str());
createDirectory((userPath+"/levelpacks").c_str());
createDirectory((userPath+"/themes").c_str());
createDirectory((userPath+"/progress").c_str());
createDirectory((userPath+"/tmp").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());
#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
//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){
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)){
v.push_back(/*path+*/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;
}
v.push_back(/*s1*/string(pDirent->d_name));
}
}
closedir(pDir);
return v;
#endif
}
std::vector<std::string> enumAllDirs(std::string path){
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){
v.push_back(/*path+*/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.
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 userPath+s.substr(7);
}else{
return userPath+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{
return prefix+s;
}
}
std::string fileNameFromPath(const std::string &path, const bool webURL){
std::string filename;
size_t pos;
#ifdef WIN32
// FIXME: '/' 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
// FIXME: '/' 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;
// FIXME: '/' 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=NULL;
//Create the destination we're going to extract to.
archive* dest=NULL;
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 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
//Booleand if the entry is deleted.
//True: succes False: failure
//Default is false.
bool r2 = false;
char* buf;
size_t len;
/* 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 {
#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 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 16f6501..f914af4 100644
--- a/src/FileManager.h
+++ b/src/FileManager.h
@@ -1,127 +1,140 @@
/****************************************************************************
** 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 FILE_MANAGER_H
#define FILE_MANAGER_H
//Included for the extractFile method.
#include <archive.h>
//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;
//Method for retrieving the userPath.
//Returns: The userPath.
inline const std::string& getUserPath(){
return userPath;
}
//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.
//Returns: A vector containing the names of the files.
std::vector<std::string> enumAllFiles(std::string path,const char* extension=NULL);
//Method that returns a list of all the directories in a given directory.
//path: The path to list the directory of.
//Returns: A vector containing the names of the directories.
std::vector<std::string> enumAllDirs(std::string path);
//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 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 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 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/Functions.cpp b/src/Functions.cpp
index 000c0c1..3cc5d4c 100644
--- a/src/Functions.cpp
+++ b/src/Functions.cpp
@@ -1,949 +1,951 @@
/****************************************************************************
** 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 <stdio.h>
#include <SDL/SDL.h>
#include <SDL/SDL_mixer.h>
#include <SDL/SDL_gfxPrimitives.h>
#include <string>
#include "Globals.h"
#include "Functions.h"
#include "FileManager.h"
#include "Objects.h"
#include "Player.h"
#include "GameObjects.h"
#include "Levels.h"
#include "TitleMenu.h"
+#include "LevelEditSelect.h"
#include "LevelEditor.h"
#include "Game.h"
-#include "LevelSelect.h"
+#include "LevelPlaySelect.h"
#include "Addons.h"
#include "ImageManager.h"
#include "MusicManager.h"
#include "ThemeManager.h"
#include "GUIListBox.h"
using namespace std;
#ifdef WIN32
#include <windows.h>
#include <shlobj.h>
#else
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>
#endif
//Initialise the imagemanager.
//The ImageManager is used to prevent loading images multiple times.
ImageManager imageManager;
//Initialise the musicManager.
//The MusicManager is used to prevent loading music files multiple times and for playing/fading music.
MusicManager musicManager;
//Pointer to the settings object.
//It is used to load and save the settings file and change the settings.
Settings* settings=0;
SDL_Surface* loadImage(string file){
//We use the imageManager to load the file.
return imageManager.loadImage(file);
}
void applySurface(int x,int y,SDL_Surface* source,SDL_Surface* dest,SDL_Rect* clip){
//The offset is needed to draw at the right location.
SDL_Rect offset;
offset.x=x;
offset.y=y;
//Let SDL do the drawing of the surface.
SDL_BlitSurface(source,clip,dest,&offset);
}
void drawRect(int x,int y,int w,int h,SDL_Surface* dest,Uint32 color){
//NOTE: We let SDL_gfx render it.
rectangleRGBA(dest,x,y,x+w,y+h,color >> 24,color >> 16,color >> 8,255);
}
//Draw a box with anti-aliased borders using SDL_gfx.
void drawGUIBox(int x,int y,int w,int h,SDL_Surface* dest,Uint32 color){
//Fill content's background color from function parameter
boxRGBA(screen,x+1,y+1,x+w-2,y+h-2,color >> 24,color >> 16,color >> 8,color >> 0);
//Draw first black borders around content and leave 1 pixel in every corner
lineRGBA(screen,x+1,y,x+w-2,y,0,0,0,255);
lineRGBA(screen,x+1,y+h-1,x+w-2,y+h-1,0,0,0,255);
lineRGBA(screen,x,y+1,x,y+h-2,0,0,0,255);
lineRGBA(screen,x+w-1,y+1,x+w-1,y+h-2,0,0,0,255);
//Fill the corners with transperent color to create anti-aliased borders
pixelRGBA(screen,x,y,0,0,0,160);
pixelRGBA(screen,x,y+h-1,0,0,0,160);
pixelRGBA(screen,x+w-1,y,0,0,0,160);
pixelRGBA(screen,x+w-1,y+h-1,0,0,0,160);
//Draw second lighter border around content
rectangleRGBA(screen,x+1,y+1,x+w-2,y+h-2,0,0,0,64);
//Create anti-aliasing in corners of second border
pixelRGBA(screen,x+1,y+1,0,0,0,50);
pixelRGBA(screen,x+1,y+h-2,0,0,0,50);
pixelRGBA(screen,x+w-2,y+1,0,0,0,50);
pixelRGBA(screen,x+w-2,y+h-2,0,0,0,50);
}
void drawLine(int x1,int y1,int x2,int y2,SDL_Surface* dest,Uint32 color){
//NOTE: We let SDL_gfx render it.
lineRGBA(dest,x1,y1,x2,y2,color >> 24,color >> 16,color >> 8,255);
}
void drawLineWithArrow(int x1,int y1,int x2,int y2,SDL_Surface* dest,Uint32 color,int spacing,int offset,int xsize,int ysize){
//Draw line first
drawLine(x1,y1,x2,y2,dest,color);
//calc delta and length
double dx=x2-x1;
double dy=y2-y1;
double length=sqrt(dx*dx+dy*dy);
if(length<0.001) return;
//calc the unit vector
dx/=length; dy/=length;
//Now draw arrows on it
for(double p=offset;p<length;p+=spacing){
drawLine(int(x1+p*dx+0.5),int(y1+p*dy+0.5),
int(x1+(p-xsize)*dx-ysize*dy+0.5),int(y1+(p-xsize)*dy+ysize*dx+0.5),dest,color);
drawLine(int(x1+p*dx+0.5),int(y1+p*dy+0.5),
int(x1+(p-xsize)*dx+ysize*dy+0.5),int(y1+(p-xsize)*dy-ysize*dx+0.5),dest,color);
}
}
bool init(){
//Initialze SDL.
if(SDL_Init(SDL_INIT_EVERYTHING)==-1) {
fprintf(stderr,"FATAL ERROR: SDL_Init failed\n");
return false;
}
//Initialze SDL_mixer (audio).
if(Mix_OpenAudio(22050,MIX_DEFAULT_FORMAT,2,512)==-1){
fprintf(stderr,"FATAL ERROR: Mix_OpenAudio failed\n");
return false;
}
//Initialze SDL_ttf (fonts).
if(TTF_Init()==-1){
fprintf(stderr,"FATAL ERROR: TTF_Init failed\n");
return false;
}
//Initialise the screen.
screen=SDL_SetVideoMode(SCREEN_WIDTH,SCREEN_HEIGHT,SCREEN_BPP,SDL_HWSURFACE | SDL_DOUBLEBUF /*|SDL_FULLSCREEN*/ );
if(screen==NULL){
fprintf(stderr,"FATAL ERROR: SDL_SetVideoMode failed\n");
return false;
}
//Set the the window caption.
SDL_WM_SetCaption(("Me and my shadow "+version).c_str(),NULL);
SDL_EnableUNICODE(1);
//Create the types of blocks.
for(int i=0;i<TYPE_MAX;i++){
Game::blockNameMap[Game::blockName[i]]=i;
}
//Nothing went wrong so we return true.
return true;
}
bool loadFiles(){
//Load the music and play it.
if(musicManager.loadMusic((getDataPath()+"music/menu.music")).empty()){
printf("WARNING: Unable to load background music! \n");
}
musicManager.playMusic("menu",false);
//Always load the default music list for fallback.
musicManager.loadMusicList((getDataPath()+"music/default.list"));
//Load the fonts.
fontTitle=TTF_OpenFont((getDataPath()+"font/knewave.ttf").c_str(),55);
fontGUI=TTF_OpenFont((getDataPath()+"font/knewave.ttf").c_str(),32);
fontText=TTF_OpenFont((getDataPath()+"font/Blokletters-Viltstift.ttf").c_str(),20);
if(fontTitle==NULL || fontGUI==NULL || fontText==NULL){
printf("ERROR: Unable to load fonts! \n");
return false;
}
//Load the default theme.
if(objThemes.appendThemeFromFile(getDataPath()+"themes/Cloudscape/theme.mnmstheme")==NULL){
printf("ERROR: Can't load default theme file\n");
return false;
}
//Nothing failed so return true.
return true;
}
bool loadSettings(){
settings=new Settings(getUserPath()+"meandmyshadow.cfg");
settings->parseFile();
//Always return true?
return true;
}
bool saveSettings(){
settings->save();
//Always return true?
return true;
}
Settings* getSettings(){
return settings;
}
MusicManager* getMusicManager(){
return &musicManager;
}
void clean(){
//We delete the settings.
if(settings){
delete settings;
settings=NULL;
}
//Get rid of the currentstate/
//NOTE: The state is probably already deleted by the changeState function.
if(currentState)
delete currentState;
//Destroy the GUI if present.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Destroy the imageManager.
imageManager.destroy();
//Destroy the musicManager.
musicManager.destroy();
//Close the fonts and quit SDL_ttf.
TTF_CloseFont(fontTitle);
TTF_CloseFont(fontGUI);
TTF_CloseFont(fontText);
TTF_Quit();
//Quit SDL.
SDL_Quit();
//And finally stop audio.
Mix_CloseAudio();
}
void setNextState(int newstate){
//Only change the state when we aren't already exiting.
if(nextState!=STATE_EXIT){
nextState=newstate;
}
}
void changeState(){
//Check if there's a nextState.
if(nextState!=STATE_NULL){
//Delete the currentState.
delete currentState;
currentState=NULL;
//Set the currentState to the nextState.
stateID=nextState;
nextState=STATE_NULL;
//Init the state.
switch(stateID){
case STATE_GAME:
currentState=new Game();
break;
case STATE_MENU:
levels.clear();
currentState=new Menu();
break;
case STATE_LEVEL_SELECT:
- currentState=new LevelSelect();
+ currentState=new LevelPlaySelect();
+ break;
+ case STATE_LEVEL_EDIT_SELECT:
+ currentState=new LevelEditSelect();
break;
case STATE_LEVEL_EDITOR:
- //Start without levels in the leveleditor.
- levels.clear();
currentState=new LevelEditor();
break;
case STATE_OPTIONS:
currentState=new Options();
break;
case STATE_ADDONS:
currentState=new Addons();
break;
}
//NOTE: STATE_EXIT isn't mentioned, meaning that currentState is null.
//This way the game loop will break and the program will exit.
//Fade out.
int fade=255;
SDL_BlitSurface(screen,NULL,tempSurface,NULL);
while(fade>0){
fade-=17;
if(fade<0)
fade=0;
SDL_FillRect(screen,NULL,0);
SDL_SetAlpha(tempSurface, SDL_SRCALPHA, fade);
SDL_BlitSurface(tempSurface,NULL,screen,NULL);
SDL_Flip(screen);
SDL_Delay(25);
}
}
}
void musicStoppedHook(){
//We just call the musicStopped method of the MusicManager.
musicManager.musicStopped();
}
bool checkCollision(const SDL_Rect& a,const SDL_Rect& b){
//Check if the left side of box a isn't past the right side of b.
if(a.x>=b.x+b.w){
return false;
}
//Check if the right side of box a isn't left of the left side of b.
if(a.x+a.w<=b.x){
return false;
}
//Check if the top side of box a isn't under the bottom side of b.
if(a.y>=b.y+b.h){
return false;
}
//Check if the bottom side of box a isn't above the top side of b.
if(a.y+a.h<=b.y){
return false;
}
//We have collision.
return true;
}
void setCamera(){
//SetCamera only works in the Level editor.
if(stateID==STATE_LEVEL_EDITOR){
//Get the mouse coordinates.
int x,y;
SDL_GetMouseState(&x,&y);
//Make sure we avoid the toolbar.
SDL_Rect mouse={x,y,0,0};
SDL_Rect toolbar={155,550,510,50};
if(checkCollision(mouse,toolbar))
return;
//Check if the mouse is near the left edge of the screen.
//Else check if the mouse is near the right edge.
if(x<50){
//We're near the left edge so move the camera.
camera.x-=5;
}else if(x>SCREEN_WIDTH-50){
//We're near the right edge so move the camera.
camera.x+=5;
}
//Check if the mouse is near the top edge of the screen.
//Else check if the mouse is near the bottom edge.
if(y<50){
//We're near the top edge so move the camera.
camera.y-=5;
}else if(y>SCREEN_HEIGHT-50){
//We're near the bottom edge so move the camera.
camera.y+=5;
}
}
}
bool parseArguments(int argc, char** argv){
//Loop through all arguments.
//We start at one since 0 is the command itself.
for(int i=1;i<argc;i++){
string argument=argv[i];
//Check if the argument is the data-dir.
if(argument=="--data-dir"){
//We need a second argument so we increase i.
i++;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return false;
}
//Configure the dataPath with the given path.
dataPath=argv[i];
if(!getDataPath().empty()){
char c=dataPath[dataPath.size()-1];
if(c!='/'&&c!='\\') dataPath+="/";
}
}else if(argument=="--user-dir"){
//We need a second argument so we increase i.
i++;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return false;
}
//Configure the userPath with the given path.
userPath=argv[i];
if(!userPath.empty()){
char c=userPath[userPath.size()-1];
if(c!='/'&&c!='\\') userPath+="/";
}
}else if(argument=="-v" || argument=="-version" || argument=="--version"){
//Print the version.
printf("Version: '%s'\n\n",version.c_str());
}else if(argument=="-h" || argument=="-help" || argument=="--help"){
//If the help is requested we'll return false without printing an error.
//This way the usage/help text will be printed.
return false;
}else{
//Any other argument is unknow so we return false.
printf("ERROR: Unknown argument %s\n\n",argument.c_str());
return false;
}
}
//If everything went well we can return true.
return true;
}
//Special structure that will recieve the GUIEventCallbacks of the messagebox.
struct msgBoxHandler:public GUIEventCallback{
public:
//Integer containing the ret(urn) value of the messageBox.
int ret;
public:
//Constructor.
msgBoxHandler():ret(0){}
void GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType){
//Make sure it's a click event.
if(eventType==GUIEventClick){
//Set the return value.
ret=obj->value;
//After a click event we can delete the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
}
};
msgBoxResult msgBox(string prompt,msgBoxButtons buttons,const string& title){
//Create the event handler.
msgBoxHandler objHandler;
//The GUI objects.
GUIObject* obj;
//We keep a pointer to the original GUIObjectRoot for later.
GUIObject* tmp=GUIObjectRoot;
//Create the GUIObjectRoot, the height and y location is temp.
//It depends on the content what it will be.
GUIObjectRoot=new GUIObject(100,200,600,200,GUIObjectFrame,title.c_str());
//Integer containing the current y location used to grow dynamic depending on the content.
int y=50;
//Now process the prompt.
{
//Pointer to the string.
char* lps=(char*)prompt.c_str();
//Pointer to a character.
char* lp=NULL;
//We keep looping forever.
//The only way out is with the break statement.
for(;;){
//As long as it's still the same sentence we continue.
//It will stop when there's a newline or end of line.
for(lp=lps;*lp!='\n'&&*lp!='\r'&&*lp!=0;lp++);
//Store the character we stopped on. (End or newline)
char c=*lp;
//Set the character in the string to 0, making lps a string containing one sentence.
*lp=0;
//Integer used to center the sentence horizontally.
int x;
TTF_SizeText(fontText,lps,&x,NULL);
x=(600-x)/2;
//Add a GUIObjectLabel with the sentence.
GUIObjectRoot->childControls.push_back(new GUIObject(x,y,584,25,GUIObjectLabel,lps));
//Increase y with 25, about the height of the text.
y+=25;
//Check the stored character if it was a stop.
if(c==0){
//It was so break out of the for loop.
lps=lp;
break;
}
//It wasn't meaning more will follow.
//We set lps to point after the "newline" forming a new string.
lps=lp+1;
}
}
//Add 70 to y to leave some space between the content and the buttons.
y+=70;
//Recalc the size of the message box.
GUIObjectRoot->top=(SCREEN_HEIGHT-y)/2;
GUIObjectRoot->height=y;
//Now we need to add the buttons.
//Integer containing the number of buttons to add.
int count=0;
//Array with the return codes for the buttons.
int value[3]={0};
//Array containing the captation for the buttons.
string button[3]={"","",""};
switch(buttons){
case MsgBoxOKCancel:
count=2;
button[0]="OK";value[0]=MsgBoxOK;
button[1]="Cancel";value[1]=MsgBoxCancel;
break;
case MsgBoxAbortRetryIgnore:
count=3;
button[0]="Abort";value[0]=MsgBoxAbort;
button[1]="Retry";value[1]=MsgBoxRetry;
button[2]="Ignore";value[2]=MsgBoxIgnore;
break;
case MsgBoxYesNoCancel:
count=3;
button[0]="Yes";value[0]=MsgBoxYes;
button[1]="No";value[1]=MsgBoxNo;
button[2]="Cancel";value[2]=MsgBoxCancel;
break;
case MsgBoxYesNo:
count=2;
button[0]="Yes";value[0]=MsgBoxYes;
button[1]="No";value[1]=MsgBoxNo;
break;
case MsgBoxRetryCancel:
count=2;
button[0]="Retry";value[0]=MsgBoxRetry;
button[1]="Cancel";value[1]=MsgBoxCancel;
break;
default:
count=1;
button[0]="OK";value[0]=MsgBoxOK;
break;
}
//Now we start making the buttons.
{
//Calculate the x location (centered).
int x=302-count*50;
//Reduce y so that the buttons fit inside the frame.
y-=40;
//Loop to add the buttons.
for(int i=0;i<count;i++,x+=100){
obj=new GUIObject(x,y,96,36,GUIObjectButton,button[i].c_str(),value[i]);
obj->eventCallback=&objHandler;
GUIObjectRoot->childControls.push_back(obj);
}
}
//Now we dim the screen and keep the GUI rendering/updating.
SDL_FillRect(tempSurface,NULL,0);
SDL_SetAlpha(tempSurface,SDL_SRCALPHA,155);
SDL_BlitSurface(tempSurface,NULL,screen,NULL);
while(GUIObjectRoot){
while(SDL_PollEvent(&event)){
GUIObjectHandleEvents(true);
//Also check for the return, escape or backspace button.
//escape = KEYUP.
//backspace and return = KEYDOWN.
if(count==1 && ((event.type==SDL_KEYUP && event.key.keysym.sym==SDLK_ESCAPE) ||
(event.type==SDL_KEYDOWN && (event.key.keysym.sym==SDLK_RETURN || event.key.keysym.sym==SDLK_BACKSPACE)))){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
//Render the gui.
if(GUIObjectRoot)
GUIObjectRoot->render();
SDL_Flip(screen);
SDL_Delay(30);
}
//We're done so set the original GUIObjectRoot back.
GUIObjectRoot=tmp;
//And return the result.
return (msgBoxResult)objHandler.ret;
}
struct fileDialogHandler:public GUIEventCallback{
public:
//The ret(urn) value, true=ok and false=cancel
bool ret;
//Boolean if it's a save dialog.
bool isSave;
//Boolean if the file should be verified.
bool verifyFile;
//Boolean if files should be listed instead of directories.
bool files;
//Pointer to the textfield containing the filename.
GUIObject* txtName;
//Pointer to the listbox containing the different files.
GUIListBox* lstFile;
//The extension the files listed should have.
const char* extension;
//The current filename.
string fileName;
//The current search path.
string path;
//Vector containing the search paths.
vector<string> searchPath;
public:
//Constructor.
fileDialogHandler(bool isSave=false,bool verifyFile=false, bool files=true):ret(false),
isSave(isSave),verifyFile(verifyFile),
files(files),txtName(NULL){}
void GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType){
//Check for the ok event.
if(name=="cmdOK"){
//Get the entered fileName from the text field.
std::string s=txtName->caption;
//If it doesn't contain a slash we need to add the path to the fileName.
if(s.find_first_of("/")==string::npos)
s=path+s;
//If the string empty we return.
if(s.empty() || s.find_first_of("*?")!=string::npos)
return;
//We only need to check for extensions if it isn't a folder dialog.
if(files){
//If there isn't right extension add it.
size_t found=s.find_first_of(".");
if(found!=string::npos)
s.replace(s.begin()+found+1,s.end(),extension);
else if (s.substr(found+1)!=extension)
s.append(string(".")+extension);
}
//Check if we should save or load the file.
//
if(isSave){
//Open the file with readpremission to check if it already exists.
FILE* f;
f=fopen(processFileName(s).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();
//Prompt the user with a Yes or No question.
if(msgBox(s+" already exists.\nDo you want to overwrite it?",MsgBoxYesNo,"Overwrite Prompt")!=MsgBoxYes){
//He answered no, so we return.
return;
}
}
//Check if we should verify the file.
//Verifying only applies to files not to directories.
if(verifyFile && files){
//Open the file with write permission.
f=fopen(processFileName(s).c_str(),"wb");
//Check if their aren't problems.
if(f){
//Close the file.
fclose(f);
}else{
//Let the currentState render once to prevent multiple GUI overlapping and prevent the screen from going black.
currentState->render();
//The file can't be opened so tell the user.
msgBox("Can't open file "+s+".",MsgBoxOKOnly,"Error");
return;
}
}
}else if(verifyFile && files){
//We need to verify a file for opening.
FILE *f;
f=fopen(processFileName(s).c_str(),"rb");
//Check if it didn't fail.
if(f){
//Succes, so close the file.
fclose(f);
}else{
//Let the currentState render once to prevent multiple GUI overlapping and prevent the screen from going black.
currentState->render();
//Unable to open file so tell the user.
msgBox("Can't open file "+s+".",MsgBoxOKOnly,"Error");
return;
}
}
//If we haven't returned then it's fine.
//Set the fileName to the chosen file.
fileName=s;
//Delete the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Set return to true.
ret=true;
}else if(name=="cmdCancel"){
//Cancel means we can kill the gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}else if(name=="lstFile"){
//Get a pointer to the listbox.
GUIListBox* obj1=lstFile;
//Make sure the option exist and change textfield to it.
if(obj1!=NULL && txtName!=NULL && obj1->value>=0 && obj1->value<(int)obj1->item.size()){
txtName->caption=obj1->item[obj1->value];
}
}else if(name=="lstSearchIn"){
//Get the searchpath listbox.
GUISingleLineListBox *obj1=dynamic_cast<GUISingleLineListBox*>(obj);
//Check if the entry exists.
if(obj1!=NULL && lstFile!=NULL && obj1->value>=0 && obj1->value<(int)searchPath.size()){
//Temp string.
string s;
//Get the new search path.
path=searchPath[obj1->value];
//Make sure it isn't empty.
if(!path.empty()){
//Process the filename.
s=processFileName(path);
}else{
//It's empty so we give the userpath.
s=getUserPath();
}
//Fill the list with files or directories.
if(files) {
lstFile->item=enumAllFiles(s,extension);
}else
lstFile->item=enumAllDirs(s);
//Remove any selection from the list.
lstFile->value=-1;
}
}
}
};
bool fileDialog(string& fileName,const char* title,const char* extension,const char* path,bool isSave,bool verifyFile,bool files){
//Pointer to GUIObject to make the GUI with.
GUIObject* obj;
//Pointer to the current GUIObjectRoot.
//We keep it so we can put it back after closing the fileDialog.
GUIObject* tmp=GUIObjectRoot;
//Create the fileDialogHandler, used for event handling.
fileDialogHandler objHandler(isSave,verifyFile,files);
//Vector containing the pathNames.
vector<string> pathNames;
//Set the extension of the objHandler.
objHandler.extension=extension;
//We now need to splits the given path into multiple path names.
if(path && path[0]){
//The string isn't empty.
//Pointer to the paths string.
char* lp=(char*)path;
//Pointer to the first newline.
char* lps=strchr(lp,'\n');
//Pointer used for checking if their's another newline.
//It will indicate if it's the last set or not.
char* lpe;
//Check for a newline.
if(lps){
//We have newline(s) so loop forever.
//We can only break out of the loop when the string ends.
for(;;){
//Add the first searchpath.
//This is the beginning of the string (lp) to the first newline. (lps)
objHandler.searchPath.push_back(string(lp,lps-lp));
//We should have another newline so search for it.
lpe=strchr(lps+1,'\n');
if(lpe){
//We found it so we add that to the pathname.
pathNames.push_back(string(lps+1,lpe-lps-1));
//And start over again by setting lp to the start of a new set of searchPath/pathName.
lp=lpe+1;
}else{
//There is no newline anymore, meaning the last entry, the rest of the string must be the pathName.
pathNames.push_back(string(lps+1));
//And break out of the loop.
break;
}
//We haven't broken out so search for a newline.
lps=strchr(lp,'\n');
//If there isn't a newline break.
if(!lps)
break;
}
}else{
//There is no newline thus the whole string is the searchPath.
objHandler.searchPath.push_back(path);
}
}else{
//Empty so put an empty string as searchPath.
objHandler.searchPath.push_back(string());
}
//It's time to create the GUI.
//If there are more than one pathNames we need to add a GUISingleLineListBox.
int base_y=pathNames.size()>0?60:20;
//Create the frame.
GUIObjectRoot=new GUIObject(100,100-base_y/2,600,400+base_y,GUIObjectFrame,title?title:(isSave?"Save File":"Load File"));
//Create the search path list box if needed.
if(pathNames.size()>0){
GUIObjectRoot->childControls.push_back(new GUIObject(8,40,184,36,GUIObjectLabel,"Search In"));
GUISingleLineListBox* obj1=new GUISingleLineListBox(160,40,432,36);
obj1->item=pathNames;
obj1->value=0;
obj1->name="lstSearchIn";
obj1->eventCallback=&objHandler;
GUIObjectRoot->childControls.push_back(obj1);
}
//Add the FileName label and textfield.
GUIObjectRoot->childControls.push_back(new GUIObject(8,20+base_y,184,36,GUIObjectLabel,"File Name"));
{
//Fill the textbox with the given fileName.
string s=fileName;
if(!isSave){
//But only if it isn't empty.
if(s.empty() && extension && extension[0])
s=string("*.")+string(extension);
}
//Create the textbox and add it to the GUI.
objHandler.txtName=new GUIObject(160,20+base_y,432,36,GUIObjectTextBox,s.c_str());
GUIObjectRoot->childControls.push_back(objHandler.txtName);
}
//Now we add the ListBox containing the files or directories.
{
GUIListBox* obj1=new GUIListBox(8,60+base_y,584,292);
//Get the searchPath.
string s=objHandler.searchPath[0];
//Make sure it isn't empty.
if(!s.empty()){
objHandler.path=s;
s=processFileName(s);
}else{
s=getUserPath();
}
//Check if we should list files or directories.
if(files){
//Fill the list with files.
obj1->item=enumAllFiles(s,extension);
}else{
//Fill the list with directories.
obj1->item=enumAllDirs(s);
}
obj1->name="lstFile";
obj1->eventCallback=&objHandler;
GUIObjectRoot->childControls.push_back(obj1);
objHandler.lstFile=obj1;
}
//Now create the OK and Cancel buttons.
obj=new GUIObject(200,360+base_y,192,36,GUIObjectButton,"OK");
obj->name="cmdOK";
obj->eventCallback=&objHandler;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(400,360+base_y,192,36,GUIObjectButton,"Cancel");
obj->name="cmdCancel";
obj->eventCallback=&objHandler;
GUIObjectRoot->childControls.push_back(obj);
//Now we keep rendering and updating the GUI.
SDL_FillRect(tempSurface,NULL,0);
SDL_SetAlpha(tempSurface,SDL_SRCALPHA,155);
SDL_BlitSurface(tempSurface,NULL,screen,NULL);
while(GUIObjectRoot){
while(SDL_PollEvent(&event))
GUIObjectHandleEvents(true);
if(GUIObjectRoot)
GUIObjectRoot->render();
SDL_Flip(screen);
SDL_Delay(30);
}
//The while loop ended meaning we can restore the previous GUI.
GUIObjectRoot=tmp;
//Now determine what the return value is (and if there is one).
if(objHandler.ret)
fileName=objHandler.fileName;
return objHandler.ret;
}
diff --git a/src/GUIObject.cpp b/src/GUIObject.cpp
index 9c389dd..e1ffd3a 100644
--- a/src/GUIObject.cpp
+++ b/src/GUIObject.cpp
@@ -1,455 +1,454 @@
/****************************************************************************
** 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 "GUIObject.h"
#include <iostream>
#include <list>
using namespace std;
//Set the GUIObjectRoot to NULL.
GUIObject* GUIObjectRoot=NULL;
//Initialise the event queue.
list<GUIEvent> GUIEventQueue;
void GUIObjectHandleEvents(bool kill){
//Make sure that GUIObjectRoot isn't null.
if(GUIObjectRoot)
GUIObjectRoot->handleEvents();
//Check for SDL_QUIT.
if(event.type==SDL_QUIT && kill){
//We get a quit event so enter the exit state.
setNextState(STATE_EXIT);
delete GUIObjectRoot;
GUIObjectRoot=NULL;
return;
}
//Keep calling events until there are none left.
while(!GUIEventQueue.empty()){
//Get one event and remove it from the queue.
GUIEvent e=GUIEventQueue.front();
GUIEventQueue.pop_front();
//If an eventCallback exist call it.
if(e.eventCallback){
e.eventCallback->GUIEventCallback_OnEvent(e.name,e.obj,e.eventType);
}
}
//We empty the event queue just to be sure.
GUIEventQueue.clear();
}
GUIObject::~GUIObject(){
//We need to delete every child we have.
for(unsigned int i=0;i<childControls.size();i++){
delete childControls[i];
}
//Deleted the childs now empty the childControls vector.
childControls.clear();
}
bool GUIObject::handleEvents(int x,int y,bool enabled,bool visible,bool processed){
//Boolean if the event is processed.
bool b=processed;
//The GUIObject is only enabled when he and his parent are enabled.
enabled=enabled && this->enabled;
//The GUIObject is only enabled when he and his parent are enabled.
visible=visible && this->visible;
//Get the absolute position.
x+=left;
y+=top;
//Type specific event handling.
switch(type){
case GUIObjectButton:
//Set state to 0.
state=0;
//Only check for events when the object is both enabled and visible.
if(enabled&&visible){
//The mouse location (x=i, y=j) and the mouse button (k).
int i,j,k;
k=SDL_GetMouseState(&i,&j);
//Check if the mouse is inside the GUIObject.
if(i>=x&&i<x+width&&j>=y&&j<y+height){
//We have hover so set state to one.
state=1;
//Check for a mouse button press.
if(k&SDL_BUTTON(1))
state=2;
//Check if there's a mouse press and the event hasn't been already processed.
if(event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT && !b){
//If event callback is configured then add an event to the queue.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventClick};
GUIEventQueue.push_back(e);
}
//Event has been processed.
b=true;
}
}
}
break;
case GUIObjectCheckBox:
//Set state to 0.
state=0;
//Only check for events when the object is both enabled and visible.
if(enabled&&visible){
//The mouse location (x=i, y=j) and the mouse button (k).
int i,j,k;
k=SDL_GetMouseState(&i,&j);
//Check if the mouse is inside the GUIObject.
if(i>=x&&i<x+width&&j>=y&&j<y+height){
//We have hover so set state to one.
state=1;
//Check for a mouse button press.
if(k&SDL_BUTTON(1))
state=2;
//Check if there's a mouse press and the event hasn't been already processed.
if(event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT && !b){
//It's a checkbox so toggle the value.
value=value?0:1;
//If event callback is configured then add an event to the queue.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventClick};
GUIEventQueue.push_back(e);
}
//Event has been processed.
b=true;
}
}
}
break;
case GUIObjectTextBox:
//NOTE: We don't reset the state to have a "focus" effect.
//Only check for events when the object is both enabled and visible.
if(enabled&&visible){
//Check if there's a key press and the event hasn't been already processed.
if(state==2 && event.type==SDL_KEYDOWN && !b){
//Get the keycode.
int key=(int)event.key.keysym.unicode;
//Check if the key is supported.
if(key>=32&&key<=126){
//Add the key to the text after the carrot.
caption.insert((size_t)value,1,char(key));
value=clamp(value+1,0,caption.length());
//If there is an event callback then call it.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
}else if(event.key.keysym.sym==SDLK_BACKSPACE){
//We need to remove a character so first make sure that there is text.
if(caption.length()>0&&value>0){
//Remove the character before the carrot.
value=clamp(value-1,0,caption.length());
caption.erase((size_t)value,1);
//If there is an event callback then call it.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
}
}else if(event.key.keysym.sym==SDLK_DELETE){
//We need to remove a character so first make sure that there is text.
if(caption.length()>0){
//Remove the character after the carrot.
value=clamp(value,0,caption.length());
caption.erase((size_t)value,1);
//If there is an event callback then call it.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
}
}else if(event.key.keysym.sym==SDLK_RIGHT){
value=clamp(value+1,0,caption.length());
}else if(event.key.keysym.sym==SDLK_LEFT){
value=clamp(value-1,0,caption.length());
}
//The event has been processed.
b=true;
}
//The mouse location (x=i, y=j) and the mouse button (k).
int i,j,k;
k=SDL_GetMouseState(&i,&j);
//Check if the mouse is inside the GUIObject.
if(i>=x&&i<x+width&&j>=y&&j<y+height){
//We can only increase our state. (nothing->hover->focus).
if(state!=2){
state=1;
}
//Check for a mouse button press.
if(k&SDL_BUTTON(1)){
//We have focus.
state=2;
//TODO Move carrot to place clicked
value=caption.length();
}
}else{
//The mouse is outside the TextBox.
//If we don't have focus but only hover we lose it.
if(state==1){
state=0;
}
//If it's a click event outside the textbox then we blur.
if(event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT){
//Set state to 0.
state=0;
}
}
}
break;
}
//Also let the children handle their events.
for(unsigned int i=0;i<childControls.size();i++){
bool b1=childControls[i]->handleEvents(x,y,enabled,visible,b);
//The event is processed when either our or the childs is true (or both).
b=b||b1;
}
return b;
}
void GUIObject::render(int x,int y){
//Rectangle the size of the GUIObject, used to draw borders.
SDL_Rect r;
//There's no need drawing the GUIObject when it's invisible.
if(!visible)
return;
//Get the absolute x and y location.
x+=left;
y+=top;
//Now do the type specific rendering.
switch(type){
case GUIObjectLabel:
{
//The rectangle is simple.
r.x=x;
r.y=y;
r.w=width;
r.h=height;
//We don't draw a background and/or border since that label is transparent.
//Get the caption and make sure it isn't empty.
const char* lp=caption.c_str();
if(lp!=NULL && lp[0]){
//Color the text will be: black.
SDL_Color black={0,0,0,0};
//Render the text using the small font.
SDL_Surface* bm=TTF_RenderText_Blended(fontText,lp,black);
//Center the text vertically and draw it to the screen.
r.y=y+(height - bm->h)/2;
SDL_BlitSurface(bm,NULL,screen,&r);
SDL_FreeSurface(bm);
}
}
break;
case GUIObjectCheckBox:
{
//The rectangle is simple.
r.x=x;
r.y=y;
r.w=width;
r.h=height;
//Get the text.
const char* lp=caption.c_str();
//Make sure it isn't empty.
if(lp!=NULL && lp[0]){
//We render black text.
SDL_Color black={0,0,0,0};
SDL_Surface* bm=TTF_RenderText_Blended(fontText,lp,black);
//Calculate the location, center it vertically.
r.x=x;
r.y=y+(height - bm->h)/2;
//Draw the text and free the surface.
SDL_BlitSurface(bm,NULL,screen,&r);
SDL_FreeSurface(bm);
}
//Draw the check (or not).
SDL_Rect r1={0,0,16,16};
if(value==1||value==2)
r1.x=value*16;
r.x=x+width-20;
r.y=y+(height-16)/2;
SDL_BlitSurface(bmGUI,&r1,screen,&r);
}
break;
case GUIObjectButton:
{
//Get the text.
const char* lp=caption.c_str();
//Make sure the text isn't empty.
if(lp!=NULL && lp[0]){
//Draw black text.
SDL_Color black={0,0,0,0};
+ //Draw in gray when disabled.
+ if(!enabled)
+ black={96,96,96,0};
+
SDL_Surface* bm;
- if(state>=1){
- //bm=TTF_RenderText_Blended(fontGUI,("> "+string(lp)+" <").c_str(),black);
- bm=TTF_RenderText_Blended(fontGUI,lp,black);
- }else{
- bm=TTF_RenderText_Blended(fontGUI,lp,black);
- }
+ bm=TTF_RenderText_Blended(fontGUI,lp,black);
//Center the text both vertically as horizontally.
r.x=x+(width-bm->w)/2;
r.y=y+(height-bm->h)/2;
SDL_Rect r2={64,0,16,16};
if(state==1){
applySurface(x+(width-bm->w)/2-25,y+(height-bm->h)/2+((bm->h-16)/2),bmGUI,screen,&r2);
r2.x-=16;
applySurface(x+(width-bm->w)/2+4+bm->w+5,y+(height-bm->h)/2+((bm->h-16)/2),bmGUI,screen,&r2);
}else if(state==2){
applySurface(x+(width-bm->w)/2-20,y+(height-bm->h)/2+((bm->h-16)/2),bmGUI,screen,&r2);
r2.x-=16;
applySurface(x+(width-bm->w)/2+4+bm->w,y+(height-bm->h)/2+((bm->h-16)/2),bmGUI,screen,&r2);
}
//Draw the text and free the surface.
SDL_BlitSurface(bm,NULL,screen,&r);
SDL_FreeSurface(bm);
}
}
break;
case GUIObjectTextBox:
{
//Default background opacity
int clr=50;
//If hovering or focused make background more visible.
if(state==1)
clr=128;
else if (state==2)
clr=100;
//Draw the box.
Uint32 color=0xFFFFFF00|clr;
drawGUIBox(x,y,width,height,screen,color);
//Get the text.
const char* lp=caption.c_str();
//Make sure it isn't empty.
if(lp!=NULL && lp[0]){
//Draw the black text.
SDL_Color black={0,0,0,0};
SDL_Surface* bm=TTF_RenderText_Blended(fontText,lp,black);
//Calculate the location, center it vertically.
r.x=x+2;
r.y=y+(height - bm->h)/2;
//Draw the text.
SDL_Rect tmp={0,0,width-2,25};
SDL_BlitSurface(bm,&tmp,screen,&r);
//Only draw the carrot when focus.
if(state==2){
r.x=x;
r.y=y+4;
r.w=2;
r.h=height-8;
int advance;
for(int n=0;n<value;n++){
TTF_GlyphMetrics(fontText,caption[n],NULL,NULL,NULL,NULL,&advance);
r.x+=advance;
}
//Make sure that the carrot is inside the textbox.
if(r.x<x+width)
SDL_FillRect(screen,&r,0);
}
//And free the surface.
SDL_FreeSurface(bm);
}else{
//Only draw the carrot when focus.
if(state==2){
r.x=x+4;
r.y=y+4;
r.w=2;
r.h=height-8;
SDL_FillRect(screen,&r,0);
}
}
}
break;
case GUIObjectFrame:
{
//Create a rectangle the size of the button and fill it.
Uint32 color=0xDDDDDDFF;
drawGUIBox(x,y,width,height,screen,color);
//Get the title text.
const char* lp=caption.c_str();
//Make sure it isn't empty.
if(lp!=NULL && lp[0]){
//The colors black and white used to render the title with white background.
SDL_Color black={0,0,0,0};
SDL_Surface* bm=TTF_RenderText_Blended(fontGUI,lp,black);
//Calculate the location, center horizontally and vertically relative to the top.
r.x=x+(width-bm->w)/2;
r.y=y;
//Draw the text and free the surface.
SDL_BlitSurface(bm,NULL,screen,&r);
//And free the surface.
SDL_FreeSurface(bm);
}
}
break;
}
//We now need to draw all the children of the GUIObject.
for(unsigned int i=0;i<childControls.size();i++){
childControls[i]->render(x,y);
}
}
diff --git a/src/Globals.h b/src/Globals.h
index 84516d3..35a84f6 100644
--- a/src/Globals.h
+++ b/src/Globals.h
@@ -1,161 +1,163 @@
/****************************************************************************
** 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 GLOBALS_H
#define GLOBALS_H
#include <SDL/SDL.h>
#include <SDL/SDL_mixer.h>
#include <SDL/SDL_ttf.h>
#include <string>
#ifdef WIN32
//#define DATA_PATH
#else
#include "config.h"
#endif
//Global constants
//The width of the screen.
const int SCREEN_WIDTH=800;
//The height of the screen.
const int SCREEN_HEIGHT=600;
//The depth of the screen.
const int SCREEN_BPP=32;
//Strin containing the
const std::string version="V0.3 Development version";
//The height of the current level.
extern int LEVEL_HEIGHT;
//The width of the current level.
extern int LEVEL_WIDTH;
//The target frames per seconds.
const int g_FPS=40;
//The screen surface, it's used to draw on before it's drawn to the real screen.
extern SDL_Surface* screen;
//SDL_Surface with the same dimensions as screen which can be used for all kinds of (temp) drawing.
extern SDL_Surface* tempSurface;
//Font that is used for titles.
//Knewave large.
extern TTF_Font* fontTitle;
//Font that is used for captions of buttons and other GUI elements.
//Knewave small.
extern TTF_Font* fontGUI;
//Font that is used for (long) text.
//Blokletter-Viltstift small.
extern TTF_Font* fontText;
//Event, used for event handling.
extern SDL_Event event;
//GUI
class GUIObject;
extern GUIObject *GUIObjectRoot;
//The state id of the current state.
extern int stateID;
//Integer containing what the next state will be.
extern int nextState;
//String containing the name of the current level.
extern std::string levelName;
//SDL rectangle used to store the camera.
//x is the x location of the camera.
//y is the y location of the camera.
//w is the width of the camera. (equal to SCREEN_WIDTH)
//h is the height of the camera. (equal to SCREEN_HEIGHT)
extern SDL_Rect camera;
//Enumeration containing the ids of the game states.
enum GameStates
{
//State null is a special state used to indicate no state.
//This is used when no next state is defined.
STATE_NULL,
+ //This state is before the actual leveleditor used to make levelpacks.
+ STATE_LEVEL_EDIT_SELECT,
//This state is for the level editor.
STATE_LEVEL_EDITOR,
//This state is for the main menu.
STATE_MENU,
//This state is for the actual game.
STATE_GAME,
//Special state used when exiting meandmyshadow.
STATE_EXIT,
//This state is for the help screen.
STATE_LEVEL_SELECT,
//This state is for the options screen.
STATE_OPTIONS,
//This state is for the addon screen.
STATE_ADDONS
};
//Enumeration containing the ids of the different block types.
enum GameTileType{
//The normal solid block.
TYPE_BLOCK=0,
//Block representing the start location of the player.
TYPE_START_PLAYER,
//Block representing the start location of the shadow.
TYPE_START_SHADOW,
//The exit of the level.
TYPE_EXIT,
//The shadow block which is only solid for the shadow.
TYPE_SHADOW_BLOCK,
//Block that can kill both the player and the shadow.
TYPE_SPIKES,
//Special point where the player can save.
TYPE_CHECKPOINT,
//Block that will switch the location of the player and the shadow when invoked.
TYPE_SWAP,
//Block that will crumble to dust when stepped on it for the third time.
TYPE_FRAGILE,
//Normal block that moves along a path.
TYPE_MOVING_BLOCK,
//Shadow block that moves along a path.
TYPE_MOVING_SHADOW_BLOCK,
//A spike block that moves along a path.
TYPE_MOVING_SPIKES,
//Special block which, once entered, moves the player/shadow to a different portal.
TYPE_PORTAL,
//A block with a button which can activate or stop moving blocks, converyor belts
TYPE_BUTTON,
//A switch which can activate or stop moving blocks, converyor belts
TYPE_SWITCH,
//Solid block which works like
TYPE_CONVEYOR_BELT,
TYPE_SHADOW_CONVEYOR_BELT,
//Block that contains a message that can be read.
TYPE_NOTIFICATION_BLOCK,
//The (max) number of tiles.
TYPE_MAX
};
#endif
diff --git a/src/LevelEditSelect.cpp b/src/LevelEditSelect.cpp
new file mode 100644
index 0000000..5ef7d9d
--- /dev/null
+++ b/src/LevelEditSelect.cpp
@@ -0,0 +1,657 @@
+/****************************************************************************
+** 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 "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 "Game.h"
+#include <SDL/SDL_ttf.h>
+#include <SDL/SDL.h>
+#include <stdio.h>
+#include <algorithm>
+#include <string>
+#include <iostream>
+using namespace std;
+
+LevelEditSelect::LevelEditSelect():LevelSelect("Map Editor"){
+ //The levelpack name text field.
+ levelpackName=new GUIObject(280,104,240,32,GUIObjectTextBox);
+ levelpackName->eventCallback=this;
+ levelpackName->visible=false;
+ GUIObjectRoot->childControls.push_back(levelpackName);
+
+ //Create the six buttons at the bottom of the screen.
+ GUIObject* obj=new GUIObject(20,480,240,32,GUIObjectButton,"New Levelpack");
+ obj->name="cmdNewLvlpack";
+ obj->eventCallback=this;
+ GUIObjectRoot->childControls.push_back(obj);
+
+ propertiesPack=new GUIObject(280,480,240,32,GUIObjectButton,"Pack Properties");
+ propertiesPack->name="cmdLvlpackProp";
+ propertiesPack->eventCallback=this;
+ GUIObjectRoot->childControls.push_back(propertiesPack);
+
+ removePack=new GUIObject(540,480,240,32,GUIObjectButton,"Remove Pack");
+ removePack->name="cmdRmLvlpack";
+ removePack->eventCallback=this;
+ GUIObjectRoot->childControls.push_back(removePack);
+
+ move=new GUIObject(20,540,240,32,GUIObjectButton,"Move Map");
+ move->name="cmdMoveMap";
+ move->eventCallback=this;
+ move->enabled=false;
+ GUIObjectRoot->childControls.push_back(move);
+
+ remove=new GUIObject(280,540,240,32,GUIObjectButton,"Remove Map");
+ remove->name="cmdRmMap";
+ remove->eventCallback=this;
+ remove->enabled=false;
+ GUIObjectRoot->childControls.push_back(remove);
+
+ edit=new GUIObject(540,540,240,32,GUIObjectButton,"Edit Map");
+ edit->name="cmdEdit";
+ edit->eventCallback=this;
+ edit->enabled=false;
+ GUIObjectRoot->childControls.push_back(edit);
+
+ //NOTE: We are changing the available list of levelpacks to prevent editing the main/addons levelpacks.
+ listPacks();
+
+ //show level list
+ refresh();
+}
+
+LevelEditSelect::~LevelEditSelect(){
+ selectedNumber=NULL;
+}
+
+void LevelEditSelect::listPacks(){
+ levelpackLocations.clear();
+ levelpacks->item.clear();
+
+ vector<string> v=enumAllDirs(getUserPath()+"custom/levelpacks/");
+ v.push_back("Levels");
+ for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
+ levelpackLocations[*i]=getUserPath()+"custom/levelpacks/"+*i;
+
+ //Check if we can find the lastlevelpack.
+ if(*i==getSettings()->getValue("lastlevelpack")){
+ levelpacks->value=i-v.begin();
+ packName=*i;
+ }
+ }
+ levelpacks->item=v;
+
+ //And call changePack since we changed the levelpack.
+ changePack();
+}
+
+void LevelEditSelect::changePack(){
+ packName=levelpacks->item[levelpacks->value];
+ if(packName=="Levels"){
+ //Clear the current levels.
+ levels.clear();
+ levels.setCurrentLevel(0);
+
+ //List the custom levels and add them one for one.
+ vector<string> v=enumAllFiles(getUserPath()+"custom/levels/");
+ for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
+ levels.addLevel(getUserPath()+"custom/levels/"+*i);
+ levels.setLocked(levels.getLevelCount()-1);
+ }
+
+ //Disable some levelpack buttons.
+ propertiesPack->enabled=false;
+ removePack->enabled=false;
+ }else{
+ //Load the levelpack in the normal way.
+ if(!levels.loadLevels(levelpackLocations[packName]+"/levels.lst")){
+ msgBox("Can't load level pack:\n"+packName,MsgBoxOKOnly,"Error");
+ }
+
+ //Enable some levelpack buttons.
+ propertiesPack->enabled=true;
+ removePack->enabled=true;
+ }
+}
+
+void LevelEditSelect::packProperties(){
+ //Open a message popup.
+
+ //Pointer to the current GUIObjectRoot.
+ //We keep it so we can put it back after closing the fileDialog.
+ GUIObject* tmp=GUIObjectRoot;
+
+ GUIObjectRoot=new GUIObject(100,(SCREEN_HEIGHT-300)/2,600,300,GUIObjectFrame,"Properties");
+ GUIObject* obj;
+
+ obj=new GUIObject(40,80,240,36,GUIObjectLabel,"Name:");
+ GUIObjectRoot->childControls.push_back(obj);
+
+ obj=new GUIObject(300,80,240,36,GUIObjectTextBox,packName.c_str());
+ obj->name="LvlpackName";
+ GUIObjectRoot->childControls.push_back(obj);
+
+ obj=new GUIObject(40,120,240,36,GUIObjectLabel,"Description:");
+ GUIObjectRoot->childControls.push_back(obj);
+
+ obj=new GUIObject(300,120,240,36,GUIObjectTextBox,levels.levelpackDescription.c_str());
+ obj->name="LvlpackDescription";
+ GUIObjectRoot->childControls.push_back(obj);
+
+ obj=new GUIObject(40,160,240,36,GUIObjectLabel,"Congratulation text:");
+ GUIObjectRoot->childControls.push_back(obj);
+
+ obj=new GUIObject(300,160,240,36,GUIObjectTextBox,levels.congratulationText.c_str());
+ obj->name="LvlpackCongratulation";
+ GUIObjectRoot->childControls.push_back(obj);
+
+ obj=new GUIObject(100,300-44,150,36,GUIObjectButton,"OK");
+ obj->name="cfgOK";
+ obj->eventCallback=this;
+ GUIObjectRoot->childControls.push_back(obj);
+ obj=new GUIObject(350,300-44,150,36,GUIObjectButton,"Cancel");
+ obj->name="cfgCancel";
+ obj->eventCallback=this;
+ GUIObjectRoot->childControls.push_back(obj);
+
+ //Dim the screen using the tempSurface.
+ SDL_FillRect(tempSurface,NULL,0);
+ SDL_SetAlpha(tempSurface,SDL_SRCALPHA,155);
+ SDL_BlitSurface(tempSurface,NULL,screen,NULL);
+ while(GUIObjectRoot){
+ while(SDL_PollEvent(&event)) GUIObjectHandleEvents(true);
+ if(GUIObjectRoot) GUIObjectRoot->render();
+ SDL_Flip(screen);
+ SDL_Delay(30);
+ }
+
+ //We're done so set the original GUIObjectRoot back.
+ GUIObjectRoot=tmp;
+}
+
+void LevelEditSelect::addLevel(){
+ //Open a message popup.
+
+ //Pointer to the current GUIObjectRoot.
+ //We keep it so we can put it back after closing the fileDialog.
+ GUIObject* tmp=GUIObjectRoot;
+
+ GUIObjectRoot=new GUIObject(100,(SCREEN_HEIGHT-200)/2,600,200,GUIObjectFrame,"Add level");
+ GUIObject* obj;
+
+ obj=new GUIObject(40,80,240,36,GUIObjectLabel,"File name:");
+ GUIObjectRoot->childControls.push_back(obj);
+
+ char s[64];
+ sprintf(s,"map%02d.map",levels.getLevelCount()+1);
+ obj=new GUIObject(300,80,240,36,GUIObjectTextBox,s);
+ obj->name="LvlFile";
+ GUIObjectRoot->childControls.push_back(obj);
+
+ obj=new GUIObject(100,200-44,150,36,GUIObjectButton,"OK");
+ obj->name="cfgAddOK";
+ obj->eventCallback=this;
+ GUIObjectRoot->childControls.push_back(obj);
+ obj=new GUIObject(350,200-44,150,36,GUIObjectButton,"Cancel");
+ obj->name="cfgAddCancel";
+ obj->eventCallback=this;
+ GUIObjectRoot->childControls.push_back(obj);
+
+ //Dim the screen using the tempSurface.
+ SDL_FillRect(tempSurface,NULL,0);
+ SDL_SetAlpha(tempSurface,SDL_SRCALPHA,155);
+ SDL_BlitSurface(tempSurface,NULL,screen,NULL);
+ while(GUIObjectRoot){
+ while(SDL_PollEvent(&event)) GUIObjectHandleEvents(true);
+ if(GUIObjectRoot) GUIObjectRoot->render();
+ SDL_Flip(screen);
+ SDL_Delay(30);
+ }
+
+ //We're done so set the original GUIObjectRoot back.
+ GUIObjectRoot=tmp;
+}
+
+void LevelEditSelect::moveLevel(){
+ //Open a message popup.
+
+ //Pointer to the current GUIObjectRoot.
+ //We keep it so we can put it back after closing the fileDialog.
+ GUIObject* tmp=GUIObjectRoot;
+
+ GUIObjectRoot=new GUIObject(100,(SCREEN_HEIGHT-200)/2,600,200,GUIObjectFrame,"Move level");
+ GUIObject* obj;
+
+ obj=new GUIObject(40,80,240,36,GUIObjectLabel,"Level: ");
+ GUIObjectRoot->childControls.push_back(obj);
+
+ obj=new GUIObject(300,80,240,36,GUIObjectTextBox,"1");
+ obj->name="MoveLevel";
+ GUIObjectRoot->childControls.push_back(obj);
+
+ obj=new GUISingleLineListBox(40,120,240,36);
+ obj->name="lstPlacement";
+ vector<string> v;
+ v.push_back("Before");
+ v.push_back("After");
+ v.push_back("Swap");
+ (dynamic_cast<GUISingleLineListBox*>(obj))->item=v;
+ obj->value=0;
+ GUIObjectRoot->childControls.push_back(obj);
+
+ obj=new GUIObject(100,200-44,150,36,GUIObjectButton,"OK");
+ obj->name="cfgMoveOK";
+ obj->eventCallback=this;
+ GUIObjectRoot->childControls.push_back(obj);
+ obj=new GUIObject(350,200-44,150,36,GUIObjectButton,"Cancel");
+ obj->name="cfgMoveCancel";
+ obj->eventCallback=this;
+ GUIObjectRoot->childControls.push_back(obj);
+
+ //Dim the screen using the tempSurface.
+ SDL_FillRect(tempSurface,NULL,0);
+ SDL_SetAlpha(tempSurface,SDL_SRCALPHA,155);
+ SDL_BlitSurface(tempSurface,NULL,screen,NULL);
+ while(GUIObjectRoot){
+ while(SDL_PollEvent(&event)) GUIObjectHandleEvents(true);
+ if(GUIObjectRoot) GUIObjectRoot->render();
+ SDL_Flip(screen);
+ SDL_Delay(30);
+ }
+
+ //We're done so set the original GUIObjectRoot back.
+ GUIObjectRoot=tmp;
+}
+void LevelEditSelect::refresh(){
+ int m=levels.getLevelCount();
+ 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%10)*64+80,(n/10)*64+184,0,0};
+ numbers[n].init(n,box);
+ }
+ SDL_Rect box={(m%10)*64+80,(m/10)*64+184,0,0};
+ numbers[m].init("+",box);
+
+ if(m>40){
+ levelScrollBar->maxValue=(m-41)/10;
+ levelScrollBar->visible=true;
+ }else{
+ levelScrollBar->maxValue=0;
+ levelScrollBar->visible=false;
+ }
+ levelpackDescription->caption=levels.levelpackDescription;
+ int width,height;
+ TTF_SizeText(fontGUI,levels.levelpackDescription.c_str(),&width,&height);
+ levelpackDescription->left=(800-width)/2;
+}
+
+void LevelEditSelect::selectNumber(int number,bool selected){
+ if(selected){
+ levels.setCurrentLevel(number);
+ setNextState(STATE_LEVEL_EDITOR);
+
+ //Pick music from the current music list.
+ getMusicManager()->pickMusic();
+ }else{
+ if(number==numbers.size()-1){
+ addLevel();
+ }else if(number>=0 && 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::renderTooltip(int number,int dy){
+ SDL_Color fg={0,0,0};
+ SDL_Surface* name;
+
+ if(number==levels.getLevelCount()){
+ //Render the name of the level.
+ name=TTF_RenderText_Blended(fontText,"Add level",fg);
+ }else{
+ //Render the name of the level.
+ name=TTF_RenderText_Blended(fontText,levels.getLevelName(number).c_str(),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*80;
+ 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=0xFFFFFF00|240;
+ 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"){
+ //Set the name of the levelpack.
+ packName=((GUISingleLineListBox*)obj)->item[obj->value];
+
+ //Check if it matches the Levels levelpack.
+ if(((GUISingleLineListBox*)obj)->item[obj->value]=="Levels"){
+ //Disable some levelpack buttons.
+ propertiesPack->enabled=false;
+ removePack->enabled=false;
+ getSettings()->setValue("lastlevelpack","Levels");
+
+ //We call changepack and return to prevent the LevelSelect to undo what we did.
+ changePack();
+ refresh();
+ return;
+ }else{
+ //Enable some levelpack buttons.
+ propertiesPack->enabled=true;
+ removePack->enabled=true;
+ }
+ }
+
+ //Let the level select handle his GUI events.
+ LevelSelect::GUIEventCallback_OnEvent(name,obj,eventType);
+
+ //Check for the edit button.
+ if(name=="cmdNewLvlpack"){
+ //Clear the current pack.
+ packName.clear();
+ levels.clear();
+
+ //Create a new pack.
+ packProperties();
+ }else if(name=="cmdLvlpackProp"){
+ //Show the pack properties.
+ packProperties();
+ }else if(name=="cmdRmLvlpack"){
+ //Show an "are you sure" message.
+ if(msgBox("Are you sure?",MsgBoxYesNo,"Remove prompt")==MsgBoxYes){
+ //Remove the directory.
+ if(!removeDirectory(levelpackLocations[packName].c_str())){
+ cerr<<"ERROR: Unable to remove levelpack directory "<<levelpackLocations[packName]<<endl;
+ }
+
+ //Remove it from the vector (levelpack list).
+ vector<string>::iterator it;
+ it=find(levelpacks->item.begin(),levelpacks->item.end(),packName);
+ if(it!=levelpacks->item.end()){
+ levelpacks->item.erase(it);
+ }
+
+ //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!="Levels"){
+ if(!removeFile((levelpackLocations[packName]+"/"+levels.getLevel(selectedNumber->getNumber())->file).c_str())){
+ cerr<<"ERROR: Unable to remove level "<<(levelpackLocations[packName]+"/"+levels.getLevel(selectedNumber->getNumber())->file).c_str()<<endl;
+ }
+ levels.removeLevel(selectedNumber->getNumber());
+ levels.saveLevels(levelpackLocations[packName]+"/levels.lst");
+ }else{
+ //This is the levels levelpack so we just remove the file.
+ if(!removeFile(levels.getLevel()->file.c_str())){
+ cerr<<"ERROR: Unable to remove level "<<(levelpackLocations[packName]+"/"+levels.getLevel(selectedNumber->getNumber())->file).c_str()<<endl;
+ }
+ changePack();
+ }
+
+ //And refresh the selection screen.
+ refresh();
+ }
+ }else if(name=="cmdEdit"){
+ if(selectedNumber!=NULL){
+ levels.setCurrentLevel(selectedNumber->getNumber());
+ setNextState(STATE_LEVEL_EDITOR);
+
+ //Pick music from the current music list.
+ getMusicManager()->pickMusic();
+ }
+ }
+
+ //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()+"custom/levelpacks/"+packName).c_str(),(getUserPath()+"custom/levelpacks/"+GUIObjectRoot->childControls[i]->caption).c_str())){
+ cerr<<"ERROR: Unable to move levelpack directory "<<(getUserPath()+"custom/levelpacks/"+packName)<<" to "<<(getUserPath()+"custom/levelpacks/"+GUIObjectRoot->childControls[i]->caption)<<endl;
+ }
+
+ //Also change the levelpack location.
+ map<string,string>::iterator it;
+ it=levelpackLocations.find(packName);
+
+ if(it!=levelpackLocations.end()){
+ levelpackLocations.erase(it);
+ }
+
+ //And the levelpack list.
+ vector<string>::iterator it1;
+ it1=find(levelpacks->item.begin(),levelpacks->item.end(),packName);
+ if(it1!=levelpacks->item.end()){
+ levelpacks->item.erase(it1);
+ if(levelpacks->value>levelpacks->item.size())
+ levelpacks->value=levelpacks->item.size()-1;
+ }
+
+ //Also add the levelpack location
+ levelpackLocations[GUIObjectRoot->childControls[i]->caption]=(getUserPath()+"custom/levelpacks/"+GUIObjectRoot->childControls[i]->caption);
+ levelpacks->item.push_back(GUIObjectRoot->childControls[i]->caption);
+ levelpacks->value=levelpacks->item.size()-1;
+ }else{
+ if(!createDirectory((getUserPath()+"custom/levelpacks/"+GUIObjectRoot->childControls[i]->caption).c_str())){
+ cerr<<"ERROR: Unable to create levelpack directory "<<(getUserPath()+"custom/levelpacks/"+GUIObjectRoot->childControls[i]->caption)<<endl;
+ }
+
+ //Also add the levelpack location.
+ levelpackLocations[GUIObjectRoot->childControls[i]->caption]=(getUserPath()+"custom/levelpacks/"+GUIObjectRoot->childControls[i]->caption);
+ levelpacks->item.push_back(GUIObjectRoot->childControls[i]->caption);
+ levelpacks->value=levelpacks->item.size()-1;
+ }
+ //And set the new name.
+ packName=GUIObjectRoot->childControls[i]->caption;
+ changePack();
+ refresh();
+ }
+ }
+ if(GUIObjectRoot->childControls[i]->name=="LvlpackDescription"){
+ levels.levelpackDescription=GUIObjectRoot->childControls[i]->caption;
+ }
+ if(GUIObjectRoot->childControls[i]->name=="LvlpackCongratulation"){
+ levels.congratulationText=GUIObjectRoot->childControls[i]->caption;
+ }
+ }
+
+ //Save the configuration.
+ levels.saveLevels(getUserPath()+"custom/levelpacks/"+packName+"/levels.lst");
+
+ //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];
+ }
+
+ //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{
+ if(!createFile((levelpackLocations[packName]+"/"+GUIObjectRoot->childControls[i]->caption).c_str())){
+ cerr<<"ERROR: Unable to create level file "<<(levelpackLocations[packName]+"/"+GUIObjectRoot->childControls[i]->caption)<<endl;
+ }
+ levels.addLevel(levelpackLocations[packName]+"/"+GUIObjectRoot->childControls[i]->caption);
+ levels.saveLevels(getUserPath()+"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.
+ levels.saveLevels(getUserPath()+"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/LevelEditSelect.h b/src/LevelEditSelect.h
new file mode 100644
index 0000000..b60eb12
--- /dev/null
+++ b/src/LevelEditSelect.h
@@ -0,0 +1,88 @@
+/****************************************************************************
+** 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 LEVELEDITSELECT_H
+#define LEVELEDITSELECT_H
+
+#include "LevelSelect.h"
+#include "GameState.h"
+#include "GameObjects.h"
+#include "Player.h"
+#include "GUIObject.h"
+#include <SDL/SDL.h>
+#include <SDL/SDL_mixer.h>
+#include <SDL/SDL_ttf.h>
+#include <vector>
+#include <string>
+
+//This is the LevelEditSelect state, here you can select levelpacks and levels.
+class LevelEditSelect :public LevelSelect{
+private:
+ //Pointer to the new levelpack textfield.
+ GUIObject* levelpackName;
+
+ //Pointer to the remove levelpack button.
+ GUIObject* propertiesPack;
+ //Pointer to the remove levelpack button.
+ GUIObject* removePack;
+
+ //Pointer to the move map button.
+ GUIObject* move;
+ //Pointer to the remove map button.
+ GUIObject* remove;
+ //Pointer to the edit map button.
+ GUIObject* edit;
+
+ //String that contains the name of the levelpack.
+ std::string packName;
+
+ //Method that will list the levelpacks and change the listbox field.
+ void listPacks();
+
+ //Method that should be called when changing the current levelpack in an abnormal way.
+ void changePack();
+
+ //This method will show a popup with levelpack specific settings.
+ void packProperties();
+
+ //This method will show an add level dialog.
+ void addLevel();
+
+ //This method will show an move level dialog.
+ void moveLevel();
+public:
+ //Constructor.
+ LevelEditSelect();
+ //Destructor.
+ ~LevelEditSelect();
+
+ //Inherited from LevelSelect.
+ void refresh();
+ void selectNumber(int number,bool selected);
+
+ //Inherited from GameState.
+ void render();
+
+ //Inherited from LevelSelect.
+ void renderTooltip(int number,int dy);
+
+ //GUI events will be handled here.
+ void GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType);
+};
+
+#endif
diff --git a/src/LevelEditor.cpp b/src/LevelEditor.cpp
index ff0fe18..98f0569 100644
--- a/src/LevelEditor.cpp
+++ b/src/LevelEditor.cpp
@@ -1,2904 +1,2570 @@
/****************************************************************************
** 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 "GameState.h"
#include "Globals.h"
#include "Functions.h"
#include "FileManager.h"
#include "GameObjects.h"
#include "ThemeManager.h"
#include "Objects.h"
#include "Levels.h"
#include "LevelEditor.h"
#include "TreeStorageNode.h"
#include "POASerializer.h"
#include "GUIListBox.h"
#include "GUITextArea.h"
#include "InputManager.h"
#include <fstream>
#include <iostream>
#include <vector>
#include <algorithm>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#ifdef WIN32
#include <windows.h>
#include <shlobj.h>
#else
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>
#endif
using namespace std;
static int levelTime,levelRecordings;
static GUIObject *levelTimeProperty,*levelRecordingsProperty;
-////////////////LEVEL PACK EDITOR////////////////////
-class LevelPackEditor:public GUIEventCallback{
-private:
- //The fileName of the levelpack file.
- string fileName;
- //Textbox for the description of the levelpack.
- GUIObject* txtLvPackName;
- //Listbox containing the levels.
- GUIListBox* lstLvPack;
- //The levelpack.
- Levels objLvPack;
-
- //Pointer to the textfield of the congratulationText configure popup.
- GUIObject* congratulationTextBox;
-private:
- void updateListBox(){
- //First clear the list.
- lstLvPack->item.clear();
-
- //Now loop the levels
- for(int i=0;i<objLvPack.getLevelCount();i++){
- char s[32];
- sprintf(s,"%d.",i+1);
- lstLvPack->item.push_back(s+objLvPack.getLevelName(i)+"("+objLvPack.getLevelFile(i)+")");
- }
- }
-
- void addLevel(const string& s){
- //Prepare to load the level.
- TreeStorageNode obj;
- POASerializer objSerializer;
-
- //Parse the level file.
- if(objSerializer.loadNodeFromFile(processFileName(s).c_str(),&obj,true)){
- //Get the name of
- string name;
- vector<string>& v=obj.attributes["name"];
-
- //Make sure that there's a name.
- if(v.size()>0)
- name=v[0];
-
- //And add the level to the levelpack.
- objLvPack.addLevel(s,lstLvPack->value);
- //Now update the list.
- updateListBox();
- }
- }
-
- void updateLevel(int lvl){
- TreeStorageNode obj;
- POASerializer objSerializer;
- if(objSerializer.loadNodeFromFile(processFileName(objLvPack.getLevelFile(lvl)).c_str(),&obj,true)){
- string name;
- vector<string>& v=obj.attributes["name"];
- if(v.size()>0) name=v[0];
- if(!name.empty()) objLvPack.setLevelName(lvl,name);
- }
- }
-
- void congratulationText(){
- //Pointer to the current GUIObjectRoot.
- //We keep it so we can put it back after closing the fileDialog.
- GUIObject* tmp=GUIObjectRoot;
-
- GUIObjectRoot=new GUIObject(100,(SCREEN_HEIGHT-200)/2,600,200,GUIObjectFrame,"Congratulations");
- GUIObject* obj;
-
- //NOTE: We reuse the objectProperty and secondProperty.
- obj=new GUIObject(40,40,240,36,GUIObjectLabel,"Text");
- GUIObjectRoot->childControls.push_back(obj);
- obj=new GUIObject(140,40,350,36,GUIObjectTextBox,objLvPack.congratulationText.c_str());
- congratulationTextBox=obj;
- GUIObjectRoot->childControls.push_back(obj);
-
- //Ok and cancel buttons.
- obj=new GUIObject(100,200-44,150,36,GUIObjectButton,"OK");
- obj->name="cmdCongratOK";
- obj->eventCallback=this;
- GUIObjectRoot->childControls.push_back(obj);
- obj=new GUIObject(350,200-44,150,36,GUIObjectButton,"Cancel");
- obj->name="cmdCongratCancel";
- obj->eventCallback=this;
- GUIObjectRoot->childControls.push_back(obj);
-
- //Let the currentState render once to prevent multiple GUI overlapping and prevent the screen from going black.
- currentState->render();
-
- //Now we keep rendering and updating the GUI.
- SDL_FillRect(tempSurface,NULL,0);
- SDL_SetAlpha(tempSurface,SDL_SRCALPHA,155);
- SDL_BlitSurface(tempSurface,NULL,screen,NULL);
- while(GUIObjectRoot){
- while(SDL_PollEvent(&event))
- GUIObjectHandleEvents(true);
- if(GUIObjectRoot)
- GUIObjectRoot->render();
- SDL_Flip(screen);
- SDL_Delay(30);
- }
-
- //Now set back the old GUI.
- GUIObjectRoot=tmp;
- }
-public:
- //Constructor.
- LevelPackEditor(){}
-
- void show(){
- GUIObject* obj;
- GUIObject* tmp=GUIObjectRoot;
-
- //===
- GUIObjectRoot=new GUIObject(50,50,700,500,GUIObjectFrame,"Level Pack Editor");
- GUIObjectRoot->childControls.push_back(new GUIObject(8,20,184,36,GUIObjectLabel,"Level Pack Name"));
- txtLvPackName=new GUIObject(200,20,492,36,GUIObjectTextBox,"Untitled Level Pack");
- GUIObjectRoot->childControls.push_back(txtLvPackName);
-
- //The add level button.
- obj=new GUIObject(8,60,192,36,GUIObjectButton,"Add Level");
- obj->name="cmdAdd";
- obj->eventCallback=this;
- GUIObjectRoot->childControls.push_back(obj);
- //The remove level button.
- obj=new GUIObject(208,60,192,36,GUIObjectButton,"Remove Level");
- obj->name="cmdRemove";
- obj->eventCallback=this;
- GUIObjectRoot->childControls.push_back(obj);
- //The congratulation text.
- obj=new GUIObject(408,60,240,36,GUIObjectButton,"Congratulations Text");
- obj->name="cmdCongratulations";
- obj->eventCallback=this;
- GUIObjectRoot->childControls.push_back(obj);
- //The move up button.
- obj=new GUIObject(8,100,192,36,GUIObjectButton,"Move Up");
- obj->name="cmdMoveUp";
- obj->eventCallback=this;
- GUIObjectRoot->childControls.push_back(obj);
- //The move down button.
- obj=new GUIObject(208,100,192,36,GUIObjectButton,"Move Down");
- obj->name="cmdMoveDown";
- obj->eventCallback=this;
- GUIObjectRoot->childControls.push_back(obj);
- //The update level names button.
- obj=new GUIObject(408,100,240,36,GUIObjectButton,"Update Level Names");
- obj->name="cmdUpdate";
- obj->eventCallback=this;
- GUIObjectRoot->childControls.push_back(obj);
-
- //The levelpack list.
- lstLvPack=new GUIListBox(8,140,684,316);
- lstLvPack->name="lstLvPack";
- lstLvPack->eventCallback=this;
- GUIObjectRoot->childControls.push_back(lstLvPack);
-
- //The load levelpack button.
- obj=new GUIObject(8,460,192,36,GUIObjectButton,"Load Level Pack");
- obj->name="cmdLoad";
- obj->eventCallback=this;
- GUIObjectRoot->childControls.push_back(obj);
- //The save levelpack button.
- obj=new GUIObject(208,460,192,36,GUIObjectButton,"Save Level Pack");
- obj->name="cmdSave";
- obj->eventCallback=this;
- GUIObjectRoot->childControls.push_back(obj);
-
- //The exit button.
- obj=new GUIObject(564,460,128,36,GUIObjectButton,"Exit");
- obj->name="cmdExit";
- obj->eventCallback=this;
- GUIObjectRoot->childControls.push_back(obj);
-
- //GUI has been created.
- //Now dim the screen and keep rendering/updating the gui.
- SDL_FillRect(tempSurface,NULL,0);
- SDL_SetAlpha(tempSurface,SDL_SRCALPHA,155);
- SDL_BlitSurface(tempSurface,NULL,screen,NULL);
- while(GUIObjectRoot){
- while(SDL_PollEvent(&event))
- GUIObjectHandleEvents(true);
- if(GUIObjectRoot)
- GUIObjectRoot->render();
- SDL_Flip(screen);
- SDL_Delay(30);
- }
- //Set the old GUI back.
- GUIObjectRoot=tmp;
-
- //Done.
- return;
- }
-
- void GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType){
- if(name=="cmdExit"){
- //Delete the GUI.
- if(GUIObjectRoot){
- delete GUIObjectRoot;
- GUIObjectRoot=NULL;
- }
- }else if(name=="cmdLoad"){
- //Let the currentState render once to prevent multiple GUI overlapping and prevent the screen from going black.
- currentState->render();
-
- //Show the fileDialog.
- string s=fileName;
- if(fileDialog(s,"Load Level Pack","","%USER%/custom/levelpacks/\nMy levelpacks\n%USER%/levelpacks/\nAddon levelpacks\n%DATA%/levelpacks/\nMain levelpacks",false,true,false)){
- if(!objLvPack.loadLevels(processFileName(s+"/levels.lst"))){
- msgBox("Can't load level pack:\n"+s,MsgBoxOKOnly,"Error");
- s="";
- }
- txtLvPackName->caption=objLvPack.levelpackDescription;
- lstLvPack->value=-1;
- updateListBox();
- fileName=s;
- }
- }else if(name=="cmdSave"){
- //Let the currentState render once to prevent multiple GUI overlapping and prevent the screen from going black.
- currentState->render();
-
- //Show the fileDialog.
- string s=fileName;
- if(fileDialog(s,"Save Level Pack","","%USER%/custom/levelpacks/",true,true,false)){
- objLvPack.levelpackDescription=txtLvPackName->caption;
- createDirectory(processFileName(s).c_str());
-
- objLvPack.saveLevels(s+"/levels.lst");
- fileName=s+"/levels.lst";
- }
- }else if(name=="cmdAdd"){
- //Let the currentState render once to prevent multiple GUI overlapping and prevent the screen from going black.
- currentState->render();
-
- //Show the fileDialog.
- string s;
- if(fileDialog(s,"Load Level","map","%USER%/custom/levels/\nMy levels\n%USER%/levels/\nAddon levels\n%DATA%/levels/\nMain levels",false,true))
- addLevel(s);
- }else if(name=="cmdCongratulations"){
- //Show the congratulationText edit popup.
- congratulationText();
- }else if(name=="cmdMoveUp"){
- //Get the current location.
- int i=lstLvPack->value;
-
- //Check if it can move up.
- if(i>0&&i<objLvPack.getLevelCount()){
- //Swap the two levels.
- objLvPack.swapLevel(i,i-1);
- //Change the selected item to the correct one.
- lstLvPack->value=i-1;
- //Update the list.
- updateListBox();
- }
- }else if(name=="cmdMoveDown"){
- //Get the current location.
- int i=lstLvPack->value;
-
- //Check if it can move up.
- if(i>=0&&i<objLvPack.getLevelCount()-1){
- //Swap the two levels.
- objLvPack.swapLevel(i,i+1);
- //Change the selected item to the correct one.
- lstLvPack->value=i+1;
- //Update the list.
- updateListBox();
- }
- }else if(name=="cmdRemove"){
- //Get the current location.
- int i=lstLvPack->value;
-
- //Check if it exists.
- if(i>=0&&i<objLvPack.getLevelCount()){
- //Remove it and update the list.
- objLvPack.removeLevel(i);
- updateListBox();
- }
- }else if(name=="cmdUpdate"){
- //Loop through the levels and update them.
- for(int i=0;i<objLvPack.getLevelCount();i++)
- updateLevel(i);
- //Let the currentState render once to prevent multiple GUI overlapping and prevent the screen from going black.
- currentState->render();
-
- //Show the user that it has been done.
- msgBox("OK!",MsgBoxOKOnly,"");
- //Update the list.
- updateListBox();
- }else if(name=="cmdCongratOK"){
- //Congratulation text configure menu, ok button.
- //Set the text.
- objLvPack.congratulationText=congratulationTextBox->caption;
- congratulationTextBox=NULL;
-
- //And delete the GUI.
- if(GUIObjectRoot){
- delete GUIObjectRoot;
- GUIObjectRoot=NULL;
- }
- }else if(name=="cmdCongratCancel"){
- //Delete the GUI.
- if(GUIObjectRoot){
- delete GUIObjectRoot;
- GUIObjectRoot=NULL;
- }
- }
-
- }
-};
-
/////////////////MovingPosition////////////////////////////
MovingPosition::MovingPosition(int x,int y,int time){
this->x=x;
this->y=y;
this->time=time;
}
MovingPosition::~MovingPosition(){}
void MovingPosition::updatePosition(int x,int y){
this->x=x;
this->y=y;
}
/////////////////LEVEL EDITOR//////////////////////////////
-LevelEditor::LevelEditor():Game(false){
+LevelEditor::LevelEditor():Game(true){
LEVEL_WIDTH=800;
LEVEL_HEIGHT=600;
levelTime=-1;
levelRecordings=-1;
- //Load an empty level.
- loadLevel(getDataPath()+"misc/Empty.map");
-
//This will set some default settings.
reset();
//Load the toolbar.
toolbar=loadImage(getDataPath()+"gfx/menu/toolbar.png");
SDL_Rect tmp={155,555,510,50};
toolbarRect=tmp;
//Load the selectionMark.
selectionMark=loadImage(getDataPath()+"gfx/menu/selection.png");
//Load the movingMark.
movingMark=loadImage(getDataPath()+"gfx/menu/moving.png");
//Create the semi transparent surface.
placement=SDL_CreateRGBSurface(SDL_SWSURFACE|SDL_SRCALPHA,800,600,32,0x000000FF,0x0000FF00,0x00FF0000,0);
SDL_SetColorKey(placement,SDL_SRCCOLORKEY|SDL_RLEACCEL,SDL_MapRGB(placement->format,255,0,255));
SDL_SetAlpha(placement,SDL_SRCALPHA,125);
}
LevelEditor::~LevelEditor(){
//Loop through the levelObjects and delete them.
for(unsigned int i=0;i<levelObjects.size();i++)
delete levelObjects[i];
levelObjects.clear();
selection.clear();
//Free the placement surface.
SDL_FreeSurface(placement);
//Reset the camera.
camera.x=0;
camera.y=0;
}
void LevelEditor::reset(){
//Set some default values.
playMode=false;
tool=ADD;
currentType=0;
pressedShift=false;
dragging=false;
selectionDrag=false;
dragCenter=NULL;
camera.x=0;
camera.y=0;
cameraXvel=0;
cameraYvel=0;
objectProperty=NULL;
secondObjectProperty=NULL;
configuredObject=NULL;
linking=false;
linkingTrigger=NULL;
currentId=0;
movingBlock=NULL;
moving=false;
movingSpeed=10;
- levelName="";
- levelFile="";
- levelTheme="";
- levelTime=-1;
- levelRecordings=-1;
tooltip=-1;
//Set the player and shadow in the top left corner.
player.setPosition(0,0);
shadow.setPosition(0,0);
selection.clear();
clipboard.clear();
triggers.clear();
movingBlocks.clear();
}
void LevelEditor::loadLevelFromNode(TreeStorageNode* obj, const std::string& fileName){
//call the method of base class.
Game::loadLevelFromNode(obj,fileName);
//now do our own stuff.
string s=editorData["time"];
if(s.empty() || !(s[0]>='0' && s[0]<='9')){
levelTime=-1;
}else{
levelTime=atoi(s.c_str());
}
s=editorData["recordings"];
if(s.empty() || !(s[0]>='0' && s[0]<='9')){
levelRecordings=-1;
}else{
levelRecordings=atoi(s.c_str());
}
}
void LevelEditor::saveLevel(string fileName){
//Create the output stream and check if it starts.
std::ofstream save(fileName.c_str());
if(!save) return;
//The dimensions of the level.
int maxX=0;
int maxY=0;
//The storageNode to put the level data in before writing it away.
TreeStorageNode node;
char s[64];
//The name of the level.
if(!levelName.empty())
node.attributes["name"].push_back(levelName);
//The leveltheme.
if(!levelTheme.empty())
node.attributes["theme"].push_back(levelTheme);
//target time and recordings.
{
char c[32];
if(levelTime>=0){
sprintf(c,"%d",levelTime);
node.attributes["time"].push_back(c);
}
if(levelRecordings>=0){
sprintf(c,"%d",levelRecordings);
node.attributes["recordings"].push_back(c);
}
}
//The width of the level.
maxX=LEVEL_WIDTH;
sprintf(s,"%d",maxX);
node.attributes["size"].push_back(s);
//The height of the level.
maxY=LEVEL_HEIGHT;
sprintf(s,"%d",maxY);
node.attributes["size"].push_back(s);
//Loop through the gameObjects and save them.
for(int o=0;o<(signed)levelObjects.size();o++){
int objectType=levelObjects[o]->type;
//Check if it's a legal gameObject type.
if(objectType>=0 && objectType<TYPE_MAX){
TreeStorageNode* obj1=new TreeStorageNode;
node.subNodes.push_back(obj1);
//It's a tile so name the node tile.
obj1->name="tile";
//Write away the type of the gameObject.
sprintf(s,"%d",objectType);
obj1->value.push_back(blockName[objectType]);
//Get the box for the location of the gameObject.
SDL_Rect box=levelObjects[o]->getBox(BoxType_Base);
//Put the location in the storageNode.
sprintf(s,"%d",box.x);
obj1->value.push_back(s);
sprintf(s,"%d",box.y);
obj1->value.push_back(s);
//Loop through the editor data and save it also.
vector<pair<string,string> > obj;
levelObjects[o]->getEditorData(obj);
for(unsigned int i=0;i<obj.size();i++){
if((!obj[i].first.empty()) && (!obj[i].second.empty())){
obj1->attributes[obj[i].first].push_back(obj[i].second);
}
}
}
}
//Create a POASerializer and write away the level node.
POASerializer objSerializer;
objSerializer.writeNode(&node,save,true,true);
}
///////////////EVENT///////////////////
void LevelEditor::handleEvents(){
//Check if we need to quit, if so we enter the exit state.
if(event.type==SDL_QUIT){
setNextState(STATE_EXIT);
}
//If playing/testing we should the game handle the events.
if(playMode){
Game::handleEvents();
//Also check if we should exit the playMode.
if(inputMgr.isKeyDownEvent(INPUTMGR_ESCAPE)){
//Reset the game and disable playMode.
Game::reset(true);
playMode=false;
camera.x=cameraSave.x;
camera.y=cameraSave.y;
}
}else{
//Also check if we should exit the editor.
if(inputMgr.isKeyDownEvent(INPUTMGR_ESCAPE)){
//Before we quit ask a make sure question.
if(msgBox("Are you sure you want to quit?",MsgBoxYesNo,"Quit prompt")==MsgBoxYes){
//We exit the level editor.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
- setNextState(STATE_MENU);
+ setNextState(STATE_LEVEL_EDIT_SELECT);
//Play the menu music again.
getMusicManager()->playMusic("menu");
}
}
//Also check if we should exit the editor.
if(inputMgr.isKeyDownEvent(INPUTMGR_SHIFT)){
pressedShift=true;
}
if(inputMgr.isKeyUpEvent(INPUTMGR_SHIFT)){
pressedShift=false;
}
//Check if delete is pressed.
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_DELETE){
if(!selection.empty()){
//Loop through the selected game objects.
while(!selection.empty()){
//Remove the objects in the selection.
removeObject(selection[0]);
}
//And clear the selection vector.
selection.clear();
dragCenter=NULL;
selectionDrag=false;
}
}
//Check for copy (Ctrl+c) or cut (Ctrl+x).
if(event.type==SDL_KEYDOWN && (event.key.keysym.sym==SDLK_c || event.key.keysym.sym==SDLK_x) && (event.key.keysym.mod & KMOD_CTRL)){
//Clear the current clipboard.
clipboard.clear();
//Check if the selection isn't empty.
if(!selection.empty()){
//Loop through the selection to find the left-top block.
int x=selection[0]->getBox().x;
int y=selection[0]->getBox().y;
for(unsigned int o=1; o<selection.size(); o++){
if(selection[o]->getBox().x<x || selection[o]->getBox().y<y){
x=selection[o]->getBox().x;
y=selection[o]->getBox().y;
}
}
//Loop through the selection for the actual copying.
for(unsigned int o=0; o<selection.size(); o++){
//Get the editor data of the object.
vector<pair<string,string> > obj;
selection[o]->getEditorData(obj);
//Loop through the editor data and convert it.
map<string,string> objMap;
for(unsigned int i=0;i<obj.size();i++){
objMap[obj[i].first]=obj[i].second;
}
//Add some entries to the map.
char s[64];
sprintf(s,"%d",selection[o]->getBox().x-x);
objMap["x"]=s;
sprintf(s,"%d",selection[o]->getBox().y-y);
objMap["y"]=s;
sprintf(s,"%d",selection[o]->type);
objMap["type"]=s;
//Overwrite the id to prevent triggers, portals, buttons, movingblocks, etc. from malfunctioning.
//We give an empty string as id, which is invalid and thus suitable.
objMap["id"]="";
//Do the same for destination if the type is portal.
if(selection[o]->type==TYPE_PORTAL){
objMap["destination"]="";
}
//And add the map to the clipboard vector.
clipboard.push_back(objMap);
if(event.key.keysym.sym==SDLK_x){
//Cutting means deleting the game object.
removeObject(selection[o]);
o--;
}
}
//Only clear the selection when Ctrl+x;
if(event.key.keysym.sym==SDLK_x){
selection.clear();
dragCenter=NULL;
selectionDrag=false;
}
}
}
//Check for paste (Ctrl+v).
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_v && (event.key.keysym.mod & KMOD_CTRL)){
//First make sure that the clipboard isn't empty.
if(!clipboard.empty()){
//Clear the current selection.
selection.clear();
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
x+=camera.x;
y+=camera.y;
//Apply snap to grid.
if(!pressedShift){
snapToGrid(&x,&y);
}else{
x-=25;
y-=25;
}
//Integers containing the diff of the x that occurs when placing a block outside the level size on the top or left.
//We use it to compensate the corrupted x and y locations of the other clipboard blocks.
int diffX=0;
int diffY=0;
//Loop through the clipboard.
for(unsigned int o=0;o<clipboard.size();o++){
Block* block=new Block(0,0,atoi(clipboard[o]["type"].c_str()),this);
block->setPosition(atoi(clipboard[o]["x"].c_str())+x+diffX,atoi(clipboard[o]["y"].c_str())+y+diffY);
block->setEditorData(clipboard[o]);
if(block->getBox().x<0){
//A block on the left side of the level, meaning we need to shift everything.
//First calc the difference.
diffX+=(0-(block->getBox().x));
}
if(block->getBox().y<0){
//A block on the left side of the level, meaning we need to shift everything.
//First calc the difference.
diffY+=(0-(block->getBox().y));
}
//And add the object using the addObject method.
addObject(block);
//Also add the block to the selection.
selection.push_back(block);
}
}
}
//Check if the return button is pressed.
//If so run the configure tool.
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_RETURN){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
SDL_Rect mouse={x+camera.x,y+camera.y,0,0};
//Loop through the selected game objects.
for(unsigned int o=0; o<levelObjects.size(); o++){
//Check for collision.
if(checkCollision(mouse,levelObjects[o]->getBox())){
tool=CONFIGURE;
//Invoke the onEnterObject.
onEnterObject(levelObjects[o]);
//Break out of the for loop.
break;
}
}
}
//Check for the arrow keys, used for moving the camera when playMode=false.
cameraXvel=0;
cameraYvel=0;
if(inputMgr.isKeyDown(INPUTMGR_RIGHT)){
if(pressedShift){
cameraXvel+=10;
}else{
cameraXvel+=5;
}
}
if(inputMgr.isKeyDown(INPUTMGR_LEFT)){
if(pressedShift){
cameraXvel-=10;
}else{
cameraXvel-=5;
}
}
if(inputMgr.isKeyDown(INPUTMGR_UP)){
if(pressedShift){
cameraYvel-=10;
}else{
cameraYvel-=5;
}
}
if(inputMgr.isKeyDown(INPUTMGR_DOWN)){
if(pressedShift){
cameraYvel+=10;
}else{
cameraYvel+=5;
}
}
//Check if the left mouse button is pressed/holded.
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_LEFT){
pressedLeftMouse=true;
}
if(event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT){
pressedLeftMouse=false;
//We also need to check if dragging is true.
if(dragging){
//Set dragging false and call the onDrop event.
dragging=false;
int x,y;
SDL_GetMouseState(&x,&y);
//We call the drop event.
onDrop(x+camera.x,y+camera.y);
}
}
//Check if the mouse is dragging.
if(pressedLeftMouse && event.type==SDL_MOUSEMOTION){
if(abs(event.motion.xrel)+abs(event.motion.yrel)>=2){
//Check if this is the start of the dragging.
if(!dragging){
//The mouse is moved enough so let's set dragging true.
dragging=true;
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//We call the dragStart event.
onDragStart(x+camera.x,y+camera.y);
}else{
//Dragging was already true meaning we call onDrag() instead of onDragStart().
onDrag(event.motion.xrel,event.motion.yrel);
}
}
}
//Check if we scroll up, meaning the currentType++;
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_WHEELUP){
//Only change the current type when using the add tool.
if(tool==ADD){
currentType++;
if(currentType>=EDITOR_ORDER_MAX){
currentType=0;
}
}
//When in configure mode.
if(tool==CONFIGURE){
movingSpeed++;
//The movingspeed is capped at 100.
if(movingSpeed>100){
movingSpeed=100;
}
}
}
//Check if we scroll down, meaning the currentType--;
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_WHEELDOWN){
//Only change the current type when using the add tool.
if(tool==ADD){
currentType--;
if(currentType<0){
currentType=EDITOR_ORDER_MAX-1;
}
}
//When in configure mode.
if(tool==CONFIGURE){
movingSpeed--;
if(movingSpeed<=0){
movingSpeed=1;
}
}
}
//Check if we should enter playMode.
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_p){
playMode=true;
cameraSave.x=camera.x;
cameraSave.y=camera.y;
}
//Check for tool shortcuts.
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_a){
tool=ADD;
}
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_s){
tool=SELECT;
}
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_d){
//We clear the selection since that can't be used in the deletion tool.
selection.clear();
tool=REMOVE;
}
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_w){
tool=CONFIGURE;
}
//Check for certain events.
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
SDL_Rect mouse={x,y,0,0};
//First make sure the mouse isn't above the toolbar.
if(checkCollision(mouse,toolbarRect)==false){
//We didn't hit the toolbar so convert the mouse location to ingame location.
mouse.x+=camera.x;
mouse.y+=camera.y;
//Boolean if there's a click event fired.
bool clickEvent=false;
//Check if a mouse button is pressed.
if(event.type==SDL_MOUSEBUTTONDOWN){
//Loop through the objects to check collision.
for(unsigned int o=0; o<levelObjects.size(); o++){
if(checkCollision(levelObjects[o]->getBox(),mouse)==true){
//We have collision meaning that the mouse is above an object.
std::vector<GameObject*>::iterator it;
it=find(selection.begin(),selection.end(),levelObjects[o]);
//Set event true since there's a click event.
clickEvent=true;
//Check if the clicked object is in the selection or not.
bool isSelected=(it!=selection.end());
if(event.button.button==SDL_BUTTON_LEFT){
onClickObject(levelObjects[o],isSelected);
}else if(event.button.button==SDL_BUTTON_RIGHT){
onRightClickObject(levelObjects[o],isSelected);
}
}
}
}
//If event is false then we clicked on void.
if(!clickEvent){
if(event.type==SDL_MOUSEBUTTONDOWN){
if(event.button.button==SDL_BUTTON_LEFT){
//Left mouse button on void.
onClickVoid(mouse.x,mouse.y);
}else if(event.button.button==SDL_BUTTON_RIGHT && tool==CONFIGURE){
//Stop linking.
linking=false;
linkingTrigger=NULL;
//Write the path to the moving block.
if(moving){
std::map<std::string,std::string> editorData;
char s[64], s0[64];
sprintf(s,"%d",movingBlocks[movingBlock].size());
editorData["MovingPosCount"]=s;
//Loop through the positions.
for(unsigned int o=0;o<movingBlocks[movingBlock].size();o++){
sprintf(s0+1,"%d",o);
sprintf(s,"%d",movingBlocks[movingBlock][o].x);
s0[0]='x';
editorData[s0]=s;
sprintf(s,"%d",movingBlocks[movingBlock][o].y);
s0[0]='y';
editorData[s0]=s;
sprintf(s,"%d",movingBlocks[movingBlock][o].time);
s0[0]='t';
editorData[s0]=s;
}
movingBlock->setEditorData(editorData);
//Stop moving.
moving=false;
movingBlock=NULL;
}
}
}
}
}
//Check for backspace when moving to remove a movingposition.
if(moving && event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_BACKSPACE){
if(movingBlocks[movingBlock].size()>0){
movingBlocks[movingBlock].pop_back();
}
}
//Check for the tab key, level settings.
if(inputMgr.isKeyDownEvent(INPUTMGR_TAB)){
//Show the levelSettings.
levelSettings();
}
//Check if we should a new level. (Ctrl+n)
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_n && (event.key.keysym.mod & KMOD_CTRL)){
reset();
loadLevel(getDataPath()+"misc/Empty.map");
}
//Check if we should load a level. (Ctrl+o)
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_o && (event.key.keysym.mod & KMOD_CTRL)){
string s="";
if(fileDialog(s,"Load Level","map","%USER%/custom/levels/\nMy levels\n%USER%/levels/\nAddon levels\n%DATA%/levels/\nMain levels",false,true)){
reset();
loadLevel(processFileName(s));
postLoad();
}
}
//Check if we should save the level (Ctrl+s) or save levelpack (Ctrl+Shift+s).
if(event.type==SDL_KEYDOWN && event.key.keysym.sym==SDLK_s && (event.key.keysym.mod & KMOD_CTRL)){
- //Check if shift was pressed or not.
- if(event.key.keysym.mod & KMOD_SHIFT){
- //Levelpack save.
- LevelPackEditor objEditor;
- objEditor.show();
- }else{
- //Normal save, open the the filedialog.
- string s=fileNameFromPath(levelFile);
- if(fileDialog(s,"Save Level","map","%USER%/custom/levels/",true,true)){
- saveLevel(processFileName(s));
- levelFile=processFileName(s);
- }
- }
+ saveLevel(levelFile);
}
}
}
void LevelEditor::levelSettings(){
//It isn't so open a popup asking for a name.
//First delete any existing gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
GUIObjectRoot=new GUIObject(100,(SCREEN_HEIGHT-300)/2,600,300,GUIObjectFrame,"Level settings");
GUIObject* obj;
//NOTE: We reuse the objectProperty and secondProperty.
obj=new GUIObject(40,40,240,36,GUIObjectLabel,"Name:");
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(140,40,350,36,GUIObjectTextBox,levelName.c_str());
objectProperty=obj;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(40,90,240,36,GUIObjectLabel,"Theme:");
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(140,90,350,36,GUIObjectTextBox,"");
secondObjectProperty=obj;
GUIObjectRoot->childControls.push_back(obj);
//target time and recordings.
{
char c[32];
if(levelTime>=0){
sprintf(c,"%-.2f",levelTime/40.0f);
}else{
c[0]='\0';
}
obj=new GUIObject(40,140,240,36,GUIObjectLabel,"Target time (s):");
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(290,140,200,36,GUIObjectTextBox,c);
levelTimeProperty=obj;
GUIObjectRoot->childControls.push_back(obj);
if(levelRecordings>=0){
sprintf(c,"%d",levelRecordings);
}else{
c[0]='\0';
}
obj=new GUIObject(40,190,240,36,GUIObjectLabel,"Target recordings:");
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(290,190,200,36,GUIObjectTextBox,c);
levelRecordingsProperty=obj;
GUIObjectRoot->childControls.push_back(obj);
}
//Ok and cancel buttons.
obj=new GUIObject(100,300-44,150,36,GUIObjectButton,"OK");
obj->name="lvlSettingsOK";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(350,300-44,150,36,GUIObjectButton,"Cancel");
obj->name="lvlSettingsCancel";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
//Now we keep rendering and updating the GUI.
SDL_FillRect(tempSurface,NULL,0);
SDL_SetAlpha(tempSurface,SDL_SRCALPHA,155);
SDL_BlitSurface(tempSurface,NULL,screen,NULL);
while(GUIObjectRoot){
while(SDL_PollEvent(&event))
GUIObjectHandleEvents(true);
if(GUIObjectRoot)
GUIObjectRoot->render();
SDL_Flip(screen);
SDL_Delay(30);
}
}
void LevelEditor::postLoad(){
//We need to find the triggers.
for(unsigned int o=0;o<levelObjects.size();o++){
//Get the editor data.
vector<pair<string,string> > objMap;
levelObjects[o]->getEditorData(objMap);
//Check for the highest id.
for(unsigned int i=0;i<objMap.size();i++){
if(objMap[i].first=="id"){
unsigned int id=atoi(objMap[i].second.c_str());
if(id>=currentId){
currentId=id+1;
}
}
}
switch(levelObjects[o]->type){
case TYPE_BUTTON:
case TYPE_SWITCH:
{
//Add the object to the triggers vector.
vector<GameObject*> linked;
triggers[levelObjects[o]]=linked;
//Now loop through the levelObjects in search for objects with the same id.
for(unsigned int oo=0;oo<levelObjects.size();oo++){
//Check if it isn't the same object but has the same id.
if(o!=oo && (dynamic_cast<Block*>(levelObjects[o]))->id==(dynamic_cast<Block*>(levelObjects[oo]))->id){
//Add the object to the link vector of the trigger.
triggers[levelObjects[o]].push_back(levelObjects[oo]);
}
}
break;
}
case TYPE_PORTAL:
{
//Add the object to the triggers vector.
vector<GameObject*> linked;
triggers[levelObjects[o]]=linked;
//If the destination is empty we return.
if((dynamic_cast<Block*>(levelObjects[o]))->destination.empty()){
return;
}
//Now loop through the levelObjects in search for objects with the same id as destination.
for(unsigned int oo=0;oo<levelObjects.size();oo++){
//Check if it isn't the same object but has the same id.
if(o!=oo && (dynamic_cast<Block*>(levelObjects[o]))->destination==(dynamic_cast<Block*>(levelObjects[oo]))->id){
//Add the object to the link vector of the trigger.
triggers[levelObjects[o]].push_back(levelObjects[oo]);
}
}
break;
}
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
{
//Add the object to the movingBlocks vector.
vector<MovingPosition> positions;
movingBlocks[levelObjects[o]]=positions;
//Get the number of entries of the editor data.
int m=objMap.size();
//Check if the editor data isn't empty.
if(m>0){
//Integer containing the positions.
int pos=0;
int currentPos=0;
//Get the number of movingpositions.
pos=atoi(objMap[1].second.c_str());
while(currentPos<pos){
int x=atoi(objMap[currentPos*3+4].second.c_str());
int y=atoi(objMap[currentPos*3+5].second.c_str());
int t=atoi(objMap[currentPos*3+6].second.c_str());
//Create a new movingPosition.
MovingPosition position(x,y,t);
movingBlocks[levelObjects[o]].push_back(position);
//Increase currentPos by one.
currentPos++;
}
}
break;
}
default:
break;
}
}
}
void LevelEditor::snapToGrid(int* x,int* y){
//Check if the x location is negative.
if(*x<0){
*x=-((abs(*x-50)/50)*50);
}else{
*x=(*x/50)*50;
}
//Now the y location.
if(*y<0){
*y=-((abs(*y-50)/50)*50);
}else{
*y=(*y/50)*50;
}
}
void LevelEditor::onClickObject(GameObject* obj,bool selected){
switch(tool){
//NOTE: We put CONFIGURE above ADD and SELECT to use the same method of selection.
//Meaning there's no break at the end of CONFIGURE.
case CONFIGURE:
{
//Check if we are linking.
if(linking){
//Check if the obj is valid to link to.
switch(obj->type){
case TYPE_CONVEYOR_BELT:
case TYPE_SHADOW_CONVEYOR_BELT:
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
{
//It's only valid when not linking a portal.
if(linkingTrigger->type==TYPE_PORTAL){
//You can't link a portal to moving blocks, etc.
//Stop linking and return.
linkingTrigger=NULL;
linking=false;
return;
}
break;
}
case TYPE_PORTAL:
{
//Make sure that the linkingTrigger is also a portal.
if(linkingTrigger->type!=TYPE_PORTAL){
//The linkingTrigger isn't a portal so stop linking and return.
linkingTrigger=NULL;
linking=false;
return;
}
break;
}
default:
//It isn't valid so stop linking and return.
linkingTrigger=NULL;
linking=false;
return;
break;
}
//Check if the linkingTrigger can handle multiple or only one link.
switch(linkingTrigger->type){
case TYPE_PORTAL:
{
//Portals can only link to one so remove all existing links.
triggers[linkingTrigger].clear();
triggers[linkingTrigger].push_back(obj);
break;
}
default:
{
//The most can handle multiple links.
triggers[linkingTrigger].push_back(obj);
break;
}
}
//Check if it's a portal.
if(linkingTrigger->type==TYPE_PORTAL){
//Portals need to get the id of the other instead of give it's own id.
vector<pair<string,string> > objMap;
obj->getEditorData(objMap);
int m=objMap.size();
if(m>0){
std::map<std::string,std::string> editorData;
char s[64];
sprintf(s,"%d",atoi(objMap[0].second.c_str()));
editorData["destination"]=s;
linkingTrigger->setEditorData(editorData);
}
}else{
//Give the object the same id as the trigger.
vector<pair<string,string> > objMap;
linkingTrigger->getEditorData(objMap);
int m=objMap.size();
if(m>0){
std::map<std::string,std::string> editorData;
char s[64];
sprintf(s,"%d",atoi(objMap[0].second.c_str()));
editorData["id"]=s;
obj->setEditorData(editorData);
}
}
//We return to prevent configuring stuff like conveyor belts, etc...
linking=false;
linkingTrigger=NULL;
return;
}
//If we're moving add a movingposition.
if(moving){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
x+=camera.x;
y+=camera.y;
//Apply snap to grid.
if(!pressedShift){
snapToGrid(&x,&y);
}else{
x-=25;
y-=25;
}
x-=movingBlock->getBox().x;
y-=movingBlock->getBox().y;
//Calculate the length.
//First get the delta x and y.
int dx,dy;
if(movingBlocks[movingBlock].empty()){
dx=x;
dy=y;
}else{
dx=x-movingBlocks[movingBlock].back().x;
dy=y-movingBlocks[movingBlock].back().y;
}
double length=sqrt(double(dx*dx+dy*dy));
movingBlocks[movingBlock].push_back(MovingPosition(x,y,(int)(length*(10/(double)movingSpeed))));
}
}
case SELECT:
case ADD:
{
//Check if object is already selected.
if(!selected){
//First check if shift is pressed or not.
if(!pressedShift){
//Clear the selection.
selection.clear();
}
//Add the object to the selection.
selection.push_back(obj);
}
break;
}
case REMOVE:
{
//Remove the object.
removeObject(obj);
break;
}
default:
break;
}
}
void LevelEditor::onRightClickObject(GameObject* obj,bool selected){
switch(tool){
case CONFIGURE:
{
//Make sure we aren't doing anything special.
if(moving || linking)
break;
//Check if it's a trigger.
if(obj->type==TYPE_PORTAL || obj->type==TYPE_BUTTON || obj->type==TYPE_SWITCH){
//Set linking true.
linking=true;
linkingTrigger=obj;
}
//Check if it's a moving block.
if(obj->type==TYPE_MOVING_BLOCK || obj->type==TYPE_MOVING_SHADOW_BLOCK || obj->type==TYPE_MOVING_SPIKES){
//Set moving true.
moving=true;
movingBlock=obj;
}
break;
}
case SELECT:
case ADD:
{
//We deselect the object if it's selected.
if(selected){
std::vector<GameObject*>::iterator it;
it=find(selection.begin(),selection.end(),obj);
//Remove the object from selection.
if(it!=selection.end()){
selection.erase(it);
}
}else{
//It wasn't a selected object so switch to configure mode.
//Check if it's the right type of object.
if(obj->type==TYPE_MOVING_BLOCK || obj->type==TYPE_MOVING_SHADOW_BLOCK || obj->type==TYPE_MOVING_SPIKES ||
obj->type==TYPE_PORTAL || obj->type==TYPE_BUTTON || obj->type==TYPE_SWITCH){
tool=CONFIGURE;
onRightClickObject(obj,selected);
}
}
break;
}
default:
break;
}
}
void LevelEditor::onClickVoid(int x,int y){
switch(tool){
case SELECT:
{
//We need to clear the selection.
selection.clear();
break;
}
case ADD:
{
//We need to clear the selection.
selection.clear();
//Now place an object.
//Apply snap to grid.
if(!pressedShift){
snapToGrid(&x,&y);
}else{
x-=25;
y-=25;
}
addObject(new Block(x,y,editorTileOrder[currentType],this));
break;
}
case CONFIGURE:
{
//We need to clear the selection.
selection.clear();
//If we're linking we should stop, user abort.
if(linking){
linking=false;
linkingTrigger=NULL;
//And return.
return;
}
//If we're moving we should add a point.
if(moving){
//Apply snap to grid.
if(!pressedShift){
snapToGrid(&x,&y);
}else{
x-=25;
y-=25;
}
x-=movingBlock->getBox().x;
y-=movingBlock->getBox().y;
//Calculate the length.
//First get the delta x and y.
int dx,dy;
if(movingBlocks[movingBlock].empty()){
dx=x;
dy=y;
}else{
dx=x-movingBlocks[movingBlock].back().x;
dy=y-movingBlocks[movingBlock].back().y;
}
double length=sqrt(double(dx*dx+dy*dy));
movingBlocks[movingBlock].push_back(MovingPosition(x,y,(int)(length*(10/(double)movingSpeed))));
//And return.
return;
}
break;
}
default:
break;
}
}
void LevelEditor::onDragStart(int x,int y){
switch(tool){
case SELECT:
case ADD:
case CONFIGURE:
{
//We can drag the selection so check if the selection isn't empty.
if(!selection.empty()){
//The selection isn't empty so search the dragCenter.
//Create a mouse rectangle.
SDL_Rect mouse={x,y,0,0};
//Loop through the objects to check collision.
for(unsigned int o=0; o<selection.size(); o++){
if(checkCollision(selection[o]->getBox(),mouse)==true){
//We have collision so set the dragCenter.
dragCenter=selection[o];
selectionDrag=true;
}
}
}
break;
}
default:
break;
}
}
void LevelEditor::onDrag(int dx,int dy){
switch(tool){
case REMOVE:
{
//No matter what we delete the item the mouse is above.
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
SDL_Rect mouse={x+camera.x,y+camera.y,0,0};
//Loop through the objects to check collision.
for(unsigned int o=0; o<levelObjects.size(); o++){
if(checkCollision(levelObjects[o]->getBox(),mouse)==true){
//Remove the object.
removeObject(levelObjects[o]);
}
}
break;
}
default:
break;
}
}
void LevelEditor::onDrop(int x,int y){
switch(tool){
case SELECT:
case ADD:
case CONFIGURE:
{
//Check if the drag center isn't null.
if(dragCenter==NULL) return;
//The location of the dragCenter.
SDL_Rect r=dragCenter->getBox();
//Apply snap to grid.
if(!pressedShift){
snapToGrid(&x,&y);
}else{
x-=25;
y-=25;
}
//Loop through the selection.
for(unsigned int o=0; o<selection.size(); o++){
SDL_Rect r1=selection[o]->getBox();
//We need to place the object at his drop place.
moveObject(selection[o],(r1.x-r.x)+x,(r1.y-r.y)+y);
}
//Make sure the dragCenter is null and set selectionDrag false.
dragCenter=NULL;
selectionDrag=false;
break;
}
default:
break;
}
}
void LevelEditor::onCameraMove(int dx,int dy){
switch(tool){
case REMOVE:
{
//Only delete when the left mouse button is pressed.
if(pressedLeftMouse){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
SDL_Rect mouse={x+camera.x,y+camera.y,0,0};
//Loop through the objects to check collision.
for(unsigned int o=0; o<levelObjects.size(); o++){
if(checkCollision(levelObjects[o]->getBox(),mouse)==true){
//Remove the object.
removeObject(levelObjects[o]);
}
}
}
break;
}
default:
break;
}
}
void LevelEditor::onEnterObject(GameObject* obj){
switch(tool){
case CONFIGURE:
{
//Check if the type is an moving block.
if(obj->type==TYPE_MOVING_BLOCK || obj->type==TYPE_MOVING_SHADOW_BLOCK || obj->type==TYPE_MOVING_SPIKES){
//Open a message popup.
//First delete any existing gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Get the properties.
vector<pair<string,string> > objMap;
obj->getEditorData(objMap);
int m=objMap.size();
if(m>0){
//Set the object we configure.
configuredObject=obj;
//Now create the GUI.
string s;
switch(obj->type){
case TYPE_MOVING_BLOCK:
s="Moving block";
break;
case TYPE_MOVING_SHADOW_BLOCK:
s="Moving shadow block";
break;
case TYPE_MOVING_SPIKES:
s="Moving spikes";
break;
}
GUIObjectRoot=new GUIObject(100,(SCREEN_HEIGHT-200)/2,600,200,GUIObjectFrame,s.c_str());
GUIObject* obj;
obj=new GUIObject(40,40,240,36,GUIObjectCheckBox,"Enabled",(objMap[2].second!="1"));
obj->name="cfgMovingBlockEnabled";
obj->eventCallback=this;
objectProperty=obj;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(300,40,240,36,GUIObjectCheckBox,"Loop",(objMap[3].second!="0"));
obj->name="cfgMovingBlockLoop";
obj->eventCallback=this;
secondObjectProperty=obj;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(40,80,160,36,GUIObjectButton,"Clear path");
obj->name="cfgMovingBlockClrPath";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(230,80,160,36,GUIObjectButton,"Make path");
obj->name="cfgMovingBlockMakePath";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(100,200-44,150,36,GUIObjectButton,"OK");
obj->name="cfgMovingBlockOK";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(350,200-44,150,36,GUIObjectButton,"Cancel");
obj->name="cfgCancel";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
//Dim the screen using the tempSurface.
SDL_FillRect(tempSurface,NULL,0);
SDL_SetAlpha(tempSurface,SDL_SRCALPHA,155);
SDL_BlitSurface(tempSurface,NULL,screen,NULL);
while(GUIObjectRoot){
while(SDL_PollEvent(&event)) GUIObjectHandleEvents(true);
if(GUIObjectRoot) GUIObjectRoot->render();
SDL_Flip(screen);
SDL_Delay(30);
}
}
}
//Check which type of object it is.
if(obj->type==TYPE_NOTIFICATION_BLOCK){
//Open a message popup.
//First delete any existing gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Get the properties.
vector<pair<string,string> > objMap;
obj->getEditorData(objMap);
int m=objMap.size();
if(m>0){
//Set the object we configure.
configuredObject=obj;
//Now create the GUI.
GUIObjectRoot=new GUIObject(100,(SCREEN_HEIGHT-250)/2,600,250,GUIObjectFrame,"Notification block");
GUIObject* obj;
obj=new GUIObject(40,40,240,36,GUIObjectLabel,"Enter message here:");
GUIObjectRoot->childControls.push_back(obj);
obj=new GUITextArea(50,80,500,100);
string tmp=objMap[1].second.c_str();
//Change \n with the characters '\n'.
while(tmp.find("\\n")!=string::npos){
tmp=tmp.replace(tmp.find("\\n"),2,"\n");
}
obj->caption=tmp.c_str();
//Set the textField.
objectProperty=obj;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(100,250-44,150,36,GUIObjectButton,"OK");
obj->name="cfgNotificationBlockOK";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(350,250-44,150,36,GUIObjectButton,"Cancel");
obj->name="cfgCancel";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
//Dim the screen using the tempSurface.
SDL_FillRect(tempSurface,NULL,0);
SDL_SetAlpha(tempSurface,SDL_SRCALPHA,155);
SDL_BlitSurface(tempSurface,NULL,screen,NULL);
while(GUIObjectRoot){
while(SDL_PollEvent(&event)) GUIObjectHandleEvents(true);
if(GUIObjectRoot) GUIObjectRoot->render();
SDL_Flip(screen);
SDL_Delay(30);
}
}
}
if(obj->type==TYPE_CONVEYOR_BELT || obj->type==TYPE_SHADOW_CONVEYOR_BELT){
//Open a message popup.
//First delete any existing gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Get the properties and check if
vector<pair<string,string> > objMap;
obj->getEditorData(objMap);
int m=objMap.size();
if(m>0){
//Set the object we configure.
configuredObject=obj;
//Now create the GUI.
string s;
if(obj->type==TYPE_CONVEYOR_BELT){
s="Shadow Conveyor belt";
}else{
s="Conveyor belt";
}
GUIObjectRoot=new GUIObject(100,(SCREEN_HEIGHT-200)/2,600,200,GUIObjectFrame,s.c_str());
GUIObject* obj;
obj=new GUIObject(40,40,240,36,GUIObjectCheckBox,"Enabled",(objMap[1].second!="1"));
obj->name="cfgConveyorBlockEnabled";
obj->eventCallback=this;
objectProperty=obj;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(40,70,240,36,GUIObjectLabel,"Enter speed here:");
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(200,110,352,36,GUIObjectTextBox,objMap[2].second.c_str());
//Set the textField.
secondObjectProperty=obj;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(100,200-44,150,36,GUIObjectButton,"OK");
obj->name="cfgConveyorBlockOK";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(350,200-44,150,36,GUIObjectButton,"Cancel");
obj->name="cfgCancel";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
//Dim the screen using the tempSurface.
SDL_FillRect(tempSurface,NULL,0);
SDL_SetAlpha(tempSurface,SDL_SRCALPHA,155);
SDL_BlitSurface(tempSurface,NULL,screen,NULL);
while(GUIObjectRoot){
while(SDL_PollEvent(&event)) GUIObjectHandleEvents(true);
if(GUIObjectRoot) GUIObjectRoot->render();
SDL_Flip(screen);
SDL_Delay(30);
}
}
}
if(obj->type==TYPE_PORTAL){
//Open a message popup.
//First delete any existing gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Get the properties and check if
vector<pair<string,string> > objMap;
obj->getEditorData(objMap);
int m=objMap.size();
if(m>0){
//Set the object we configure.
configuredObject=obj;
//Now create the GUI.
GUIObjectRoot=new GUIObject(100,(SCREEN_HEIGHT-200)/2,600,200,GUIObjectFrame,"Portal");
GUIObject* obj;
obj=new GUIObject(40,40,240,36,GUIObjectCheckBox,"Automatic",(objMap[1].second=="1"));
obj->name="cfgPortalAutomatic";
obj->eventCallback=this;
objectProperty=obj;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(40,80,160,36,GUIObjectButton,"Select target");
obj->name="cfgPortalLink";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(230,80,160,36,GUIObjectButton,"Remove target");
obj->name="cfgPortalUnlink";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(100,200-44,150,36,GUIObjectButton,"OK");
obj->name="cfgPortalOK";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(350,200-44,150,36,GUIObjectButton,"Cancel");
obj->name="cfgCancel";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
//Dim the screen using the tempSurface.
SDL_FillRect(tempSurface,NULL,0);
SDL_SetAlpha(tempSurface,SDL_SRCALPHA,155);
SDL_BlitSurface(tempSurface,NULL,screen,NULL);
while(GUIObjectRoot){
while(SDL_PollEvent(&event))
GUIObjectHandleEvents(true);
if(GUIObjectRoot)
GUIObjectRoot->render();
SDL_Flip(screen);
SDL_Delay(30);
}
}
}
if(obj->type==TYPE_BUTTON || obj->type==TYPE_SWITCH){
//Open a message popup.
//First delete any existing gui.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//Get the properties and check if
vector<pair<string,string> > objMap;
obj->getEditorData(objMap);
int m=objMap.size();
if(m>0){
//Set the object we configure.
configuredObject=obj;
//Now create the GUI.
string s;
if(obj->type==TYPE_BUTTON){
s="Button";
}else{
s="Switch";
}
GUIObjectRoot=new GUIObject(100,(SCREEN_HEIGHT-200)/2,600,200,GUIObjectFrame,s.c_str());
GUIObject* obj;
obj=new GUIObject(40,40,240,36,GUIObjectLabel,"Behaviour");
obj->name="cfgTriggerBehaviour";
GUIObjectRoot->childControls.push_back(obj);
obj=new GUISingleLineListBox(250,40,300,36);
obj->name="lstBehaviour";
vector<string> v;
v.push_back("on");
v.push_back("off");
v.push_back("toggle");
(dynamic_cast<GUISingleLineListBox*>(obj))->item=v;
//Get the current behaviour.
if(objMap[1].second=="on"){
obj->value=0;
}else if(objMap[1].second=="off"){
obj->value=1;
}else{
//There's no need to check for the last one, since it's also the default.
obj->value=2;
}
objectProperty=obj;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(40,80,160,36,GUIObjectButton,"Select targets");
obj->name="cfgTriggerLink";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(230,80,160,36,GUIObjectButton,"Remove targets");
obj->name="cfgTriggerUnlink";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(100,200-44,150,36,GUIObjectButton,"OK");
obj->name="cfgTriggerOK";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(350,200-44,150,36,GUIObjectButton,"Cancel");
obj->name="cfgCancel";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
//Dim the screen using the tempSurface.
SDL_FillRect(tempSurface,NULL,0);
SDL_SetAlpha(tempSurface,SDL_SRCALPHA,155);
SDL_BlitSurface(tempSurface,NULL,screen,NULL);
while(GUIObjectRoot){
while(SDL_PollEvent(&event))
GUIObjectHandleEvents(true);
if(GUIObjectRoot)
GUIObjectRoot->render();
SDL_Flip(screen);
SDL_Delay(30);
}
}
}
break;
}
default:
break;
}
}
void LevelEditor::addObject(GameObject* obj){
//If it's a player or shadow start then we need to remove the previous one.
if(obj->type==TYPE_START_PLAYER || obj->type==TYPE_START_SHADOW){
//Loop through the levelObjects.
for(unsigned int o=0; o<levelObjects.size(); o++){
//Check if the type is the same.
if(levelObjects[o]->type==obj->type){
removeObject(levelObjects[o]);
}
}
}
//Add it to the levelObjects.
levelObjects.push_back(obj);
//Check if the object is inside the level dimensions, etc.
//Just call moveObject() to perform this.
moveObject(obj,obj->getBox().x,obj->getBox().y);
//GameObject type specific stuff.
switch(obj->type){
case TYPE_BUTTON:
case TYPE_SWITCH:
case TYPE_PORTAL:
{
//Add the object to the triggers.
vector<GameObject*> linked;
triggers[obj]=linked;
//Give it it's own id.
std::map<std::string,std::string> editorData;
char s[64];
sprintf(s,"%d",currentId);
currentId++;
editorData["id"]=s;
obj->setEditorData(editorData);
break;
}
case TYPE_MOVING_BLOCK:
case TYPE_MOVING_SHADOW_BLOCK:
case TYPE_MOVING_SPIKES:
{
//Add the object to the moving blocks.
vector<MovingPosition> positions;
movingBlocks[obj]=positions;
//Get the editor data.
vector<pair<string,string> > objMap;
obj->getEditorData(objMap);
//Get the number of entries of the editor data.
int m=objMap.size();
//Check if the editor data isn't empty.
if(m>0){
//Integer containing the positions.
int pos=0;
int currentPos=0;
//Get the number of movingpositions.
pos=atoi(objMap[1].second.c_str());
while(currentPos<pos){
int x=atoi(objMap[currentPos*3+4].second.c_str());
int y=atoi(objMap[currentPos*3+5].second.c_str());
int t=atoi(objMap[currentPos*3+6].second.c_str());
//Create a new movingPosition.
MovingPosition position(x,y,t);
movingBlocks[obj].push_back(position);
//Increase currentPos by one.
currentPos++;
}
}
//Give it it's own id.
std::map<std::string,std::string> editorData;
char s[64];
sprintf(s,"%d",currentId);
currentId++;
editorData["id"]=s;
obj->setEditorData(editorData);
break;
}
default:
break;
}
}
void LevelEditor::moveObject(GameObject* obj,int x,int y){
//Set the obj at it's new position.
obj->setPosition(x,y);
//Check if the object is inside the level dimensions.
//If not let the level grow.
if(obj->getBox().x+50>LEVEL_WIDTH){
LEVEL_WIDTH=obj->getBox().x+50;
}
if(obj->getBox().y+50>LEVEL_HEIGHT){
LEVEL_HEIGHT=obj->getBox().y+50;
}
if(obj->getBox().x<0 || obj->getBox().y<0){
//A block on the left (or top) side of the level, meaning we need to shift everything.
//First calc the difference.
int diffx=(0-(obj->getBox().x));
int diffy=(0-(obj->getBox().y));
if(diffx<0) diffx=0;
if(diffy<0) diffy=0;
//Change the level size first.
//The level grows with the difference, 0-(x+50).
LEVEL_WIDTH+=diffx;
LEVEL_HEIGHT+=diffy;
//cout<<"x:"<<diffx<<",y:"<<diffy<<endl; //debug
camera.x+=diffx;
camera.y+=diffy;
//Set the position of player and shadow
//(although it's unnecessary if there is player and shadow start)
player.setPosition(player.getBox().x+diffx,player.getBox().y+diffy);
shadow.setPosition(shadow.getBox().x+diffx,shadow.getBox().y+diffy);
for(unsigned int o=0; o<levelObjects.size(); o++){
//FIXME: shouldn't recuesive call me (to prevent stack overflow bugs)
moveObject(levelObjects[o],levelObjects[o]->getBox().x+diffx,levelObjects[o]->getBox().y+diffy);
}
}
//If the object is a player or shadow start then change the start position of the player or shadow.
if(obj->type==TYPE_START_PLAYER){
//Center the player horizontally.
player.fx=obj->getBox().x+(50-23)/2;
player.fy=obj->getBox().y;
//Now reset the player to get him to it's new start position.
player.reset(true);
}
if(obj->type==TYPE_START_SHADOW){
//Center the shadow horizontally.
shadow.fx=obj->getBox().x+(50-23)/2;
shadow.fy=obj->getBox().y;
//Now reset the shadow to get him to it's new start position.
shadow.reset(true);
}
}
void LevelEditor::removeObject(GameObject* obj){
std::vector<GameObject*>::iterator it;
std::map<GameObject*,vector<GameObject*> >::iterator mapIt;
//Check if the object is in the selection.
it=find(selection.begin(),selection.end(),obj);
if(it!=selection.end()){
//It is so we delete it.
selection.erase(it);
}
//Check if the object is in the triggers.
mapIt=triggers.find(obj);
if(mapIt!=triggers.end()){
//It is so we remove it.
triggers.erase(mapIt);
}
//Boolean if it could be a target.
if(obj->type==TYPE_MOVING_BLOCK || obj->type==TYPE_MOVING_SHADOW_BLOCK || obj->type==TYPE_MOVING_SPIKES
|| obj->type==TYPE_CONVEYOR_BELT || obj->type==TYPE_SHADOW_CONVEYOR_BELT || obj->type==TYPE_PORTAL){
for(mapIt=triggers.begin();mapIt!=triggers.end();++mapIt){
//Now loop the target vector.
for(unsigned int o=0;o<(*mapIt).second.size();o++){
//Check if the obj is in the target vector.
if((*mapIt).second[o]==obj){
(*mapIt).second.erase(find((*mapIt).second.begin(),(*mapIt).second.end(),obj));
o--;
}
}
}
}
//Check if the object is in the movingObjects.
std::map<GameObject*,vector<MovingPosition> >::iterator movIt;
movIt=movingBlocks.find(obj);
if(movIt!=movingBlocks.end()){
//It is so we remove it.
movingBlocks.erase(movIt);
}
//Now we remove the object from the levelObjects.
it=find(levelObjects.begin(),levelObjects.end(),obj);
if(it!=levelObjects.end()){
levelObjects.erase(it);
}
delete obj;
obj=NULL;
}
void LevelEditor::GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType){
//Check for GUI events.
//Notification block configure events.
if(name=="cfgNotificationBlockOK"){
if(GUIObjectRoot){
//Set the message of the notification block.
std::map<std::string,std::string> editorData;
editorData["message"]=objectProperty->caption;
configuredObject->setEditorData(editorData);
//And delete the GUI.
objectProperty=NULL;
secondObjectProperty=NULL;
configuredObject=NULL;
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
//Conveyor belt block configure events.
if(name=="cfgConveyorBlockOK"){
if(GUIObjectRoot){
//Set the message of the notification block.
std::map<std::string,std::string> editorData;
editorData["speed"]=secondObjectProperty->caption;
editorData["disabled"]=(objectProperty->value==0)?"1":"0";
configuredObject->setEditorData(editorData);
//And delete the GUI.
objectProperty=NULL;
secondObjectProperty=NULL;
configuredObject=NULL;
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
//Moving block configure events.
if(name=="cfgMovingBlockOK"){
if(GUIObjectRoot){
//Set if the moving block is enabled/disabled.
std::map<std::string,std::string> editorData;
editorData["disabled"]=(objectProperty->value==0)?"1":"0";
editorData["loop"]=(secondObjectProperty->value==1)?"1":"0";
configuredObject->setEditorData(editorData);
//And delete the GUI.
objectProperty=NULL;
secondObjectProperty=NULL;
configuredObject=NULL;
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
if(name=="cfgMovingBlockClrPath"){
if(GUIObjectRoot){
//Set the message of the notification block.
std::map<std::string,std::string> editorData;
editorData["MovingPosCount"]="0";
configuredObject->setEditorData(editorData);
std::map<GameObject*,vector<MovingPosition> >::iterator it;
it=movingBlocks.find(configuredObject);
if(it!=movingBlocks.end()){
(*it).second.clear();
}
//And delete the GUI.
objectProperty=NULL;
secondObjectProperty=NULL;
configuredObject=NULL;
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
if(name=="cfgMovingBlockMakePath"){
if(GUIObjectRoot){
//Set moving.
moving=true;
movingBlock=configuredObject;
//And delete the GUI.
objectProperty=NULL;
secondObjectProperty=NULL;
configuredObject=NULL;
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
//Portal block configure events.
if(name=="cfgPortalOK"){
if(GUIObjectRoot){
//Set the message of the notification block.
std::map<std::string,std::string> editorData;
editorData["automatic"]=(objectProperty->value==1)?"1":"0";
configuredObject->setEditorData(editorData);
//And delete the GUI.
objectProperty=NULL;
secondObjectProperty=NULL;
configuredObject=NULL;
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
if(name=="cfgPortalLink"){
//We set linking true.
linking=true;
linkingTrigger=configuredObject;
//And delete the GUI.
objectProperty=NULL;
secondObjectProperty=NULL;
configuredObject=NULL;
if(GUIObjectRoot){
delete GUIObjectRoot;
}
GUIObjectRoot=NULL;
}
if(name=="cfgPortalUnlink"){
std::map<GameObject*,vector<GameObject*> >::iterator it;
it=triggers.find(configuredObject);
if(it!=triggers.end()){
//Remove the targets.
(*it).second.clear();
}
//We give the portal a new id to prevent activating unlinked targets.
std::map<std::string,std::string> editorData;
char s[64];
sprintf(s,"%d",currentId);
currentId++;
editorData["id"]=s;
configuredObject->setEditorData(editorData);
//And delete the GUI.
objectProperty=NULL;
secondObjectProperty=NULL;
configuredObject=NULL;
if(GUIObjectRoot){
delete GUIObjectRoot;
}
GUIObjectRoot=NULL;
}
//Trigger block configure events.
if(name=="cfgTriggerOK"){
if(GUIObjectRoot){
//Set the message of the notification block.
std::map<std::string,std::string> editorData;
editorData["behaviour"]=(dynamic_cast<GUISingleLineListBox*>(objectProperty))->item[objectProperty->value];
configuredObject->setEditorData(editorData);
//And delete the GUI.
objectProperty=NULL;
secondObjectProperty=NULL;
configuredObject=NULL;
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
if(name=="cfgTriggerLink"){
//We set linking true.
linking=true;
linkingTrigger=configuredObject;
//And delete the GUI.
objectProperty=NULL;
secondObjectProperty=NULL;
configuredObject=NULL;
if(GUIObjectRoot){
delete GUIObjectRoot;
}
GUIObjectRoot=NULL;
}
if(name=="cfgTriggerUnlink"){
std::map<GameObject*,vector<GameObject*> >::iterator it;
it=triggers.find(configuredObject);
if(it!=triggers.end()){
//Remove the targets.
(*it).second.clear();
}
//We give the trigger a new id to prevent activating unlinked targets.
std::map<std::string,std::string> editorData;
char s[64];
sprintf(s,"%d",currentId);
currentId++;
editorData["id"]=s;
configuredObject->setEditorData(editorData);
//And delete the GUI.
objectProperty=NULL;
secondObjectProperty=NULL;
configuredObject=NULL;
if(GUIObjectRoot){
delete GUIObjectRoot;
}
GUIObjectRoot=NULL;
}
//Cancel.
if(name=="cfgCancel"){
if(GUIObjectRoot){
//Delete the GUI.
objectProperty=NULL;
secondObjectProperty=NULL;
configuredObject=NULL;
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
//LevelSetting events.
if(name=="lvlSettingsOK"){
levelName=objectProperty->caption;
levelTheme=secondObjectProperty->caption;
//target time and recordings.
string s=levelTimeProperty->caption;
if(s.empty() || !(s[0]>='0' && s[0]<='9')){
levelTime=-1;
}else{
levelTime=int(atof(s.c_str())*40.0+0.5);
}
s=levelRecordingsProperty->caption;
if(s.empty() || !(s[0]>='0' && s[0]<='9')){
levelRecordings=-1;
}else{
levelRecordings=atoi(s.c_str());
}
//And delete the GUI.
if(GUIObjectRoot){
objectProperty=NULL;
secondObjectProperty=NULL;
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
if(name=="lvlSettingsCancel"){
if(GUIObjectRoot){
//Delete the GUI.
objectProperty=NULL;
secondObjectProperty=NULL;
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
}
////////////////LOGIC////////////////////
void LevelEditor::logic(){
if(playMode){
//PlayMode so let the game do it's logic.
Game::logic();
}else{
//Move the camera.
if(cameraXvel!=0 || cameraYvel!=0){
camera.x+=cameraXvel;
camera.y+=cameraYvel;
//Call the onCameraMove event.
onCameraMove(cameraXvel,cameraYvel);
}
//Move the camera with the mouse.
setCamera();
//It isn't playMode so the mouse should be checked.
tooltip=-1;
//Get the mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
SDL_Rect mouse={x,y,0,0};
//We loop through the number of tools + the number of buttons.
for(int t=0; t<NUMBER_TOOLS+6; t++){
SDL_Rect toolRect={155+(t*40)+(t*10),555,40,40};
//Check for collision.
if(checkCollision(mouse,toolRect)==true){
//Set the tooltip tool.
tooltip=t;
//Check if there's a mouse click.
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_LEFT){
if(t<NUMBER_TOOLS){
tool=(Tools)t;
}else{
//The selected button isn't a tool.
//Now check which button it is.
if(t==NUMBER_TOOLS){
playMode=true;
cameraSave.x=camera.x;
cameraSave.y=camera.y;
if(tool==CONFIGURE){
//Also stop linking or moving.
if(linking){
linking=false;
linkingTrigger=NULL;
}
if(moving){
//Write the path to the moving block.
std::map<std::string,std::string> editorData;
char s[64], s0[64];
sprintf(s,"%d",movingBlocks[movingBlock].size());
editorData["MovingPosCount"]=s;
//Loop through the positions.
for(unsigned int o=0;o<movingBlocks[movingBlock].size();o++){
sprintf(s0+1,"%d",o);
sprintf(s,"%d",movingBlocks[movingBlock][o].x);
s0[0]='x';
editorData[s0]=s;
sprintf(s,"%d",movingBlocks[movingBlock][o].y);
s0[0]='y';
editorData[s0]=s;
sprintf(s,"%d",movingBlocks[movingBlock][o].time);
s0[0]='t';
editorData[s0]=s;
}
movingBlock->setEditorData(editorData);
moving=false;
movingBlock=NULL;
}
}
}
if(t==NUMBER_TOOLS+2){
//Levelsettings.
levelSettings();
}
if(t==NUMBER_TOOLS+3){
- //Levelpack save.
- LevelPackEditor objEditor;
- objEditor.show();
+ setNextState(STATE_LEVEL_EDIT_SELECT);
}
if(t==NUMBER_TOOLS+4){
- string s=fileNameFromPath(levelFile);
- if(fileDialog(s,"Save Level","map","%USER%/custom/levels/",true,true)){
- saveLevel(processFileName(s));
- levelFile=processFileName(s);
- }
+ saveLevel(levelFile);
}
if(t==NUMBER_TOOLS+5){
string s="";
if(fileDialog(s,"Load Level","map","%USER%/custom/levels/\nMy levels\n%USER%/levels/\nAddon levels\n%DATA%/levels/\nMain levels",false,true)){
reset();
loadLevel(processFileName(s));
postLoad();
}
}
}
}
}
}
}
}
/////////////////RENDER//////////////////////
void LevelEditor::render(){
//Always let the game render the game.
Game::render();
//Only render extra stuff like the toolbar, selection, etc.. when not in playMode.
if(!playMode){
//Render the selectionmarks.
//TODO: Check if block is in sight.
for(unsigned int o=0; o<selection.size(); o++){
//Get the location to draw.
SDL_Rect r=selection[o]->getBox();
r.x-=camera.x;
r.y-=camera.y;
//Draw the selectionMarks.
applySurface(r.x,r.y,selectionMark,screen,NULL);
applySurface(r.x+r.w-5,r.y,selectionMark,screen,NULL);
applySurface(r.x,r.y+r.h-5,selectionMark,screen,NULL);
applySurface(r.x+r.w-5,r.y+r.h-5,selectionMark,screen,NULL);
}
//Clear the placement surface.
SDL_FillRect(placement,NULL,0x00FF00FF);
//Draw the dark areas marking the outside of the level.
SDL_Rect r;
if(camera.x<0){
//Draw left side.
r.x=0;
r.y=0;
r.w=0-camera.x;
r.h=600;
SDL_FillRect(placement,&r,0);
}
if(camera.x>LEVEL_WIDTH-800){
//Draw right side.
r.x=LEVEL_WIDTH-camera.x;
r.y=0;
r.w=800-(LEVEL_WIDTH-camera.x);
r.h=600;
SDL_FillRect(placement,&r,0);
}
if(camera.y<0){
//Draw the top.
r.x=0;
r.y=0;
r.w=800;
r.h=0-camera.y;
SDL_FillRect(placement,&r,0);
}
if(camera.y>LEVEL_HEIGHT-600){
//Draw the bottom.
r.x=0;
r.y=LEVEL_HEIGHT-camera.y;
r.w=800;
r.h=600-(LEVEL_HEIGHT-camera.y);
SDL_FillRect(placement,&r,0);
}
//Check if we should draw on the placement surface.
if(selectionDrag){
showSelectionDrag();
}else{
if(tool==ADD){
showCurrentObject();
}
if(tool==CONFIGURE){
showConfigure();
}
}
//Draw the level borders.
drawRect(-camera.x,-camera.y,LEVEL_WIDTH,LEVEL_HEIGHT,screen);
//Render the placement surface.
applySurface(0,0,placement,screen,NULL);
//Render the hud layer.
renderHUD();
//On top of all render the toolbar.
applySurface(145,550,toolbar,screen,NULL);
//Now render a tooltip.
if(tooltip>=0){
//The back and foreground colors.
SDL_Color fg={0,0,0};
//Tool specific text.
SDL_Surface* tip=NULL;
switch(tooltip){
case 0:
tip=TTF_RenderText_Blended(fontText,"Select",fg);
break;
case 1:
tip=TTF_RenderText_Blended(fontText,"Add",fg);
break;
case 2:
tip=TTF_RenderText_Blended(fontText,"Delete",fg);
break;
case 3:
tip=TTF_RenderText_Blended(fontText,"Configure",fg);
break;
case 4:
tip=TTF_RenderText_Blended(fontText,"Play",fg);
break;
case 6:
tip=TTF_RenderText_Blended(fontText,"Level settings",fg);
break;
case 7:
tip=TTF_RenderText_Blended(fontText,"Levelpack editor",fg);
break;
case 8:
tip=TTF_RenderText_Blended(fontText,"Save level",fg);
break;
case 9:
tip=TTF_RenderText_Blended(fontText,"Load level",fg);
break;
default:
break;
}
//Draw only if there's a tooltip available
if(tip!=NULL){
SDL_Rect r={155+(tooltip*40)+(tooltip*10),555,40,40};
r.y=550-tip->h;
if(r.x+tip->w>SCREEN_WIDTH-50)
r.x=SCREEN_WIDTH-50-tip->w;
//Draw borders around text
Uint32 color=0xFFFFFF00|230;
drawGUIBox(r.x-2,r.y-2,tip->w+4,tip->h+4,screen,color);
//Draw tooltip's text
SDL_BlitSurface(tip,NULL,screen,&r);
SDL_FreeSurface(tip);
}
}
//Draw a rectangle around the current tool.
Uint32 color=0xFFFFFF00;
drawGUIBox(154+(tool*40)+(tool*10),554,42,42,screen,color);
}
}
void LevelEditor::renderHUD(){
//Switch the tool.
switch(tool){
case CONFIGURE:
//If moving show the moving speed in the top right corner.
if(moving){
SDL_Rect r={620,0,180,30};
SDL_FillRect(screen,&r,0);
//Shrink the rectangle by one pixel and fill with white leaving an one pixel border.
r.x+=1;
r.w-=2;
r.h-=1;
SDL_FillRect(screen,&r,0xFFFFFF);
//Now render the text.
SDL_Color black={0,0,0,0};
SDL_Color white={255,255,255,255};
char s[64];
sprintf(s,"%d",movingSpeed);
SDL_Surface* bm=TTF_RenderText_Shaded(fontText,("Movespeed: "+string(s)).c_str(),black,white);
r.x+=2;
r.y+=2;
//Draw the text and free the surface.
SDL_BlitSurface(bm,NULL,screen,&r);
SDL_FreeSurface(bm);
}
break;
default:
break;
}
}
void LevelEditor::showCurrentObject(){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
x+=camera.x;
y+=camera.y;
//Check if we should snap the block to grid or not.
if(!pressedShift){
snapToGrid(&x,&y);
}else{
x-=25;
y-=25;
}
//Check if the currentType is a legal type.
if(currentType>=0 && currentType<EDITOR_ORDER_MAX){
ThemeBlock* obj=objThemes.getBlock(editorTileOrder[currentType]);
if(obj){
obj->editorPicture.draw(placement,x-camera.x,y-camera.y);
}
}
}
void LevelEditor::showSelectionDrag(){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Create the rectangle.
x+=camera.x;
y+=camera.y;
//Check if we should snap the block to grid or not.
if(!pressedShift){
snapToGrid(&x,&y);
}else{
x-=25;
y-=25;
}
//Check if the drag center isn't null.
if(dragCenter==NULL) return;
//The location of the dragCenter.
SDL_Rect r=dragCenter->getBox();
//Loop through the selection.
//TODO: Check if block is in sight.
for(unsigned int o=0; o<selection.size(); o++){
ThemeBlock* obj=objThemes.getBlock(selection[o]->type);
if(obj){
SDL_Rect r1=selection[o]->getBox();
obj->editorPicture.draw(placement,(r1.x-r.x)+x-camera.x,(r1.y-r.y)+y-camera.y);
}
}
}
void LevelEditor::showConfigure(){
//arrow animation value. go through 0-65535 and loops.
static unsigned short arrowAnimation=0;
arrowAnimation++;
//Draw the trigger lines.
{
map<GameObject*,vector<GameObject*> >::iterator it;
for(it=triggers.begin();it!=triggers.end();it++){
//Check if the trigger has linked targets.
if(!(*it).second.empty()){
//The location of the trigger.
SDL_Rect r=(*it).first->getBox();
//Loop through the targets.
for(unsigned int o=0;o<(*it).second.size();o++){
//Get the location of the target.
SDL_Rect r1=(*it).second[o]->getBox();
//Draw the line from the center of the trigger to the center of the target.
drawLineWithArrow(r.x-camera.x+25,r.y-camera.y+25,r1.x-camera.x+25,r1.y-camera.y+25,placement,0,32,arrowAnimation%32);
//Also draw two selection marks.
applySurface(r.x-camera.x+25-2,r.y-camera.y+25-2,selectionMark,screen,NULL);
applySurface(r1.x-camera.x+25-2,r1.y-camera.y+25-2,selectionMark,screen,NULL);
}
}
}
//Draw a line to the mouse from the linkingTrigger when linking.
if(linking){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Draw the line from the center of the trigger to mouse.
drawLineWithArrow(linkingTrigger->getBox().x-camera.x+25,linkingTrigger->getBox().y-camera.y+25,x,y,placement,0,32,arrowAnimation%32);
}
}
//Draw the moving positions.
map<GameObject*,vector<MovingPosition> >::iterator it;
for(it=movingBlocks.begin();it!=movingBlocks.end();it++){
//Check if the block has positions.
if(!(*it).second.empty()){
//The location of the moving block.
SDL_Rect block=(*it).first->getBox();
block.x+=25-camera.x;
block.y+=25-camera.y;
//The location of the previous position.
//The first time it's the moving block's position self.
SDL_Rect r=block;
//Loop through the positions.
for(unsigned int o=0;o<(*it).second.size();o++){
//Draw the line from the center of the previous position to the center of the position.
//x and y are the coordinates for the current moving position.
int x=block.x+(*it).second[o].x;
int y=block.y+(*it).second[o].y;
//Check if we need to draw line
double dx=r.x-x;
double dy=r.y-y;
double d=sqrt(dx*dx+dy*dy);
if(d>0.001f){
if(it->second[o].time>0){
//Calculate offset to contain the moving speed.
int offset=int(d*arrowAnimation/it->second[o].time)%32;
drawLineWithArrow(r.x,r.y,x,y,placement,0,32,offset);
}else{
//time==0 ???? so don't draw arrow at all
drawLine(r.x,r.y,x,y,placement);
}
}
//And draw a marker at the end.
applySurface(x-13,y-13,movingMark,screen,NULL);
//Get the box of the previous position.
SDL_Rect tmp={x,y,0,0};
r=tmp;
}
}
}
//Draw a line to the mouse from the previous moving pos.
if(moving){
//Get the current mouse location.
int x,y;
SDL_GetMouseState(&x,&y);
//Check if we should snap the block to grid or not.
if(!pressedShift){
x+=camera.x;
y+=camera.y;
snapToGrid(&x,&y);
x-=camera.x;
y-=camera.y;
}else{
x-=25;
y-=25;
}
int posX,posY;
//Check if there are moving positions for the moving block.
if(!movingBlocks[movingBlock].empty()){
//Draw the line from the center of the previouse moving positions to mouse.
posX=movingBlocks[movingBlock].back().x;
posY=movingBlocks[movingBlock].back().y;
posX-=camera.x;
posY-=camera.y;
posX+=movingBlock->getBox().x;
posY+=movingBlock->getBox().y;
}else{
//Draw the line from the center of the movingblock to mouse.
posX=movingBlock->getBox().x-camera.x;
posY=movingBlock->getBox().y-camera.y;
}
//Calculate offset to contain the moving speed.
int offset=int(double(arrowAnimation)*movingSpeed/10.0)%32;
drawLineWithArrow(posX+25,posY+25,x+25,y+25,placement,0,32,offset);
applySurface(x+12,y+12,movingMark,screen,NULL);
}
}
//Filling the order array
const int LevelEditor::editorTileOrder[EDITOR_ORDER_MAX]={
TYPE_BLOCK,
TYPE_SHADOW_BLOCK,
TYPE_SPIKES,
TYPE_FRAGILE,
TYPE_MOVING_BLOCK,
TYPE_MOVING_SHADOW_BLOCK,
TYPE_MOVING_SPIKES,
TYPE_CONVEYOR_BELT,
TYPE_SHADOW_CONVEYOR_BELT,
TYPE_BUTTON,
TYPE_SWITCH,
TYPE_PORTAL,
TYPE_SWAP,
TYPE_CHECKPOINT,
TYPE_NOTIFICATION_BLOCK,
TYPE_START_PLAYER,
TYPE_START_SHADOW,
TYPE_EXIT
};
diff --git a/src/LevelPlaySelect.cpp b/src/LevelPlaySelect.cpp
new file mode 100644
index 0000000..0fcf37f
--- /dev/null
+++ b/src/LevelPlaySelect.cpp
@@ -0,0 +1,424 @@
+/****************************************************************************
+** 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 "LevelPlaySelect.h"
+#include "GameState.h"
+#include "Functions.h"
+#include "FileManager.h"
+#include "Globals.h"
+#include "Objects.h"
+#include "LevelSelect.h"
+#include "GUIObject.h"
+#include "GUIListBox.h"
+#include "GUIScrollBar.h"
+#include "InputManager.h"
+#include "Game.h"
+#include <SDL/SDL_ttf.h>
+#include <SDL/SDL.h>
+#include <stdio.h>
+#include <string>
+#include <sstream>
+#include <iostream>
+using namespace std;
+
+static SDL_Surface* playButtonImage=NULL;
+
+/////////////////////LEVEL SELECT/////////////////////
+static string levelDescription,levelMedal,levelMedal2,levelMedal3;
+static string bestTimeFilePath,bestRecordingFilePath;
+
+LevelPlaySelect::LevelPlaySelect():LevelSelect("Select Level"){
+ //Load the play button if needed.
+ if(playButtonImage==NULL){
+ playButtonImage=loadImage(getDataPath()+"gfx/playbutton.png");
+ }
+
+ play=new GUIObject(560,540,240,32,GUIObjectButton,"Play");
+ play->name="cmdPlay";
+ play->eventCallback=this;
+ play->enabled=false;
+ GUIObjectRoot->childControls.push_back(play);
+
+ //show level list
+ refresh();
+}
+
+LevelPlaySelect::~LevelPlaySelect(){
+ play=NULL;
+}
+
+void LevelPlaySelect::refresh(){
+ int m=levels.getLevelCount();
+ numbers.clear();
+
+ //clear the selected level
+ if(selectedNumber!=NULL){
+ delete selectedNumber;
+ selectedNumber=NULL;
+ }
+ //Disable the play button.
+ play->enabled=false;
+
+ for(int n=0; n<m; n++){
+ numbers.push_back(Number());
+ }
+
+ for(int n=0; n<m; n++){
+ SDL_Rect box={(n%10)*64+80,(n/10)*64+184,0,0};
+ numbers[n].init(n,box);
+ numbers[n].setLocked(levels.getLocked(n));
+ int medal=levels.getLevel(n)->won;
+ if(medal){
+ if(levels.getLevel(n)->targetTime<0 || levels.getLevel(n)->time<=levels.getLevel(n)->targetTime)
+ medal++;
+ if(levels.getLevel(n)->targetRecordings<0 || levels.getLevel(n)->recordings<=levels.getLevel(n)->targetRecordings)
+ medal++;
+ }
+ numbers[n].setMedal(medal);
+ }
+
+ if(m>40){
+ levelScrollBar->maxValue=(m-41)/10;
+ levelScrollBar->visible=true;
+ }else{
+ levelScrollBar->maxValue=0;
+ levelScrollBar->visible=false;
+ }
+ levelpackDescription->caption=levels.levelpackDescription;
+ int width;
+ TTF_SizeText(fontGUI,levels.levelpackDescription.c_str(),&width,NULL);
+ levelpackDescription->width=width;
+ levelpackDescription->left=(SCREEN_WIDTH-width)/2;
+}
+
+void LevelPlaySelect::selectNumber(int number,bool selected){
+ if(selected){
+ levels.setCurrentLevel(number);
+ setNextState(STATE_GAME);
+
+ //Pick music from the current music list.
+ getMusicManager()->pickMusic();
+ }else{
+ displayLevelInfo(number);
+ }
+}
+
+void LevelPlaySelect::checkMouse(){
+ int x,y,dy=0,m=levels.getLevelCount();
+
+ //Get the current mouse location.
+ SDL_GetMouseState(&x,&y);
+
+ //Check if we should replay the record.
+ if(selectedNumber!=NULL){
+ SDL_Rect mouse={x,y,0,0};
+ if(!bestTimeFilePath.empty()){
+ SDL_Rect box={380,440,372,32};
+ if(checkCollision(box,mouse)){
+ Game::recordFile=bestTimeFilePath;
+ levels.setCurrentLevel(selectedNumber->getNumber());
+ setNextState(STATE_GAME);
+
+ //Pick music from the current music list.
+ getMusicManager()->pickMusic();
+ return;
+ }
+ }
+ if(!bestRecordingFilePath.empty()){
+ SDL_Rect box={380,472,372,32};
+ if(checkCollision(box,mouse)){
+ Game::recordFile=bestRecordingFilePath;
+ levels.setCurrentLevel(selectedNumber->getNumber());
+ setNextState(STATE_GAME);
+
+ //Pick music from the current music list.
+ getMusicManager()->pickMusic();
+ return;
+ }
+ }
+ }
+
+ //Call the base method from the super class.
+ LevelSelect::checkMouse();
+}
+
+void LevelPlaySelect::displayLevelInfo(int number){
+ //Update currently selected level
+ if(selectedNumber==NULL){
+ selectedNumber=new Number();
+ }
+ SDL_Rect box={40,440,50,50};
+ selectedNumber->init(number,box);
+
+ //Show level description
+ levelDescription=levels.getLevelName(number);
+
+ //Show level medal
+ int medal=levels.getLevel(number)->won;
+ int time=levels.getLevel(number)->time;
+ int targetTime=levels.getLevel(number)->targetTime;
+ int recordings=levels.getLevel(number)->recordings;
+ int targetRecordings=levels.getLevel(number)->targetRecordings;
+
+ if(medal){
+ if(targetTime<0 && targetTime<0){
+ medal=-1;
+ }else{
+ if(targetTime<0 || time<=targetTime)
+ medal++;
+ if(targetRecordings<0 || recordings<=targetRecordings)
+ medal++;
+ }
+ }
+ switch(medal){
+ case 0:
+ levelMedal="You haven't finished this level ";//"Medal: None";
+ break;
+ case 1:
+ levelMedal="Medal: Bronze";
+ break;
+ case 2:
+ levelMedal="Medal: Silver";
+ break;
+ case 3:
+ levelMedal="Medal: Gold";
+ break;
+ default:
+ levelMedal="Medal: Not avaliable for this level";
+ break;
+ }
+
+ //Show best time and recordings
+ if(medal){
+ char s[64];
+
+ if(time>0)
+ if(targetTime>0)
+ sprintf(s,"%-.2fs / %-.2fs",time/40.0f,targetTime/40.0f);
+ else
+ sprintf(s,"%-.2fs",time/40.0f);
+ else
+ s[0]='\0';
+ levelMedal2=string("Time: ")+s;
+
+ if(recordings>=0)
+ if(targetRecordings>=0)
+ sprintf(s,"%5d / %d",recordings,targetRecordings);
+ else
+ sprintf(s,"%d",recordings);
+ else
+ s[0]='\0';
+ levelMedal3=string("Recordings: ")+s;
+ }else{
+ levelMedal2.clear();
+ levelMedal3.clear();
+ }
+
+ //Show the play button.
+ play->enabled=true;
+
+ //Check if there is auto record file
+ levels.getLevelAutoSaveRecordPath(number,bestTimeFilePath,bestRecordingFilePath,false);
+ if(!bestTimeFilePath.empty()){
+ FILE *f;
+ f=fopen(bestTimeFilePath.c_str(),"rb");
+ if(f==NULL){
+ bestTimeFilePath.clear();
+ }else{
+ fclose(f);
+ }
+ }
+ if(!bestRecordingFilePath.empty()){
+ FILE *f;
+ f=fopen(bestRecordingFilePath.c_str(),"rb");
+ if(f==NULL){
+ bestRecordingFilePath.clear();
+ }else{
+ fclose(f);
+ }
+ }
+}
+
+void LevelPlaySelect::render(){
+ //First let the levelselect render.
+ LevelSelect::render();
+
+ int x,y,dy=0,m=levels.getLevelCount();
+
+ //Get the current mouse location.
+ SDL_GetMouseState(&x,&y);
+
+ if(levelScrollBar)
+ dy=levelScrollBar->value;
+ if(m>dy*10+40)
+ m=dy*10+40;
+ y+=dy*64;
+
+ SDL_Rect mouse={x,y,0,0};
+
+ //Show currently selected level (if any)
+ if(selectedNumber!=NULL){
+ //Draw a background for the stats.
+ drawGUIBox(0,420,SCREEN_WIDTH,SCREEN_HEIGHT-420,screen,0xFFFFFF80);
+
+ selectedNumber->show(0);
+
+ SDL_Color fg={0,0,0};
+ SDL_Surface* bm;
+
+ if(!levelDescription.empty()){
+ bm=TTF_RenderText_Blended(fontText,levelDescription.c_str(),fg);
+ applySurface(100,440,bm,screen,NULL);
+ SDL_FreeSurface(bm);
+ }
+
+ if(!levelMedal.empty()){
+ bm=TTF_RenderText_Blended(fontText,levelMedal.c_str(),fg);
+ applySurface(40,504,bm,screen,NULL);
+ SDL_FreeSurface(bm);
+ }
+
+ //Only show the replay if the level is completed (won).
+ if(levels.getLevel(selectedNumber->getNumber())->won){
+ if(!bestTimeFilePath.empty()){
+ SDL_Rect r={0,0,32,32};
+ SDL_Rect box={380,440,372,32};
+
+ if(checkCollision(box,mouse)){
+ r.x=32;
+ SDL_FillRect(screen,&box,0xFFCCCCCC);
+ }
+
+ applySurface(720,440,playButtonImage,screen,&r);
+ }
+
+ if(!bestRecordingFilePath.empty()){
+ SDL_Rect r={0,0,32,32};
+ SDL_Rect box={380,472,372,32};
+
+ if(checkCollision(box,mouse)){
+ r.x=32;
+ SDL_FillRect(screen,&box,0xFFCCCCCC);
+ }
+
+ applySurface(720,472,playButtonImage,screen,&r);
+ }
+ }
+
+ if(!levelMedal2.empty()){
+ bm=TTF_RenderText_Blended(fontText,levelMedal2.c_str(),fg);
+ applySurface(380,440+(32-bm->h)/2,bm,screen,NULL);
+ SDL_FreeSurface(bm);
+ }
+
+ if(!levelMedal3.empty()){
+ bm=TTF_RenderText_Blended(fontText,levelMedal3.c_str(),fg);
+ applySurface(380,472+(32-bm->h)/2,bm,screen,NULL);
+ SDL_FreeSurface(bm);
+ }
+ }
+}
+
+void LevelPlaySelect::renderTooltip(int number,int dy){
+ SDL_Color fg={0,0,0};
+ char s[64];
+
+ //Render the name of the level.
+ SDL_Surface* name=TTF_RenderText_Blended(fontText,levels.getLevelName(number).c_str(),fg);
+ SDL_Surface* time=NULL;
+ SDL_Surface* recordings=NULL;
+
+ //The time it took.
+ if(levels.getLevel(number)->time>0){
+ sprintf(s,"%-.2fs",levels.getLevel(number)->time/40.0f);
+ time=TTF_RenderText_Blended(fontText,(string("Time: ")+s).c_str(),fg);
+ }
+
+ //The number of recordings it took.
+ if(levels.getLevel(number)->recordings>=0){
+ sprintf(s,"%d",levels.getLevel(number)->recordings);
+ recordings=TTF_RenderText_Blended(fontText,(string("Recordings: ")+s).c_str(),fg);
+ }
+
+
+ //Now draw a square the size of the three texts combined.
+ SDL_Rect r=numbers[number].box;
+ r.y-=dy*80;
+ if(time!=NULL && recordings!=NULL){
+ r.w=(name->w)>time->w?(name->w)>recordings->w?name->w:recordings->w:(time->w)>recordings->w?time->w:recordings->w;
+ r.h=name->h+5+time->h+recordings->h;
+ }else{
+ 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=0xFFFFFF00|240;
+ 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);
+ }
+ //Increase the height to leave a gap between name and stats.
+ r2.y+=5;
+ if(time!=NULL){
+ //Now draw the time.
+ r2.y+=name->h;
+ SDL_BlitSurface(time,NULL,screen,&r2);
+ }
+ if(recordings!=NULL){
+ //Now draw the recordings.
+ r2.y+=time->h;
+ SDL_BlitSurface(recordings,NULL,screen,&r2);
+ }
+
+ //And free the surfaces.
+ SDL_FreeSurface(name);
+ SDL_FreeSurface(time);
+ SDL_FreeSurface(recordings);
+}
+
+void LevelPlaySelect::GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType){
+ //Let the level select handle his GUI events.
+ LevelSelect::GUIEventCallback_OnEvent(name,obj,eventType);
+
+ //Check for the play button.
+ if(name=="cmdPlay"){
+ if(selectedNumber!=NULL){
+ levels.setCurrentLevel(selectedNumber->getNumber());
+ setNextState(STATE_GAME);
+
+ //Pick music from the current music list.
+ getMusicManager()->pickMusic();
+ }
+ }
+}
diff --git a/src/LevelPlaySelect.h b/src/LevelPlaySelect.h
new file mode 100644
index 0000000..e424867
--- /dev/null
+++ b/src/LevelPlaySelect.h
@@ -0,0 +1,65 @@
+/****************************************************************************
+** 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 LEVELPLAYSELECT_H
+#define LEVELPLAYSELECT_H
+
+#include "GameState.h"
+#include "LevelSelect.h"
+#include "GameObjects.h"
+#include "Player.h"
+#include "GUIObject.h"
+#include <SDL/SDL.h>
+#include <SDL/SDL_mixer.h>
+#include <SDL/SDL_ttf.h>
+#include <vector>
+#include <string>
+
+//This is the LevelSelect state, here you can select levelpacks and levels.
+class LevelPlaySelect : public LevelSelect{
+private:
+ //Pointer to the play button, it is only shown when a level is selected.
+ GUIObject* play;
+
+ //display level info.
+ void displayLevelInfo(int number);
+
+ //Check where and if the mouse clicked on a number.
+ //If so it will start the level.
+ void checkMouse();
+public:
+ //Constructor.
+ LevelPlaySelect();
+ //Destructor.
+ ~LevelPlaySelect();
+
+ //Inherited from LevelSelect.
+ void refresh();
+ void selectNumber(int number,bool selected);
+
+ //Inherited from GameState.
+ void render();
+
+ //Inherited from LevelSelect.
+ void renderTooltip(int number,int dy);
+
+ //GUI events will be handled here.
+ void GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType);
+};
+
+#endif
diff --git a/src/LevelSelect.cpp b/src/LevelSelect.cpp
index f4673aa..aac5727 100644
--- a/src/LevelSelect.cpp
+++ b/src/LevelSelect.cpp
@@ -1,697 +1,377 @@
/****************************************************************************
** 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 "GameState.h"
#include "Functions.h"
#include "FileManager.h"
#include "Globals.h"
#include "Objects.h"
#include "LevelSelect.h"
#include "GUIObject.h"
#include "GUIListBox.h"
#include "GUIScrollBar.h"
#include "InputManager.h"
#include "Game.h"
#include <SDL/SDL_ttf.h>
#include <SDL/SDL.h>
#include <stdio.h>
#include <string>
#include <sstream>
#include <iostream>
using namespace std;
-static SDL_Surface* playButtonImage=NULL;
-
////////////////////NUMBER////////////////////////
Number::Number(){
image=NULL;
- background=NULL;
number=0;
medal=0;
selected=false;
+ locked=false;
//Set the default dimensions.
box.x=0;
box.y=0;
box.h=50;
box.w=50;
//Load the medals image.
+ background=loadImage(getDataPath()+"gfx/level.png");
+ backgroundLocked=loadImage(getDataPath()+"gfx/levellocked.png");
medals=loadImage(getDataPath()+"gfx/medals.png");
}
Number::~Number(){
//We only need to free the SDLSurface.
if(image) SDL_FreeSurface(image);
}
void Number::init(int number,SDL_Rect box){
//First set the number and update our status.
this->number=number;
- update();
//Write our text, number+1 since the counting doens't start with 0, but with 1.
std::stringstream text;
number++;
text<<number;
//Create the text image.
SDL_Color black={0,0,0};
if(image) SDL_FreeSurface(image);
//Create the text image.
//Also check which font to use, if the number is higher than 100 use the small font.
image=TTF_RenderText_Blended(fontGUI,text.str().c_str(),black);
//Set the new location of the number.
this->box.x=box.x;
this->box.y=box.y;
}
+void Number::init(std::string text,SDL_Rect box){
+ //First set the number and update our status.
+ this->number=-1;
+
+ //Create the text image.
+ SDL_Color black={0,0,0};
+ if(image) SDL_FreeSurface(image);
+ //Create the text image.
+ //Also check which font to use, if the number is higher than 100 use the small font.
+ image=TTF_RenderText_Blended(fontGUI,text.c_str(),black);
+
+ //Set the new location of the number.
+ this->box.x=box.x;
+ this->box.y=box.y;
+}
+
void Number::show(int dy){
//First draw the background, also apply the yOffset(dy).
- applySurface(box.x,box.y-dy,background,screen,NULL);
+ if(!locked)
+ applySurface(box.x,box.y-dy,background,screen,NULL);
+ else
+ applySurface(box.x,box.y-dy,backgroundLocked,screen,NULL);
//Now draw the text image over the background.
//We draw it centered inside the box.
applySurface((box.x+25-(image->w/2)),box.y+((TTF_FontAscent(fontGUI)+TTF_FontDescent(fontGUI))/2)-dy,image,screen,NULL);
//Draw the selection mark.
if(selected){
drawGUIBox(box.x,box.y-dy,50,50,screen,0xFFFFFF23);
}
//Draw the medal.
if(medal>0){
SDL_Rect r={(medal-1)*30,0,30,30};
applySurface(box.x+30,(box.y+30)-dy,medals,screen,&r);
}
}
-void Number::update(){
- //Check if the level is locked, if so change the background to the locked image.
- if(levels.getLocked(number)==false){
- background=loadImage(getDataPath()+"gfx/level.png");
- }else{
- background=loadImage(getDataPath()+"gfx/levellocked.png");
- }
-
- //Set the medal.
- medal=levels.getLevel(number)->won;
- if(medal){
- if(levels.getLevel(number)->targetTime<0 || levels.getLevel(number)->time<=levels.getLevel(number)->targetTime)
- medal++;
- if(levels.getLevel(number)->targetRecordings<0 || levels.getLevel(number)->recordings<=levels.getLevel(number)->targetRecordings)
- medal++;
- }
+void Number::setLocked(bool locked){
+ this->locked=locked;
}
+void Number::setMedal(int medal){
+ this->medal=medal;
+}
-/////////////////////LEVEL SELECT/////////////////////
-static GUIScrollBar* levelScrollBar=NULL;
-static GUIObject* levelpackDescription=NULL;
-static string levelDescription,levelMedal,levelMedal2,levelMedal3;
-static string bestTimeFilePath,bestRecordingFilePath;
-LevelSelect::LevelSelect(){
+/////////////////////LEVEL SELECT/////////////////////
+LevelSelect::LevelSelect(string titleText){
//clear the selected level
selectedNumber=NULL;
- //Set the play button to NULL.
- play=NULL;
-
//Load the background image.
background=loadImage(getDataPath()+"gfx/menu/background.png");
- if(playButtonImage==NULL){
- playButtonImage=loadImage(getDataPath()+"gfx/playbutton.png");
- }
-
//Render the title.
SDL_Color black={0,0,0};
- title=TTF_RenderText_Blended(fontTitle,"Select Level",black);
+ title=TTF_RenderText_Blended(fontTitle,titleText.c_str(),black);
//create GUI (test only)
GUIObject* obj;
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
GUIObjectRoot=new GUIObject(0,0,800,600);
//the level select scroll bar
levelScrollBar=new GUIScrollBar(768,184,16,242,ScrollBarVertical,0,0,0,1,4,true,false);
GUIObjectRoot->childControls.push_back(levelScrollBar);
//level pack description
levelpackDescription=new GUIObject(60,140,800,32,GUIObjectLabel);
GUIObjectRoot->childControls.push_back(levelpackDescription);
- GUISingleLineListBox* levelpacks=new GUISingleLineListBox(150,104,500,32);
+ levelpacks=new GUISingleLineListBox(150,104,500,32);
levelpacks->name="cmdLvlPack";
levelpacks->eventCallback=this;
vector<string> v=enumAllDirs(getDataPath()+"levelpacks/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelpackLocations[*i]=getDataPath()+"levelpacks/"+*i;
}
vector<string> v2=enumAllDirs(getUserPath()+"levelpacks/");
for(vector<string>::iterator i=v2.begin(); i!=v2.end(); ++i){
levelpackLocations[*i]=getUserPath()+"levelpacks/"+*i;
}
vector<string> v3=enumAllDirs(getUserPath()+"custom/levelpacks/");
for(vector<string>::iterator i=v3.begin(); i!=v3.end(); ++i){
levelpackLocations[*i]=getUserPath()+"custom/levelpacks/"+*i;
}
v.insert(v.end(),v2.begin(),v2.end());
v.insert(v.end(),v3.begin(),v3.end());
//Now we add a special levelpack that will contain the levels not in a levelpack.
v.push_back("Levels");
levelpacks->item=v;
levelpacks->value=0;
//Check if we can find the lastlevelpack.
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
if(*i==getSettings()->getValue("lastlevelpack")){
levelpacks->value=i-v.begin();
string s1=getUserPath()+"progress/"+*i+".progress";
//Check if this is the special Levels levelpack.
if(*i=="Levels"){
//Clear the current levels.
levels.clear();
levels.setCurrentLevel(0);
//List the custom levels and add them one for one.
vector<string> v=enumAllFiles(getUserPath()+"custom/levels/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levels.addLevel(getUserPath()+"custom/levels/"+*i);
levels.setLocked(levels.getLevelCount()-1);
}
//List the addon levels and add them one for one.
v=enumAllFiles(getUserPath()+"levels/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levels.addLevel(getUserPath()+"levels/"+*i);
levels.setLocked(levels.getLevelCount()-1);
}
}else{
//This isn't so load the levelpack in the normal way.
if(!levels.loadLevels(levelpackLocations[*i]+"/levels.lst")){
msgBox("Can't load level pack:\n"+*i,MsgBoxOKOnly,"Error");
}
}
//Load the progress.
levels.loadProgress(s1);
}
}
GUIObjectRoot->childControls.push_back(levelpacks);
- obj=new GUIObject(20,540,240,32,GUIObjectButton,"Back");
+ obj=new GUIObject(20,20,100,32,GUIObjectButton,"Back");
obj->name="cmdBack";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
- play=new GUIObject(560,540,240,32,GUIObjectButton,"Play");
- play->name="cmdPlay";
- play->eventCallback=this;
- play->visible=false;
- GUIObjectRoot->childControls.push_back(play);
-
- //show level list
- refresh();
-}
-
-void LevelSelect::refresh(){
- int m=levels.getLevelCount();
- numbers.clear();
-
- //clear the selected level
- if(selectedNumber!=NULL){
- delete selectedNumber;
- selectedNumber=NULL;
- }
- //Hide the play button.
- play->visible=false;
-
- for(int n=0; n<m; n++){
- numbers.push_back(Number());
- }
-
- for(int n=0; n<m; n++){
- SDL_Rect box={(n%10)*64+80,(n/10)*64+184,0,0};
- numbers[n].init(n,box);
- }
-
- if(m>40){
- levelScrollBar->maxValue=(m-41)/10;
- levelScrollBar->visible=true;
- }else{
- levelScrollBar->maxValue=0;
- levelScrollBar->visible=false;
- }
- levelpackDescription->caption=levels.levelpackDescription;
- int width,height;
- TTF_SizeText(fontGUI,levels.levelpackDescription.c_str(),&width,&height);
- levelpackDescription->left=(800-width)/2;
}
LevelSelect::~LevelSelect(){
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
- play=NULL;
levelScrollBar=NULL;
levelpackDescription=NULL;
- if(selectedNumber!=NULL)
- delete selectedNumber;
+ selectedNumber=NULL;
//Free the rendered title surface.
SDL_FreeSurface(title);
}
void LevelSelect::handleEvents(){
//Check for an SDL_QUIT event.
if(event.type==SDL_QUIT){
setNextState(STATE_EXIT);
}
//Check for a mouse click.
if(event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT){
checkMouse();
}
//Check if escape is pressed.
if(inputMgr.isKeyUpEvent(INPUTMGR_ESCAPE)){
setNextState(STATE_MENU);
}
//Check for scrolling down and up.
if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_WHEELDOWN && levelScrollBar){
if(levelScrollBar->value<levelScrollBar->maxValue) levelScrollBar->value++;
return;
}else if(event.type==SDL_MOUSEBUTTONDOWN && event.button.button==SDL_BUTTON_WHEELUP && levelScrollBar){
if(levelScrollBar->value>0) levelScrollBar->value--;
return;
}
}
void LevelSelect::checkMouse(){
int x,y,dy=0,m=levels.getLevelCount();
//Get the current mouse location.
SDL_GetMouseState(&x,&y);
- //Check if we should replay the record.
- if(selectedNumber!=NULL){
- SDL_Rect mouse={x,y,0,0};
- if(!bestTimeFilePath.empty()){
- SDL_Rect box={380,440,372,32};
- if(checkCollision(box,mouse)){
- Game::recordFile=bestTimeFilePath;
- levels.setCurrentLevel(selectedNumber->getNumber());
- setNextState(STATE_GAME);
-
- //Pick music from the current music list.
- getMusicManager()->pickMusic();
- return;
- }
- }
- if(!bestRecordingFilePath.empty()){
- SDL_Rect box={380,472,372,32};
- if(checkCollision(box,mouse)){
- Game::recordFile=bestRecordingFilePath;
- levels.setCurrentLevel(selectedNumber->getNumber());
- setNextState(STATE_GAME);
-
- //Pick music from the current music list.
- getMusicManager()->pickMusic();
- return;
- }
- }
- }
-
//Check if there's a scrollbar, if so get the value.
if(levelScrollBar)
dy=levelScrollBar->value;
if(m>dy*10+50)
m=dy*10+50;
y+=dy*64;
SDL_Rect mouse={x,y,0,0};
- for(int n=dy*10; n<m; n++){
- if(levels.getLocked(n)==false){
+ for(int n=dy*10; n<numbers.size(); n++){
+ if(!numbers[n].getLocked()){
if(checkCollision(mouse,numbers[n].box)==true){
if(numbers[n].selected){
- //Current level was selected, so play it
- levels.setCurrentLevel(n);
- setNextState(STATE_GAME);
-
- //Pick music from the current music list.
- getMusicManager()->pickMusic();
+ selectNumber(n,true);
}else{
//Select current level
for(int i=0;i<levels.getLevelCount();i++){
numbers[i].selected=(i==n);
}
- //Display level info
- displayLevelInfo(n);
+ selectNumber(n,false);
}
break;
}
}
}
}
-void LevelSelect::displayLevelInfo(int number){
- //Update currently selected level
- if(selectedNumber==NULL){
- selectedNumber=new Number();
- }
- SDL_Rect box={40,440,50,50};
- selectedNumber->init(number,box);
-
- //Show level description
- levelDescription=levels.getLevelName(number);
-
- //Show level medal
- int medal=levels.getLevel(number)->won;
- int time=levels.getLevel(number)->time;
- int targetTime=levels.getLevel(number)->targetTime;
- int recordings=levels.getLevel(number)->recordings;
- int targetRecordings=levels.getLevel(number)->targetRecordings;
-
- if(medal){
- if(targetTime<0 && targetTime<0){
- medal=-1;
- }else{
- if(targetTime<0 || time<=targetTime)
- medal++;
- if(targetRecordings<0 || recordings<=targetRecordings)
- medal++;
- }
- }
- switch(medal){
- case 0:
- levelMedal="You haven't finished this level ";//"Medal: None";
- break;
- case 1:
- levelMedal="Medal: Bronze";
- break;
- case 2:
- levelMedal="Medal: Silver";
- break;
- case 3:
- levelMedal="Medal: Gold";
- break;
- default:
- levelMedal="Medal: Not avaliable for this level";
- break;
- }
-
- //Show best time and recordings
- if(medal){
- char s[64];
-
- if(time>0)
- if(targetTime>0)
- sprintf(s,"%-.2fs / %-.2fs",time/40.0f,targetTime/40.0f);
- else
- sprintf(s,"%-.2fs",time/40.0f);
- else
- s[0]='\0';
- levelMedal2=string("Time: ")+s;
-
- if(recordings>=0)
- if(targetRecordings>=0)
- sprintf(s,"%5d / %d",recordings,targetRecordings);
- else
- sprintf(s,"%d",recordings);
- else
- s[0]='\0';
- levelMedal3=string("Recordings: ")+s;
- }else{
- levelMedal2.clear();
- levelMedal3.clear();
- }
-
- //Show the play button.
- play->visible=true;
-
- //Check if there is auto record file
- levels.getLevelAutoSaveRecordPath(number,bestTimeFilePath,bestRecordingFilePath,false);
- if(!bestTimeFilePath.empty()){
- FILE *f;
- f=fopen(bestTimeFilePath.c_str(),"rb");
- if(f==NULL){
- bestTimeFilePath.clear();
- }else{
- fclose(f);
- }
- }
- if(!bestRecordingFilePath.empty()){
- FILE *f;
- f=fopen(bestRecordingFilePath.c_str(),"rb");
- if(f==NULL){
- bestRecordingFilePath.clear();
- }else{
- fclose(f);
- }
- }
-}
-
void LevelSelect::logic(){}
void LevelSelect::render(){
int x,y,dy=0,m=levels.getLevelCount();
int idx=-1;
//Get the current mouse location.
SDL_GetMouseState(&x,&y);
if(levelScrollBar)
dy=levelScrollBar->value;
if(m>dy*10+40)
m=dy*10+40;
y+=dy*64;
SDL_Rect mouse={x,y,0,0};
//Draw the background.
applySurface(0,0,background,screen,NULL);
//Draw the title.
applySurface((800-title->w)/2,40,title,screen,NULL);
//Loop through the level blocks and draw them.
- for(int n=dy*10; n<m;n++){
+ for(int n=dy*10;n<numbers.size();n++){
numbers[n].show(dy*64);
- if(levels.getLocked(n)==false && checkCollision(mouse,numbers[n].box)==true)
+ if(numbers[n].getLocked()==false && checkCollision(mouse,numbers[n].box)==true)
idx=n;
}
-
- //Show currently selected level (if any)
- if(selectedNumber!=NULL){
- //Draw a background for the stats.
- drawGUIBox(0,420,SCREEN_WIDTH,SCREEN_HEIGHT-420,screen,0xFFFFFF80);
-
- selectedNumber->show(0);
-
- SDL_Color fg={0,0,0};
- SDL_Surface* bm;
-
- if(!levelDescription.empty()){
- bm=TTF_RenderText_Blended(fontText,levelDescription.c_str(),fg);
- applySurface(100,440,bm,screen,NULL);
- SDL_FreeSurface(bm);
- }
-
- if(!levelMedal.empty()){
- bm=TTF_RenderText_Blended(fontText,levelMedal.c_str(),fg);
- applySurface(40,504,bm,screen,NULL);
- SDL_FreeSurface(bm);
- }
-
- //Only show the replay if the level is completed (won).
- if(levels.getLevel(selectedNumber->getNumber())->won){
- if(!bestTimeFilePath.empty()){
- SDL_Rect r={0,0,32,32};
- SDL_Rect box={380,440,372,32};
-
- if(checkCollision(box,mouse)){
- r.x=32;
- SDL_FillRect(screen,&box,0xFFCCCCCC);
- }
-
- applySurface(720,440,playButtonImage,screen,&r);
- }
-
- if(!bestRecordingFilePath.empty()){
- SDL_Rect r={0,0,32,32};
- SDL_Rect box={380,472,372,32};
-
- if(checkCollision(box,mouse)){
- r.x=32;
- SDL_FillRect(screen,&box,0xFFCCCCCC);
- }
-
- applySurface(720,472,playButtonImage,screen,&r);
- }
- }
-
- if(!levelMedal2.empty()){
- bm=TTF_RenderText_Blended(fontText,levelMedal2.c_str(),fg);
- applySurface(380,440+(32-bm->h)/2,bm,screen,NULL);
- SDL_FreeSurface(bm);
- }
-
- if(!levelMedal3.empty()){
- bm=TTF_RenderText_Blended(fontText,levelMedal3.c_str(),fg);
- applySurface(380,472+(32-bm->h)/2,bm,screen,NULL);
- SDL_FreeSurface(bm);
- }
- }
-
+
//Show the tool tip text.
if(idx>=0){
- SDL_Color fg={0,0,0};
- char s[64];
-
- //Render the name of the level.
- SDL_Surface* name=TTF_RenderText_Blended(fontText,levels.getLevelName(idx).c_str(),fg);
- SDL_Surface* time=NULL;
- SDL_Surface* recordings=NULL;
-
- //The time it took.
- if(levels.getLevel(idx)->time>0){
- sprintf(s,"%-.2fs",levels.getLevel(idx)->time/40.0f);
- time=TTF_RenderText_Blended(fontText,(string("Time: ")+s).c_str(),fg);
- }
-
- //The number of recordings it took.
- if(levels.getLevel(idx)->recordings>=0){
- sprintf(s,"%d",levels.getLevel(idx)->recordings);
- recordings=TTF_RenderText_Blended(fontText,(string("Recordings: ")+s).c_str(),fg);
- }
-
-
- //Now draw a square the size of the three texts combined.
- SDL_Rect r=numbers[idx].box;
- r.y-=dy*80;
- if(time!=NULL && recordings!=NULL){
- r.w=(name->w)>time->w?(name->w)>recordings->w?name->w:recordings->w:(time->w)>recordings->w?time->w:recordings->w;
- r.h=name->h+5+time->h+recordings->h;
- }else{
- 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[idx].box.h+2;
- }
- if(r.x+r.w>SCREEN_WIDTH-50)
- r.x=SCREEN_WIDTH-50-r.w;
-
- //Draw a rectange
- Uint32 color=0xFFFFFF00|240;
- 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);
- }
- //Increase the height to leave a gap between name and stats.
- r2.y+=5;
- if(time!=NULL){
- //Now draw the time.
- r2.y+=name->h;
- SDL_BlitSurface(time,NULL,screen,&r2);
- }
- if(recordings!=NULL){
- //Now draw the recordings.
- r2.y+=time->h;
- SDL_BlitSurface(recordings,NULL,screen,&r2);
- }
-
- //And free the surfaces.
- SDL_FreeSurface(name);
- SDL_FreeSurface(time);
- SDL_FreeSurface(recordings);
+ renderTooltip(idx,dy);
}
}
-void LevelSelect::GUIEventCallback_OnEvent(std::string Name,GUIObject* obj,int nEventType){
+void LevelSelect::GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType){
string s;
- if(Name=="cmdLvlPack"){
+ if(name=="cmdLvlPack"){
s=levelpackLocations[((GUISingleLineListBox*)obj)->item[obj->value]];
getSettings()->setValue("lastlevelpack",((GUISingleLineListBox*)obj)->item[obj->value]);
- }else if(Name=="cmdBack"){
+ }else if(name=="cmdBack"){
setNextState(STATE_MENU);
return;
- }else if(Name=="cmdPlay"){
- if(selectedNumber!=NULL){
- levels.setCurrentLevel(selectedNumber->getNumber());
- setNextState(STATE_GAME);
-
- //Pick music from the current music list.
- getMusicManager()->pickMusic();
- }
- return;
}else{
return;
}
string s1=getUserPath()+"progress/"+((GUISingleLineListBox*)obj)->item[obj->value]+".progress";
//Check if this is the special Levels levelpack.
if(((GUISingleLineListBox*)obj)->item[obj->value]=="Levels"){
//Clear the current levels.
levels.clear();
levels.setCurrentLevel(0);
//List the custom levels and add them one for one.
vector<string> v=enumAllFiles(getUserPath()+"custom/levels/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levels.addLevel(getUserPath()+"custom/levels/"+*i);
levels.setLocked(levels.getLevelCount()-1);
}
//List the addon levels and add them one for one.
v=enumAllFiles(getUserPath()+"levels/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levels.addLevel(getUserPath()+"levels/"+*i);
levels.setLocked(levels.getLevelCount()-1);
}
}else{
//This isn't so load the levelpack in the normal way.
if(!levels.loadLevels(levelpackLocations[((GUISingleLineListBox*)obj)->item[obj->value]]+"/levels.lst")){
msgBox("Can't load level pack:\n"+((GUISingleLineListBox*)obj)->item[obj->value],MsgBoxOKOnly,"Error");
}
}
//Load the progress file.
levels.loadProgress(s1);
//And refresh the numbers.
refresh();
}
diff --git a/src/LevelSelect.h b/src/LevelSelect.h
index 729372f..ed8107b 100644
--- a/src/LevelSelect.h
+++ b/src/LevelSelect.h
@@ -1,119 +1,154 @@
/****************************************************************************
** 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 LEVELSELECT_H
#define LEVELSELECT_H
#include "GameState.h"
#include "GameObjects.h"
#include "Player.h"
#include "GUIObject.h"
+#include "GUIScrollBar.h"
+#include "GUIListBox.h"
#include <SDL/SDL.h>
#include <SDL/SDL_mixer.h>
#include <SDL/SDL_ttf.h>
#include <vector>
#include <string>
//Class that represents a level in the levelselect menu.
class Number{
private:
//The background image of the number.
SDL_Surface* background;
+ //The background image of the number when it's locked.
+ SDL_Surface* backgroundLocked;
//The (text) image of the number.
SDL_Surface* image;
//Image containing the three stars a player can earn.
SDL_Surface* medals;
- //The number.
+ //The number (or text).
int number;
//Integer containing the medal the player got.
//0 = none, 1 = bronze, 2 = silver, 3 = gold
int medal;
+
+ //Boolean if the number is locked or not.
+ bool locked;
public:
//The location and size of the number.
SDL_Rect box;
//If the Number is selected then we draw something indicates it.
bool selected;
//Constructor.
Number();
//Destructor.
~Number();
//Method used for initialising the number.
//number: The number.
//box: The location and size of the number.
- void init(int number, SDL_Rect box);
+ void init(int number,SDL_Rect box);
+
+ //Method used for initialising the number.
+ //text: The caption of the number.
+ //box: The location and size of the number.
+ void init(std::string text,SDL_Rect box);
//get current number.
inline int getNumber(){return number;}
- //This will update the number. (locked and medal)
- void update();
+ //Method used to set the locked status of the number.
+ //locked: Boolean if it should be locked or not.
+ void setLocked(bool locked=true);
+ //Method used to retrieve the locked status of the number.
+ //Returns: True if the number is locked.
+ inline bool getLocked(){return locked;}
+
+ //Method used to set the medal for this number.
+ //medal: The new medal for this number.
+ void setMedal(int medal);
+ //Method that is used to draw the number.
+ //dy: The y offset.
void show(int dy);
};
//This is the LevelSelect state, here you can select levelpacks and levels.
-class LevelSelect :public GameState,public GUIEventCallback{
-private:
+class LevelSelect : public GameState,public GUIEventCallback{
+protected:
//The background image which is drawn before the rest.
SDL_Surface* background;
//Surface containing the title.
SDL_Surface* title;
//Vector containing the numbers.
std::vector<Number> numbers;
//This hashmap is used to get the path to the levelpack by giving the name of the levelpack.
std::map<std::string,std::string> levelpackLocations;
//Contains selected level number (displayed at bottom left corner).
//If it's NULL then nothing selected.
Number* selectedNumber;
- //Pointer to the play button, it is only shown when a level is selected.
- GUIObject* play;
+ //Pointer to the scrollbar.
+ GUIScrollBar* levelScrollBar;
+ //Pointer to the description.
+ GUIObject* levelpackDescription;
+
+ //Pointer to the levelpack list.
+ GUISingleLineListBox* levelpacks;
- //display level info.
- void displayLevelInfo(int number);
-
//Check where and if the mouse clicked on a number.
- //If so it will start the level.
- void checkMouse();
+ //If so select that number.
+ virtual void checkMouse();
public:
//Constructor.
- LevelSelect();
+ //titleText: The title that is shown at the top of the screen.
+ LevelSelect(std::string titleText);
//Destructor.
- ~LevelSelect();
+ virtual ~LevelSelect();
//Method used to update the numbers and the scrollbar.
- void refresh();
+ virtual void refresh()=0;
+
+ //Method that is called when a number is selected.
+ //number: The selected number.
+ //selected: Boolean if the number was already selected.
+ virtual void selectNumber(int number,bool selected)=0;
//Inherited from GameState.
void handleEvents();
void logic();
void render();
+
+ //Method that is called to render the tooltip.
+ //number: The number that the tooltip should be drawn for.
+ //dy: The y offset of the number, used to draw the tooltip in the right place.
+ virtual void renderTooltip(int number,int dy)=0;
//GUI events will be handled here.
- void GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType);
+ virtual void GUIEventCallback_OnEvent(std::string name,GUIObject* obj,int eventType)=0;
};
#endif
diff --git a/src/Levels.cpp b/src/Levels.cpp
index 6f82fac..921532f 100644
--- a/src/Levels.cpp
+++ b/src/Levels.cpp
@@ -1,462 +1,481 @@
/****************************************************************************
** 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 "Levels.h"
#include "Functions.h"
#include "FileManager.h"
#include "TreeStorageNode.h"
#include "POASerializer.h"
#include "MD5.h"
#include <string>
#include <vector>
#include <fstream>
#include <iostream>
using namespace std;
void Levels::clear(){
currentLevel=0;
loaded=false;
levels.clear();
levelpackDescription.clear();
levelpackPath.clear();
levelProgressFile.clear();
congratulationText.clear();
}
bool Levels::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;
}
}
//Look for the description.
{
vector<string> &v=obj.attributes["description"];
if(v.size()>0)
levelpackDescription=v[0];
}
//Look for the congratulation text.
{
vector<string> &v=obj.attributes["congratulations"];
if(v.size()>0)
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.size()>0)
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.size()>0)
level.targetTime=atoi(v[0].c_str());
else
level.targetTime=-1;
//Get the target recordings of the level.
v=obj.attributes["recordings"];
if(v.size()>0)
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 Levels::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 Levels::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);
//We copy them to the levelpack folder
//Check if the levelpath is relative or absolute.
if(levels[i].file[0]=='%'){
copyFile(processFileName(levels[i].file).c_str(),(pathFromFileName(levelListNew)+fileNameFromPath(levels[i].file)).c_str());
}else{
//Make sure we aren't copying to the same location.
if((levelpackPath+levels[i].file)!=(pathFromFileName(levelListNew)+fileNameFromPath(levels[i].file))){
copyFile((levelpackPath+levels[i].file).c_str(),(pathFromFileName(levelListNew)+fileNameFromPath(levels[i].file)).c_str());
}
}
}
//Write the it away.
POASerializer objSerializer;
objSerializer.writeNode(&obj,level,false,true);
}
void Levels::addLevel(const string& levelFileName,int levelno){
//Fill in the details.
Level level;
- level.file=levelFileName;
+ 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.size()>0)
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.size()>0)
level.targetTime=atoi(v[0].c_str());
else
level.targetTime=-1;
//Get the target recordings of the level.
v=obj.attributes["recordings"];
if(v.size()>0)
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.size()>0?true:false;
//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 Levels::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 Levels::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& Levels::getLevelName(int level){
if(level<0)
level=currentLevel;
return levels[level].name;
}
const unsigned char* Levels::getLevelMD5(int level){
if(level<0)
level=currentLevel;
return levels[level].md5Digest;
}
void Levels::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=Levels::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 Levels::setLevelName(unsigned int level,const std::string& name){
if(level<levels.size())
levels[level].name=name;
}
const string& Levels::getLevelFile(int level){
if(level<0)
level=currentLevel;
return levels[level].file;
}
const string& Levels::getLevelpackPath(){
return levelpackPath;
}
struct Levels::Level* Levels::getLevel(int level){
if(level<0)
return &levels[currentLevel];
return &levels[level];
}
void Levels::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 Levels::nextLevel(){
currentLevel++;
}
bool Levels::getLocked(unsigned int level){
return levels[level].locked;
}
void Levels::setCurrentLevel(unsigned int level){
currentLevel=level;
}
void Levels::setLocked(unsigned int level,bool locked){
levels[level].locked=locked;
}
void Levels::swapLevel(unsigned int level1,unsigned int level2){
if(level1<levels.size()&&level2<levels.size()){
swap(levels[level1],levels[level2]);
}
}
void Levels::removeLevel(unsigned int level){
if(level<levels.size()){
levels.erase(levels.begin()+level);
}
}
diff --git a/src/Levels.h b/src/Levels.h
index 3f7c56b..17c283a 100644
--- a/src/Levels.h
+++ b/src/Levels.h
@@ -1,161 +1,165 @@
/****************************************************************************
** 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 LEVELS_H
#define LEVELS_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"
class Levels{
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 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;
//Constructor.
Levels():currentLevel(0),loaded(false),levels(){};
//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.
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/TitleMenu.cpp b/src/TitleMenu.cpp
index 4f1a5b3..aa44877 100644
--- a/src/TitleMenu.cpp
+++ b/src/TitleMenu.cpp
@@ -1,414 +1,413 @@
/****************************************************************************
** 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>
using namespace std;
/////////////////////////MAIN_MENU//////////////////////////////////
//Integer containing the highlighted/selected menu option.
static int highlight=0;
Menu::Menu(){
highlight=0;
animation=0;
//Load the background and the title image.
background=loadImage(getDataPath()+"gfx/menu/background.png");
title=loadImage(getDataPath()+"gfx/menu/title.png");
//Now render the five entries.
SDL_Color black={0,0,0};
entries[0]=TTF_RenderText_Blended(fontTitle,"Play",black);
entries[1]=TTF_RenderText_Blended(fontTitle,"Options",black);
entries[2]=TTF_RenderText_Blended(fontTitle,"Map Editor",black);
entries[3]=TTF_RenderText_Blended(fontTitle,"Addons",black);
entries[4]=TTF_RenderText_Blended(fontTitle,"Exit",black);
entries[5]=TTF_RenderText_Blended(fontTitle,">",black);
entries[6]=TTF_RenderText_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<600&&y>=200&&y<520){
highlight=(y-136)/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) ||
(event.type==SDL_KEYUP && (event.key.keysym.sym==SDLK_RETURN || event.key.keysym.sym==SDLK_KP_ENTER))){
//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_EDITOR);
-
- //Pick music from the current music list.
- getMusicManager()->pickMusic();
+ 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;
}
}
//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,background,screen,NULL);
//Draw the title.
applySurface(90,40,title,screen,NULL);
//Draw the menu entries.
for(unsigned int i=0;i<5;i++){
applySurface((800-entries[i]->w)/2,200+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=(800-entries[highlight-1]->w)/2-(25-abs(animation)/2)-entries[5]->w;
int y=136+64*highlight+(64-entries[5]->h)/2;
applySurface(x,y,entries[5],screen,NULL);
//Draw the '<' sign, which is entry 6.
x=(800-entries[highlight-1]->w)/2+entries[highlight-1]->w+(25-abs(animation)/2);
y=136+64*highlight+(64-entries[6]->h)/2;
applySurface(x,y,entries[6],screen,NULL);
}
}
/////////////////////////OPTIONS_MENU//////////////////////////////////
//Some varables for the options.
static bool sound,music,fullscreen,leveltheme,internet;
static string themeName;
static bool useProxy;
static string internetProxy;
static bool restartFlag;
Options::Options(){
//Load the background image.
background=loadImage(getDataPath()+"gfx/menu/background.png");
//Render the title.
SDL_Color black={0,0,0};
title=TTF_RenderText_Blended(fontTitle,"Settings",black);
//Set some default settings.
music=getSettings()->getBoolValue("music");
sound=getSettings()->getBoolValue("sound");
fullscreen=getSettings()->getBoolValue("fullscreen");
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;
//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(150,150,240,36,GUIObjectCheckBox,"Music",music?1:0);
obj->name="chkMusic";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(150,190,240,36,GUIObjectCheckBox,"Sound",sound?1:0);
obj->name="chkSound";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(150,230,240,36,GUIObjectCheckBox,"Fullscreen",fullscreen?1:0);
obj->name="chkFullscreen";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(150,270,240,36,GUIObjectLabel,"Theme:");
obj->name="theme";
GUIObjectRoot->childControls.push_back(obj);
//Create the theme option gui element.
theme=new GUISingleLineListBox(370,270,300,36);
theme->name="lstTheme";
vector<string> v=enumAllDirs(getUserPath()+"themes/");
for(vector<string>::iterator i = v.begin(); i != v.end(); ++i){
themeLocations[*i]=getUserPath()+"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;
+ int value=-1;
for(vector<string>::iterator i = v.begin(); i != v.end(); ++i){
if(themeLocations[*i]==themeName) {
- value=i - v.begin();
+ value=i-v.begin();
}
}
theme->item=v;
- if(value == -1)
- value=theme->item.size() - 1;
+ 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(150,310,240,36,GUIObjectCheckBox,"Level themes",leveltheme?1:0);
obj->name="chkLeveltheme";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(150,350,240,36,GUIObjectCheckBox,"Internet",internet?1:0);
obj->name="chkInternet";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
//new: proxy settings
obj=new GUIObject(150,390,240,36,GUIObjectLabel,"Internet proxy");
obj->name="chkProxy";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(370,390,300,36,GUIObjectTextBox,internetProxy.c_str());
obj->name="txtProxy";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
//new: key settings
obj=new GUIObject(150,430,240,36,GUIObjectButton,"Config Keys");
obj->name="cmdKeys";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
//Reset progress settings.
obj=new GUIObject(410,430,240,36,GUIObjectButton,"Clear Progress");
obj->name="cmdReset";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(100,520,284,36,GUIObjectButton,"Cancel");
obj->name="cmdBack";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
obj=new GUIObject(400,520,284,36,GUIObjectButton,"Save Changes");
obj->name="cmdSave";
obj->eventCallback=this;
GUIObjectRoot->childControls.push_back(obj);
}
Options::~Options(){
//Delete the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//And free the title image.
SDL_FreeSurface(title);
}
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.
//And goto the main menu.
setNextState(STATE_MENU);
}else if(name=="cmdSave"){
//Save is pressed thus save
getSettings()->setValue("sound",sound?"1":"0");
getSettings()->setValue("music",music?"1":"0");
getMusicManager()->setEnabled(music);
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);
//Save the key configuration.
inputMgr.saveConfig();
//Save the settings.
saveSettings();
//Before we return show a restart message, if needed.
if(restartFlag)
msgBox("Restart needed before the changes have effect.",MsgBoxOKOnly,"Restart needed");
//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()+"/progress").c_str());
createDirectory((getUserPath()+"/progress").c_str());
#endif
}
return;
}else if(name=="chkMusic"){
music=obj->value?true:false;
}else if(name=="chkSound"){
sound=obj->value?true:false;
}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())!=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();
}
}
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.isKeyDownEvent(INPUTMGR_ESCAPE)){
setNextState(STATE_MENU);
}
}
//Nothing to do here.
void Options::logic(){}
void Options::render(){
//Render the background image.
applySurface(0,0,background,screen,NULL);
//Now render the title.
applySurface((800-title->w)/2,40,title,screen,NULL);
//NOTE: The rendering of the GUI is done in Main.
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Jun 20, 7:28 PM (1 w, 1 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
72982
Default Alt Text
(253 KB)
Attached To
Mode
R79 meandmyshadow
Attached
Detach File
Event Timeline