-
Notifications
You must be signed in to change notification settings - Fork 6
en DataModules System
Documentation moved: This page is archived. See the up-to-date guide at innovault.wiki.
DataModuleStoreaggregates and persists multiple versionedDataModulesubclasses; each module is a set of serializable fields.
DataModules splits gameplay data into independent, versioned DataModule subclasses. DataModuleStore reads/writes them as one TagCompound. Mount that tag on ModPlayer, ModSystem, or nested inside SaveContent.
Use when:
- Quest progress, favor, unlock lists per player
- Narrative flags (via
NarrativeProgressDataModule) - World events, tech trees — any modular, versioned, migratable save fields
Don't use when:
- You need a separate
.nbtfile path for mod-global config → SaveContent Save System - Runtime multiplayer state that should not hit disk → SyncVar Network Sync
| DataModules | SaveContent | SyncVar | |
|---|---|---|---|
| Persistence | Yes (consumer writes TagCompound in Terraria save hooks) |
Yes (standalone .nbt files) |
No |
| Shape | Multi-module aggregate, each with own key/version | Single file, multiple instances | Attribute-tagged field sync |
| Typical mount |
ModPlayer / ModSystem
|
SaveMod, custom SaveWorld
|
ModPlayer / NPCs, etc. |
| Best for | Structured gameplay data (quests, favor, narrative) | Cross-world mod settings | Live MP state |
Copy these three pieces to persist a counter in player save data:
// 1) Module
using InnoVault.DataModules;
public sealed class MyQuestData : DataModule { public int QuestStage; }
// 2) ModPlayer store
using Terraria.ModLoader.IO;
public class MyPlayer : ModPlayer {
public DataModuleStore Modules { get; private set; } = new();
public MyQuestData Quest => Modules.Get<MyQuestData>();
public override void SaveData(TagCompound tag) => Modules.SaveData(tag);
public override void LoadData(TagCompound tag) => Modules.LoadData(tag);
}
// 3) Business logic
// Main.LocalPlayer.GetModPlayer<MyPlayer>().Quest.QuestStage++;flowchart LR
MP[ModPlayer / ModSystem] --> Store[DataModuleStore]
Store --> M1[MyQuestData]
Store --> M2[NarrativeProgressDataModule]
Store -->|SaveData / LoadData| Tag[TagCompound]
Tag -->|Terraria save hooks| Save[World / Player save]
Registry[DataModuleRegistry] -.->|restore by key| Store
| Component | Description |
|---|---|
DataModule |
Module base extending VaultType<DataModule>; autoloaded and registered |
DataModuleStore |
Holds module instances per scope; SaveData / LoadData
|
DataModuleRegistry |
Global SaveKey → type map |
DataModuleReflector |
Default reflection serialization (internal) |
NarrativeProgressDataModule |
Built-in bridge implementing INarrativeProgressStore
|
| Scope | Mount on | Example |
|---|---|---|
| Per player |
ModPlayer + SaveData / LoadData
|
Quests, favor, personal unlocks |
| Per world |
ModSystem world save hooks |
World boss flags, global events |
| Mod-global | Nested in SaveMod tag or dedicated ModSystem
|
Cross-character stats (uncommon) |
Minimal module + ModPlayer save (see 5-minute example above). Key points:
- Modules need a public parameterless constructor
-
SaveKeydefaults toModName/TypeName— no short class names across mods -
DataModuleStoredoes not bind to a persistence location
public sealed class MyQuestData : DataModule
{
public bool FirstMet;
public int QuestStage;
[DataModuleName("state", "OldState")]
public int State;
[DataModuleIgnore]
public int RuntimeCache;
}public override void CopyClientState(ModPlayer targetCopy)
=> ((MyPlayer)targetCopy).Modules = Modules.Clone();public class WorldStorySystem : ModSystem
{
public DataModuleStore Modules { get; private set; } = new();
public override void SaveWorldData(TagCompound tag) => Modules.SaveData(tag);
public override void LoadWorldData(TagCompound tag) => Modules.LoadData(tag);
}using InnoVault.DataModules.Integrations;
using InnoVault.Narrative.Services;
public override void OnEnterWorld() {
NarrativeServices.Progress = Player.GetModPlayer<MyPlayer>().Modules.Get<NarrativeProgressDataModule>();
}public sealed class FavorData : DataModule
{
public int GuideFavor;
public List<int> UnlockedNpcIds = [];
}
var favor = Main.LocalPlayer.GetModPlayer<StoryPlayer>().Modules.Get<FavorData>();
favor.GuideFavor += 10;Override SaveData / LoadData without calling base for full control. Compare loadedVersion from save (__v) against Version:
public override int Version => 2;
public override void LoadData(TagCompound tag, int loadedVersion) {
if (loadedVersion < 2) {
// migrate old fields
}
base.LoadData(tag, loadedVersion);
}Override when the module holds mutable reference graphs beyond supported types.
| Member | Description |
|---|---|
Types |
All discovered module types |
EnsureBuilt() |
Lazy-build mapping |
TryCreate(key) |
Create instance by SaveKey |
Built at InnoVault PostSetupContent; cleared on unload. Unregistered types used in Get<T>() log a warning and won't auto-restore on load.
| Member | Description |
|---|---|
SaveKey |
Serialization key, default ModName/TypeName
|
Version |
Module data version, default 1
|
SaveData(tag) |
Write fields (reflection by default) |
LoadData(tag, loadedVersion) |
Read fields |
Reset() |
Reset to defaults |
Clone() |
Deep-copy module instance |
| Method | Description |
|---|---|
Get<T>() |
Get or lazily create module |
TryGet<T>(out T) |
Try get existing (no create) |
GetByKey(string key) |
Lookup by SaveKey |
Add(DataModule module) |
Add instance; SaveKey conflicts logged |
SaveData(tag) |
Write all modules |
LoadData(tag) |
Load all; reset existing first; create missing from registry |
Clone() |
Deep-copy store |
Reset() |
Reset all module fields |
Clear() |
Remove all instances |
Modules |
Read-only collection |
| Attribute | Purpose |
|---|---|
[DataModuleIgnore] |
Exclude from default save |
[DataModuleName("name", "oldAlias")] |
Custom key; reads try Name then Aliases
|
| Type | Notes |
|---|---|
bool, int, long, float, double, string
|
Scalars |
Any enum
|
Stored as int |
Item |
Via ItemIO
|
TagCompound |
Nested tags |
IList<T> / List<T>
|
Lists of the above |
TagCompound (written by consumer ModPlayer etc.)
├─ "__dmVersion" : 1
└─ "YourMod/MyQuestData" : TagCompound
├─ "__v" : 1
├─ "FirstMet" : bool
└─ "state" : int
| Issue | Cause / fix |
|---|---|
| Fields reset to defaults after load | Module not autoloaded or CanLoad() false; check log warnings |
| Two mods overwrite each other | Short class name as SaveKey; use default ModName/TypeName
|
| MP clients out of sync | DataModules is save only; use SyncVar or server-authoritative writes |
| Complex nested objects fail | Beyond reflection support; override SaveData / LoadData
|
| Narrative progress lost on restart |
NarrativeServices.Progress not wired to persistent store |
CopyClientState shares references |
Use Modules.Clone(), not direct assignment |
| Previous | Home | Next |
|---|---|---|
| Actor Entity System | Home | Narrative System |