Class ModManager
- Namespace
- GrindFest
- Assembly
- GrindFest.dll
Central mod manager. Handles discovery, deployment, and hot-reload of all mod types.
Currently supports:
- Ink Story mods (.ink files → runtime compilation → world regeneration)
- UI mods (JavaScript/TypeScript → evaluated by the OneJS JSRunner)
Future:
- Prefab mods (Addressable prefab overrides)
- Script mods (C# bot scripts)
Folder structure: persistentDataPath/Mods/GrindFest/Story/ — base story mod (smart-copied from StreamingAssets) persistentDataPath/Mods/[ModName]/Story/ — additional story mods persistentDataPath/Mods/[ModName]/UI/dist/index.js — UI mod entry point (eval'd after app.js) persistentDataPath/Mods/[ModName]/Prefabs/ — .prefab DSL files defining custom prefabs (resolved by InkWorldGenerator) persistentDataPath/Mods/[ModName]/Scripts/ — (future) script mods
In Editor, can optionally use Assets/_GrindFest/Story/ directly (toggle via UseEditorStoryFiles).
[DefaultExecutionOrder(-50)]
public class ModManager : Singleton<ModManager>
- Inheritance
-
objectModManager
- Inherited Members
Fields
- ReopenModBrowserAfterReload
When true, the UI app will auto-open the Mod Browser after the next Runner.Reload(). Set by JS before triggering a reload from the Mod Browser context.
- StoryManifestFileName
Lists the story files a build shipped (.inkdata, one relative path per line). Written by StoryBuildPreprocessor next to those files and read back here, because StreamingAssets on WebGL/Android is a URL: it can be fetched but not enumerated. Both halves must agree on the name, which is why it lives here rather than being spelled out in each place.
Properties
- ActiveStoryDirectory
The active story directory used for compilation. In Editor with UseEditorStoryFiles=true: Assets/_GrindFest/Story/ Otherwise: persistentDataPath/Mods/GrindFest/Story/
- BaseModStoryDirectory
Base mod directory: persistentDataPath/Mods/GrindFest/Story/
- EditorStoryDirectory
Editor source: Assets/_GrindFest/Story/
- LoadedUIMods
Discovered UI mods: modName → full path to index.js
- ModsRootDirectory
Root mods directory: persistentDataPath/Mods/
- StartupCompiledStory
Story compiled at startup with mod knots included. Set during Awake continuation (after async Workshop discovery). StoryManager picks this up via OnStoryRecompiled, or checks it in Awake.
- StreamingAssetsStoryDirectory
StreamingAssets story source: StreamingAssets/Mods/GrindFest/Story/
- TypingsDirectory
Shared typings directory: persistentDataPath/typings/ (sibling to Mods/)
Methods
- CompileStory()
Compile Story.ink from the active directory (with mod injection) and return a Story object. Returns null on compilation failure.
- DeployBaseStoryModAsync()
Deploy base .ink files to Mods/GrindFest/Story/ (persistentDataPath). Uses hash-based detection to avoid overwriting player-modified files. In editor: copies from Assets/_GrindFest/Story/ directly (.ink files). In builds: copies from StreamingAssets/Story/ (.inkdata files, renamed to .ink). StoryBuildPreprocessor uses .inkdata extension to avoid Ink editor auto-compilation.
Asynchronous because on WebGL/Android the shipped files are fetched over HTTP; the boot awaits DeployBaseStoryModAsync() so the story folder is populated before the world generator looks.
- DeployStoryToModsFolder()
Copy .ink files from Assets/_GrindFest/Story/ to persistentDataPath/Mods/GrindFest/Story/. Use this to test the mod pipeline: toggle UseEditorStoryFiles off, then edit files in the Mods folder.
- DiscoverLocalMods()
Synchronously discover mods from the local Mods/ folder. Safe to call from Awake — no async / await needed.
- DiscoverMods()
Discover all mods from local Mods/ folder and Steam Workshop. Populates the Mods list with ModDefinitions. Local mods take priority (if same Name exists in both).
- DiscoverWorkshopMods()
Async — discover Workshop mods from Steam download folder. Skips mods already found locally (local takes priority).
- GetAllModPaths()
Get all mod folder paths (local + Workshop). Used by JS-side loadUIMods to find UI/dist/index.js files.
- RecompileStoryAndReload()
Recompile ink and reload the story + world.
- RefreshAndReload()
Live refresh used by the mod browser after subscribing/unsubscribing to Workshop mods and after the user trusts a blocked mod. Re-runs the boot pipeline in boot order — discover, order, compile (only mods that have not been compiled yet), run entry points (once per mod per session), re-merge content, recompile the story if a mod contributes knots — and then reloads the UI via GrindFest.ModManager._uiModDirty (Runner.Reload() happens safely on the next frame, never while JS code is still on the call stack).
Two things deliberately stay behind a restart, and are reported as such: a compiled assembly cannot be unloaded (a removed mod gets its OnUnload() and its session state is dropped, but code it scheduled elsewhere keeps running until the next boot), and the world is not regenerated here — content definitions apply to future spawns, while the ink/prefab watchers own world regeneration.
Events
- OnCompilationError
Fired when compilation fails. Listeners receive the error message.
- OnStoryRecompiled
Fired when story is recompiled from .ink sources. Listeners receive the compiled Story object.
- OnUIModError
Fired when a UI mod fails to load. Listeners receive (modName, error).
- OnUIModLoaded
Fired when a UI mod is loaded. Listeners receive (modName, modPath).