Decompiled source of FairGiantsFixed v0.2.1

plugins/FairGiantsFixed.dll

Decompiled 2 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using GameNetcodeStuff;
using HarmonyLib;
using Unity.Netcode;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("FairGiantsFixed")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("FairGiantsFixed")]
[assembly: AssemblyCopyright("Copyright ©  2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("b4551337-cf5e-4171-8774-cc6f7476b59a")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace FairGiantsFixed;

public enum StealthDecayMode
{
	Never,
	Solo,
	Always
}
[BepInPlugin("taylor.fairgiantsfixed", "Fair Giants Fixed", "0.2.1")]
public sealed class Plugin : BaseUnityPlugin
{
	internal static ManualLogSource Log;

	internal static ConfigEntry<float> VisionDivisor;

	internal static ConfigEntry<bool> AffectFoggy;

	internal static ConfigEntry<bool> AffectSnowy;

	internal static ConfigEntry<string> SnowWeatherNames;

	internal static ConfigEntry<StealthDecayMode> StealthDecaysWhen;

	internal static ConfigEntry<float> PassiveStealthDecay;

	internal static ConfigEntry<bool> EnhancedAntiCamp;

	internal static ConfigEntry<bool> RandomWander;

	internal static ConfigEntry<float> ShipCampRadius;

	internal static ConfigEntry<float> ShipEscapeExtraDistance;

	internal static ConfigEntry<float> ShipEscapeReassertInterval;

	internal static ConfigEntry<float> ShipEscapeStuckTime;

	internal static ConfigEntry<float> ShipEscapeMaxTime;

	private Harmony _harmony;

	private void Awake()
	{
		//IL_02f0: Unknown result type (might be due to invalid IL or missing references)
		//IL_02fa: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		VisionDivisor = ((BaseUnityPlugin)this).Config.Bind<float>("Vision", "VisionDivisor", 3f, "Divides Forest Giant vision range during affected weather.");
		AffectFoggy = ((BaseUnityPlugin)this).Config.Bind<bool>("Vision", "AffectFoggy", true, "Reduce Forest Giant vision during foggy weather.");
		AffectSnowy = ((BaseUnityPlugin)this).Config.Bind<bool>("Vision", "AffectSnowy", true, "Reduce Forest Giant vision during snowy weather.");
		SnowWeatherNames = ((BaseUnityPlugin)this).Config.Bind<string>("Vision", "SnowWeatherNames", "snow,snowy,blizzard", "Comma-separated weather names treated as snowy.");
		StealthDecaysWhen = ((BaseUnityPlugin)this).Config.Bind<StealthDecayMode>("Aggro", "StealthDecaysWhen", StealthDecayMode.Solo, "When stealth meters should passively decay while the giant sees nobody.");
		PassiveStealthDecay = ((BaseUnityPlugin)this).Config.Bind<float>("Aggro", "PassiveStealthDecay", 0.2f, "Stealth memory lost per second while no players are visible.");
		EnhancedAntiCamp = ((BaseUnityPlugin)this).Config.Bind<bool>("Ship", "EnhancedAntiCamp", true, "Makes giants commit to leaving the ship area after losing a player nearby.");
		RandomWander = ((BaseUnityPlugin)this).Config.Bind<bool>("Ship", "RandomWander", true, "Choose randomly from distant AI nodes instead of always using the farthest node.");
		ShipCampRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Ship", "ShipCampRadius", 40f, "Distance from the ship within which anti-camp can activate.");
		ShipEscapeExtraDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Ship", "ShipEscapeExtraDistance", 20f, "Additional distance beyond ShipCampRadius the giant must reach.");
		ShipEscapeReassertInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Ship", "ShipEscapeReassertInterval", 0.2f, "How often the mod forces the escape destination while anti-camp is active.");
		ShipEscapeStuckTime = ((BaseUnityPlugin)this).Config.Bind<float>("Ship", "ShipEscapeStuckTime", 4f, "How long the giant may fail to make meaningful progress before a new escape node is chosen.");
		ShipEscapeMaxTime = ((BaseUnityPlugin)this).Config.Bind<float>("Ship", "ShipEscapeMaxTime", 30f, "Emergency maximum duration of an anti-camp escape.");
		((BaseUnityPlugin)this).Logger.LogInfo((object)"======================================");
		((BaseUnityPlugin)this).Logger.LogInfo((object)"Fair Giants Fixed 0.2.1 LOADED");
		((BaseUnityPlugin)this).Logger.LogInfo((object)"Vision + Aggro + AUTHORITATIVE Ship Anti-Camp");
		((BaseUnityPlugin)this).Logger.LogInfo((object)("Vision divisor: " + VisionDivisor.Value));
		((BaseUnityPlugin)this).Logger.LogInfo((object)("Stealth decay mode: " + StealthDecaysWhen.Value));
		((BaseUnityPlugin)this).Logger.LogInfo((object)("Passive stealth decay: " + PassiveStealthDecay.Value));
		((BaseUnityPlugin)this).Logger.LogInfo((object)("Enhanced anti-camp: " + EnhancedAntiCamp.Value));
		((BaseUnityPlugin)this).Logger.LogInfo((object)("Ship escape target distance: " + (ShipCampRadius.Value + ShipEscapeExtraDistance.Value)));
		((BaseUnityPlugin)this).Logger.LogInfo((object)"======================================");
		try
		{
			_harmony = new Harmony("taylor.fairgiantsfixed.harmony");
			_harmony.PatchAll();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"[FGF] Harmony patches applied.");
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)"[FGF] Harmony patching failed:");
			((BaseUnityPlugin)this).Logger.LogError((object)ex);
		}
	}

	public static int GetGiantVisionRange(int vanillaRange)
	{
		try
		{
			string currentWeatherName = GetCurrentWeatherName();
			if (!ShouldReduceVision(currentWeatherName))
			{
				VisionLogging.LogNormal(currentWeatherName, vanillaRange);
				return vanillaRange;
			}
			float num = VisionDivisor.Value;
			if (num < 1f)
			{
				num = 1f;
			}
			int num2 = Mathf.Max(1, Mathf.RoundToInt((float)vanillaRange / num));
			VisionLogging.LogReduced(currentWeatherName, vanillaRange, num, num2);
			return num2;
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("[FGF] Vision calculation failed. Using vanilla range. " + ex.Message));
			return vanillaRange;
		}
	}

	private static string GetCurrentWeatherName()
	{
		//IL_001f: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			if ((Object)(object)TimeOfDay.Instance == (Object)null)
			{
				return "<no TimeOfDay>";
			}
			object obj = TimeOfDay.Instance.currentLevelWeather;
			if (obj == null)
			{
				return "<null weather>";
			}
			return obj.ToString();
		}
		catch
		{
			return "<weather error>";
		}
	}

	private static bool ShouldReduceVision(string weatherName)
	{
		if (string.IsNullOrWhiteSpace(weatherName))
		{
			return false;
		}
		string text = weatherName.Trim().ToLowerInvariant();
		if (AffectFoggy.Value && text.Contains("fog"))
		{
			return true;
		}
		if (AffectSnowy.Value && MatchesConfiguredSnowWeather(text))
		{
			return true;
		}
		return false;
	}

	private static bool MatchesConfiguredSnowWeather(string normalizedWeather)
	{
		string value = SnowWeatherNames.Value;
		if (string.IsNullOrWhiteSpace(value))
		{
			return false;
		}
		string[] array = value.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);
		string[] array2 = array;
		foreach (string text in array2)
		{
			string text2 = text.Trim().ToLowerInvariant();
			if (text2.Length > 0 && normalizedWeather.Contains(text2))
			{
				return true;
			}
		}
		return false;
	}
}
internal static class VisionLogging
{
	private static float _nextNormalLogTime;

