Skip to content

en DataModules System

HoCha113 edited this page Jun 22, 2026 · 3 revisions

Documentation moved: This page is archived. See the up-to-date guide at innovault.wiki.

Expanded coverage on innovault.wiki

中文版本

DataModules System

DataModuleStore aggregates and persists multiple versioned DataModule subclasses; each module is a set of serializable fields.


What is this? When to use?

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:

vs SaveContent / SyncVar

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

Quick start (5 min)

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++;

Architecture

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
Loading
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

Store scopes

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)

Level 1 — Hello World

Minimal module + ModPlayer save (see 5-minute example above). Key points:

  • Modules need a public parameterless constructor
  • SaveKey defaults to ModName/TypeName — no short class names across mods
  • DataModuleStore does not bind to a persistence location

Level 2 — Common patterns

Field attributes and runtime cache

public sealed class MyQuestData : DataModule
{
    public bool FirstMet;
    public int QuestStage;

    [DataModuleName("state", "OldState")]
    public int State;

    [DataModuleIgnore]
    public int RuntimeCache;
}

Multiplayer: CopyClientState

public override void CopyClientState(ModPlayer targetCopy)
    => ((MyPlayer)targetCopy).Modules = Modules.Clone();

World-level store (ModSystem)

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);
}

Narrative progress bridge

using InnoVault.DataModules.Integrations;
using InnoVault.Narrative.Services;

public override void OnEnterWorld() {
    NarrativeServices.Progress = Player.GetModPlayer<MyPlayer>().Modules.Get<NarrativeProgressDataModule>();
}

Business read/write

public sealed class FavorData : DataModule
{
    public int GuideFavor;
    public List<int> UnlockedNpcIds = [];
}

var favor = Main.LocalPlayer.GetModPlayer<StoryPlayer>().Modules.Get<FavorData>();
favor.GuideFavor += 10;

Level 3 — Advanced

Custom serialization and migration

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);
}

Custom Clone()

Override when the module holds mutable reference graphs beyond supported types.

DataModuleRegistry

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.


API reference

DataModule

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

DataModuleStore

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

Attributes

Attribute Purpose
[DataModuleIgnore] Exclude from default save
[DataModuleName("name", "oldAlias")] Custom key; reads try Name then Aliases

Reflection-supported types

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

Save shape

TagCompound (written by consumer ModPlayer etc.)
 ├─ "__dmVersion" : 1
 └─ "YourMod/MyQuestData" : TagCompound
      ├─ "__v" : 1
      ├─ "FirstMet" : bool
      └─ "state" : int

Common mistakes / FAQ

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

Related modules


Navigation

Previous Home Next
Actor Entity System Home Narrative System

Clone this wiki locally