docs(ChatCommandApiPatch): add XML doc comments and internal notes to Prefix
This commit is contained in:
@@ -6,9 +6,48 @@ using Vintagestory.Common;
|
|||||||
|
|
||||||
namespace CommandHook;
|
namespace CommandHook;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Harmony prefix patch on <c>ChatCommandApi.Execute</c>. This is the only place
|
||||||
|
/// CommandHook hooks into the game, everything else in the mod feeds into this.
|
||||||
|
/// </summary>
|
||||||
[HarmonyPatch(typeof(ChatCommandApi))]
|
[HarmonyPatch(typeof(ChatCommandApi))]
|
||||||
public static class ChatCommandApiPatch
|
public static class ChatCommandApiPatch
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Runs before the game's own command execution. Fires Before listeners for
|
||||||
|
/// <paramref name="commandName"/> and, if none of them cancel, lets the
|
||||||
|
/// original <c>Execute</c> run and wraps <paramref name="onCommandComplete"/>
|
||||||
|
/// so After listeners fire once a result exists.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Runs for both invocation paths, server console and a connected player
|
||||||
|
/// typing in chat, since both go through <c>Execute</c> the same way
|
||||||
|
/// internally.
|
||||||
|
/// <para>
|
||||||
|
/// If a Before listener sets <see cref="CommandData.Cancel"/>, this returns
|
||||||
|
/// false to stop the original <c>Execute</c> from running at all, and
|
||||||
|
/// invokes <paramref name="onCommandComplete"/> directly with a
|
||||||
|
/// <see cref="EnumCommandStatus.Deferred"/> result so the caller doesn't see
|
||||||
|
/// a generic failure. After listeners are not fired in this case.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// If nothing cancels, this returns true and lets the original <c>Execute</c>
|
||||||
|
/// run, but first replaces <paramref name="onCommandComplete"/> with a wrapper
|
||||||
|
/// that calls the original callback, then fires After. The original callback
|
||||||
|
/// always runs first, so nothing about the vanilla command flow changes from
|
||||||
|
/// the caller's point of view.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="commandName">The command name being executed, without the leading slash.</param>
|
||||||
|
/// <param name="args">The calling args supplied by the game, used here to resolve the sender.</param>
|
||||||
|
/// <param name="onCommandComplete">
|
||||||
|
/// The completion callback supplied by the game. Replaced with a wrapper that
|
||||||
|
/// fires After listeners after invoking the original.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// False to skip the original <c>Execute</c> (a Before listener cancelled it),
|
||||||
|
/// true to let it run normally.
|
||||||
|
/// </returns>
|
||||||
[HarmonyPatch(
|
[HarmonyPatch(
|
||||||
"Execute",
|
"Execute",
|
||||||
new[] { typeof(string), typeof(TextCommandCallingArgs), typeof(Action<TextCommandResult>) }
|
new[] { typeof(string), typeof(TextCommandCallingArgs), typeof(Action<TextCommandResult>) }
|
||||||
@@ -21,24 +60,38 @@ public static class ChatCommandApiPatch
|
|||||||
)
|
)
|
||||||
{
|
{
|
||||||
var system = CommandHookModSystem.Instance;
|
var system = CommandHookModSystem.Instance;
|
||||||
|
// Instance is null before StartServerSide runs or after Dispose. Harmony
|
||||||
|
// patches stay applied for the process lifetime, so this null check is
|
||||||
|
// what actually guards against firing into a torn-down mod.
|
||||||
if (system == null)
|
if (system == null)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
var sender = args.Caller.Player as IServerPlayer;
|
var sender = args.Caller.Player as IServerPlayer;
|
||||||
|
// Console invocations have no IServerPlayer, the cast just yields null,
|
||||||
|
// which is exactly what CommandData(sender, ...) expects to mean "console".
|
||||||
var data = new CommandData(sender, commandName, "/" + commandName);
|
var data = new CommandData(sender, commandName, "/" + commandName);
|
||||||
|
|
||||||
if (system.FireBefore(commandName, ref data))
|
if (system.FireBefore(commandName, ref data))
|
||||||
{
|
{
|
||||||
|
// Cancelled by a Before listener. Report Deferred rather than just
|
||||||
|
// swallowing it silently, so the original caller (console or player)
|
||||||
|
// sees something other than the command quietly doing nothing.
|
||||||
onCommandComplete?.Invoke(
|
onCommandComplete?.Invoke(
|
||||||
new TextCommandResult { Status = EnumCommandStatus.Deferred }
|
new TextCommandResult { Status = EnumCommandStatus.Deferred }
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Not cancelled. Capture the game's own callback before replacing it,
|
||||||
|
// since we still need to call it, this patch only adds behavior around
|
||||||
|
// the vanilla flow, it never removes the vanilla completion handling.
|
||||||
var original = onCommandComplete;
|
var original = onCommandComplete;
|
||||||
onCommandComplete = result =>
|
onCommandComplete = result =>
|
||||||
{
|
{
|
||||||
original?.Invoke(result);
|
original?.Invoke(result);
|
||||||
|
// data is captured by the closure, copy it so FireAfter gets its
|
||||||
|
// own ref target instead of sharing one with whatever else might
|
||||||
|
// still be holding the original local.
|
||||||
var afterData = data;
|
var afterData = data;
|
||||||
system.FireAfter(commandName, ref afterData, result);
|
system.FireAfter(commandName, ref afterData, result);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user