	private static float _nextReducedLogTime;

	internal static void LogNormal(string weather, int range)
	{
		if (!(Time.realtimeSinceStartup < _nextNormalLogTime))
		{
			_nextNormalLogTime = Time.realtimeSinceStartup + 10f;
			Plugin.Log.LogInfo((object)("[FGF] Giant vision | Weather=" + weather + " | Range=" + range + " (vanilla)"));
		}
	}

	internal static void LogReduced(string weather, int vanillaRange, float divisor, int reducedRange)
	{
		if (!(Time.realtimeSinceStartup < _nextReducedLogTime))
		{
			_nextReducedLogTime = Time.realtimeSinceStartup + 5f;
			Plugin.Log.LogInfo((object)"[FGF] FOREST GIANT VISION REDUCED");
			Plugin.Log.LogInfo((object)("[FGF] Weather=" + weather + " | Vanilla=" + vanillaRange + " | Effective=" + reducedRange + " | Divisor=" + divisor.ToString("0.00")));
		}
	}
}
[HarmonyPatch(typeof(ForestGiantAI), "LookForPlayers")]
internal static class ForestGiantVisionPatch
{
	[HarmonyTranspiler]
	private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
	{
		//IL_012c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0136: Expected O, but got Unknown
		List<CodeInstruction> list = new List<CodeInstruction>(instructions);
		try
		{
			MethodInfo methodInfo = AccessTools.Method(typeof(EnemyAI), "GetAllPlayersInLineOfSightNonAlloc", new Type[5]
			{
				typeof(float),
				typeof(int),
				typeof(Transform),
				typeof(float),
				typeof(int)
			}, (Type[])null);
			MethodInfo methodInfo2 = AccessTools.Method(typeof(Plugin), "GetGiantVisionRange", (Type[])null, (Type[])null);
			if (methodInfo == null || methodInfo2 == null)
			{
				Plugin.Log.LogError((object)"[FGF] Vision dependencies could not be found.");
				return list;
			}
			int num = FindVisionRangeInstruction(list, methodInfo);
			if (num < 0)
			{
				Plugin.Log.LogError((object)"[FGF] VISION PATCH NOT APPLIED.");
				return list;
			}
			if (num + 1 < list.Count && list[num + 1].opcode == OpCodes.Call && object.Equals(list[num + 1].operand, methodInfo2))
			{
				return list;
			}
			list.Insert(num + 1, new CodeInstruction(OpCodes.Call, (object)methodInfo2));
			Plugin.Log.LogInfo((object)"[FGF] FOREST GIANT VISION PATCH APPLIED");
			Plugin.Log.LogInfo((object)("[FGF] Matched modern LOS range at IL index " + num + "."));
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)"[FGF] Vision transpiler failed:");
			Plugin.Log.LogError((object)ex);
		}
		return list;
	}

	private static int FindVisionRangeInstruction(List<CodeInstruction> codes, MethodInfo losMethod)
	{
		FieldInfo fieldInfo = AccessTools.Field(typeof(EnemyAI), "eye");
		if (fieldInfo == null)
		{
			return -1;
		}
		for (int i = 1; i < codes.Count - 4; i++)
		{
			if (!LoadsInteger(codes[i], 70) || !LoadsFloat(codes[i - 1], 50f) || codes[i + 1].opcode != OpCodes.Ldarg_0 || codes[i + 2].opcode != OpCodes.Ldfld || !object.Equals(codes[i + 2].operand, fieldInfo) || !LoadsFloat(codes[i + 3], 3f))
			{
				continue;
			}
			int num = Math.Min(codes.Count, i + 12);
			for (int j = i + 4; j < num; j++)
			{
				if ((codes[j].opcode == OpCodes.Call || codes[j].opcode == OpCodes.Callvirt) && object.Equals(codes[j].operand, losMethod))
				{
					return i;
				}
			}
		}
		return -1;
	}

	private static bool LoadsInteger(CodeInstruction code, int value)
	{
		if (code.opcode != OpCodes.Ldc_I4 && code.opcode != OpCodes.Ldc_I4_S)
		{
			return false;
		}
		try
		{
			return Convert.ToInt32(code.operand) == value;
		}
		catch
		{
			return false;
		}
	}

	private static bool LoadsFloat(CodeInstruction code, float value)
	{
		if (code.opcode != OpCodes.Ldc_R4 || code.operand == null)
		{
			return false;
		}
		try
		{
			return Math.Abs(Convert.ToSingle(code.operand) - value) < 0.001f;
		}
		catch
		{
			return false;
		}
	}
}
[HarmonyPatch(typeof(ForestGiantAI), "LookForPlayers")]
internal static class ForestGiantAggroPatch
{
	internal sealed class AggroState
	{
		internal float[] Before;
	}

