namespace Aeshnidae.MoveGuard;
///
/// Three patches. The prefix is the guard; the postfix is how it learns which
/// positions ACE actually accepted; the logout hook is housekeeping.
///
/// The prefix never throws into ACE: a failure inside the guard is logged and the
/// move is allowed, because a bug here must degrade to stock behaviour rather than
/// freeze every player on the shard.
///
[HarmonyPatch]
public static class Patches
{
[HarmonyPrefix]
[HarmonyPatch(typeof(Player), nameof(Player.UpdatePlayerPosition))]
public static bool UpdatePlayerPosition_Prefix(Player __instance, Position newPosition, bool forceUpdate, ref bool __result)
{
try
{
return Guard.Judge(__instance, newPosition, forceUpdate, ref __result);
}
catch (Exception ex)
{
ModManager.Log($"[{Mod.Name}] guard failed for {__instance?.Name}, move allowed: {ex}", ModManager.LogLevel.Error);
return true;
}
}
///
/// UpdatePlayerPosition's return value means "changed landblock", not "accepted",
/// so acceptance is read off the side effect instead: on success ACE assigns
/// Location = newPosition, the very same object. A rejected move - ours or
/// ACE's own - leaves Location as it was and the clock where it was.
///
[HarmonyPostfix]
[HarmonyPatch(typeof(Player), nameof(Player.UpdatePlayerPosition))]
public static void UpdatePlayerPosition_Postfix(Player __instance, Position newPosition)
{
try
{
if (newPosition is not null && ReferenceEquals(__instance.Location, newPosition))
Tracker.For(__instance).LastAccepted = DateTime.UtcNow;
}
catch (Exception ex)
{
ModManager.Log($"[{Mod.Name}] postfix failed for {__instance?.Name}: {ex.Message}", ModManager.LogLevel.Error);
}
}
[HarmonyPostfix]
[HarmonyPatch(typeof(PlayerManager), nameof(PlayerManager.SwitchPlayerFromOnlineToOffline))]
public static void Logout_Postfix(Player player)
{
try
{
if (player is not null)
Tracker.Forget(player);
}
catch { /* housekeeping only */ }
}
}