Files

134 lines
4.4 KiB
Markdown

# 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.
## Usage
Install it like any other mod. It has no effect unless something else
depends on it.
## For mod development
Set the `COMMANDHOOK` environment variable to the folder containing
`CommandHook.dll` (i.e. wherever you've built CommandHook), the same way
`VINTAGE_STORY` points at your game install.
**Linux/macOS** (add to `~/.bashrc`, `~/.zshrc`, etc.):
```bash
export COMMANDHOOK="/path/to/CommandHook/CommandHook/bin/Release"
```
**Windows (PowerShell):**
```powershell
[Environment]::SetEnvironmentVariable("COMMANDHOOK", "C:\path\to\CommandHook\CommandHook\bin\Release", "User")
```
Then add a `Reference` to your `.csproj` next to your other game references:
```xml
<Reference Include="CommandHook">
<HintPath>$(COMMANDHOOK)/CommandHook.dll</HintPath>
<Private>false</Private>
</Reference>
```
Add it as a dependency in your `modinfo.json`:
```json
"dependencies": {
"game": "1.22.3",
"commandhook": "2.1.0"
}
```
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` receives the engine's own `TextCommandCallingArgs` for the
invocation, live and unmodified. Return a non-null `TextCommandResult` to
cancel the command and report that result to the caller, use the engine's
own factories, e.g. `TextCommandResult.Error(...)` for a visible reason, or
`TextCommandResult.Deferred` to cancel silently (per its own doc comment,
this prints no output). Return `null` to let the command run normally. If
you cancel, any remaining `Before` listeners for that command are skipped,
and `After` never fires for that invocation.
`After` receives the same `TextCommandCallingArgs` object plus the
`TextCommandResult` the command actually produced. It only fires on the
path where the command ran.
### A note on `TextCommandCallingArgs` at Before vs After
This is the engine's own live object, not a copy, what's safe to read
depends on timing and on how the specific command you're watching is
implemented internally.
### 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<string> Commands => new[] { "tp" };
// Wire up Before, After, or both.
public CommandRegistration Registration => new(Before, After);
private TextCommandResult? Before(TextCommandCallingArgs args)
{
// args.Caller.Player is null for console invocations.
if (args.Caller.Player is IServerPlayer player && !IsAllowed(player))
{
// Cancels the command, skips any remaining Before listeners,
// and After never fires for this invocation. The caller sees
// this exact result.
return TextCommandResult.Error("You're not allowed to do that", "notallowed");
}
return null;
}
private void After(TextCommandCallingArgs args, 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($"A watched command failed: {result.StatusMessage}");
}
}
```
Register it once your mod starts, unregister it on dispose:
```csharp
public override void StartServerSide(ICoreServerAPI api)
{
// Register your listener once, here. No-ops silently if CommandHook
// isn't loaded.
CommandHookModSystem.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.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.