	private static bool _loggedWorking;

	private static bool _losFallbackLogged;

	private static float _nextDecayLogTime;

	[HarmonyPrefix]
	private static void Prefix(ForestGiantAI __instance, ref AggroState __state)
	{
		__state = new AggroState();
		if ((Object)(object)__instance == (Object)null || !ShouldUsePassiveDecay())
		{
			return;
		}
		try
		{
			float[] fieldValue = ReflectionUtil.GetFieldValue<float[]>(__instance, "playerStealthMeters");
			if (fieldValue != null)
			{
				__state.Before = new float[fieldValue.Length];
				Array.Copy(fieldValue, __state.Before, fieldValue.Length);
			}
		}
		catch
		{
		}
	}

	[HarmonyPostfix]
	private static void Postfix(ForestGiantAI __instance, AggroState __state)
	{
		if ((Object)(object)__instance == (Object)null)
		{
			return;
		}
		try
		{
			ShipAntiCamp.Update(__instance);
			if (__state == null || __state.Before == null || !ShouldUsePassiveDecay() || GiantCanCurrentlySeeAnyPlayer(__instance))
			{
				return;
			}
			float[] fieldValue = ReflectionUtil.GetFieldValue<float[]>(__instance, "playerStealthMeters");
			if (fieldValue != null)
			{
				float num = Mathf.Max(0f, Plugin.PassiveStealthDecay.Value) * Time.deltaTime;
				int num2 = Math.Min(fieldValue.Length, __state.Before.Length);
				for (int i = 0; i < num2; i++)
				{
					fieldValue[i] = Mathf.Clamp01(__state.Before[i] - num);
				}
				if (!_loggedWorking)
				{
					_loggedWorking = true;
					Plugin.Log.LogInfo((object)"[FGF] AGGRO DECAY ACTIVE");
					Plugin.Log.LogInfo((object)"[FGF] Stealth memory now decays when no players are visible.");
				}
				if (Time.realtimeSinceStartup >= _nextDecayLogTime)
				{
					_nextDecayLogTime = Time.realtimeSinceStartup + 10f;
					Plugin.Log.LogInfo((object)("[FGF] Passive stealth decay running at " + Plugin.PassiveStealthDecay.Value + "/second."));
				}
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("[FGF] Aggro patch error: " + ex.Message));
		}
	}

