Page MenuHomePhabricator (Chris)

No OneTemporary

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

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)

Event Timeline