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"); /// /// 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. /// 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; } }