	private static bool ShouldUsePassiveDecay()
	{
		switch (Plugin.StealthDecaysWhen.Value)
		{
		case StealthDecayMode.Never:
			return false;
		case StealthDecayMode.Always:
			return true;
		default:
			try
			{
				if ((Object)(object)StartOfRound.Instance == (Object)null || StartOfRound.Instance.allPlayerScripts == null)
				{
					return false;
				}
				int num = 0;
				PlayerControllerB[] allPlayerScripts = StartOfRound.Instance.allPlayerScripts;
				foreach (PlayerControllerB val in allPlayerScripts)
				{
					if ((Object)(object)val != (Object)null && val.isPlayerControlled)
					{
						num++;
					}
				}
				return num <= 1;
			}
			catch
			{
				return false;
			}
		}
	}

	private static bool GiantCanCurrentlySeeAnyPlayer(ForestGiantAI giant)
	{
		try
		{
			MethodInfo methodInfo = AccessTools.Method(typeof(EnemyAI), "GetAllPlayersInLineOfSightNonAlloc", new Type[5]
			{
				typeof(float),
				typeof(int),
				typeof(Transform),
				typeof(float),
				typeof(int)
			}, (Type[])null);
			FieldInfo fieldInfo = AccessTools.Field(typeof(EnemyAI), "eye");
			FieldInfo fieldInfo2 = AccessTools.Field(typeof(StartOfRound), "collidersRoomDefaultAndFoliage");
			if (methodInfo != null && fieldInfo != null && fieldInfo2 != null && (Object)(object)StartOfRound.Instance != (Object)null)
			{
				object? value = fieldInfo.GetValue(giant);
				Transform val = (Transform)((value is Transform) ? value : null);
				int num = Convert.ToInt32(fieldInfo2.GetValue(StartOfRound.Instance));
				int giantVisionRange = Plugin.GetGiantVisionRange(70);
				object obj = methodInfo.Invoke(giant, new object[5] { 50f, giantVisionRange, val, 3f, num });
				if (obj != null)
				{
					return Convert.ToInt32(obj) > 0;
				}
			}
		}
		catch
		{
		}
		if (!_losFallbackLogged)
		{
			_losFallbackLogged = true;
			Plugin.Log.LogWarning((object)"[FGF] Aggro LOS helper unavailable; using chasingPlayerInLOS fallback.");
		}
		return ReflectionUtil.GetFieldValue<bool>(giant, "chasingPlayerInLOS");
	}
}
internal static class ShipAntiCamp
{
	internal sealed class EscapeState
	{
		internal Vector3 Target;

