| Area | Assembly | Status |
|---|---|---|
| Core plugin lifecycle, context, settings, content APIs | ModAPI.dll |
Current |
| Backward-compat game helpers/events used by v1.2 mods | ModAPI.dll |
Current (Deprecated for future major) |
| Sheltered-specific adapters and implementations | ShelteredAPI.dll |
Current |
Docs labeled v1.2 in this repo |
Historical reference | Deprecated where conflicting |
Exact signatures: documentation/API_Signatures_Reference.md.
- Plugin lifecycle and context usage:
documentation/how to develop a plugin.md - Harmony + transpilers:
documentation/how to develop a patch with harmony.md - Transpiler safety/debugging:
documentation/Transpiler_and_Debugging_Guide.md - Loader/runtime architecture:
documentation/ModAPI_Architecture_guide.md - Spine settings UI:
documentation/Spine_Settings_Guide.md - Settings + persistence patterns:
documentation/SETTINGS.md - ShelteredAPI helper surface:
documentation/ShelteredAPI_Guide.md - Actor registry/components/bindings/adapters:
documentation/ShelteredAPI_Characters_Guide.md - Failures and log signatures:
documentation/API_Troubleshooting.md
using ModAPI.Core;
public class MyPlugin : IModPlugin
{
public void Initialize(IPluginContext ctx)
{
ctx.Log.Info("Initialize");
}
public void Start(IPluginContext ctx)
{
ctx.Log.Info("Start");
}
}Register via ContentRegistry in Start(...) (safe lifecycle guidance below).
ItemDefinition exists both in game code and in ModAPI.Content. Use aliases in mod code:
using ContentItemDefinition = ModAPI.Content.ItemDefinition;
using GameItemDefinition = global::ItemDefinition;using ModAPI.Content;
using ContentItemDefinition = ModAPI.Content.ItemDefinition;
public void Start(IPluginContext ctx)
{
var item = new ContentItemDefinition()
.WithId("com.mymod.power_cell")
.WithDisplayNameText("Power Cell")
.WithDescriptionText("A high-capacity energy cell")
.WithCategory(ItemCategory.Normal)
.WithStackSize(10)
.WithScrapValue(5f)
.WithIcon("Assets/Icons/power_cell.png");
var result = ContentRegistry.RegisterItem(item);
if (!result.Success)
{
ctx.Log.Error("Item registration failed: " + result.ErrorMessage);
return;
}
ContentRegistry.RegisterRecipe(
new RecipeDefinition()
.WithId("recipe.power_cell")
.WithResultItem("com.mymod.power_cell")
.WithStation(CraftStation.Workbench)
.WithLevel(1)
.WithCraftTime(45f)
.WithIngredient(VanillaItems.Component, 3)
.WithIngredient(VanillaItems.Metal, 2));
}Use explicit APIs when possible:
.WithDisplayNameKey("mymod.items.power_cell.name").WithDescriptionKey("mymod.items.power_cell.desc").WithDisplayNameText("Power Cell").WithDescriptionText("A high-capacity energy cell")
Backward-compatible behavior:
- Legacy
.WithDisplayName(...)/.WithDescription(...)still work. - Values are treated as keys if they look like keys (
.and no spaces), otherwise treated as literal text. - For literal text, ModAPI generates and registers internal keys before item UI reads localization.
- This prevents vanilla fallback lowercasing issues when key lookup misses.
Use this ordering:
Initialize(...): cache context, wire events, set up state only.Start(...): register items/recipes/patches.
Rationale:
ContentInjectorbootstraps only when managers are ready (ItemManager.InstanceandCraftingManager.Instance).- Definitions registered by
Start(...)are available by the time injector bootstraps. - Registering in constructors is unsafe and can race before loader context exists.
Guaranteed-safe recipe:
- Put all
ContentRegistry.RegisterItem/RegisterRecipe/RegisterCookingRecipecalls inStart(...). - Do not require managers directly in
Start(...); let injector consume registry entries.
Two supported patterns:
- Pattern A:
ModManagerBase<T>auto-controller and auto-load. - Pattern B:
ISettingsProvidermanual provider withSpineSettingsHelper.Scan.
Use A unless you explicitly need B. Full examples are in:
documentation/Spine_Settings_Guide.mddocumentation/SETTINGS.md
using ModAPI.Events;
public void Start(IPluginContext ctx)
{
GameEvents.OnNewDay += day => ctx.Log.Info("Day " + day);
GameEvents.OnSixHourTick += batch => ctx.Log.Info("6h tick seq=" + batch.Sequence);
GameEvents.OnStaggeredTick += batch => ctx.Log.Info("Staggered every " + batch.IntervalHours + "h");
}ShelteredAPI ships additional helpers under existing namespaces (ModAPI.Core, ModAPI.Events).
Example: explicit trigger registration and priority ordering.
using ModAPI.Events;
public void Start(IPluginContext ctx)
{
GameTimeTriggerHelper.RegisterTrigger(
triggerId: "com.mymod.economy.tick",
priority: 50,
cadence: TimeTriggerCadence.SixHour,
callback: batch => ctx.Log.Info("Economy tick " + batch.TotalHours));
}Actor services are exposed through ctx.Actors:
using ModAPI.Actors;
public void Start(IPluginContext ctx)
{
var actor = ctx.Actors.Ensure(new ActorCreateRequest
{
Kind = ActorKind.Faction,
Domain = "com.mymod",
LifecycleState = ActorLifecycleState.Active,
PresenceState = ActorPresenceState.Offscreen,
Flags = ActorFlags.Persistent | ActorFlags.Synthetic
});
}public class SaveState { public int Counter; }
private readonly SaveState _state = new SaveState();
public void Initialize(IPluginContext ctx)
{
ctx.SaveSystem.RegisterModData("state", _state);
}ctx.SaveData("stats", myStats);
if (ctx.LoadData("stats", out MyStats loaded))
{
myStats = loaded;
}- Preferred for mod logs:
ctx.Log.Info/Warn/Error/Debug. - Internal/static logs:
MMLog.WriteInfo,MMLog.WriteWarning,MMLog.WriteError,MMLog.WriteDebug.