Persistent Data - MSUTeam/MSU GitHub Wiki
Persistent Data refers to data that the modder and users wish to retain between between campaigns. This includes Mod Settings and Keybinds, but can be extended to other mod functionality as well.
As of MSU 1.3.0, the Persistent Data system no longer requires BBParser and is more flexible, allowing modders to create files with arbitrary data. This data is stored in save files with a specific name format. These do get synced by Steam, and would normally be visible to users as save games, but we filter them out in save/load menu.
Mods are able to create an arbitrary number of files to store their data, but we recommend minimizing the number of files your mod creates. The Persistent Data System is able to reserialize information an arbitrary number of times, and is able to gracefully handle your mod changing serialization methods, so the only time your mod should require more than one file is if you want to allow users to share files. In that case, those additional files should only be created on user request.
In general, when using the persistent data system a mod will use the createFile function and pass in any arbitrary information, and will then be able to retrieve that data using the readFile function.
function createFile( _filename, _data )
// _filename is a string
// _data can be a null, string, integer, float, boolean, array, table (but not a BB class or BB object), or SerializationData instance
Creates a new persistent file with the given _filename
containing a serialized representation of _data
.
Filenames only need to be unique within a specific mod, so two different mods can have the same _filename
without conflicts. This will overwrite existing files with that name.
It is preferable to only make one file for your mod, as this avoids filling up the savegames folders of your users with files. To allow for changing parameters, we recommend to have a central table, which you can then use to save your data.
function readFile( _filename )
// _filename is a string
Returns the data stored in a file using createFile
with the specified _filename
.
This data (except for any SerializationData it contains) should Globals#deepequals the data initially passed to createFile
.
function hasFile( _filename )
// _filename is a string
Return true
if _filename
exists as a persistent data store on the users machine, false
otherwise.
function getFiles()
Returns an array of strings with the names of the files created by your mod using createFile
, these can then be read using readFile
.
function deleteFile( _filename )
// _filename is a string
Permanently removes a file created by your mod with the given _filename
.
local mod = ::MSU.Class.Mod("mod_my_mod", "1.0.0", "My Mod");
local myData = {
my_array = [1, 2, 3];
};
// this will create a file with our mod ID, "mod_my_mod", in the savegame folder.
// you can take any string for the filename, but the ID is a convenient one.
mod.PersistentData.createFile(mod.ID, myData);
...
if (mod.PersistentData.hasFile(mod.ID)) // check if the persistent data file exists
{
local deserialisedData = mod.PersistentData.readFile(mod.ID); // read the stored data: {my_array = [1, 2, 3]}
// do something with the data...
}
::Hooks.register("mod_my_mod", "1.0.0", "My Mod");
local mod = ::MSU.Class.Mod("mod_my_mod", "1.0.0", "My Mod");
local oldData = {
i = 1,
f = 3.14,
s = "astring",
b = true,
n = null,
a = [[null], {}, 2], // arbitrary nesting is permitted
s = ::MSU.Class.SerializationData()
};
local serEmu = oldData.s.getSerializationEmulator() // if we want to serialize a bb object we have to use a SerializationEmulator.
local myItem = ::new("scripts/items/weapons/named/named_sword");
myItem.onSerialize(serEmu);
mod.PersistentData.createFile("MyFile", oldData);
local myFiles = mod.PersistentData.getFiles(); // myFiles should be ["MyFile"]
local readData = mod.PersistentData.readFile("MyFile"); // should normally be prefixed by a hasFile check
// to simplify an equality check we use ::MSU.deepEquals, but for that we need to first remove the instances and compare them separately.
local oldSerData = delete oldData.s;
local readSerData = delete readData.s;
assert(::MSU.deepEquals(oldData, readData));
// create a DeserializationEmulator from the SerializationData
local deserEmu = readSerData.getDeserializationEmulator();
// create a new item to deserialize into
local readItem = ::new("scripts/items/weapons/named/named_sword");
// deserialize the item
readItem.onDeserialize(deserEmu);
assert(::MSU.deepEquals(myItem.m, readItem.m));
The Mod-Settings and Keybinds systems are integrated with PersistentData. If a Mod Setting or Keybind is changed, a command will automatically be printed to the log. This will include the ID of the mod the setting belongs to.
BBParser is deprecated as of 1.3.0 as it is superseded by the above functions
For example,
the user of Plan your Perks
might want to keep his planned builds between campaigns.
It relies on BBParser
to read through the log.html file
and find specific commands.
Parsed commands will be saved in a Battle Brothers\data\mod_config\<modID>\<fileID>.nut
file.
For example: Battle Brothers\data\mod_config\mod_msu\ModSetting.nut
<Mod>.PersistentData.writeToLog( _fileID, _payload )
With this function,
the modder can instruct the PersistentDataSystem
to write a command to the log.
_fileID
is a string variable.
_payload
can be either a string,
or an array of variables that will be transformed to strings.
_fileID
can be any string.
It refers to the type of command.
It is used to identify commands
for the purpose of reading them,
as well as for instructing BBParser to treat the command in a specific way.
At this time, BBParser recognizes the following fileIDs:
-
ModSetting
: Used automatically by the Mod Settings system. -
Keybind
: Used automatically by the Keybinds system. -
String
/*
: Any ID that is not one of the above is treated as a String ID.
The String
ID _payload
parameter takes either a string,
or an array of values of types string, boolean, integer, float
that will be transformed to string.
By default, any string setting will overwrite the settings file of that type.
This will avoid the settings file filling up with functionally equivalent commands.
To demonstrate, here is a possible command:
<Mod>.PersistentData.writeToLog("Greeting", "this.logInfo('Welcome to my mod!')")
When this setting is being read, it will print 'Welcome to my mod!' to the log. Should you then add another command, such as
<Mod>.PersistentData.writeToLog("Greeting", "this.logInfo('Goodbye!');")
then only 'Goodbye!' will print to the log.
To overwrite this behavior,
add :APPEND
to the _settingID
:
<Mod>.PersistentData.writeToLog("Greeting:APPEND", "this.logInfo('Welcome to my mod!');")
<Mod>.PersistentData.writeToLog("Greeting:APPEND", "this.logInfo('Goodbye!');")
Now, both strings will be printed.
:APPEND
will be removed from the ID.
If you pass an array for _payload
, each entry will be added to the file with a linebreak inbetween.
<Mod>.PersistentData.writeToLog("Test", ["array", "of", "strings"]);
will translate to
array
of
strings
Every file can only be read and executed once per game start.
This is a limitation of the include()
function
that is used to read and execute the files.
<Mod>.PersistentData.loadFile( _fileID )
This will load and execute the file with the ID _fileID
belonging to your mod.
<Mod>.PersistentData.loadAllFiles()
This will load and execute every file belonging to your mod.
// this will write the command to the log
myMod.PersistentData.writeToLog("VerboseMode", "this.Const.AI.VerboseMode = true;");
// this will read and execute the command with ID `VerboseMode`
myMod.PersistentData.loadFile("VerboseMode");
// VerboseMode will now be set to 'true'