		internal float Started;

		internal float NextReassert;

		internal float LastProgressTime;

		internal Vector3 LastPosition;

		internal float LastDistanceFromShip;

		internal int RetargetCount;
	}

	private static readonly Dictionary<int, EscapeState> ActiveEscapes = new Dictionary<int, EscapeState>();

	private static bool _nodeFailureLogged;

	private static bool _destinationFailureLogged;

	internal static void Update(ForestGiantAI giant)
	{
		if (!Plugin.EnhancedAntiCamp.Value || (Object)(object)giant == (Object)null)
		{
			return;
		}
		try
		{
			if (!((NetworkBehaviour)giant).IsServer)
			{
				return;
			}
			Transform shipTransform = GetShipTransform();
			if (!((Object)(object)shipTransform == (Object)null))
			{
				int instanceID = ((Object)giant).GetInstanceID();
				if (ActiveEscapes.TryGetValue(instanceID, out var value))
				{
					ContinueEscape(giant, shipTransform, instanceID, value);
				}
				else
				{
					TryStartEscape(giant, shipTransform, instanceID);
				}
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("[FGF] Anti-camp error: " + ex.Message));
		}
	}

	private unsafe static void TryStartEscape(ForestGiantAI giant, Transform ship, int id)
	{
		//IL_0049: Unknown result type (might be due to invalid IL or missing references)
		//IL_004f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0075: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00da: Unknown result type (might be due to invalid IL or missing references)
		//IL_0145: Unknown result type (might be due to invalid IL or missing references)
		//IL_0147: Unknown result type (might be due to invalid IL or missing references)
		bool fieldValue = ReflectionUtil.GetFieldValue<bool>(giant, "lostPlayerInChase");
		bool fieldValue2 = ReflectionUtil.GetFieldValue<bool>(giant, "chasingPlayerInLOS");
		int fieldValue3 = ReflectionUtil.GetFieldValue<int>(giant, "currentBehaviourStateIndex");
		if (!(!fieldValue || fieldValue2) && fieldValue3 == 1)
		{
			float num = Vector3.Distance(((Component)giant).transform.position, ship.position);
			if (!(num > Plugin.ShipCampRadius.Value) && TryChooseEscapeNode(giant, ship.position, out var result))
			{
				float realtimeSinceStartup = Time.realtimeSinceStartup;
				EscapeState escapeState = new EscapeState();
				escapeState.Target = result;
				escapeState.Started = realtimeSinceStartup;
				escapeState.NextReassert = 0f;
				escapeState.LastProgressTime = realtimeSinceStartup;
				escapeState.LastDistanceFromShip = num;
				escapeState.LastPosition = ((Component)giant).transform.position;
				escapeState.RetargetCount = 0;
				ActiveEscapes[id] = escapeState;
				Plugin.Log.LogInfo((object)"[FGF] ======================================");
				Plugin.Log.LogInfo((object)"[FGF] SHIP ANTI-CAMP TRIGGERED");
				Plugin.Log.LogInfo((object)("[FGF] Giant lost player " + num.ToString("0.0") + " units from ship."));
				ManualLogSource log = Plugin.Log;
				Vector3 val = result;
				log.LogInfo((object)("[FGF] Escape destination: " + ((object)(*(Vector3*)(&val))/*cast due to .constrained prefix*/).ToString()));
				Plugin.Log.LogInfo((object)("[FGF] Escape will remain authoritative until giant reaches " + (Plugin.ShipCampRadius.Value + Plugin.ShipEscapeExtraDistance.Value).ToString("0.0") + " units."));
				Plugin.Log.LogInfo((object)"[FGF] ======================================");
				ForceEscapeState(giant, escapeState);
			}
		}
	}

