Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Savegame list fix and filename changes #375

Merged
merged 9 commits into from
Jun 1, 2018
160 changes: 108 additions & 52 deletions FeLib/Source/feio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -933,32 +933,37 @@ struct fileInfo{
char* GameScript = 0; //dummy
int CurrentDungeonIndex = -1;
int CurrentLevelIndex = -1;
festring name=festring(); //TODO this init helps with festring? it is buggy?
festring fullName=festring();
festring fileName=festring(); //TODO this init helps with festring? is it buggy?
festring absFileName=festring(); //contains the full path
festring time=festring();
festring idOnList=festring();
festring dungeonID=festring();
festring nameAutoSave=festring();
festring fileNameAutoSave=festring();
std::vector<festring> vBackups;
bool bIsBkp = false;
};
std::vector<fileInfo> vFiles;
bool addFileInfo(char* c){
bool addFileInfo(const char* c){
// skippers
if(strcmp(c,".")==0)return false;
if(strcmp(c,"..")==0)return false;

// do add
fileInfo fi;
fi.name<<c; //TODO this assigning helps with festring instead of '=', it is buggy?
fi.fileName<<c; //TODO this assigning helps with festring instead of '=', it is buggy?
vFiles.push_back(fi); //stores a copy

return true; //added
}
skipseeksave skipSeek=NULL;
void iosystem::SetSkipSeekSave(skipseeksave sss){skipSeek = sss;}
/* DirectoryName is the directory where the savefiles are located. Returns
the selected file or "" if an error occures or if no files are found. */

/**
* DirectoryName is the directory where the savefiles are located. Returns
* the selected file or "" if an error occures or if no files are found.
*
* returns the savegame basename (w/o path)
*/
festring iosystem::ContinueMenu(col16 TopicColor, col16 ListColor,
cfestring& DirectoryName, const int iSaveFileVersion, bool bAllowImportOldSavegame)
{
Expand Down Expand Up @@ -1024,70 +1029,121 @@ festring iosystem::ContinueMenu(col16 TopicColor, col16 ListColor,
}

/////////////////////////////// sort work //////////////////////////////////////

// /**
// * this is rerquired when there is only one savegame and it is just an ...AutoSave.sav
// */
// static festring fsLastChangeDetector="_Last_Change_Detector_.sav";
// addFileInfo(fsLastChangeDetector.CStr());

int iTmSz=100;
struct stat attr;
for(int i=0;i<vFiles.size();i++)
{
vFiles[i].fullName<<DirectoryName<<"/"<<vFiles[i].name;
if(stat(vFiles[i].fullName.CStr(), &attr)<0)ABORT("stat() failed: %s %s",vFiles[i].fullName.CStr(),std::strerror(errno));
vFiles[i].absFileName<<DirectoryName<<"/"<<vFiles[i].fileName;
if(stat(vFiles[i].absFileName.CStr(), &attr)<0)ABORT("stat() failed: %s %s",vFiles[i].absFileName.CStr(),std::strerror(errno));

char cTime[iTmSz];
strftime(cTime,iTmSz,"%Y/%m/%d-%H:%M:%S",localtime(&(attr.st_mtime))); // this format is important for the sorting that is text based
vFiles[i].time<<cTime;
DBG6(cTime,attr.st_mtime,attr.st_ctime,attr.st_size,vFiles[i].fullName.CStr(),attr.st_ino);
DBG6(cTime,attr.st_mtime,attr.st_ctime,attr.st_size,vFiles[i].absFileName.CStr(),attr.st_ino);
}

// sort the vector BY NAME ONLY (to get the dungeon ids easily, numbers come before letters)
std::sort( vFiles.begin(), vFiles.end(),
[ ]( const fileInfo& l, const fileInfo& r ){ return l.name < r.name; }
[ ]( const fileInfo& l, const fileInfo& r ){ return l.fileName < r.fileName; }
);DBGLN;

std::vector<festring> vIds,vInvIds,vBackups;
std::vector<fileInfo> vComponents;
festring autoSaveFound("");
for(int i=0;i<vFiles.size();i++)
int iPrepareSavFileIndex=-1;
int iAutoSaveSavFileIndex=-1;
std::string sPrettyNamePrevious="";
for(int i=0;i<vFiles.size();) //no i++ here!!!
{
DBGSC(vFiles[i].name.CStr());

if(vFiles[i].name.Find(".wm" ) != vFiles[i].name.NPos){
//skipped from the list
}else
if(vFiles[i].name.Find(".bkp") != vFiles[i].name.NPos){ //skipped from the list
vBackups.push_back(vFiles[i].fullName);
vFiles[i].bIsBkp=true;
}else
if(vFiles[i].name.Find(".AutoSave.") != vFiles[i].name.NPos){ //skipped from the list
if(vFiles[i].name.Find(".AutoSave.sav") != vFiles[i].name.NPos){ // the correct autosave to be returned
autoSaveFound=vFiles[i].name;
DBG2(vFiles[i].fileName.CStr(),sPrettyNamePrevious);

/** caution with this about endless loop! */
bool bNextFile=false;

std::string sPrettyName=vFiles[i].fileName.CStr(); //as pretty as possible... TODO a simple text file containing the correct player name related to the current savegame file
sPrettyName=sPrettyName.substr(0,sPrettyName.find("."));
std::string sPrettyNameWork=sPrettyName;

// if(!sPrettyNamePrevious.empty() && sPrettyNamePrevious!=sPrettyName){
bool bPlayerFilesGroupChanged = !sPrettyNamePrevious.empty() && sPrettyNamePrevious!=sPrettyName;
if(i==vFiles.size()-1 || bPlayerFilesGroupChanged){
/**
* the .sav main file for one player character was not found on the previous loop step
* or it reached the last file w/o finding it
*/
if(iAutoSaveSavFileIndex>-1){
// but has .AutoSave.sav and will use it
iPrepareSavFileIndex=iAutoSaveSavFileIndex;
sPrettyNameWork=sPrettyNamePrevious;
// bNextFile will remain false, so at next loop step it will use the current one again.
}
}

if(iPrepareSavFileIndex==-1){
// if(vFiles[i].fileName == fsLastChangeDetector){
// break;
// }else
if(vFiles[i].fileName.Find(".wm" ) != vFiles[i].fileName.NPos){
//skipped from the list
}else
if(vFiles[i].fileName.Find(".bkp") != vFiles[i].fileName.NPos){ //skipped from the list
vBackups.push_back(vFiles[i].absFileName);
vFiles[i].bIsBkp=true;
}else
if(vFiles[i].fileName.Find(".AutoSave.") != vFiles[i].fileName.NPos){ //skipped from the list
if(vFiles[i].fileName.Find(".AutoSave.sav") != vFiles[i].fileName.NPos){ // the correct autosave to be returned
autoSaveFound=vFiles[i].fileName;
iAutoSaveSavFileIndex=i;
}
}else
if(vFiles[i].fileName.Find(".sav") != vFiles[i].fileName.NPos){
iPrepareSavFileIndex=i;
}else{ //dungeon IDs TODO this will mess if player's name has dots '.', prevent it during filename creation as spaces are
std::string s=vFiles[i].fileName.CStr();
s=s.substr(s.find(".")+1);
vFiles[i].dungeonID<<festring(s.c_str());
vComponents.push_back(vFiles[i]);
DBG3(vFiles[i].fileName.CStr(),s,vComponents.size());//,dungeonIds.CStr());
}
}else
if(vFiles[i].name.Find(".sav") != vFiles[i].name.NPos){

bNextFile=true;
}

if(iPrepareSavFileIndex>-1){
fileInfo& rfi = vFiles[iPrepareSavFileIndex];

festring id("");

// savegame version (structure taken from game::Load()
inputfile SaveFile(vFiles[i].fullName, 0, false);
SaveFile >> vFiles[i].Version; DBGSI(vFiles[i].Version);
// savegame version (save structure taken from game::Load())
inputfile SaveFile(rfi.absFileName, 0, false);
SaveFile >> rfi.Version; DBGSI(rfi.Version);
if(skipSeek==NULL)ABORT("SkipSeek function not set");
skipSeek(&SaveFile); //DUMMY (for here) binary data skipper
SaveFile >> vFiles[i].CurrentDungeonIndex >> vFiles[i].CurrentLevelIndex; DBG2(vFiles[i].CurrentDungeonIndex,vFiles[i].CurrentLevelIndex);
//TODO the current dungeon and many other useful info could be stored in a simple text file just to be used during this game loading list!
SaveFile >> rfi.CurrentDungeonIndex >> rfi.CurrentLevelIndex; DBG2(rfi.CurrentDungeonIndex,rfi.CurrentLevelIndex);
SaveFile.Close();

festring fsVer("");
if(vFiles[i].Version != iSaveFileVersion)
fsVer<<"(v"<<vFiles[i].Version<<") ";
if(rfi.Version != iSaveFileVersion)
fsVer<<"(v"<<rfi.Version<<") ";

if(bSaveGameSortModeByDtTm)
id<<fsVer<<vFiles[i].time<<" ";
id<<fsVer<<rfi.time<<" ";

std::string sname=vFiles[i].name.CStr();
sname=sname.substr(0,sname.find(".")); //prettier name
id<<sname.c_str()<<" ";
id<<sPrettyNameWork.c_str()<<" ";

if(!bSaveGameSortModeByDtTm)
id<<fsVer<<" "; //after to not compromise the alphanumeric default sorting in case user want's to use it

festring currentDungeonLevel("");
currentDungeonLevel << vFiles[i].CurrentDungeonIndex << vFiles[i].CurrentLevelIndex; DBG1(currentDungeonLevel.CStr()); //TODO tricky part if any dungeon or level goes beyond 9 ?
currentDungeonLevel << rfi.CurrentDungeonIndex << rfi.CurrentLevelIndex; DBG1(currentDungeonLevel.CStr()); //TODO tricky part if any dungeon or level goes beyond 9 ?
if(bSaveGameSortModeProgress && !vComponents.empty()){
for(int k=0;k<vComponents.size();k++){
if(k>0)id<<" ";
Expand All @@ -1101,23 +1157,23 @@ festring iosystem::ContinueMenu(col16 TopicColor, col16 ListColor,

if(!autoSaveFound.IsEmpty()){
id<<" has AutoSave!";
vFiles[i].nameAutoSave=autoSaveFound;
rfi.fileNameAutoSave=autoSaveFound;
}

if(vBackups.size()>0){
id<<" has backup(s)!";
vFiles[i].vBackups=vBackups; //makes a copy
rfi.vBackups=vBackups; //makes a copy
}

vFiles[i].idOnList<<id;
rfi.idOnList<<id;

bool bValid=false;
static const int iOldSavegameVersionImportSince=131;
if(!bValid && vFiles[i].Version == iSaveFileVersion)
if(!bValid && rfi.Version == iSaveFileVersion)
bValid=true;
if(!bValid && vFiles[i].Version < iSaveFileVersion)
if(!bValid && rfi.Version < iSaveFileVersion)
if(bAllowImportOldSavegame)
if(vFiles[i].Version >= iOldSavegameVersionImportSince)
if(rfi.Version >= iOldSavegameVersionImportSince)
bValid=true;

if(bValid){
Expand All @@ -1130,13 +1186,13 @@ festring iosystem::ContinueMenu(col16 TopicColor, col16 ListColor,
vComponents.clear();
vBackups.clear();
autoSaveFound.Empty();
}else{ //dungeon IDs TODO this will mess if player's name has dots '.', deny it during new game
std::string s=vFiles[i].name.CStr();
s=s.substr(s.find(".")+1);
vFiles[i].dungeonID<<festring(s.c_str());
vComponents.push_back(vFiles[i]);
DBG3(vFiles[i].name.CStr(),s,vComponents.size());//,dungeonIds.CStr());
iPrepareSavFileIndex=-1;
iAutoSaveSavFileIndex=-1;
}

sPrettyNamePrevious=sPrettyName;

if(bNextFile)i++;
}

// this will sort the ids alphabetically even if by time (so will sort the time as a string)
Expand Down Expand Up @@ -1168,12 +1224,12 @@ festring iosystem::ContinueMenu(col16 TopicColor, col16 ListColor,
festring chosen;chosen<<List.GetEntry(Check);
for(int i=0;i<vFiles.size();i++){
if(chosen == vFiles[i].idOnList){
if(!vFiles[i].nameAutoSave.IsEmpty()){
if(!vFiles[i].fileNameAutoSave.IsEmpty()){
/**
* autosaves are old and apparently a safe thing,
* therefore will be preferred as restoration override.
*/
return vFiles[i].nameAutoSave;
return vFiles[i].fileNameAutoSave;
}else{
/**
* Backups are a fallback mainly when a crash happens like this:
Expand Down Expand Up @@ -1216,7 +1272,7 @@ festring iosystem::ContinueMenu(col16 TopicColor, col16 ListColor,
}
}

return vFiles[i].name;
return vFiles[i].fileName;
}
}
ABORT("failed to match chosen file with id %s",chosen.CStr()); //shouldnt happen
Expand Down
56 changes: 37 additions & 19 deletions Main/Source/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -628,10 +628,13 @@ void FantasyName(festring& rfsName){ DBG2(rfsName.CStr(),ivanconfig::GetFantasyN
rfsName << gen.toString().c_str(); DBG1(rfsName.CStr());
}

truth game::Init(cfestring& Name)
{ DBG2(Name.CStr(),ivanconfig::GetDefaultName().CStr());
if(Name.IsEmpty())
{
truth game::Init(cfestring& loadBaseName)
{
festring absLoadNameOk;

if(!loadBaseName.IsEmpty()){
absLoadNameOk = SaveName(loadBaseName); //will prepend the path
}else{
if(ivanconfig::GetDefaultName().IsEmpty())
{
PlayerName.Empty();
Expand All @@ -645,9 +648,9 @@ truth game::Init(cfestring& Name)
}
else
PlayerName = ivanconfig::GetDefaultName();

absLoadNameOk = SaveName(); //default is to use PlayerName
}
else
PlayerName = Name;

#ifdef WIN32
_mkdir("Save");
Expand All @@ -669,8 +672,8 @@ truth game::Init(cfestring& Name)
DangerFound = 0;
CausePanicFlag = false;

bool bSuccess=false;
switch(Load(SaveName(PlayerName)))
bool bSuccess=false; DBG3(PlayerName.CStr(),loadBaseName.CStr(),absLoadNameOk.CStr());
switch(Load(absLoadNameOk))
{
case LOADED:
{
Expand Down Expand Up @@ -2968,9 +2971,9 @@ truth game::Save(cfestring& SaveName)
return true;
}

int game::Load(cfestring& SaveName)
int game::Load(cfestring& saveName)
{DBGLN;
inputfile SaveFile(SaveName + ".sav", 0, false);
inputfile SaveFile(saveName + ".sav", 0, false);

if(!SaveFile.IsOpen())
return NEW_GAME;
Expand Down Expand Up @@ -3033,13 +3036,13 @@ int game::Load(cfestring& SaveName)

if(InWilderness)
{
SetCurrentArea(LoadWorldMap(SaveName));
SetCurrentArea(LoadWorldMap(saveName));
CurrentWSquareMap = WorldMap->GetMap();
igraph::CreateBackGround(GRAY_FRACTAL);
}
else
{
SetCurrentArea(CurrentLevel = GetCurrentDungeon()->LoadLevel(SaveName, CurrentLevelIndex));
SetCurrentArea(CurrentLevel = GetCurrentDungeon()->LoadLevel(saveName, CurrentLevelIndex));
CurrentLSquareMap = CurrentLevel->GetMap();
igraph::CreateBackGround(*CurrentLevel->GetLevelScript()->GetBackGroundType());
}
Expand Down Expand Up @@ -3068,14 +3071,29 @@ festring game::SaveName(cfestring& Base)
{
festring SaveName = GetSaveDir();

if(!Base.GetSize())
SaveName << PlayerName;
else
SaveName << Base;
/**
* Base must come OK, will just prepend directory,
* the problem on modifying it is that as it is read from the filesystem
* it will not be found if it gets changed...
*/
festring BaseOk;
BaseOk << Base; DBG2(PlayerName.CStr(),Base.CStr());

if(Base.GetSize()==0){
BaseOk.Empty();
BaseOk << PlayerName; //ATTENTION! festring reuses the internal festring data pointer of other festring when using operator= even to char* !

for(festring::sizetype c = 0; c < BaseOk.GetSize(); ++c){
// this prevents all possibly troublesome characters in all OSs
if(BaseOk[c]>=0x41 && BaseOk[c]<=0x5A)continue; //A-Z
if(BaseOk[c]>=0x61 && BaseOk[c]<=0x7A)continue; //a-z
if(BaseOk[c]>=0x30 && BaseOk[c]<=0x39)continue; //0-9

BaseOk[c] = '_';
}
}DBG3(PlayerName.CStr(),BaseOk.CStr(),Base.CStr());

for(festring::sizetype c = 0; c < SaveName.GetSize(); ++c)
if(SaveName[c] == ' ') //TODO prevent other possibly troublesome invalid characters like ;:^/\... etc etc, should allow only a-zA-Z0-9
SaveName[c] = '_';
SaveName << BaseOk;

#if defined(WIN32) || defined(__DJGPP__)
if(SaveName.GetSize() > 13)
Expand Down