Using persistent savedata - TiberiumFusion/TTPlugins GitHub Wiki
This section explains how to enable Persistent Savedata for your plugin and provides a crash course in Linq XML.
For a complete demo of a plugin that uses persistent savedata in a meaningful way, please refer to the PersistentSavedataDemo example plugin.
When a user enables a plugin in one of their Tweak Lists, an XML element is created inside that Tweak List to store data for the plugin. During Terraria gameplay, the plugin can modify an in-memory copy of that XML element to read and write new savedata. When the user exits Terraria normally (i.e. the Exit button on the main menu), the in-memory copy of the XML is written back into the Tweak List. In this way, a plugin can store and retrieve persistent data between Terraria launches without needing to manage a custom savedata solution.
Persistent savedata is useful for:
- Storing user preferences, such as the intensity of a configurable speed boost plugin
- Keeping statistics, like tracking the player's lifetime monster kills
- Maintaining state information for the plugin between Terraria launches
NOTE: To remain consistent with how & when Terraria saves things to disk, persistent savedata is only saved when Terraria closes normally. If the user kills the Terraria process or uses Alt+F4, the on-disk persistent savedata will not be updated.
Enabling persistent savedata is very simple. In your plugin's Initialize()
method, set the plugin's HasPersistentSavedata
property to true
. When your plugin's ConfigurationLoaded()
method is called, you can retrieve the persistent savedata from the Configuration.Savedata
property.
In contrast, if HasPersistentSavedata
is set to false, your plugin will receive a blank Configuration.Savedata
element, and any changes you made to that element will not be written back to disk when Terraria closes.
To work with persistent savedata, you will use the the "LINQ to XML" classes in .NET, namely XElement. Your plugin's Configuration.Savedata
property is an XElement that might look something like this:
<Savedata>
<!--This <Savedata> element contains the persistent savedata for this plugin.-->
<!--If you are manually editing this savedata, do NOT modify the top-level <Savedata> element!-->
</Savedata>
That <Savedata>
tag (aka element), plus its content, equals your plugin's Configuration.Savedata
XElement
. You can put anything you want inside the Savedata
element. The data inside will be maintained between Terraria launches.
IMPORTANT: Do not rename the Savedata
object to have a different tag name.
Using Terraria Tweaker 2's plugin manager, the end user can modify the persistent savedata of any plugin at their discretion. In other words, the end user can mismanage and corrupt your plugin's persistent savedata. So, when your plugin reads its persistent savedata, always check for null values and invalid data! Your plugin will crash easily if it doesn't parse the XML in a safe manner.
You don't have to validate the top-level <Savedata>
element. If <Savedata>
element is missing, the plugin framework will generate a new (empty) one before providing it to your plugin.
With Terraria Tweaker 2, you can inspect and modify the persistent savedata stored for each plugin inside your Tweak Lists. This may aid in debugging during plugin development.
Every XML node in an XML tree is an XElement
that you can manipulate.
XElement
has many members, but these the 7 members that you will (probably) commonly use:
-
new XElement(string name, object value)
- Creates a new XElement with the provided name and value.
- For example,
new XElement("SomeData", 123)
will serialize into<SomeData>123</SomeData>
- The
name
parameter is actually anXName
, but you can supply a string that will be converted to anXName
.
-
XElement.Value
- Gets or sets the string value that is inside this
XElement
- Use this for "dead-end"
XElement
s that have plain data inside them.
- Gets or sets the string value that is inside this
-
XElement.Element(string name)
- Retrieves a child
XElement
inside the callingXElement
using the specified child element name. - Returns null if no such element exists.
- The
name
parameter is actually anXName
, but you can supply a string that will be converted to anXName
. - Use this for
XElement
s that have otherXElement
(s) as children.
- Retrieves a child
-
XElement.Elements()
- Returns a collection of all child
XElements
that are inside the callingXElement
. - Use this on
XElement
s that constitute a list or dictionary of otherXElement
s.
- Returns a collection of all child
-
XElement.Add(object content)
- Adds the provided object to the calling
XElement
's children. Typically used to put moreXElement
s inside the callingXElement
. - Use this to add new
XElement
s to anotherXElement
that constitutes a list or dictionary of otherXElement
s.
- Adds the provided object to the calling
-
XElement.SetAttributeValue(string name, object value)
- Sets the value of the attribute specified by name for the calling
XElement
. Will create a new attribute if it does not exist. - The
name
parameter is actually anXName
, but you can supply a string that will be converted to anXName
. - Use this to decorate an
XElement
will a small piece of data, like an ID or type.
- Sets the value of the attribute specified by name for the calling
-
XElement.Attribute(string name)
- Retrieves the
XAttribute
specified by the providedname
on the callingXElement
. - Returns null if no such attribute exists.
- The
name
parameter is actually anXName
, but you can supply a string that will be converted to anXName
. - Use this to check for the existence of an attribute and then retrieve its value (using
XAttribute.Value
).
- Retrieves the
-
Another useful XML class is the
XComment
, which allows you to create XML comments<!--like this-->
. You can addXComments
to anyXElement
usingXElement.Add(XComment)
.
This code will generate some XML:
Configuration.Savedata.RemoveAll();
Configuration.Savedata.Add(new XElement("BoostPower", 10f));
XElement boostTime = new XElement("BoostTime");
boostTime.SetAttributeValue("Time", 50);
Configuration.Savedata.Add(boostTime);
XElement boostItems = new XElement("BoostItems");
int[] ids = new int[] { 1, 2, 7, 41, 3920 };
foreach (int id in ids)
{
XElement item = new XElement("Item");
item.SetAttributeValue("ID", id);
item.SetAttributeValue("Type", id < 1000 ? "Primary" : "Secondary");
boostItems.Add(item);
}
Configuration.Savedata.Add(boostItems);
Here's the XML that is produced:
<Savedata>
<BoostPower>10</BoostPower>
<BoostTime Time="10"/>
<BoostItems>
<Item ID="1" Type="Primary"/>
<Item ID="2" Type="Primary"/>
<Item ID="7" Type="Primary"/>
<Item ID="41" Type="Primary"/>
<Item ID="3920" Type="Secondary"/>
</BoostItems>
</Savedata>
And finally, here's more code which safely reads the above XML:
int BoostTime;
float BoostPower;
List<BoostItem> BoostItems;
class BoostItem
{
public int ID = 0;
public string Type = null;
}
void SafelyLoadXML()
{
BoostPower = 0f;
BoostTime = 0;
BoostItems = new List<BoostItem>();
BoostPower = float.Parse(Configuration.Savedata.Element("BoostPower")?.Value ?? "0");
BoostTime = int.Parse(Configuration.Savedata.Element("BoostTime")?.Value ?? "0");
XElement xmlBoostItems = Configuration.Savedata.Element("BoostItems");
if (xmlBoostItems != null)
{
foreach (XElement xmlBoostItem in xmlBoostItems.Elements())
{
int itemID = 0;
XAttribute idAttr = xmlBoostItem.Attribute("ID");
if (idAttr != null && idAttr.Value != null)
{
if (!int.TryParse(idAttr.Value, out itemID))
continue;
}
else
continue;
string itemType = null;
XAttribute typeAttr = xmlBoostItem.Attribute("Type");
if (typeAttr != null && typeAttr.Value != null)
itemType = typeAttr.Value.ToString();
else
continue;
BoostItems.Add(new BoostItem() { ID = itemID, Type = itemType });
}
}
}
The PersistentSavedataDemo example plugin is a complete demo of how to use persistent savedata in a meaningful way.
Read the Helper methods article to learn about the various helper methods that your plugin can use.