1
0
mirror of https://github.com/TerryCavanagh/VVVVVV.git synced 2024-06-25 05:58:30 +02:00

Refactor how custom level stats are stored, read, and written

There were a few problems with the old way of doing things:

(1) Level stats were an ad-hoc object. Basically, it's an object whose
attributes are stored in separate arrays, instead of being an actual
object with its attributes stored in one array.
(2) Level filenames with pipes in them could cause trouble. This is
because the filename attribute array was stored in the XML by being
separated by pipes.
(3) There was an arbitrary limit of only having 200 level stats, for
whatever reason.

To remedy this issue, I've made a new struct named CustomLevelStat that
is a proper object. The separate attribute arrays have been replaced
with a proper vector, which also doesn't have a size limit.

For compatibility with versions 2.2 and below, I've kept being able to
read the old format. This only happens if the new format doesn't exist.
However, I also WRITE the old format as well, in case you want to go
back to version 2.2 or below for whatever reason. It's slightly
wasteful to have both, but that way there's no risk of breaking
compatibility.
This commit is contained in:
Misa 2020-06-29 18:39:22 -07:00
parent f64e9237c4
commit 9a008dc77c
2 changed files with 89 additions and 38 deletions

View File

@ -541,13 +541,8 @@ void Game::lifesequence()
void Game::clearcustomlevelstats()
{
//just clearing the arrays
for(int i=0; i<200; i++)
{
customlevelstats[i]="";
customlevelscore[i]=0;
}
numcustomlevelstats=0;
//just clearing the array
customlevelstats.clear();
customlevelstatsloaded=false; //To ensure we don't load it where it isn't needed
}
@ -560,28 +555,24 @@ void Game::updatecustomlevelstats(std::string clevel, int cscore)
clevel = clevel.substr(7);
}
int tvar=-1;
for(int j=0; j<numcustomlevelstats; j++)
for(size_t j=0; j<customlevelstats.size(); j++)
{
if(clevel==customlevelstats[j])
if(clevel==customlevelstats[j].name)
{
tvar=j;
j=numcustomlevelstats+1;
break;
}
}
if(tvar>=0 && cscore > customlevelscore[tvar])
if(tvar>=0 && cscore > customlevelstats[tvar].score)
{
//update existing entry
customlevelscore[tvar]=cscore;
customlevelstats[tvar].score=cscore;
}
else
{
//add a new entry
if(numcustomlevelstats<200)
{
customlevelstats[numcustomlevelstats]=clevel;
customlevelscore[numcustomlevelstats]=cscore;
numcustomlevelstats++;
}
CustomLevelStat levelstat = {clevel, cscore};
customlevelstats.push_back(levelstat);
}
savecustomlevelstats();
}
@ -595,11 +586,15 @@ void Game::loadcustomlevelstats()
if (!FILESYSTEM_loadTiXml2Document("saves/levelstats.vvv", doc))
{
//No levelstats file exists; start new
numcustomlevelstats=0;
customlevelstats.clear();
savecustomlevelstats();
}
else
{
// Old system
std::vector<std::string> customlevelnames;
std::vector<int> customlevelscores;
tinyxml2::XMLHandle hDoc(&doc);
tinyxml2::XMLElement* pElem;
tinyxml2::XMLHandle hRoot(NULL);
@ -616,7 +611,42 @@ void Game::loadcustomlevelstats()
hRoot=tinyxml2::XMLHandle(pElem);
}
// First pass, look for the new system of storing stats
// If they don't exist, then fall back to the old system
for (pElem = hRoot.FirstChildElement("Data").FirstChild().ToElement(); pElem; pElem = pElem->NextSiblingElement())
{
std::string pKey(pElem->Value());
const char* pText = pElem->GetText();
if (pText == NULL)
{
pText = "";
}
if (pKey == "stats")
{
for (tinyxml2::XMLElement* stat_el = pElem->FirstChildElement(); stat_el; stat_el = stat_el->NextSiblingElement())
{
CustomLevelStat stat = {};
if (stat_el->GetText() != NULL)
{
stat.score = atoi(stat_el->GetText());
}
if (stat_el->Attribute("name"))
{
stat.name = stat_el->Attribute("name");
}
customlevelstats.push_back(stat);
}
return;
}
}
// Since we're still here, we must be on the old system
for( pElem = hRoot.FirstChildElement( "Data" ).FirstChild().ToElement(); pElem; pElem=pElem->NextSiblingElement())
{
std::string pKey(pElem->Value());
@ -626,12 +656,6 @@ void Game::loadcustomlevelstats()
pText = "";
}
if (pKey == "numcustomlevelstats")
{
numcustomlevelstats = atoi(pText);
if(numcustomlevelstats>=200) numcustomlevelstats=199;
}
if (pKey == "customlevelscore")
{
std::string TextString = (pText);
@ -640,7 +664,7 @@ void Game::loadcustomlevelstats()
std::vector<std::string> values = split(TextString,',');
for(size_t i = 0; i < values.size(); i++)
{
if(i<200) customlevelscore[i]=(atoi(values[i].c_str()));
customlevelscores.push_back(atoi(values[i].c_str()));
}
}
}
@ -653,11 +677,18 @@ void Game::loadcustomlevelstats()
std::vector<std::string> values = split(TextString,'|');
for(size_t i = 0; i < values.size(); i++)
{
if(i<200) customlevelstats[i]=values[i];
customlevelnames.push_back(values[i]);
}
}
}
}
// If the two arrays happen to differ in length, just go with the smallest one
for (size_t i = 0; i < std::min(customlevelnames.size(), customlevelscores.size()); i++)
{
CustomLevelStat stat = {customlevelnames[i], customlevelscores[i]};
customlevelstats.push_back(stat);
}
}
}
}
@ -678,6 +709,7 @@ void Game::savecustomlevelstats()
tinyxml2::XMLElement * msgs = doc.NewElement( "Data" );
root->LinkEndChild( msgs );
int numcustomlevelstats = customlevelstats.size();
if(numcustomlevelstats>=200)numcustomlevelstats=199;
msg = doc.NewElement( "numcustomlevelstats" );
msg->LinkEndChild( doc.NewText( help.String(numcustomlevelstats).c_str() ));
@ -686,7 +718,7 @@ void Game::savecustomlevelstats()
std::string customlevelscorestr;
for(int i = 0; i < numcustomlevelstats; i++ )
{
customlevelscorestr += help.String(customlevelscore[i]) + ",";
customlevelscorestr += help.String(customlevelstats[i].score) + ",";
}
msg = doc.NewElement( "customlevelscore" );
msg->LinkEndChild( doc.NewText( customlevelscorestr.c_str() ));
@ -695,12 +727,27 @@ void Game::savecustomlevelstats()
std::string customlevelstatsstr;
for(int i = 0; i < numcustomlevelstats; i++ )
{
customlevelstatsstr += customlevelstats[i] + "|";
customlevelstatsstr += customlevelstats[i].name + "|";
}
msg = doc.NewElement( "customlevelstats" );
msg->LinkEndChild( doc.NewText( customlevelstatsstr.c_str() ));
msgs->LinkEndChild( msg );
// New system
msg = doc.NewElement("stats");
tinyxml2::XMLElement* stat_el;
for (size_t i = 0; i < customlevelstats.size(); i++)
{
stat_el = doc.NewElement("stat");
CustomLevelStat& stat = customlevelstats[i];
stat_el->SetAttribute("name", stat.name.c_str());
stat_el->LinkEndChild(doc.NewText(help.String(stat.score).c_str()));
msg->LinkEndChild(stat_el);
}
msgs->LinkEndChild(msg);
if(FILESYSTEM_saveTiXml2Document("saves/levelstats.vvv", doc))
{
printf("Level stats saved\n");
@ -7029,26 +7076,26 @@ void Game::createmenu( enum Menu::MenuName t, bool samemenu/*= false*/ )
{
//This is, er, suboptimal. Whatever, life optimisation and all that
int tvar=-1;
for(int j=0; j<numcustomlevelstats; j++)
for(size_t j=0; j<customlevelstats.size(); j++)
{
if(ed.ListOfMetaData[i].filename.substr(7) == customlevelstats[j])
if(ed.ListOfMetaData[i].filename.substr(7) == customlevelstats[j].name)
{
tvar=j;
j=numcustomlevelstats+1;
break;
}
}
std::string text;
if(tvar>=0)
{
if(customlevelscore[tvar]==0)
if(customlevelstats[tvar].score==0)
{
text = " " + ed.ListOfMetaData[i].title;
}
else if(customlevelscore[tvar]==1)
else if(customlevelstats[tvar].score==1)
{
text = " * " + ed.ListOfMetaData[i].title;
}
else if(customlevelscore[tvar]==3)
else if(customlevelstats[tvar].score==3)
{
text = "** " + ed.ListOfMetaData[i].title;
}

View File

@ -76,6 +76,12 @@ struct MenuStackFrame
enum Menu::MenuName name;
};
struct CustomLevelStat
{
std::string name;
int score; //0 - not played, 1 - finished, 2 - all trinkets, 3 - finished, all trinkets
};
class Game
{
@ -363,9 +369,7 @@ public:
void savecustomlevelstats();
void updatecustomlevelstats(std::string clevel, int cscore);
std::string customlevelstats[200]; //string array containing level filenames
int customlevelscore[200];//0 - not played, 1 - finished, 2 - all trinkets, 3 - finished, all trinkets
int numcustomlevelstats;
std::vector<CustomLevelStat> customlevelstats;
bool customlevelstatsloaded;