-
Notifications
You must be signed in to change notification settings - Fork 2
Registries
exlib registration is attribute-driven: tag a class, and a single RegisterAll call in your
ModSystem finds and registers every tagged class by reflection. No hand-maintained lists. There
are four registry families - entities, commands, preferences, and config - plus
recipe profiles. This page covers entities, commands and preferences.
The reflection scan tolerates partial load failures (ReflectionScan.GetCandidateTypes), so one
unloadable type can't break registration of the rest.
Registries/Entities/ registers blocks, items, entities and behaviours. The base attribute:
public abstract class RegisterAttribute(string? code = null) : Attribute
{
public string? Code { get; } // override the registry key; default {modid}.{ClassName}
public bool PrefixModId { get; init; } = true; // false -> register under a bare key (replace vanilla)
}Six sealed attributes inherit it, each validating the target's base type:
| Attribute | Target base type |
|---|---|
[BlockRegister] |
Block |
[ItemRegister] |
Item |
[BlockEntityRegister] |
BlockEntity |
[BlockBehaviorRegister] |
BlockBehavior |
[BlockEntityBehaviorRegister] |
BlockEntityBehavior |
[CollectibleBehaviorRegister] |
CollectibleBehavior |
[BlockRegister] // -> "yourmod.BlockPipe"
public partial class BlockPipe : BlockNetworkNode { }
[BlockRegister("pipeStraight")] // -> "yourmod.pipeStraight"
public class BlockPipeStraight : BlockPipe { }
[BlockRegister("MultiblockStructure", PrefixModId = false)] // -> "MultiblockStructure" (replaces vanilla)
public class BlockMultiblock : Block { }
[BlockEntityRegister] // -> "yourmod.BlockEntityPipe" + aliases "yourmod.Pipe","Pipe","pipe"
public class BlockEntityPipe : BlockEntity { }A class named BlockEntityXxx automatically also registers the short-name aliases
{modid}.{Xxx}, {Xxx}, {xxx} (when you don't set an explicit Code), so your JSON can use the
short entityClass.
Register them all from Start:
public static class EntityRegistry
{
public static void RegisterAll(ICoreAPI api, Mod mod, Assembly? asm = null); // default asm = caller's
}public override void Start(ICoreAPI api)
=> EntityRegistry.RegisterAll(api, Mod, GetType().Assembly);Marking a registered block
partiallets the attribute generator surface its JSONattributesas typed members - see that page.
Registries/Commands/ discovers two kinds of class. A top-level command implements
IExCommand; a sub-command that hangs off an existing command implements IExSubCommand.
The Side on each attribute gates registration to Universal / Server / Client.
public interface IExCommand
{
void Register(ICoreAPI api, Mod mod); // build via api.ChatCommands.Create(...)
}
public interface IExSubCommand
{
string ParentName { get; } // existing command to attach to, e.g. "exmod"
void Register(ICoreAPI api, Mod mod, IChatCommand parent); // build via parent.BeginSubCommand(...)
}[SubCommandRegister(Side = EnumAppSide.Server)]
public sealed class StatusSubCommand : IExSubCommand
{
public string ParentName => "exmod";
public void Register(ICoreAPI api, Mod mod, IChatCommand parent)
{
parent.BeginSubCommand("status")
.WithDescription(Lang.Get(mod.Info.ModID + ":command-status-desc"))
.HandleWith(args => /* ... */)
.EndSubCommand();
}
}Register them from Start (or the side-specific start methods):
public static class CommandRegistry
{
public static void RegisterAll(ICoreAPI api, Mod mod, Assembly? asm = null);
}The registry resolves (or creates) each sub-command's parent via api.ChatCommands.GetOrCreate,
so multiple mods can safely add sub-commands to the shared exmod root. See Commands
for the /exmod / .exmod root exlib provides and its built-in sub-commands.
Registries/Preferences/ is a per-player, client-side display-preference store (e.g. a
metric/imperial unit toggle). Implement IExPreference, tag it [PreferenceRegister]:
public interface IExPreference
{
string Key { get; } // lower-case, no spaces: config key + sub-command name + lang stem
IReadOnlyList<string> Options { get; } // allowed values, lower-case
string Default { get; } // must be in Options
void Apply(string value); // push the stored value into live client state
}[PreferenceRegister]
public sealed class MeasurePreference : IExPreference
{
public string Key => "measure";
public IReadOnlyList<string> Options { get; } = ["metric", "imperial"];
public string Default => "metric";
public void Apply(string value) => ExMeasure.System = ExMeasure.Parse(value);
}Wire it up in StartClientSide, after loading the store and before commands register:
public override void StartClientSide(ICoreClientAPI api)
{
ExPreferences.LoadConfig(api);
PreferenceRegistry.RegisterAll(api, Mod, GetType().Assembly);
}The shared store persists to ModConfig/exmod_preferences.json keyed by player UID, applies each
preference on join and when changed:
public static class ExPreferences
{
public const string ConfigFileName = "exmod_preferences.json";
public static void Register(IExPreference preference);
public static IExPreference? Find(string key);
public static void LoadConfig(ICoreAPI api);
public static string GetForPlayer(string playerUid, string key);
public static void SetForPlayer(string playerUid, string key, string value); // store + apply + persist
public static void ApplyForPlayer(string playerUid); // apply all, on join
public static IEnumerable<IExPreference> All { get; }
}The Key doubles as the lang-key stem: "measure" drives command-measure-desc,
pref-measure-label, pref-measure-metric, etc.
public override void Start(ICoreAPI api)
{
EntityRegistry.RegisterAll(api, Mod, GetType().Assembly); // blocks/items/entities/behaviours
CommandRegistry.RegisterAll(api, Mod, GetType().Assembly); // [CommandRegister]/[SubCommandRegister]
YourValues.Load(api); // generated config accessor (Config System)
}
public override void StartClientSide(ICoreClientAPI api)
{
ExPreferences.LoadConfig(api);
PreferenceRegistry.RegisterAll(api, Mod, GetType().Assembly);
}-
Config System -
[ExConfigRegister]and the generated value accessor. -
Source Generators - what
partialblocks and config classes generate. -
Commands - the shared
/exmodroot.
Expanded Library · framework mod for Vintage Story
exlib - Blocks
exlib - Registration
exlib - Utilities
exlib.testing