From 9a008dc77c20e0f372b8a0f3ba19c28a78d16f8c Mon Sep 17 00:00:00 2001 From: Misa Date: Mon, 29 Jun 2020 18:39:22 -0700 Subject: [PATCH] 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. --- desktop_version/src/Game.cpp | 117 ++++++++++++++++++++++++----------- desktop_version/src/Game.h | 10 ++- 2 files changed, 89 insertions(+), 38 deletions(-) diff --git a/desktop_version/src/Game.cpp b/desktop_version/src/Game.cpp index 96ded99f..de3121b7 100644 --- a/desktop_version/src/Game.cpp +++ b/desktop_version/src/Game.cpp @@ -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=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 customlevelnames; + std::vector 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 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 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=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; } diff --git a/desktop_version/src/Game.h b/desktop_version/src/Game.h index 32891871..9001a239 100644 --- a/desktop_version/src/Game.h +++ b/desktop_version/src/Game.h @@ -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 customlevelstats; bool customlevelstatsloaded;