From 2c6be63c3315dcaf1b0c7e1e0bc3bf26ba04557e Mon Sep 17 00:00:00 2001 From: anth64 Date: Thu, 4 Jun 2026 22:10:40 +0200 Subject: [PATCH] feat: replace PlayerChat hook with Harmony patch on command dispatch --- CommandHook/ChatCommandApiPatch.cs | 48 +++++++++++++++++++++++++++++ CommandHook/CommandHookModSystem.cs | 39 +++++++++++++++-------- 2 files changed, 74 insertions(+), 13 deletions(-) create mode 100644 CommandHook/ChatCommandApiPatch.cs diff --git a/CommandHook/ChatCommandApiPatch.cs b/CommandHook/ChatCommandApiPatch.cs new file mode 100644 index 0000000..a4e5b6e --- /dev/null +++ b/CommandHook/ChatCommandApiPatch.cs @@ -0,0 +1,48 @@ +using System; +using HarmonyLib; +using Vintagestory.API.Common; +using Vintagestory.API.Server; +using Vintagestory.Common; + +namespace CommandHook; + +[HarmonyPatch(typeof(ChatCommandApi))] +public static class ChatCommandApiPatch +{ + [HarmonyPatch( + "Execute", + new[] { typeof(string), typeof(TextCommandCallingArgs), typeof(Action) } + )] + [HarmonyPrefix] + public static bool Prefix( + string commandName, + TextCommandCallingArgs args, + ref Action onCommandComplete + ) + { + var system = CommandHookModSystem.Instance; + if (system == null) + return true; + + var sender = args.Caller.Player as IServerPlayer; + var data = new CommandData(sender, "/" + commandName); + + if (system.FireBefore(commandName, ref data)) + { + onCommandComplete?.Invoke( + new TextCommandResult { Status = EnumCommandStatus.Deferred } + ); + return false; + } + + var original = onCommandComplete; + onCommandComplete = result => + { + original?.Invoke(result); + var afterData = data; + system.FireAfter(commandName, ref afterData, result); + }; + + return true; + } +} diff --git a/CommandHook/CommandHookModSystem.cs b/CommandHook/CommandHookModSystem.cs index 474eeae..a8db59e 100644 --- a/CommandHook/CommandHookModSystem.cs +++ b/CommandHook/CommandHookModSystem.cs @@ -1,35 +1,48 @@ using System.Collections.Frozen; +using HarmonyLib; using Vintagestory.API.Common; -using Vintagestory.API.Datastructures; using Vintagestory.API.Server; namespace CommandHook; public class CommandHookModSystem : ModSystem { + internal static CommandHookModSystem? Instance; + private FrozenDictionary> registrations = FrozenDictionary>.Empty; + private Harmony? harmony; + public override bool ShouldLoad(EnumAppSide forSide) => forSide == EnumAppSide.Server; public override void StartServerSide(ICoreServerAPI api) { + Instance = this; + harmony = new Harmony(Mod.Info.ModID); + harmony.PatchAll(); Mod.Logger.Notification("Loaded"); - api.Event.PlayerChat += OnPlayerChat; } - private void OnPlayerChat( - IServerPlayer player, - int channelId, - ref string message, - ref string data, - BoolRef consumed - ) + public override void Dispose() { - if (!message.StartsWith('/')) - return; + harmony?.UnpatchAll(Mod.Info.ModID); + Instance = null; + } - var cmdData = new CommandData(player, message); - // TODO dispatch + public bool FireBefore(string commandName, ref CommandData data) + { + if (registrations.TryGetValue(commandName, out var mods)) + foreach (var (_, reg) in mods) + reg.Before?.Invoke(ref data); + + return data.Cancel; + } + + public void FireAfter(string commandName, ref CommandData data, TextCommandResult result) + { + if (registrations.TryGetValue(commandName, out var mods)) + foreach (var (_, reg) in mods) + reg.After?.Invoke(ref data, result); } }