ModDataStore
Versioned per-savegame data persistence.
What is it for?
Use ModDataStore when your mod needs to save data that belongs to the current world/save, not to a global config:
- Player progression counters.
- World state, territorial ownership, faction treasuries.
- Item/effect cooldowns and status state.
- Any data that must survive server restarts but is tied to a save.
It is server-side only and serializes to JSON inside the savegame.
Quick example
using ArcanumLib.Persistence;
var store = ModDataStore.GetOrCreate<MySaveData>(sapi, "mymod", "state", 1);
store.Data.Counter++;
store.MarkDirty();
store.Save();
Usage
Create a store
var store = ModDataStore.GetOrCreate<MySaveData>(sapi, "mymod", "state", dataVersion: 1);
MySaveData must have a parameterless constructor. dataVersion starts at 1 and is incremented when the data shape changes.
Read and write
store.Load();
store.Data.Counter++;
store.Save();
Data is created lazily. If no save exists, a fresh instance is returned.
Use a custom root type
Useful when the root object is a dictionary:
var store = ModDataStore.GetOrCreate(
sapi, "mymod", "counters", 1,
() => new Dictionary<string, int>());
store.Data["players"]++;
store.Save();
Global API
If ModDataStoreModSystem is loaded, you can omit sapi:
var store = ModDataStore.GetOrCreate<MySaveData>("mymod", "state", 1);
ModSystem integration
public class MyModSystem : ModSystem
{
private IModDataStore<MySaveData>? store;
public override void StartServerSide(ICoreServerAPI sapi)
{
store = ModDataStore.GetOrCreate<MySaveData>(sapi, "mymod", "state", 1);
}
public override void OnSaveGameData()
{
store?.Save();
}
}
Notes
ModDataStoreis server-side only.- In unit tests, register a test
ICoreServerAPIinArcanumServicesunderArcanumServiceScope.Server(e.g.,ArcanumServices.Register<ICoreServerAPI>(sapi, ArcanumServiceScope.Server)) or use the overload that acceptssapidirectly. - Increment
dataVersionwhen the data shape changes; future migrations can check this value. MarkDirty()must be called whenDatais modified, otherwiseSave()is a no-op.IsDirtyreturnstrueif the store has been modified since the last successfulSave().
Migrations
When you bump dataVersion, register a migration to transform the old loaded JSON before it is assigned to Data:
store.RegisterMigration(1, 2, old =>
{
var data = old.ToObject<MySaveData>();
data.NewField = data.OldField;
data.OldField = default;
return JToken.FromObject(data);
});