	private unsafe static void ContinueEscape(ForestGiantAI giant, Transform ship, int id, EscapeState state)
	{
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0013: Unknown result type (might be due to invalid IL or missing references)
		//IL_0139: Unknown result type (might be due to invalid IL or missing references)
		//IL_013f: Unknown result type (might be due to invalid IL or missing references)
		//IL_017b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0180: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d9: Unknown result type (might be due to invalid IL or missing references)
		//IL_01de: Unknown result type (might be due to invalid IL or missing references)
		//IL_0222: Unknown result type (might be due to invalid IL or missing references)
		//IL_0224: Unknown result type (might be due to invalid IL or missing references)
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		float num = Vector3.Distance(((Component)giant).transform.position, ship.position);
		float num2 = Plugin.ShipCampRadius.Value + Plugin.ShipEscapeExtraDistance.Value;
		if (num >= num2)
		{
			ActiveEscapes.Remove(id);
			Plugin.Log.LogInfo((object)"[FGF] ======================================");
			Plugin.Log.LogInfo((object)"[FGF] GIANT ANTI-CAMP ESCAPE SUCCESS");
			Plugin.Log.LogInfo((object)("[FGF] Distance from ship=" + num.ToString("0.0")));
			Plugin.Log.LogInfo((object)"[FGF] ======================================");
			return;
		}
		if (realtimeSinceStartup - state.Started > Plugin.ShipEscapeMaxTime.Value)
		{
			ActiveEscapes.Remove(id);
			Plugin.Log.LogWarning((object)"[FGF] ANTI-CAMP ESCAPE FAILED / TIMED OUT");
			Plugin.Log.LogWarning((object)("[FGF] Giant still only " + num.ToString("0.0") + " units from ship after " + Plugin.ShipEscapeMaxTime.Value + " seconds."));
			return;
		}
		float num3 = Vector3.Distance(((Component)giant).transform.position, state.LastPosition);
		bool flag = num > state.LastDistanceFromShip + 0.75f;
		if (num3 > 1f || flag)
		{
			state.LastProgressTime = realtimeSinceStartup;
			state.LastPosition = ((Component)giant).transform.position;
			state.LastDistanceFromShip = num;
		}
		if (realtimeSinceStartup - state.LastProgressTime >= Plugin.ShipEscapeStuckTime.Value && TryChooseEscapeNode(giant, ship.position, out var result))
		{
			state.Target = result;
			state.LastProgressTime = realtimeSinceStartup;
			state.LastPosition = ((Component)giant).transform.position;
			state.LastDistanceFromShip = num;
			state.RetargetCount++;
			Plugin.Log.LogWarning((object)"[FGF] Giant made insufficient escape progress.");
			ManualLogSource log = Plugin.Log;
			string text = state.RetargetCount.ToString();
			Vector3 val = result;
			log.LogInfo((object)("[FGF] Retargeting anti-camp destination #" + text + " -> " + ((object)(*(Vector3*)(&val))/*cast due to .constrained prefix*/).ToString()));
		}
		if (realtimeSinceStartup >= state.NextReassert)
		{
			state.NextReassert = realtimeSinceStartup + Mathf.Max(0.05f, Plugin.ShipEscapeReassertInterval.Value);
			ForceEscapeState(giant, state);
		}
		ActiveEscapes[id] = state;
	}

