Page MenuHomePhabricator (Chris)

No OneTemporary

Authored By
Unknown
Size
57 KB
Referenced Files
None
Subscribers
None
diff --git a/src/LevelPack.cpp b/src/LevelPack.cpp
index 81ecb78..99ee47c 100644
--- a/src/LevelPack.cpp
+++ b/src/LevelPack.cpp
@@ -1,698 +1,712 @@
/*
* Copyright (C) 2011-2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "LevelPack.h"
#include "Functions.h"
#include "FileManager.h"
#include "TreeStorageNode.h"
#include "POASerializer.h"
#include "MD5.h"
#include <string.h>
#include <string>
#include <vector>
#include <fstream>
#include <algorithm>
#include <iostream>
using namespace std;
int LevelPack::Level::getMedal(bool arcade, int time, int targetTime, int recordings, int targetRecordings) {
int medal = 1;
if (arcade) {
if (time >= 0 && time >= targetTime)
medal++;
if (recordings >= 0 && recordings >= targetRecordings)
medal++;
} else {
if (time >= 0 && (targetTime < 0 || time <= targetTime))
medal++;
if (recordings >= 0 && (targetRecordings < 0 || recordings <= targetRecordings))
medal++;
}
return medal;
}
int LevelPack::Level::getBetterTime(int newTime) const {
if (!won || time < 0) return newTime;
if (arcade) return std::max(time, newTime);
else return std::min(time, newTime);
}
int LevelPack::Level::getBetterRecordings(int newRecordings) const {
if (!won || recordings < 0) return newRecordings;
if (arcade) return std::max(recordings, newRecordings);
else return std::min(recordings, newRecordings);
}
//This is a special TreeStorageNode which only load node name/value and attributes, early exists when meeting any subnodes.
//This is used for fast loading of levels during game startup.
class LoadAttributesOnlyTreeStorageNode : public TreeStorageNode {
public:
virtual ITreeStorageBuilder* newNode() override {
//Early exit.
return NULL;
}
};
LevelPack::LevelPack():currentLevel(0),loaded(false),levels(),customTheme(false){
//We need to set the pointer to the dictionaryManager to NULL.
dictionaryManager=NULL;
//The type of levelpack is determined in the loadLevels method, but 'fallback' is CUSTOM.
type=CUSTOM;
}
LevelPack::~LevelPack(){
//We call clear, since that already takes care of the deletion, including the dictionaryManager.
clear();
}
void LevelPack::clear(){
currentLevel=0;
loaded=false;
levels.clear();
levelpackDescription.clear();
levelpackPath.clear();
levelProgressFile.clear();
congratulationText.clear();
levelpackMusicList.clear();
//Also delete the dictionaryManager if it isn't null.
if(dictionaryManager){
delete dictionaryManager;
dictionaryManager=NULL;
}
}
bool LevelPack::loadLevels(const std::string& levelListFile){
//We're going to load a new levellist so first clean any existing levels.
clear();
//If the levelListFile is empty we have nothing to load so we return false.
if(levelListFile.empty()){
cerr<<"ERROR: No levellist file given."<<endl;
return false;
}
//Determine the levelpack type.
if(levelListFile.find(getDataPath())==0){
type=MAIN;
}else if(levelListFile.find(getUserPath(USER_DATA)+"levelpacks/")==0){
type=ADDON;
}else{
type=CUSTOM;
}
levelpackPath=pathFromFileName(levelListFile);
//Create input streams for the levellist file.
ifstream level(levelListFile.c_str());
if(!level){
cerr<<"ERROR: Can't load level list "<<levelListFile<<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 "<<levelListFile<<endl;
return false;
}
}
//Check for folders inside the levelpack folder.
{
//Get all the sub directories.
vector<string> v;
v=enumAllDirs(pathFromFileName(levelListFile),false);
//Check if there's a locale folder containing translations.
if(std::find(v.begin(),v.end(),"locale")!=v.end()){
//Folder is present so configure the levelDictionaryManager.
dictionaryManager=new tinygettext::DictionaryManager();
dictionaryManager->set_use_fuzzy(false);
dictionaryManager->add_directory(pathFromFileName(levelListFile)+"locale/");
dictionaryManager->set_charset("UTF-8");
dictionaryManager->set_language(tinygettext::Language::from_name(language));
}else{
dictionaryManager=NULL;
}
//Check for a theme folder.
if(std::find(v.begin(),v.end(),"theme")!=v.end()){
customTheme=true;
}
}
//Look for the name.
{
vector<string> &v=obj.attributes["name"];
if(!v.empty()){
levelpackName=v[0];
}else{
//Name is not defined so take the folder name.
levelpackName=pathFromFileName(levelListFile);
//Remove the last character '/'
levelpackName=levelpackName.substr(0,levelpackName.size()-1);
levelpackName=fileNameFromPath(levelpackName);
}
}
//Look for the description.
{
vector<string> &v=obj.attributes["description"];
if(!v.empty())
levelpackDescription=v[0];
}
//Look for the congratulation text.
{
vector<string> &v=obj.attributes["congratulations"];
if(!v.empty())
congratulationText=v[0];
}
//Look for the music list.
{
vector<string> &v=obj.attributes["musiclist"];
if(!v.empty())
levelpackMusicList=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.empty() && obj1->name=="levelfile"){
Level level;
level.file=obj1->value[0];
level.targetTime=0;
level.targetRecordings=0;
level.arcade = false;
memset(level.md5Digest, 0, sizeof(level.md5Digest));
memset(level.md5InLevelProgress, 0, sizeof(level.md5InLevelProgress));
//The path to the file to open.
//NOTE: In this function we are always loading levels from a level pack, so levelpackPath is always used.
string levelFile=levelpackPath+level.file;
//Open the level file to retrieve the name and target time/recordings.
LoadAttributesOnlyTreeStorageNode obj;
POASerializer objSerializer;
if(objSerializer.loadNodeFromFile(levelFile.c_str(),&obj,true)){
//Get the name of the level.
vector<string>& v=obj.attributes["name"];
if(!v.empty())
level.name=v[0];
//If the name is empty then we set it to the file name.
if(level.name.empty())
level.name=fileNameFromPath(level.file);
//Get the target time of the level.
v=obj.attributes["time"];
if(!v.empty())
level.targetTime=atoi(v[0].c_str());
else
level.targetTime=-1;
//Get the target recordings of the level.
v=obj.attributes["recordings"];
if(!v.empty())
level.targetRecordings=atoi(v[0].c_str());
else
level.targetRecordings=-1;
//Get the arcade property of the level.
v = obj.attributes["arcade"];
if (!v.empty())
level.arcade = atoi(v[0].c_str()) != 0;
else
level.arcade = false;
}
//The default for locked is true, unless it's the first one.
level.locked=!levels.empty();
level.won=false;
level.time=-1;
level.recordings=-1;
//Add the level to the levels.
levels.push_back(level);
}
}
loaded=true;
return true;
}
void LevelPack::loadProgress(){
//Make sure that a levelProgressFile is set.
if(levelProgressFile.empty()){
levelProgressFile=getLevelProgressPath();
}
//Open the file.
ifstream levelProgress;
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.empty() && 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()));
}
if (i->first == "md5") {
if (!Md5::fromString(i->second[0].c_str(), level->md5InLevelProgress)) {
cerr << "ERROR: The MD5 string '" << i->second[0].c_str() << "' is invalid." << endl;
memset(level->md5InLevelProgress, 0, sizeof(level->md5InLevelProgress));
}
}
}
}
}
//NOTE: If the "locked" is true, we recalculate the "locked" property in terms of "won" property,
//fixed the bug that the level locked permanently after reordering in level pack editor
for (unsigned int o = 0; o < levels.size(); o++) {
if (levels[o].locked) {
levels[o].locked = (o == 0 || levels[o - 1].won) ? false : true;
}
}
}
}
void LevelPack::saveLevels(const std::string& levelListFile){
//Get the fileName.
string levelListNew=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;
//Also store the name of the levelpack.
if(!levelpackName.empty())
obj.attributes["name"].push_back(levelpackName);
//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);
//Make sure that there's a music list.
if (!levelpackMusicList.empty())
obj.attributes["musiclist"].push_back(levelpackMusicList);
//Add the levels to the file.
for(unsigned int i=0;i<levels.size();i++){
TreeStorageNode* obj1=new TreeStorageNode;
obj1->name="levelfile";
obj1->value.push_back(fileNameFromPath(levels[i].file));
obj1->value.push_back(levels[i].name);
obj.subNodes.push_back(obj1);
}
//Write the it away.
POASerializer objSerializer;
objSerializer.writeNode(&obj,level,false,true);
}
void LevelPack::updateLanguage(){
if(dictionaryManager!=NULL)
dictionaryManager->set_language(tinygettext::Language::from_name(language));
}
void LevelPack::addLevel(const string& levelFileName,int levelno){
//Fill in the details.
Level level;
if(type!=COLLECTION && !levelpackPath.empty() && levelFileName.compare(0,levelpackPath.length(),levelpackPath)==0){
level.file=fileNameFromPath(levelFileName);
}else{
level.file=levelFileName;
}
level.targetTime=0;
level.targetRecordings=0;
level.arcade = false;
memset(level.md5Digest, 0, sizeof(level.md5Digest));
memset(level.md5InLevelProgress, 0, sizeof(level.md5InLevelProgress));
//Get the name of the level.
LoadAttributesOnlyTreeStorageNode obj;
POASerializer objSerializer;
if(objSerializer.loadNodeFromFile(levelFileName.c_str(),&obj,true)){
//Get the name of the level.
vector<string>& v=obj.attributes["name"];
if(!v.empty())
level.name=v[0];
//If the name is empty then we set it to the file name.
if(level.name.empty())
level.name=fileNameFromPath(levelFileName);
//Get the target time of the level.
v=obj.attributes["time"];
if(!v.empty())
level.targetTime=atoi(v[0].c_str());
else
level.targetTime=-1;
//Get the target recordings of the level.
v=obj.attributes["recordings"];
if(!v.empty())
level.targetRecordings=atoi(v[0].c_str());
else
level.targetRecordings=-1;
//Get the arcade property of the level.
v = obj.attributes["arcade"];
if (!v.empty())
level.arcade = atoi(v[0].c_str()) != 0;
else
level.arcade = false;
}
//Set if it should be locked or not.
level.won=false;
level.time=-1;
level.recordings=-1;
level.locked=levels.empty()?false:true;
//Check if the level should be at the end or somewhere in the middle.
if(levelno<0 || levelno>=int(levels.size())){
levels.push_back(level);
}else{
levels.insert(levels.begin()+levelno,level);
}
//NOTE: We set loaded to true.
loaded=true;
}
void LevelPack::moveLevel(unsigned int level1,unsigned int level2){
if(level1>=levels.size())
return;
if(level2>=levels.size())
return;
if(level1==level2)
return;
levels.insert(levels.begin()+level2,levels[level1]);
if(level2<=level1)
levels.erase(levels.begin()+level1+1);
else
levels.erase(levels.begin()+level1);
}
void LevelPack::saveLevelProgress(){
//Check if the levels are loaded and a progress file is given.
if(!loaded || levelProgressFile.empty())
return;
//Open the progress file.
ofstream levelProgress(processFileName(levelProgressFile).c_str());
if(!levelProgress)
return;
//Open an output stream.
TreeStorageNode node;
//Loop through the levels.
for(unsigned int o=0;o<levels.size();o++){
TreeStorageNode* obj=new TreeStorageNode;
node.subNodes.push_back(obj);
char s[64];
//Set the name of the node.
obj->name="level";
obj->value.push_back(levels[o].file);
//Set the values.
obj->attributes["locked"].push_back(levels[o].locked?"1":"0");
obj->attributes["won"].push_back(levels[o].won?"1":"0");
sprintf(s,"%d",levels[o].time);
obj->attributes["time"].push_back(s);
sprintf(s,"%d",levels[o].recordings);
obj->attributes["recordings"].push_back(s);
obj->attributes["md5"].push_back(Md5::toString(levels[o].md5InLevelProgress));
}
//Create a POASerializer and write away the leve node.
POASerializer objSerializer;
objSerializer.writeNode(&node,levelProgress,true,true);
}
const string& LevelPack::getLevelName(int level){
if(level<0)
level=currentLevel;
return levels[level].name;
}
const unsigned char* LevelPack::getLevelMD5(int level){
if(level<0)
level=currentLevel;
//Check if the md5Digest is not initialized.
bool notInitialized = true;
for (int i = 0; i < 16; i++) {
if (levels[level].md5Digest[i]) {
notInitialized = false;
break;
}
}
//Calculate md5Digest if needed.
if (notInitialized) {
string levelFile = getLevelFile(level);
TreeStorageNode obj;
POASerializer objSerializer;
if (objSerializer.loadNodeFromFile(levelFile.c_str(), &obj, true)) {
obj.name.clear();
obj.calcMD5(levels[level].md5Digest);
} else {
cerr << "ERROR: Failed to load file '" << levelFile << "' for calculating MD5" << endl;
//Fill in a fake MD5
for (int i = 0; i < 16; i++) {
levels[level].md5Digest[i] = 0xCC;
}
}
}
return levels[level].md5Digest;
}
-void LevelPack::getLevelAutoSaveRecordPath(int level,std::string &bestTimeFilePath,std::string &bestRecordingFilePath,bool createPath){
- if(level<0)
- level=currentLevel;
-
- bestTimeFilePath.clear();
- bestRecordingFilePath.clear();
-
+std::string LevelPack::getLevelpackAutoSaveRecordPath(bool createPath) {
//get level pack path.
string levelpackPath = (type == COLLECTION ? std::string() : LevelPack::levelpackPath);
- string s=levels[level].file;
//process level pack name
- for(;;){
- string::size_type lps=levelpackPath.find_last_of("/\\");
- if(lps==string::npos){
+ for (;;){
+ string::size_type lps = levelpackPath.find_last_of("/\\");
+ if (lps == string::npos){
break;
- }else if(lps==levelpackPath.size()-1){
+ } else if (lps == levelpackPath.size() - 1){
levelpackPath.resize(lps);
- }else{
- levelpackPath=levelpackPath.substr(lps+1);
+ } 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());
- //check if it's custom level
+ //over
+ return path;
+}
+
+std::string LevelPack::getLevelAutoSaveRecordPrefix(int level) {
+ if (level<0)
+ level = currentLevel;
+
+ //get level file name
+ string s = levels[level].file;
+
+ //profess file name
{
- string path="%USER%/records/autosave/";
- if(!levelpackPath.empty()){
- path+=levelpackPath;
- path+='/';
- }
- path=processFileName(path);
- if(createPath) createDirectory(path.c_str());
- s=path+s;
+ string::size_type lps = s.find_last_of("/\\");
+ if (lps != string::npos) s = s.substr(lps + 1);
}
+ //over
+ return s;
+}
+
+void LevelPack::getLevelAutoSaveRecordPath(int level,std::string &bestTimeFilePath,std::string &bestRecordingFilePath,bool createPath){
+ if(level<0)
+ level=currentLevel;
+
+ bestTimeFilePath.clear();
+ bestRecordingFilePath.clear();
+
+ string s = getLevelpackAutoSaveRecordPath(createPath) + getLevelAutoSaveRecordPrefix(level);
+
//calculate MD5
s+='-';
s += Md5::toString(getLevelMD5(level));
//over
bestTimeFilePath=s+"-best-time.mnmsrec";
bestRecordingFilePath=s+"-best-recordings.mnmsrec";
}
string LevelPack::getLevelProgressPath(){
if(levelProgressFile.empty()){
levelProgressFile="%USER%/progress/";
//Use the levelpack folder name instead of the levelpack name.
//NOTE: Remove the trailing slash.
string folderName=levelpackPath.substr(0,levelpackPath.size()-1);
folderName=fileNameFromPath(folderName);
//Depending on the levelpack type add a folder.
switch(type){
case MAIN:
levelProgressFile+="main/";
levelProgressFile+=folderName+".progress";
break;
case ADDON:
levelProgressFile+="addon/";
levelProgressFile+=folderName+".progress";
break;
case CUSTOM:
levelProgressFile+="custom/";
levelProgressFile+=folderName+".progress";
break;
case COLLECTION:
//NOTE: For collections we use their name since they don't have a folder.
//FIXME: Make sure the name contains legal characters.
levelProgressFile+=levelpackName+".progress";
break;
}
}
return levelProgressFile;
}
void LevelPack::setLevelName(unsigned int level,const std::string& name){
if(level<levels.size())
levels[level].name=name;
}
const string LevelPack::getLevelFile(int level){
if(level<0)
level=currentLevel;
string levelFile;
if(type!=COLLECTION)
levelFile=levelpackPath+levels[level].file;
else
levelFile=levels[level].file;
return levelFile;
}
const string& LevelPack::getLevelpackPath(){
return levelpackPath;
}
struct LevelPack::Level* LevelPack::getLevel(int level){
if(level<0)
return &levels[currentLevel];
return &levels[level];
}
void LevelPack::resetLevel(int level){
if(level<0)
level=currentLevel;
//Set back to default.
levels[level].locked=(level!=0);
levels[level].won=false;
levels[level].time=-1;
levels[level].recordings=-1;
}
void LevelPack::nextLevel(){
currentLevel++;
}
bool LevelPack::getLocked(unsigned int level){
return levels[level].locked;
}
void LevelPack::setCurrentLevel(unsigned int level){
currentLevel=level;
}
void LevelPack::setLocked(unsigned int level,bool locked){
levels[level].locked=locked;
}
void LevelPack::swapLevel(unsigned int level1,unsigned int level2){
if(level1<levels.size()&&level2<levels.size()){
swap(levels[level1],levels[level2]);
}
}
void LevelPack::removeLevel(unsigned int level){
if(level<levels.size()){
levels.erase(levels.begin()+level);
}
}
diff --git a/src/LevelPack.h b/src/LevelPack.h
index 1c980fa..d9263e5 100644
--- a/src/LevelPack.h
+++ b/src/LevelPack.h
@@ -1,236 +1,242 @@
/*
* Copyright (C) 2011-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LEVELPACK_H
#define LEVELPACK_H
#include <vector>
#include <string>
#include "libs/tinygettext/tinygettext.hpp"
enum LevelPackType{
//Main levelpacks are distibuted along with the game and located in the data path.
MAIN,
//Addon levelpacks are downloaded/added packs which reside in the user data path.
ADDON,
//Custom levelpacks are user made and are located in the
CUSTOM,
//Collection levelpacks can contain levels from different locations.
//This type is used for the Levels and Custom Levels levelpacks.
//NOTE: The levelpackPath is ignored for these type of levelpacks since levels can be anywhere.
COLLECTION
};
class LevelPack{
public:
//A level entry structure.
struct Level{
//The name of the level.
std::string name;
//The filename of the level.
std::string file;
//Boolean if the level is locked.
bool locked;
//Boolean if the level is won.
bool won;
//Boolean if the level is arcade.
bool arcade;
//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.
//If the arcade mode is true, this means the number of collectibles instead.
//When not won the value is -1.
int recordings;
//Integer containing the target recordings to get a medal.
//If the arcade mode is true, this means the target number of collectibles instead.
int targetRecordings;
//MD5 of level node. :/
unsigned char md5Digest[16];
//MD5 in the level progress file.
unsigned char md5InLevelProgress[16];
//Get the medal of current level based on the time/targetTime/recordings/targetRecordings etc of this level.
//Return value: 0=no medal, 1=bronze medal, 2=silver medal, 3=gold medal
int getMedal() const {
if (won) return getMedal(arcade, time, targetTime, recordings, targetRecordings);
else return 0;
}
//Get the medal of current level, providing own time/recordings and assuming "won" is true.
//Return value: 1=bronze medal, 2=silver medal, 3=gold medal
int getMedal(int time, int recordings) const {
return getMedal(arcade, time, targetTime, recordings, targetRecordings);
}
//A generic function to get the modal of current level, assuming "won" is true.
//Return value: 1=bronze medal, 2=silver medal, 3=gold medal
static int getMedal(bool arcade, int time, int targetTime, int recordings, int targetRecordings);
//Get the better time selected from existing best time and the new time.
int getBetterTime(int newTime) const;
//Get the better recordings selected from existing best recordings and the new recordings.
int getBetterRecordings(int newRecordings) const;
};
private:
//Index of the current level.
int currentLevel;
//Boolean if the levels are loaded.
bool loaded;
//Vector containing the filenames of the levels.
std::vector<Level> levels;
//The file name of the level progress.
std::string levelProgressFile;
public:
//The name of the levelpack.
std::string levelpackName;
//The location the levelpack is stored.
std::string levelpackPath;
//A description of the levelpack.
std::string levelpackDescription;
//The type of levelpack.
LevelPackType type;
//The text that will be displayed when the levels are finished.
std::string congratulationText;
//The preferred music list to be used with this levelpack.
std::string levelpackMusicList;
//The dictionaryManager of the levelpack, used to translate strings.
tinygettext::DictionaryManager* dictionaryManager;
//Boolean if the levelpack has a custom theme/partial theme bundled with it.
//NOTE: Themes can't be preloaded since they would be destroyed by the ThemeStack.
bool customTheme;
//Constructor.
LevelPack();
//Destructor.
~LevelPack();
//gettext function
inline tinygettext::DictionaryManager* getDictionaryManager() const{
return dictionaryManager;
}
//Method for updating the language to the configured one.
//NOTE: This is called when changing the translation in the Options menu.
void updateLanguage();
//Adds a level to the levels.
//levelFileName: The filename of the level to add.
//level: The index of the level to add.
void addLevel(const std::string& levelFileName,int levelno=-1);
//Removes a level from the levels.
//level: The index of the level to remove.
void removeLevel(unsigned int level);
//Moves the level to a given index.
//level1: The level to move.
//level2: The destination.
void moveLevel(unsigned int level1,unsigned int level2);
//Swaps two level.
//level1: The first level to swap.
//level2: The second level to swap.
void swapLevel(unsigned int level1,unsigned int level2);
//Get the levelFile for a given level.
//level: The level index to get the levelFile from.
//Returns: String containing the levelFileName (full path to the file).
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] containing the digest.
const unsigned char* getLevelMD5(int level=-1);
//get level's auto-save record path,
//using level's MD5, file name and other information.
void getLevelAutoSaveRecordPath(int level,std::string &bestTimeFilePath,std::string &bestRecordingFilePath,bool createPath);
+ //get levelpack's auto-save record path, ends with '/'.
+ std::string getLevelpackAutoSaveRecordPath(bool createPath);
+
+ //get level's auto-save record file prefix (without path), using level's file name.
+ std::string getLevelAutoSaveRecordPrefix(int level);
+
//Method for getting the path to the progress file.
//Returns: The path + filename to the progress file.
std::string getLevelProgressPath();
//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();
void saveLevels(const std::string& levelListFile);
void saveLevelProgress();
void nextLevel();
};
#endif
diff --git a/src/LevelPlaySelect.cpp b/src/LevelPlaySelect.cpp
index 6b4c48e..e215cd3 100644
--- a/src/LevelPlaySelect.cpp
+++ b/src/LevelPlaySelect.cpp
@@ -1,531 +1,752 @@
/*
* Copyright (C) 2011-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "LevelPlaySelect.h"
#include "GameState.h"
#include "Functions.h"
#include "FileManager.h"
#include "Globals.h"
#include "LevelSelect.h"
#include "GUIObject.h"
#include "GUIListBox.h"
#include "GUIScrollBar.h"
+#include "GUIOverlay.h"
#include "InputManager.h"
#include "ThemeManager.h"
+#include "MD5.h"
#include "SoundManager.h"
#include "StatisticsManager.h"
#include "Game.h"
#include <stdio.h>
#include <string.h>
#include <string>
#include <sstream>
#include <iostream>
+#include <SDL_ttf.h>
+
#include "libs/tinyformat/tinyformat.h"
+class ReplayListOverlay : public GUIOverlay {
+private:
+ GUIListBox *list;
+
+public:
+ ReplayListOverlay(SDL_Renderer &renderer, GUIObject* root, GUIListBox *list)
+ : GUIOverlay(renderer, root), list(list)
+ {
+ }
+
+ void handleEvents(ImageManager& imageManager, SDL_Renderer& renderer) override {
+ GUIOverlay::handleEvents(imageManager, renderer);
+
+ //Do our own stuff.
+ if (!list) return;
+
+ //Check vertical movement
+ if (inputMgr.isKeyDownEvent(INPUTMGR_UP)){
+ isKeyboardOnly = true;
+ list->value--;
+ if (list->value < 0) list->value = 0;
+
+ //FIXME: ad-hoc stupid code
+ list->scrollScrollbar(0xC0000000);
+ list->scrollScrollbar(list->value);
+ } else if (inputMgr.isKeyDownEvent(INPUTMGR_DOWN)){
+ isKeyboardOnly = true;
+ list->value++;
+ if (list->value >= (int)list->item.size()) list->value = list->item.size() - 1;
+
+ //FIXME: ad-hoc stupid code
+ list->scrollScrollbar(0xC0000000);
+ list->scrollScrollbar(list->value);
+ }
+
+ if (isKeyboardOnly && list->eventCallback && inputMgr.isKeyDownEvent(INPUTMGR_SELECT) && list->value >= 0 && list->value<(int)list->item.size()) {
+ list->eventCallback->GUIEventCallback_OnEvent(imageManager, renderer, list->name, list, GUIEventChange); // ???
+ }
+ }
+};
+
/////////////////////LEVEL SELECT/////////////////////
LevelPlaySelect::LevelPlaySelect(ImageManager& imageManager, SDL_Renderer& renderer)
:LevelSelect(imageManager,renderer,_("Select Level")),
levelInfoRender(imageManager,renderer,getDataPath(),*fontText,objThemes.getTextColor(false)){
//Load the play button if needed.
playButtonImage=imageManager.loadTexture(getDataPath()+"gfx/playbutton.png", renderer);
//Create the gui.
createGUI(imageManager,renderer, true);
//Show level list
refresh(imageManager,renderer);
}
LevelPlaySelect::~LevelPlaySelect(){
play=NULL;
+ replayList = NULL;
//Clear the selected level.
if(selectedNumber!=NULL){
delete selectedNumber;
selectedNumber=NULL;
}
}
void LevelPlaySelect::createGUI(ImageManager& imageManager,SDL_Renderer &renderer, bool initial){
//Create the play button.
if(initial){
- play=new GUIButton(imageManager,renderer,SCREEN_WIDTH-240,SCREEN_HEIGHT-60,240,32,_("Play"));
- }else{
- play->left=SCREEN_WIDTH-240;
+ play=new GUIButton(imageManager,renderer,SCREEN_WIDTH-60,SCREEN_HEIGHT-60,-1,32,_("Play"),0,true,true,GUIGravityRight);
+ replayList = new GUIButton(imageManager, renderer, 60, SCREEN_HEIGHT - 60, -1, 32, _("More replays"), 0, true, true, GUIGravityLeft);
+ } else{
+ play->left=SCREEN_WIDTH-60;
play->top=SCREEN_HEIGHT-60;
+ play->width = -1;
+ replayList->left = 60;
+ replayList->top = SCREEN_HEIGHT - 60;
+ play->width = -1;
}
play->name="cmdPlay";
play->eventCallback=this;
play->enabled=false;
- if(initial)
+ replayList->name = "cmdReplayList";
+ replayList->eventCallback = this;
+ replayList->enabled = false;
+ if (initial) {
GUIObjectRoot->addChild(play);
+ GUIObjectRoot->addChild(replayList);
+ }
}
void LevelPlaySelect::refresh(ImageManager& imageManager, SDL_Renderer& renderer, bool /*change*/){
const int m=levels->getLevelCount();
numbers.clear();
levelInfoRender.resetText(renderer, *fontText, objThemes.getTextColor(false));
//Create the non selected number.
if (selectedNumber == NULL){
selectedNumber = new Number(imageManager, renderer);
}
SDL_Rect box={40,SCREEN_HEIGHT-130,50,50};
selectedNumber->init(renderer," ",box);
selectedNumber->setLocked(true);
selectedNumber->setMedal(0);
bestTimeFilePath.clear();
bestRecordingFilePath.clear();
//Disable the play button.
play->enabled=false;
+ replayList->enabled = false;
for(int n=0; n<m; n++){
numbers.emplace_back(imageManager, renderer);
}
for(int n=0; n<m; n++){
SDL_Rect box={(n%LEVELS_PER_ROW)*64+static_cast<int>(SCREEN_WIDTH*0.2)/2,(n/LEVELS_PER_ROW)*64+184,0,0};
numbers[n].init(renderer,n,box);
numbers[n].setLocked(n>0 && levels->getLocked(n));
int medal = levels->getLevel(n)->getMedal();
numbers[n].setMedal(medal);
}
if(m>LEVELS_DISPLAYED_IN_SCREEN){
levelScrollBar->maxValue=(m-LEVELS_DISPLAYED_IN_SCREEN+(LEVELS_PER_ROW-1))/LEVELS_PER_ROW;
levelScrollBar->visible=true;
}else{
levelScrollBar->maxValue=0;
levelScrollBar->visible=false;
}
if (levels->levelpackPath == LEVELS_PATH || levels->levelpackPath == CUSTOM_LEVELS_PATH)
levelpackDescription->caption = _("Individual levels which are not contained in any level packs");
else if (!levels->levelpackDescription.empty())
levelpackDescription->caption = _CC(levels->getDictionaryManager(), levels->levelpackDescription);
else
levelpackDescription->caption = "";
}
void LevelPlaySelect::selectNumber(ImageManager& imageManager, SDL_Renderer& renderer, unsigned int number,bool selected){
if (selected) {
if (number >= 0 && number < levels->getLevelCount()) {
levels->setCurrentLevel(number);
setNextState(STATE_GAME);
}
}else{
displayLevelInfo(imageManager, renderer,number);
}
}
void LevelPlaySelect::checkMouse(ImageManager &imageManager, SDL_Renderer &renderer){
int x,y;
//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={SCREEN_WIDTH-420,SCREEN_HEIGHT-130,372,32};
if(pointOnRect(mouse, box)){
Game::recordFile=bestTimeFilePath;
levels->setCurrentLevel(selectedNumber->getNumber());
setNextState(STATE_GAME);
return;
}
}
if(!bestRecordingFilePath.empty()){
SDL_Rect box={SCREEN_WIDTH-420,SCREEN_HEIGHT-98,372,32};
if(pointOnRect(mouse, box)){
Game::recordFile=bestRecordingFilePath;
levels->setCurrentLevel(selectedNumber->getNumber());
setNextState(STATE_GAME);
return;
}
}
}
//Call the base method from the super class.
LevelSelect::checkMouse(imageManager, renderer);
}
void LevelPlaySelect::displayLevelInfo(ImageManager& imageManager, SDL_Renderer& renderer, int number){
//Update currently selected level
if(selectedNumber==NULL){
selectedNumber=new Number(imageManager, renderer);
}
SDL_Rect box={40,SCREEN_HEIGHT-130,50,50};
if (number >= 0 && number < levels->getLevelCount()) {
selectedNumber->init(renderer, number, box);
selectedNumber->setLocked(false);
//Show level medal
LevelPack::Level *level = levels->getLevel(number);
int medal = level->getMedal();
int time = level->time;
int targetTime = level->targetTime;
int recordings = level->recordings;
int targetRecordings = level->targetRecordings;
selectedNumber->setMedal(medal);
//Check if there is auto-saved 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);
}
}
//If there exists any auto-saved record file, and the MD5 of record is not set,
//then we assume the MD5 is not changed and the record file is updated from old version of MnMS.
if (!bestTimeFilePath.empty() || !bestRecordingFilePath.empty()) {
bool b = true;
for (int i = 0; i < 16; i++) {
if (level->md5InLevelProgress[i]) {
b = false;
break;
}
}
if (b) {
//Just copy level MD5 to md5InLevelProgress.
memcpy(level->md5InLevelProgress, level->md5Digest, sizeof(level->md5Digest));
}
}
//Check if MD5 is changed
bool md5Changed = false;
for (int i = 0; i < 16; i++) {
if (level->md5Digest[i] != level->md5InLevelProgress[i]) {
md5Changed = true;
break;
}
}
//Show best time and recordings
std::string levelTime;
std::string levelRecs;
if (medal){
if (time >= 0) {
if (targetTime >= 0)
levelTime = tfm::format("%-.2fs / %-.2fs", time / 40.0, targetTime / 40.0);
else
levelTime = tfm::format("%-.2fs / -", time / 40.0);
if (md5Changed) {
levelTime += " ";
/// TRANSLATORS: This means best time or recordings are outdated due to level MD5 changed. Please make it short since there are not enough spaces.
levelTime += _("(old)");
}
} else
levelTime.clear();
if (recordings >= 0) {
if (targetRecordings >= 0)
levelRecs = tfm::format("%5d / %d", recordings, targetRecordings);
else
levelRecs = tfm::format("%5d / -", recordings);
if (md5Changed) {
levelRecs += " ";
/// TRANSLATORS: This means best time or recordings are outdated due to level MD5 changed. Please make it short since there are not enough spaces.
levelRecs += _("(old)");
}
} else
levelRecs.clear();
} else{
levelTime = "- / -";
levelRecs = "- / -";
}
//Show the play button.
play->enabled = true;
+ replayList->enabled = true;
//Show level description
levelInfoRender.update(renderer, *fontText, objThemes.getTextColor(false),
_CC(levels->getDictionaryManager(), levels->getLevelName(number)), levelTime, levelRecs);
} else {
levelInfoRender.resetText(renderer, *fontText, objThemes.getTextColor(false));
selectedNumber->init(renderer, " ", box);
selectedNumber->setLocked(true);
selectedNumber->setMedal(0);
bestTimeFilePath.clear();
bestRecordingFilePath.clear();
//Disable the play button.
play->enabled = false;
+ replayList->enabled = false;
}
}
void LevelPlaySelect::handleEvents(ImageManager& imageManager, SDL_Renderer& renderer){
//Call handleEvents() of base class.
LevelSelect::handleEvents(imageManager, renderer);
//Check if the cheat code is input which is used to skip locked level.
//NOTE: The cheat code is NOT in plain text, since we don't want you to find it out immediately.
//NOTE: If you type it wrong, please press a key which is NOT a-z before retype it (as the code suggests).
if (event.type == SDL_KEYDOWN) {
static Uint32 hash = 0;
if (event.key.keysym.sym >= SDLK_a && event.key.keysym.sym <= SDLK_z) {
Uint32 c = event.key.keysym.sym - SDLK_a + 1;
hash = hash * 1296096U + c;
if (hash == 498506457U) {
if (selectedNumber) {
int n = selectedNumber->getNumber();
if (n >= 0 && n < (int)numbers.size() - 1 && numbers[n + 1].getLocked()) {
//unlock the level temporarily
numbers[n + 1].setLocked(false);
//play a sound effect
getSoundManager()->playSound("hit");
//new achievement
statsMgr.newAchievement("cheat");
}
}
hash = 0;
}
} else {
hash = 0;
}
}
if (section == 3) {
//Check focus movement
if (inputMgr.isKeyDownEvent(INPUTMGR_DOWN) || inputMgr.isKeyDownEvent(INPUTMGR_RIGHT)){
isKeyboardOnly = true;
section2++;
} else if (inputMgr.isKeyDownEvent(INPUTMGR_UP) || inputMgr.isKeyDownEvent(INPUTMGR_LEFT)){
isKeyboardOnly = true;
section2--;
}
- if (section2 > 3) section2 = 1;
- else if (section2 < 1) section2 = 3;
+ if (section2 > 4) section2 = 1;
+ else if (section2 < 1) section2 = 4;
//Check if enter is pressed
if (isKeyboardOnly && inputMgr.isKeyDownEvent(INPUTMGR_SELECT) && selectedNumber) {
int n = selectedNumber->getNumber();
if (n >= 0) {
switch (section2) {
case 1:
if (!bestTimeFilePath.empty()) {
Game::recordFile = bestTimeFilePath;
levels->setCurrentLevel(n);
setNextState(STATE_GAME);
}
break;
case 2:
if (!bestRecordingFilePath.empty()) {
Game::recordFile = bestRecordingFilePath;
levels->setCurrentLevel(n);
setNextState(STATE_GAME);
}
break;
case 3:
+ displayReplayList(imageManager, renderer, n);
+ break;
+ case 4:
selectNumber(imageManager, renderer, n, true);
break;
}
}
}
}
}
void LevelPlaySelect::render(ImageManager& imageManager, SDL_Renderer &renderer){
//First let the levelselect render.
LevelSelect::render(imageManager,renderer);
int x,y,dy=0;
//Get the current mouse location.
SDL_GetMouseState(&x,&y);
if(levelScrollBar)
dy=levelScrollBar->value;
//Upper bound of levels we'd like to display.
y+=dy*64;
SDL_Rect mouse={x,y,0,0};
//Show currently selected level (if any)
if(selectedNumber!=NULL){
selectedNumber->show(renderer, 0);
const int num = selectedNumber->getNumber();
bool arcade = false;
//Only show the replay button if the level is completed (won).
if(num>=0 && num<levels->getLevelCount()) {
auto lev = levels->getLevel(num);
arcade = lev->arcade;
if(lev->won){
if(!bestTimeFilePath.empty()){
SDL_Rect r={0,0,32,32};
const SDL_Rect box={SCREEN_WIDTH-408,SCREEN_HEIGHT-130,360,32};
if (isKeyboardOnly ? (section == 3 && section2 == 1) : pointOnRect(mouse, box)){
r.x = 32;
drawGUIBox(box.x, box.y, box.w, box.h, renderer, 0xFFFFFF40);
}
const SDL_Rect dstRect = {SCREEN_WIDTH-80,SCREEN_HEIGHT-130,r.w,r.h};
SDL_RenderCopy(&renderer,playButtonImage.get(),&r, &dstRect);
}
if(!bestRecordingFilePath.empty()){
SDL_Rect r={0,0,32,32};
const SDL_Rect box={SCREEN_WIDTH-408,SCREEN_HEIGHT-98,360,32};
if (isKeyboardOnly ? (section == 3 && section2 == 2) : pointOnRect(mouse, box)){
r.x = 32;
drawGUIBox(box.x, box.y, box.w, box.h, renderer, 0xFFFFFF40);
}
const SDL_Rect dstRect = {SCREEN_WIDTH-80,SCREEN_HEIGHT-98,r.w,r.h};
SDL_RenderCopy(&renderer,playButtonImage.get(),&r, &dstRect);
}
}
}
levelInfoRender.render(renderer, arcade);
}
//Draw highlight for play button.
- if (isKeyboardOnly && play && play->enabled) {
- play->state = (section == 3 && section2 == 3) ? 1 : 0;
+ if (isKeyboardOnly) {
+ if (play && play->enabled) {
+ play->state = (section == 3 && section2 == 4) ? 1 : 0;
+ }
+ if (replayList && replayList->enabled) {
+ replayList->state = (section == 3 && section2 == 3) ? 1 : 0;
+ }
}
}
void LevelPlaySelect::renderTooltip(SDL_Renderer &renderer, unsigned int number, int dy){
if (!toolTip.name || toolTip.number != number) {
//Render the name of the level.
toolTip.name = textureFromText(renderer, *fontText, _CC(levels->getDictionaryManager(), levels->getLevelName(number)), objThemes.getTextColor(true));
toolTip.time=nullptr;
toolTip.recordings=nullptr;
toolTip.number=number;
//The time it took.
if(levels->getLevel(number)->time>0){
toolTip.time = textureFromText(renderer, *fontText,
tfm::format("%-.2fs", levels->getLevel(number)->time / 40.0).c_str(),
objThemes.getTextColor(true));
}
//The number of recordings it took.
if(levels->getLevel(number)->recordings>=0){
toolTip.recordings = textureFromText(renderer, *fontText,
tfm::format("%d", levels->getLevel(number)->recordings).c_str(),
objThemes.getTextColor(true));
}
}
const SDL_Rect nameSize = rectFromTexture(*toolTip.name);
//Now draw a square the size of the three texts combined.
SDL_Rect r=numbers[number].box;
r.y-=dy*64;
if(toolTip.time && toolTip.recordings){
const int recW = textureWidth(*toolTip.recordings);
const int timeW = textureWidth(*toolTip.time);
r.w=(nameSize.w)>(25+timeW+40+recW)?(nameSize.w):(25+timeW+40+recW);
r.h=nameSize.h+5+20;
}else{
r.w=nameSize.w;
r.h=nameSize.h;
}
//Make sure the tooltip doesn't go outside the window.
if(r.y>SCREEN_HEIGHT-200){
r.y-=nameSize.h+4;
}else{
r.y+=numbers[number].box.h+2;
}
if(r.x+r.w>SCREEN_WIDTH-50)
r.x=SCREEN_WIDTH-50-r.w;
//Draw a rectange
Uint32 color=0xFFFFFFFF;
drawGUIBox(r.x-5,r.y-5,r.w+10,r.h+10,renderer,color);
//Calc the position to draw.
SDL_Rect r2=r;
//Now we render the name if the surface isn't null.
if(toolTip.name){
//Draw the name.
applyTexture(r2.x, r2.y, toolTip.name, renderer);
}
//Increase the height to leave a gap between name and stats.
r2.y+=30;
if(toolTip.time){
//Now draw the time.
applyTexture(r2.x,r2.y,levelInfoRender.timeIcon,renderer);
r2.x+=25;
applyTexture(r2.x, r2.y, toolTip.time, renderer);
r2.x+=textureWidth(*toolTip.time)+15;
}
if(toolTip.recordings){
//Now draw the recordings.
if (levels->getLevel(number)->arcade) {
levelInfoRender.collectable.draw(renderer, r2.x - 16, r2.y - 16);
} else {
applyTexture(r2.x, r2.y, levelInfoRender.recordingsIcon, renderer);
}
r2.x+=25;
applyTexture(r2.x, r2.y, toolTip.recordings, renderer);
}
}
void LevelPlaySelect::resize(ImageManager &imageManager, SDL_Renderer &renderer){
//Let the LevelSelect do his stuff.
LevelSelect::resize(imageManager, renderer);
//Now create our gui again.
createGUI(imageManager,renderer, false);
}
void LevelPlaySelect::GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name,GUIObject* obj,int eventType){
//Let the level select handle his GUI events.
LevelSelect::GUIEventCallback_OnEvent(imageManager,renderer,name,obj,eventType);
//Check for the play button.
if(name=="cmdPlay"){
- if(selectedNumber!=NULL){
+ if(selectedNumber){
levels->setCurrentLevel(selectedNumber->getNumber());
setNextState(STATE_GAME);
}
+ } else if (name == "cmdReplayList") {
+ if (selectedNumber){
+ displayReplayList(imageManager, renderer, selectedNumber->getNumber());
+ }
+ } else if (name == "cmdCancel") {
+ if (GUIObjectRoot) {
+ delete GUIObjectRoot;
+ GUIObjectRoot = NULL;
+ }
+ } else if (name == "lstReplays") {
+ //Check which type of event.
+ if (GUIObjectRoot && eventType == GUIEventChange) {
+ if (auto list = dynamic_cast<GUIListBox*>(GUIObjectRoot->getChild("lstReplays"))) {
+ //Make sure an item is selected.
+ if (list->value >= 0 && list->value < (int)list->item.size()) {
+ Game::recordFile = list->item[list->value];
+
+ delete GUIObjectRoot;
+ GUIObjectRoot = NULL;
+ }
+ }
+ }
+ }
+}
+
+void LevelPlaySelect::displayReplayList(ImageManager &imageManager, SDL_Renderer &renderer, int number) {
+ //Get levelpack autosave record path
+ std::string path = levels->getLevelpackAutoSaveRecordPath(false);
+
+ //Get prefix
+ std::string prefix = levels->getLevelAutoSaveRecordPrefix(number);
+
+ //Enumerate and filter replays
+ std::vector<std::string> files = enumAllFiles(path, "mnmsrec");
+ for (int i = files.size() - 1; i >= 0; i--) {
+ if (files[i].size() < prefix.size() || files[i].substr(0, prefix.size()) != prefix) {
+ files.erase(files.begin() + i);
+ }
+ }
+
+ if (files.empty()) {
+ //There are no replays
+ msgBox(imageManager, renderer, _("There are no replays for this level."), MsgBoxOKOnly, _("Error"));
+ return;
+ }
+
+ const std::string levelMD5 = Md5::toString(levels->getLevelMD5(number));
+ const bool levelArcade = levels->getLevel(number)->arcade;
+
+ //Create a root object.
+ GUIObject* root = new GUIFrame(imageManager, renderer, (SCREEN_WIDTH - 600) / 2, (SCREEN_HEIGHT - 500) / 2, 600, 500, _("More replays"));
+
+ GUIListBox* list = new GUIListBox(imageManager, renderer, 40, 80, 520, 340);
+ list->name = "lstReplays";
+ list->eventCallback = this;
+ root->addChild(list);
+
+ SDL_Color color = objThemes.getTextColor(true);
+ SDL_Color grayed = {
+ 128 + color.r / 2,
+ 128 + color.g / 2,
+ 128 + color.b / 2,
+ };
+
+ for (int i = 0, m = files.size(); i < m; i++) {
+ std::string s = files[i].substr(prefix.size() + 1, files[i].size() - prefix.size() - 9);
+
+ std::string version;
+ bool err = false;
+ if (s.size() >= 33 && s[32] == '-') {
+ for (int lp = 0; lp < 32; lp++) {
+ char c = s[lp];
+ if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
+ version.push_back(c);
+ } else if (c >= 'A' && c <= 'F') {
+ version.push_back(c + ('a' - 'A'));
+ } else {
+ err = true;
+ break;
+ }
+ }
+ } else {
+ err = true;
+ }
+
+ size_t lps = s.find_first_of('-');
+ if (err) {
+ if (lps == std::string::npos) version.clear();
+ else version = s.substr(0, lps);
+ }
+ s = s.substr(lps + 1);
+
+ if (s == "best-time") {
+ s = _("Best time");
+ } else if (s == "best-recordings") {
+ if (levelArcade) {
+ s = _("Best collectibles");
+ } else {
+ s = _("Best recordings");
+ }
+ }
+
+ //Create a surface.
+ SurfacePtr surf(createSurface(list->width, 48));
+
+ //Create description text.
+ {
+ SurfacePtr tmp(TTF_RenderUTF8_Blended(fontText, s.c_str(), color));
+ SDL_SetSurfaceBlendMode(tmp.get(), SDL_BLENDMODE_NONE);
+ applySurface(240, 12, tmp.get(), surf.get(), NULL);
+ }
+
+ //Create version text.
+ {
+ std::string ver;
+ if (err) {
+ /// TRANSLATORS: This means the replay file has unknown version (file name doesn't contain MD5).
+ ver = _("Unknown version");
+ } else {
+ ver = (version == levelMD5) ?
+ /// TRANSLATORS: This means the replay file matches the level (different MD5).
+ _("Current version") :
+ /// TRANSLATORS: This means the replay file doesn't match the level (different MD5).
+ _("Outdated version");
+ }
+
+ SurfacePtr tmp(TTF_RenderUTF8_Blended(fontText, ver.c_str(), color));
+ SDL_SetSurfaceBlendMode(tmp.get(), SDL_BLENDMODE_NONE);
+ applySurface(4, version.empty() ? 12 : 2, tmp.get(), surf.get(), NULL);
+ }
+
+ //Create MD5 text.
+ if (!version.empty()) {
+ SurfacePtr tmp(TTF_RenderUTF8_Blended(fontMono, version.c_str(), grayed));
+ SDL_SetSurfaceBlendMode(tmp.get(), SDL_BLENDMODE_NONE);
+ applySurface(4, 24, tmp.get(), surf.get(), NULL);
+ }
+
+ //Done creating surface
+ list->addItem(renderer, path + files[i], textureFromSurface(renderer, std::move(surf)));
+ }
+
+ GUIObject* obj = new GUIButton(imageManager, renderer, 300, 500 - 44, -1, 36, _("Close"), 0, true, true, GUIGravityCenter);
+ obj->name = "cmdCancel";
+ obj->eventCallback = this;
+ root->addChild(obj);
+
+ Game::recordFile.clear();
+
+ (new ReplayListOverlay(renderer, root, list))->enterLoop(imageManager, renderer, true, false);
+
+ if (!Game::recordFile.empty()) {
+ levels->setCurrentLevel(number);
+ setNextState(STATE_GAME);
}
}
diff --git a/src/LevelPlaySelect.h b/src/LevelPlaySelect.h
index 3a2f438..e6dfbb9 100644
--- a/src/LevelPlaySelect.h
+++ b/src/LevelPlaySelect.h
@@ -1,81 +1,87 @@
/*
* Copyright (C) 2011-2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LEVELPLAYSELECT_H
#define LEVELPLAYSELECT_H
#include "GameState.h"
#include "LevelSelect.h"
#include "LevelInfoRender.h"
#include "GameObjects.h"
#include "Player.h"
#include "GUIObject.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;
-
+
+ //And the replay list button.
+ GUIObject* replayList;
+
//Image of a play icon used as button to start replays.
SharedTexture playButtonImage;
//Textures to display level info.
LevelInfoRender levelInfoRender;
std::string bestTimeFilePath;
std::string bestRecordingFilePath;
//Method that will create the GUI elements.
//initial: Boolean if it is the first time the gui is created.
void createGUI(ImageManager& imageManager, SDL_Renderer& renderer, bool initial);
//display level info.
void displayLevelInfo(ImageManager &imageManager, SDL_Renderer &renderer, int number);
+ //display replay list.
+ void displayReplayList(ImageManager &imageManager, SDL_Renderer &renderer, int number);
+
//Check where and if the mouse clicked on a number.
//If so it will start the level.
void checkMouse(ImageManager &imageManager, SDL_Renderer &renderer) override;
public:
//Constructor.
LevelPlaySelect(ImageManager &imageManager, SDL_Renderer &renderer);
//Destructor.
~LevelPlaySelect();
//Inherited from LevelSelect.
void handleEvents(ImageManager& imageManager, SDL_Renderer& renderer) override;
void refresh(ImageManager &imageManager, SDL_Renderer &renderer, bool change = true) override;
void selectNumber(ImageManager &imageManager, SDL_Renderer &renderer, unsigned int number, bool selected) override;
//Inherited from GameState.
void render(ImageManager&imageManager, SDL_Renderer& renderer) override;
//Inherited from GameState.
void resize(ImageManager &imageManager, SDL_Renderer& renderer) override;
//Inherited from LevelSelect.
void renderTooltip(SDL_Renderer& renderer,unsigned int number,int dy) override;
//GUI events will be handled here.
void GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name,GUIObject* obj,int eventType);
};
#endif

File Metadata

Mime Type
text/x-diff
Expires
Sat, May 16, 5:57 AM (11 h, 32 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
63158
Default Alt Text
(57 KB)

Event Timeline