# CommandHook Server-side mod for Vintage Story. Lets other mods hook into chat commands before and after they run, namespaced per mod so multiple mods can watch the same command without colliding. ## How it works CommandHook patches `ChatCommandApi.Execute` and dispatches to whoever has registered. Your mod doesn't patch anything itself, it just implements `ICommandHookListener` and calls `CommandHookModSystem.Register`. A listener provides: - `ModId`, your mod's id. Used to namespace your registration so it doesn't collide with another mod watching the same command - `Commands`, the command names you want to watch, without the leading slash - `Registration`, a `Before` delegate, an `After` delegate, or both `Before` runs before the command executes. Set `data.Cancel = true` inside it to stop the command from running. If you cancel, any remaining `Before` listeners for that command are skipped, and `After` never fires for that invocation. `After` runs once the command has produced a result. It only fires on the path where the command actually ran. `CommandData` is passed by `ref` the whole way through, no allocation per command. ## Example ```csharp public class MyListener : ICommandHookListener { // Your mod's id, used to namespace this registration. public string ModId => "mymod"; // Commands you want to watch, no leading slash. public IReadOnlyList Commands => new[] { "tp" }; // Wire up Before, After, or both. public CommandRegistration Registration => new(Before, After); private void Before(ref CommandData data) { // data.Sender is null for console invocations. if (data.Sender != null && !IsAllowed(data.Sender)) { // Stops the command from running and skips any remaining // Before listeners. After never fires for this invocation. data.Cancel = true; } } private void After(ref CommandData data, TextCommandResult result) { // Only runs if nothing cancelled. result is whatever the command // actually produced, check result.Status for success/error/deferred. if (result.Status != EnumCommandStatus.Success) Logger.Warn($"/{data.CommandName} failed: {result.StatusMessage}"); } } ``` Register it once your mod starts, unregister it on dispose: ```csharp public override void StartServerSide(ICoreServerAPI api) { // CommandHookModSystem.Instance is null if CommandHook isn't loaded, // hence the ?. Register your listener once, here. CommandHookModSystem.Instance?.Register(myListener); } public override void Dispose() { // Always unregister on dispose, otherwise a stale listener stays in // the dispatch table after your mod is gone. CommandHookModSystem.Instance?.Unregister(myListener); } ``` Calling `Register` again with the same `ModId` replaces your existing registration. If your `Commands` list hasn't changed, it's a no-op.