	private static void ForceEscapeState(ForestGiantAI giant, EscapeState state)
	{
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		ReflectionUtil.TryInvokeIntMethod(giant, "SwitchToBehaviourState", 0);
		ReflectionUtil.TrySetField(giant, "lostPlayerInChase", false);
		ReflectionUtil.TrySetField(giant, "chasingPlayerInLOS", false);
		ReflectionUtil.TrySetField(giant, "movingTowardsTargetPlayer", false);
		SetDestination(giant, state.Target);
	}

	private static Transform GetShipTransform()
	{
		try
		{
			if ((Object)(object)StartOfRound.Instance != (Object)null && (Object)(object)StartOfRound.Instance.elevatorTransform != (Object)null)
			{
				return StartOfRound.Instance.elevatorTransform;
			}
		}
		catch
		{
		}
		return null;
	}

	private static bool TryChooseEscapeNode(ForestGiantAI giant, Vector3 shipPosition, out Vector3 result)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_0008: Unknown result type (might be due to invalid IL or missing references)
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
		//IL_008a: Unknown result type (might be due to invalid IL or missing references)
		//IL_008f: Unknown result type (might be due to invalid IL or missing references)
		result = Vector3.zero;
		List<Transform> aINodes = GetAINodes(giant);
		if (aINodes.Count == 0)
		{
			if (!_nodeFailureLogged)
			{
				_nodeFailureLogged = true;
				Plugin.Log.LogWarning((object)"[FGF] Anti-camp could not locate outdoor AI nodes.");
			}
			return false;
		}
		aINodes.Sort(delegate(Transform a, Transform b)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			float value = Vector3.SqrMagnitude(a.position - shipPosition);
			return Vector3.SqrMagnitude(b.position - shipPosition).CompareTo(value);
		});
		if (!Plugin.RandomWander.Value)
		{
			result = aINodes[0].position;
			return true;
		}
		int num = Mathf.Clamp(aINodes.Count / 4, 1, Mathf.Min(8, aINodes.Count));
		int index = Random.Range(0, num);
		result = aINodes[index].position;
		return true;
	}

	private static List<Transform> GetAINodes(ForestGiantAI giant)
	{
		List<Transform> list = new List<Transform>();
		object fieldObject = ReflectionUtil.GetFieldObject(giant, "allAINodes");
		AddNodes(list, fieldObject);
		if (list.Count > 0)
		{
			return list;
		}
		try
		{
			GameObject[] array = GameObject.FindGameObjectsWithTag("OutsideAINode");
			if (array != null)
			{
				GameObject[] array2 = array;
				foreach (GameObject val in array2)
				{
					if ((Object)(object)val != (Object)null)
					{
						list.Add(val.transform);
					}
				}
			}
		}
		catch
		{
		}
		return list;
	}

	private static void AddNodes(List<Transform> target, object value)
	{
		if (value == null || !(value is IEnumerable enumerable))
		{
			return;
		}
		foreach (object item in enumerable)
		{
			GameObject val = (GameObject)((item is GameObject) ? item : null);
			if ((Object)(object)val != (Object)null)
			{
				target.Add(val.transform);
				continue;
			}
			Transform val2 = (Transform)((item is Transform) ? item : null);
			if ((Object)(object)val2 != (Object)null)
			{
				target.Add(val2);
			}
		}
	}

	private static void SetDestination(ForestGiantAI giant, Vector3 position)
	{
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		if (!ReflectionUtil.TrySetDestination(giant, position) && !_destinationFailureLogged)
		{
			_destinationFailureLogged = true;
			Plugin.Log.LogWarning((object)"[FGF] Could not invoke EnemyAI.SetDestinationToPosition.");
		}
	}
}
internal static class ReflectionUtil
{
	internal static T GetFieldValue<T>(object instance, string fieldName)
	{
		object fieldObject = GetFieldObject(instance, fieldName);
		if (fieldObject == null)
		{
			return default(T);
		}
		try
		{
			return (T)fieldObject;
		}
		catch
		{
			return default(T);
		}
	}

