The mod context
Every mod gets a ModContext in Load. It's how the mod registers everything it adds to the game, and what StoneForge uses to take it all back when the mod is switched off.
using StoneForge;
namespace MyMod;
public class MyMod : IStoneMod
{
public void Load(ModContext context)
{
context.Log($"Hello from {context.Name} {context.Manifest.Version}");
}
public void Unload() { }
}
Lifecycle
Load(context) |
Runs once, as the game starts (or when the player switches the mod on). Register items, buffs, skills, objects, hooks, UI and settings here. The game isn't running frames yet: do things that touch the game in events, Tick or UI callbacks. |
Unload() |
The player switched the mod off while the game runs. Undo anything you changed that you didn't register through the context: globals you set, instances you made, files. Everything registered through the context is taken back for you afterwards. Not called when the game quits. |
What's taken back for you: event and script hooks, ITickables, DrawGui handlers, UI and windows, main menu buttons, settings (saved first), and your items, buffs and skills (removed from the game; a save from before still has them for when the mod is back).
Every frame: ITickable
A mod class that implements ITickable is ticked every frame from when it's loaded:
public class MyMod : IStoneMod, ITickable
{
public void Tick(double deltaTime) // seconds since the last frame
{
if (Keyboard.Pressed(Keyboard.F7))
_context.Items.Give(_blade);
}
...
}
Reading keys and the mouse: Keyboard and mouse.
Anything else can be ticked with context.AddTickable(thing) (and stopped with RemoveTickable). Items and buffs that implement ITickable are ticked once they're added.
What the context has
| Member | |
|---|---|
Manifest, Id, Name |
The mod's mod.json. |
Log(text) |
A line in the loader's log, tagged with the mod's name. |
Items, Buffs, Skills, Objects |
Registering items, buffs, skills and game objects, and using them. |
ContentId(key) |
The full id of your content: "mymod:key". |
Settings |
Settings the player can change in the Mods window. |
UI |
Screens to put UI on. |
DrawGui, DrawHud |
Events to draw every frame: over everything, or with the game's HUD under its windows. |
OnScript, OnCode |
Hooks by name. |
LoadSprite, LoadSound |
Pictures and sounds from your Assets folder. |
Files |
Reading and writing files. |
Mods |
The other mods running: see Using other mods. |
Localization |
The mod's translations: see Localization. |
Quests, Contracts |
Custom quests and contracts. |
Dialogues |
NPC dialogues. |
Failed |
An event: one of the mod's handlers threw. See When a handler fails. |
IsContributor |
Whether the current Steam account is in the mod's contributors. |
AddTickable, RemoveTickable |
Ticking things other than the mod class. |
When a handler fails
When one of a mod's handlers throws, StoneForge logs it, shows its error box and carries on without that handler's work. context.Failed tells the mod, the first time each handler throws: where it was ("Tick", "scr_effect_create (script after)"...) and the exception. Use it to look after your players' data: warn them, or keep a copy of their save.
context.Failed += (where, error) =>
{
SaveBackups.Keep("mymod-error");
context.Log($"{where} failed: {error.Message}");
};
It runs on the game thread. A Failed handler that throws is only logged. See also Save backups.
Values of your own: ModData
A mod's own values, kept in what the game keeps (an item's data, the save data, an instance's variables), go under keys only that mod uses: two mods' "kills" are two values, and neither touches the game's.
var mine = item.ModData(context); // an InventoryItem, an Item or a GroundItem
mine["kills"] = mine["kills"].AsInt + 1;
SaveData.ModData(context)["visits"] = 3; // saved with the game
player.Instance.ModData(context)["marked"] = true; // a variable on an instance
| Where | Kept |
|---|---|
InventoryItem.ModData, Item.ModData, GroundItem.ModData |
In the item's data: with it and saved with it wherever it goes (a chest, the ground, a save). |
SaveData.ModData |
In the save data: saved with the game, back when it's loaded. Nothing's kept with no game. |
Instance.ModData |
As variables on the instance, for as long as it's there. Not saved. |
ModData |
|
|---|---|
this[key] |
A value; undefined if it hasn't been set. Setting it sets it. |
Has(key), Remove(key) |
Whether it's set; take it away (a variable is left undefined). |
Keys |
The keys this mod has set here. |
GameKey(key) |
The key as the game holds it: "mymod:kills" in a map, "mymod__kills" as a variable. |
Pictures and sounds
int icon = context.LoadSprite("icon.png"); // Assets\icon.png
int strip = context.LoadSprite("fx/spark.png", frames: 6); // 6 frames side by side
int music = context.LoadSound("theme.ogg"); // an OGG stream
Paths are relative to the mod's Assets folder. The sprite is owned by the mod: loading the same path again reuses it, and it's retired when the mod unloads (don't delete it yourself). Both return -1 if the file is missing or can't be loaded.
Files
Mods can't use System.IO. context.Files is their file access:
var files = context.Files;
string names = files.ReadAllText(files.AssetPath("names.txt"));
files.WriteAllText("notes.txt", "..."); // mods\MyMod\notes.txt
string[] logs = files.ListFiles(files.ModFolder, "*.txt"); // full paths
| Relative paths | In the mod's own folder (mods\<mod>\). |
| Reading | Anywhere in the game's folder (ModFiles.GameFolder) or Stoneshard's data folder (ModFiles.DataFolder, %LOCALAPPDATA%\StoneShard: saves, settings). |
| Writing | Only in the data folder or the mod's own folder: never the game's files, the loader or other mods. |
Exists, DirectoryExists, ReadAllText, ReadAllLines, ReadAllBytes, ListFiles, WriteAllText, AppendAllText, WriteAllBytes, Delete, CreateDirectory. A path outside what's allowed throws UnauthorizedAccessException.
Trusted mods
A mod can ask for full access with "trusted": true in its mod.json: its own DLLs, the whole of .NET (networking, threads, files, reflection) and Game.CallBuiltinUnrestricted. It runs only once the player allows it in the Mods window, warned that it can do anything a program on their PC can. Ask for it only when a mod truly needs it, such as for multiplayer networking.