2020-09-25 18:08:20 +02:00
|
|
|
#include <SDL.h>
|
|
|
|
#include <tinyxml2.h>
|
|
|
|
|
|
|
|
namespace xml
|
|
|
|
{
|
|
|
|
|
Move all settings to settings.vvv
The game previously did this dumb thing where it lumped in all its
settings with its file that tracked your records and unlocks,
`unlock.vvv`. It wasn't really an issue, until 2.3 came along and added
a few settings, suddenly making a problem where 2.3 settings would be
reset by chance if you decided to touch 2.2.
The solution to this is to move all settings to a new file,
`settings.vvv`. However, for compatibility with 2.2, settings will still
be written to `unlock.vvv`.
The game will prioritize reading from `settings.vvv` instead of
`unlock.vvv`, so if there's a setting that's missing from `unlock.vvv`,
no worries there. But if `settings.vvv` is missing, then it'll read
settings from `unlock.vvv`. As well, if `unlock.vvv` is missing, then
`settings.vvv` will be read from instead (I explicitly tested for this,
and found that I had to write special code to handle this case,
otherwise the game would overwrite the existing `settings.vvv` before
reading from it; kids, make sure to always test your code!).
Closes #373 fully.
2020-11-04 08:11:21 +01:00
|
|
|
// Just get the document, because TinyXML-2 is annoying.
|
|
|
|
// Useful outside of this file.
|
|
|
|
// TODO: But XML handling should really be put in a separate file (maybe this
|
|
|
|
// one renamed?) instead of lumped in with Game.cpp, and when that happens
|
|
|
|
// maybe this will be unexported again?
|
|
|
|
tinyxml2::XMLDocument& get_document(tinyxml2::XMLNode* parent)
|
2020-09-25 18:08:20 +02:00
|
|
|
{
|
|
|
|
return *(parent->GetDocument());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a new element if it doesn't exist. Returns the element.
|
|
|
|
tinyxml2::XMLElement* update_element(tinyxml2::XMLNode* parent, const char* name)
|
|
|
|
{
|
|
|
|
if (parent == NULL)
|
|
|
|
{
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
tinyxml2::XMLDocument& doc = get_document(parent);
|
|
|
|
|
|
|
|
tinyxml2::XMLElement* element = parent->FirstChildElement(name);
|
|
|
|
if (element == NULL)
|
|
|
|
{
|
|
|
|
// It doesn't exist, so create a new one
|
|
|
|
element = doc.NewElement(name);
|
|
|
|
parent->LinkEndChild(element);
|
|
|
|
}
|
|
|
|
|
|
|
|
return element;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Same thing as above, but takes &parent instead of *parent
|
|
|
|
tinyxml2::XMLElement* update_element(tinyxml2::XMLNode& parent, const char* name)
|
|
|
|
{
|
|
|
|
return update_element(&parent, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Same thing as above, but deletes the content inside the element, too.
|
|
|
|
tinyxml2::XMLElement* update_element_delete_contents(tinyxml2::XMLNode* parent, const char* name)
|
|
|
|
{
|
|
|
|
tinyxml2::XMLDocument& doc = get_document(parent);
|
|
|
|
|
|
|
|
tinyxml2::XMLElement* element = update_element(parent, name);
|
|
|
|
|
|
|
|
for (tinyxml2::XMLNode* node = element->FirstChild();
|
|
|
|
node != NULL;
|
|
|
|
/* Increment code handled separately */)
|
|
|
|
{
|
|
|
|
// Unfortunately, element->DeleteNode() is private
|
|
|
|
// Can't just doc.DeleteNode(node) and then go to next,
|
|
|
|
// node->NextSiblingElement() will be NULL.
|
|
|
|
// Instead, store pointer of node we want to delete. Then increment
|
|
|
|
// `node`. And THEN delete the node.
|
|
|
|
tinyxml2::XMLNode* delete_this = node;
|
|
|
|
|
|
|
|
node = node->NextSiblingElement();
|
|
|
|
|
|
|
|
doc.DeleteNode(delete_this);
|
|
|
|
}
|
|
|
|
|
|
|
|
return element;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Call update_element_delete_contents(), then immediately set its value to a
|
|
|
|
// string. Returns the element.
|
|
|
|
tinyxml2::XMLElement* update_tag(tinyxml2::XMLNode* parent, const char* name, const char* value)
|
|
|
|
{
|
|
|
|
tinyxml2::XMLElement* element = update_element_delete_contents(parent, name);
|
|
|
|
|
|
|
|
element->InsertNewText(value);
|
|
|
|
|
|
|
|
return element;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Same as above, but takes an int instead, and automatically converts it to a
|
|
|
|
// string.
|
|
|
|
tinyxml2::XMLElement* update_tag(tinyxml2::XMLNode* parent, const char* name, const int value)
|
|
|
|
{
|
|
|
|
char string[16];
|
|
|
|
SDL_snprintf(string, sizeof(string), "%i", value);
|
|
|
|
|
|
|
|
return update_tag(parent, name, string);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete the declaration, if it exists. Then create a new declaration.
|
|
|
|
// Returns the declaration.
|
|
|
|
tinyxml2::XMLDeclaration* update_declaration(tinyxml2::XMLDocument& doc)
|
|
|
|
{
|
|
|
|
if (doc.FirstChild() != NULL)
|
|
|
|
{
|
|
|
|
tinyxml2::XMLDeclaration* decl = doc.FirstChild()->ToDeclaration();
|
|
|
|
|
|
|
|
if (decl != NULL)
|
|
|
|
{
|
|
|
|
doc.DeleteNode(decl);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
tinyxml2::XMLDeclaration* decl = doc.NewDeclaration();
|
|
|
|
doc.InsertFirstChild(decl);
|
|
|
|
|
|
|
|
return decl;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a new comment if the first child of the node isn't a comment.
|
|
|
|
// Returns the comment.
|
|
|
|
tinyxml2::XMLComment* update_comment(tinyxml2::XMLNode* parent, const char* text)
|
|
|
|
{
|
|
|
|
if (parent == NULL)
|
|
|
|
{
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
tinyxml2::XMLDocument& doc = get_document(parent);
|
|
|
|
|
|
|
|
if (parent->FirstChild() == NULL)
|
|
|
|
{
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
tinyxml2::XMLComment* comment = parent->FirstChild()->ToComment();
|
|
|
|
|
|
|
|
if (comment == NULL)
|
|
|
|
{
|
|
|
|
// It doesn't exist, so create a new one
|
|
|
|
comment = doc.NewComment(text);
|
|
|
|
parent->InsertFirstChild(comment);
|
|
|
|
}
|
|
|
|
|
|
|
|
return comment;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace xml
|