8de369869f
Ground-up reimplementation of yox92/SmartAction targeting SPT 4.0.13 / EFT 0.16.9.0.40087. Only the walk-while-healing feature is implemented; see NOTES.md for status and the deferred sprint work. Design departures from upstream: - CanWalk is a guarded postfix, not a transpiler. Upstream rewrote the method body, which affects every MovementContext in the raid including bots, and is hard-incompatible with the three other mods already patching MovementContext in a typical load order (SAIN, ORBIT, maschine-DualSideDoorBreach). A postfix that only flips false -> true for IsYourPlayer is inert for other entities by construction. - The postfix re-tests ObstacleCollisionFacade_1.CanMove() before lifting. Upstream's transpiler dropped that check along with the HealingLegs check, silently disabling obstacle blocking. - Targets resolve by name via AccessTools.PropertyGetter + nameof rather than by GClass number, so they survive EFT version bumps. Verified in-game against 78 loaded plugins: both patches attach, the guard rejected 985 non-local MovementContext calls with zero leaks, and _player was never null across ~4000 guard calls. CanSprintPatch is parked in deferred/ rather than deleted: MovementContext.CanSprint does not gate sprinting (EnableSprint calls Physical.Sprint unconditionally), and forcing it true makes EnableSprint run ForceStopInteractions()/RemoveLeftHandItem() mid-heal. Details and the 3.11 -> 4.0.13 rename table are in NOTES.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
48 lines
1.7 KiB
C#
48 lines
1.7 KiB
C#
using System.Reflection;
|
|
using EFT;
|
|
using HarmonyLib;
|
|
|
|
namespace SmartAction.Patch;
|
|
|
|
internal static class MovementPatchUtil
|
|
{
|
|
// MovementContext._player is protected, so it needs reflection. Resolved once at
|
|
// type-init rather than per call — these postfixes run on every movement query.
|
|
private static readonly FieldInfo PlayerField = AccessTools.Field(typeof(MovementContext), "_player");
|
|
|
|
/// <summary>
|
|
/// True only for the player this client controls.
|
|
///
|
|
/// Every bot and, under Fika, every remote player owns its own MovementContext, and these
|
|
/// postfixes fire for all of them. Gating on IsYourPlayer is what keeps the patches inert
|
|
/// for everyone else — it is the entire multiplayer-safety story for Stage 1.
|
|
/// </summary>
|
|
internal static bool IsLocalPlayer(MovementContext context)
|
|
{
|
|
var value = PlayerField?.GetValue(context);
|
|
|
|
// Fast path when instrumentation is off: no counters, no branches beyond the check.
|
|
if (!Diagnostics.Enabled)
|
|
{
|
|
return value is Player { IsYourPlayer: true };
|
|
}
|
|
|
|
if (value is not Player player)
|
|
{
|
|
// Predicted failure mode: a MovementContext whose _player is not yet assigned
|
|
// (bot mid-spawn). The pattern match absorbs it rather than throwing an NRE.
|
|
Diagnostics.NullPlayerCalls++;
|
|
Diagnostics.OnceNullPlayer();
|
|
Diagnostics.Tick();
|
|
return false;
|
|
}
|
|
|
|
var isLocal = player.IsYourPlayer;
|
|
if (isLocal) Diagnostics.LocalCalls++;
|
|
else Diagnostics.NonLocalCalls++;
|
|
|
|
Diagnostics.Tick();
|
|
return isLocal;
|
|
}
|
|
}
|