134 lines
4.5 KiB
Markdown
134 lines
4.5 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.0.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)
|
|
{
|
|
// 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.
|