Shared Data Systems
Overview
Imlight implements the game's original shared data architecture, where client and server load identical archives. The most critical of these shared archives is Root.wad, which contains global game configuration data that both client and server must understand.
WARNING
Imlight does not actively enforce data integrity between the client and server by default. You can enable this flag in Imlight.ini: EnforceRevision = true. If true, clients will be disconnected if their Root.wad revision does not match the server's.
Shared Archive Architecture
Both client and server share not only Root.wad but all zone archives, ensuring identical understanding of:
- Object templates and properties
- Zone layouts and content
- Spell, item, and NPC definitions
Shared Data Access
Imlight has two different systems for accessing shared data:
- The locale wad cache
- The patch server
The first will always be checked first, and the second will be used as a fallback if the locale wad cache does not have the required archive.
Updating Game Revision
Imlight allows you to specify a game revision in Imlight.ini. If you still contain a cache file in your project directory, Imlight will automatically determine if your cached assets are out of date and download the latest versions from the patch server. This happens once on startup.
Initialization Options
For now, Imlight will only download new archives as they are needed. Your players may experience a slight delay the first time they enter a new zone while the server downloads the required zone archive. In the future, Imlight may support preloading all archives on startup.
Root.wad - The Foundation
Root.wad contains the core configuration data that drives the entire game:
Root.wad (Shared Client/Server Data)
├── CantripXPConfig.xml → CantripFactory
├── StateData/ → StateFactory
├── Sigils/ → SigilFactory
├── SpellTemplates/ → SpellFactory
└── ...Root.wad (Shared Client/Server Data)
├── CantripXPConfig.xml → CantripFactory
├── StateData/ → StateFactory
├── Sigils/ → SigilFactory
├── SpellTemplates/ → SpellFactory
└── ...For example, when a player casts a spell, both client and server reference the same spell template from Root.wad, ensuring they agree on requirements, effects, and behavior.
INFO
Interestingly, the server never even sends damage data over the network. Instead, the client just assumes the server will apply the same damage calculation based on the shared spell template.
Of course, you can't use this to cheat. The server will still send the final health value back to the client on the next round, which will correct any discrepancies.
Factory System Abstractions
Imlight provides two base patterns for creating systems that load data from Root.wad.
To add a new data-driven system:
- Choose base class:
RootSingleResourceSingleton<T>orRootDirectoryResourceSingleton<T> - Specify data source: Override
ResourceNameorDirectoryName - Implement loading: Override
AfterLoad()to deserialize and cache data - Provide API: Create static methods for accessing loaded data
Single File Factories
For systems that load one configuration file:
public class CantripFactory : RootSingleResourceSingleton<CantripFactory> {
protected override string ResourceName => "CantripXPConfig.xml";
private readonly static List<SpellTemplate> _myTemplateCache = [];
protected override void AfterLoad() {
var serializer = new BindSerializer();
serializer.Deserialize(Stream.ToArray(), out CantripConfig config);
_myTemplateCache.AddRange(config.Templates);
}
public static CantripsSpellTemplate CreateCantripTemplateFromId(uint templateId) {
var template = _myTemplateCache
.FirstOrDefault(t => t.Id == templateId);
return template as CantripsSpellTemplate;
}
}public class CantripFactory : RootSingleResourceSingleton<CantripFactory> {
protected override string ResourceName => "CantripXPConfig.xml";
private readonly static List<SpellTemplate> _myTemplateCache = [];
protected override void AfterLoad() {
var serializer = new BindSerializer();
serializer.Deserialize(Stream.ToArray(), out CantripConfig config);
_myTemplateCache.AddRange(config.Templates);
}
public static CantripsSpellTemplate CreateCantripTemplateFromId(uint templateId) {
var template = _myTemplateCache
.FirstOrDefault(t => t.Id == templateId);
return template as CantripsSpellTemplate;
}
}Directory-Based Factories
For systems that load multiple related files:
public class StateFactory : RootDirectoryResourceSingleton<StateFactory> {
protected override string DirectoryName => "StateData/";
private static readonly Dictionary<string, ObjStateSet> s_objectStateSets = [];
protected override void AfterLoad() {
foreach (var file in base.Files) {
// Load and cache all state files from directory
}
}
internal static ObjStateSet GetStateSet(string setName) {
return s_objectStateSets.GetValueOrDefault(setName);
}
}public class StateFactory : RootDirectoryResourceSingleton<StateFactory> {
protected override string DirectoryName => "StateData/";
private static readonly Dictionary<string, ObjStateSet> s_objectStateSets = [];
protected override void AfterLoad() {
foreach (var file in base.Files) {
// Load and cache all state files from directory
}
}
internal static ObjStateSet GetStateSet(string setName) {
return s_objectStateSets.GetValueOrDefault(setName);
}
}
Imlight