	internal static object GetFieldObject(object instance, string fieldName)
	{
		if (instance == null)
		{
			return null;
		}
		FieldInfo fieldInfo = FindField(instance.GetType(), fieldName);
		if (fieldInfo == null)
		{
			return null;
		}
		try
		{
			return fieldInfo.GetValue(instance);
		}
		catch
		{
			return null;
		}
	}

	internal static bool TrySetField(object instance, string fieldName, object value)
	{
		if (instance == null)
		{
			return false;
		}
		FieldInfo fieldInfo = FindField(instance.GetType(), fieldName);
		if (fieldInfo == null)
		{
			return false;
		}
		try
		{
			fieldInfo.SetValue(instance, value);
			return true;
		}
		catch
		{
			return false;
		}
	}

	private static FieldInfo FindField(Type type, string fieldName)
	{
		Type type2 = type;
		while (type2 != null && type2 != typeof(object))
		{
			FieldInfo field = type2.GetField(fieldName, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (field != null)
			{
				return field;
			}
			type2 = type2.BaseType;
		}
		return null;
	}

	internal static bool TryInvokeIntMethod(object instance, string methodName, int value)
	{
		if (instance == null)
		{
			return false;
		}
		Type type = instance.GetType();
		while (type != null && type != typeof(object))
		{
			MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			MethodInfo[] array = methods;
			foreach (MethodInfo methodInfo in array)
			{
				if (methodInfo.Name != methodName)
				{
					continue;
				}
				ParameterInfo[] parameters = methodInfo.GetParameters();
				if (parameters.Length == 1 && parameters[0].ParameterType == typeof(int))
				{
					try
					{
						methodInfo.Invoke(instance, new object[1] { value });
						return true;
					}
					catch
					{
						return false;
					}
				}
			}
			type = type.BaseType;
		}
		return false;
	}

	internal static bool TrySetDestination(object instance, Vector3 position)
	{
		//IL_009d: Unknown result type (might be due to invalid IL or missing references)
		if (instance == null)
		{
			return false;
		}
		Type type = instance.GetType();
		while (type != null && type != typeof(object))
		{
			MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			MethodInfo[] array = methods;
			foreach (MethodInfo methodInfo in array)
			{
				if (methodInfo.Name != "SetDestinationToPosition")
				{
					continue;
				}
				ParameterInfo[] parameters = methodInfo.GetParameters();
				if (parameters.Length < 1 || parameters[0].ParameterType != typeof(Vector3))
				{
					continue;
				}
				object[] array2 = new object[parameters.Length];
				array2[0] = position;
				for (int j = 1; j < parameters.Length; j++)
				{
					if (parameters[j].ParameterType == typeof(bool))
					{
						array2[j] = true;
					}
					else if (parameters[j].HasDefaultValue)
					{
						array2[j] = parameters[j].DefaultValue;
					}
					else if (parameters[j].ParameterType.IsValueType)
					{
						array2[j] = Activator.CreateInstance(parameters[j].ParameterType);
					}
					else
					{
						array2[j] = null;
					}
				}
				try
				{
					methodInfo.Invoke(instance, array2);
					return true;
				}
				catch
				{
					return false;
				}
			}
			type = type.BaseType;
		}
		return false;
	}
}