Decompiled source of PredatorsHunt v0.1.0

PredatorsHunt.dll

Decompiled 21 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;
using UnityEngine.AI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("0.0.0.0")]
namespace PredatorsHunt;

internal static class Hooks
{
	private static readonly List<string> Installed = new List<string>();

	private static readonly List<string> Failed = new List<string>();

	private static bool Active
	{
		get
		{
			if (PhConfig.Enabled.Value)
			{
				return Access.Ready;
			}
			return false;
		}
	}

	internal static void Install(Harmony harmony)
	{
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Expected O, but got Unknown
		//IL_006e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0079: Expected O, but got Unknown
		//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b9: Expected O, but got Unknown
		//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
		//IL_0106: Expected O, but got Unknown
		//IL_0129: Unknown result type (might be due to invalid IL or missing references)
		//IL_0134: Expected O, but got Unknown
		//IL_0158: Unknown result type (might be due to invalid IL or missing references)
		//IL_0162: Expected O, but got Unknown
		//IL_0186: Unknown result type (might be due to invalid IL or missing references)
		//IL_0190: Expected O, but got Unknown
		//IL_01b4: Unknown result type (might be due to invalid IL or missing references)
		//IL_01be: Expected O, but got Unknown
		Type typeFromHandle = typeof(Hooks);
		TryPatch(harmony, "register predators", AccessTools.Method(typeof(BaseAI), "Awake", (Type[])null, (Type[])null), null, new HarmonyMethod(typeFromHandle, "BaseAIAwakePostfix", (Type[])null));
		TryPatch(harmony, "state machine", AccessTools.Method(typeof(MonsterAI), "UpdateAI", new Type[1] { typeof(float) }, (Type[])null), new HarmonyMethod(typeFromHandle, "UpdateAIPrefix", (Type[])null), null);
		TryPatch(harmony, "scent movement", AccessTools.Method(typeof(BaseAI), "IdleMovement", new Type[1] { typeof(float) }, (Type[])null), new HarmonyMethod(typeFromHandle, "IdleMovementPrefix", (Type[])null), null);
		TryPatch(harmony, "sight and hearing", AccessTools.Method(typeof(BaseAI), "CanSenseTarget", new Type[2]
		{
			typeof(Character),
			typeof(bool)
		}, (Type[])null), null, new HarmonyMethod(typeFromHandle, "CanSenseTargetPostfix", (Type[])null));
		TryPatch(harmony, "kill blood", AccessTools.Method(typeof(Character), "OnDeath", (Type[])null, (Type[])null), new HarmonyMethod(typeFromHandle, "OnDeathPrefix", (Type[])null), null);
		TryPatch(harmony, "pursuit navmesh", AccessTools.Method(typeof(Pathfinding), "SetupAgents", (Type[])null, (Type[])null), null, new HarmonyMethod(typeFromHandle, "SetupAgentsPostfix", (Type[])null));
		TryPatch(harmony, "food chests", AccessTools.Method(typeof(Container), "Awake", (Type[])null, (Type[])null), null, new HarmonyMethod(typeFromHandle, "ContainerAwakePostfix", (Type[])null));
		TryPatch(harmony, "cache recheck on building", AccessTools.Method(typeof(Piece), "OnPlaced", (Type[])null, (Type[])null), null, new HarmonyMethod(typeFromHandle, "PieceOnPlacedPostfix", (Type[])null));
	}

	private static void TryPatch(Harmony harmony, string feature, MethodBase target, HarmonyMethod prefix, HarmonyMethod postfix)
	{
		if (target == null)
		{
			Failed.Add(feature);
			Plugin.Log.LogWarning((object)("Predators Hunt: hook for '" + feature + "' not found in this Valheim build; that feature is off."));
			return;
		}
		try
		{
			harmony.Patch(target, prefix, postfix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			Installed.Add(feature);
		}
		catch (Exception ex)
		{
			Failed.Add(feature);
			Plugin.Log.LogWarning((object)("Predators Hunt: could not patch " + target.DeclaringType?.ToString() + "." + target.Name + " for '" + feature + "': " + ex.Message));
		}
	}

	internal static string Summary()
	{
		string text = string.Join(", ", Installed.ToArray());
		if (Failed.Count > 0)
		{
			text = text + " (off: " + string.Join(", ", Failed.ToArray()) + ")";
		}
		return text;
	}

	private static void BaseAIAwakePostfix(BaseAI __instance)
	{
		try
		{
			Predators.OnAwake(__instance);
		}
		catch (Exception ex)
		{
			Plugin.LogOnce("awake", "Predators Hunt: registering a predator threw (" + ex.Message + ").");
		}
	}

	private static void UpdateAIPrefix(MonsterAI __instance, float dt)
	{
		if (!Active)
		{
			return;
		}
		Predator predator = Predators.Get((BaseAI)(object)__instance);
		if (predator == null)
		{
			return;
		}
		try
		{
			predator.Tick(dt);
		}
		catch (Exception ex)
		{
			Plugin.LogOnce("tick-" + ex.GetType().Name + ex.Message, "Predators Hunt: the state machine threw on " + predator.Prefab + " (" + ex?.ToString() + ").");
		}
	}

	private static bool IdleMovementPrefix(BaseAI __instance, float dt)
	{
		if (!Active)
		{
			return true;
		}
		Predator predator = Predators.Get(__instance);
		if (predator == null)
		{
			return true;
		}
		try
		{
			return !predator.Drive(dt);
		}
		catch (Exception ex)
		{
			Plugin.LogOnce("drive-" + ex.GetType().Name + ex.Message, "Predators Hunt: scent movement threw on " + predator.Prefab + " (" + ex?.ToString() + ").");
			return true;
		}
	}

	private static void CanSenseTargetPostfix(BaseAI __instance, Character target, ref bool __result)
	{
		if (!__result || !Active)
		{
			return;
		}
		Player val = (Player)(object)((target is Player) ? target : null);
		if ((Object)(object)val == (Object)null)
		{
			return;
		}
		Predator predator = Predators.Get(__instance);
		if (predator == null)
		{
			return;
		}
		try
		{
			if (!predator.FilterSense(val))
			{
				__result = false;
			}
		}
		catch (Exception ex)
		{
			Plugin.LogOnce("sense", "Predators Hunt: the sight filter threw (" + ex.Message + ").");
		}
	}

	private static void OnDeathPrefix(Character __instance)
	{
		if (!PhConfig.Enabled.Value)
		{
			return;
		}
		try
		{
			PlayerScent.OnCreatureDeath(__instance);
		}
		catch (Exception ex)
		{
			Plugin.LogOnce("death", "Predators Hunt: kill blood threw (" + ex.Message + ").");
		}
	}

	private static void SetupAgentsPostfix(Pathfinding __instance)
	{
		try
		{
			PathAgents.Setup(__instance);
		}
		catch (Exception ex)
		{
			Plugin.LogOnce("agents", "Predators Hunt: pursuit navmesh setup threw (" + ex.Message + ").");
		}
	}

	private static void ContainerAwakePostfix(Container __instance)
	{
		try
		{
			StoredFood.OnContainerAwake(__instance);
		}
		catch (Exception ex)
		{
			Plugin.LogOnce("container", "Predators Hunt: tracking a chest threw (" + ex.Message + ").");
		}
	}

	private static void PieceOnPlacedPostfix(Piece __instance)
	{
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		if (!((Object)(object)__instance == (Object)null))
		{
			StoredFood.MarkDirty(((Component)__instance).transform.position, 12f);
		}
	}
}
internal static class MigrantLink
{
	private static readonly int GroupKey = StringExtensionMethods.GetStableHashCode("mc_group");

	internal static bool InHerd(ZDO zdo)
	{
		if (zdo != null)
		{
			return zdo.GetInt(GroupKey, 0) != 0;
		}
		return false;
	}
}
internal static class PathAgents
{
	private const int PursuitOffset = 64;

	private static readonly AgentType[] Walkers;

	private static readonly MethodInfo AddAgentMethod;

	private static readonly MethodInfo GetSettingsMethod;

	private static readonly Type SettingsType;

	private static readonly FieldInfo BuildField;

	private static readonly FieldInfo AreaMaskField;

	private static readonly string[] CopiedFields;

	private static readonly HashSet<int> Registered;

	internal static string Report { get; private set; }

	internal static bool Ready => Registered.Count > 0;

	internal static AgentType PursuitOf(AgentType type)
	{
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_0003: Unknown result type (might be due to invalid IL or missing references)
		return (AgentType)(64 + type);
	}

	internal static bool HasPursuit(AgentType type)
	{
		//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_000e: Expected I4, but got Unknown
		return Registered.Contains(64 + type);
	}

	internal static bool IsPursuit(AgentType type)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0003: Invalid comparison between Unknown and I4
		return (int)type >= 64;
	}

	internal unsafe static void Setup(Pathfinding pathfinding)
	{
		//IL_0076: Unknown result type (might be due to invalid IL or missing references)
		//IL_0085: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
		//IL_0118: Unknown result type (might be due to invalid IL or missing references)
		//IL_011d: Unknown result type (might be due to invalid IL or missing references)
		//IL_012b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0130: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b3: 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_01c7: Unknown result type (might be due to invalid IL or missing references)
		//IL_01cd: Expected I4, but got Unknown
		//IL_01ce: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d0: Invalid comparison between Unknown and I4
		//IL_01d7: Unknown result type (might be due to invalid IL or missing references)
		//IL_01eb: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f0: Unknown result type (might be due to invalid IL or missing references)
		Registered.Clear();
		if (!PhConfig.PursuitNavmesh.Value)
		{
			Report = "off (PursuitNavmesh = false)";
			return;
		}
		if (AddAgentMethod == null || GetSettingsMethod == null || BuildField == null)
		{
			Report = "unavailable (Pathfinding internals not found)";
			Plugin.Log.LogWarning((object)"Predators Hunt: pursuit navmesh unavailable in this Valheim build; pursuers use the normal navmesh.");
			return;
		}
		List<string> list = new List<string>();
		for (int i = 0; i < Walkers.Length; i++)
		{
			AgentType val = Walkers[i];
			try
			{
				object obj = GetSettingsMethod.Invoke(pathfinding, new object[1] { val });
				if (obj == null)
				{
					continue;
				}
				object obj2 = AddAgentMethod.Invoke(pathfinding, new object[2]
				{
					PursuitOf(val),
					obj
				});
				if (obj2 == null)
				{
					continue;
				}
				for (int j = 0; j < CopiedFields.Length; j++)
				{
					FieldInfo fieldInfo = AccessTools.Field(SettingsType, CopiedFields[j]);
					if (fieldInfo != null)
					{
						fieldInfo.SetValue(obj2, fieldInfo.GetValue(obj));
					}
				}
				NavMeshBuildSettings val2 = (NavMeshBuildSettings)BuildField.GetValue(obj);
				NavMeshBuildSettings val3 = (NavMeshBuildSettings)BuildField.GetValue(obj2);
				((NavMeshBuildSettings)(ref val3)).agentClimb = Mathf.Min(Mathf.Max(((NavMeshBuildSettings)(ref val2)).agentClimb, PhConfig.PursuitStepHeight.Value), ((NavMeshBuildSettings)(ref val3)).agentHeight * 0.9f);
				((NavMeshBuildSettings)(ref val3)).agentSlope = PhConfig.PursuitSlopeLimit.Value;
				((NavMeshBuildSettings)(ref val3)).ledgeDropHeight = Mathf.Max(((NavMeshBuildSettings)(ref val3)).ledgeDropHeight, PhConfig.PursuitJumpHeight.Value);
				((NavMeshBuildSettings)(ref val3)).maxJumpAcrossDistance = Mathf.Max(((NavMeshBuildSettings)(ref val3)).maxJumpAcrossDistance, PhConfig.PursuitJumpDistance.Value);
				BuildField.SetValue(obj2, val3);
				Registered.Add(64 + val);
				if ((int)val == 1)
				{
					string[] array = ((NavMeshBuildSettings)(ref val3)).ValidationReport(new Bounds(Vector3.zero, new Vector3(64f, 6000f, 64f)));
					list.Add("Humanoid step " + ((NavMeshBuildSettings)(ref val2)).agentClimb.ToString("0.0#") + " -> " + ((NavMeshBuildSettings)(ref val3)).agentClimb.ToString("0.0#") + " m, slope " + ((NavMeshBuildSettings)(ref val2)).agentSlope.ToString("0") + " -> " + ((NavMeshBuildSettings)(ref val3)).agentSlope.ToString("0") + " deg" + ((array != null && array.Length != 0) ? (" (Unity: " + string.Join("; ", array) + ")") : ""));
				}
			}
			catch (Exception ex)
			{
				Plugin.LogOnce("agent-" + ((object)(*(AgentType*)(&val))/*cast due to .constrained prefix*/).ToString(), "Predators Hunt: pursuit agent for " + ((object)(*(AgentType*)(&val))/*cast due to .constrained prefix*/).ToString() + " failed (" + ex.Message + ").");
			}
		}
		Report = Registered.Count + " pursuit agent types" + ((list.Count > 0) ? ("; " + string.Join("; ", list.ToArray())) : "");
		Plugin.Log.LogInfo((object)("Predators Hunt: " + Report));
	}

	internal static bool TryFilter(AgentType type, out NavMeshQueryFilter filter)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0043: Unknown result type (might be due to invalid IL or missing references)
		//IL_0062: Unknown result type (might be due to invalid IL or missing references)
		//IL_0067: Unknown result type (might be due to invalid IL or missing references)
		filter = default(NavMeshQueryFilter);
		if ((Object)(object)Pathfinding.instance == (Object)null || GetSettingsMethod == null || BuildField == null)
		{
			return false;
		}
		try
		{
			object obj = GetSettingsMethod.Invoke(Pathfinding.instance, new object[1] { type });
			if (obj == null)
			{
				return false;
			}
			NavMeshBuildSettings val = (NavMeshBuildSettings)BuildField.GetValue(obj);
			((NavMeshQueryFilter)(ref filter)).agentTypeID = ((NavMeshBuildSettings)(ref val)).agentTypeID;
			((NavMeshQueryFilter)(ref filter)).areaMask = ((AreaMaskField != null) ? ((int)AreaMaskField.GetValue(obj)) : (-1));
			return true;
		}
		catch
		{
			return false;
		}
	}

	static PathAgents()
	{
		AgentType[] array = new AgentType[11];
		RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/);
		Walkers = (AgentType[])(object)array;
		AddAgentMethod = AccessTools.Method(typeof(Pathfinding), "AddAgent", (Type[])null, (Type[])null);
		GetSettingsMethod = AccessTools.Method(typeof(Pathfinding), "GetSettings", (Type[])null, (Type[])null);
		SettingsType = AccessTools.Inner(typeof(Pathfinding), "AgentSettings");
		BuildField = ((SettingsType != null) ? AccessTools.Field(SettingsType, "m_build") : null);
		AreaMaskField = ((SettingsType != null) ? AccessTools.Field(SettingsType, "m_areaMask") : null);
		CopiedFields = new string[5] { "m_canWalk", "m_avoidWater", "m_canSwim", "m_swimDepth", "m_areaMask" };
		Registered = new HashSet<int>();
		Report = "not set up yet";
	}
}
internal enum HuntState
{
	Unaware,
	Interested,
	Tracking,
	Pursuit,
	Searching,
	GiveUp,
	Feeding
}
internal sealed class Predator
{
	private const float LegLength = 20f;

	private const float LongGoal = 25f;

	private const float ArriveDistance = 2.5f;

	private const float SightCheckInterval = 0.25f;

	private const float AgentCheckInterval = 1f;

	private const float JumpCheckInterval = 0.25f;

	private const float StuckSeconds = 1f;

	private const float StuckDistance = 0.3f;

	private const float StuckMinDistance = 4f;

	private const float WeakFactor = 0.5f;

	private const int WeakSamplesToDrop = 2;

	private const int MaxMoveFailures = 6;

	private const float FeedSatedAfter = 20f;

	private const float LingerRadius = 4f;

	private const float HandOffApproach = 8f;

	private const float HearingClueError = 0.25f;

	private const float GiveUpMinSeconds = 10f;

	internal readonly MonsterAI Ai;

	internal readonly Character Character;

	internal readonly ZNetView NView;

	internal readonly string Prefab;

	internal readonly float Nose;

	internal readonly bool CarcassEater;

	private readonly AgentType baseAgent;

	private readonly float fleeIfLowHealth;

	private readonly bool fleeIfHurtUnreachable;

	private readonly bool fleeIfNotAlerted;

	internal ScentFix Fix;

	private Player sourcePlayer;

	private float nextSample;

	private int strongSamples;

	private int weakSamples;

	private float lastScent;

	private float arrivedAt = -1f;

	private float lingerUntil;

	private float handOffSince = -1f;

	private bool hasClue;

	private Vector3 clue;

	private Player cluePlayer;

	private float lastSeen;

	private float nextSight;

	private bool seenNow;

	private Vector3 lastKnown;

	private Vector3 lastKnownVelocity;

	private bool bursting;

	private float burstUntil;

	private float nextBurst;

	private float speedBeforeBurst;

	private bool baseReachable = true;

	private bool reachLogged;

	private float nextAgentCheck;

	private float nextJumpCheck;

	private float nextJump;

	private Vector3 stuckRef;

	private float stuckSince;

	private readonly List<Vector3> waypoints = new List<Vector3>();

	private int waypointIndex;

	private float searchUntil;

	private float dwellUntil;

	private float searchRadius;

	private float searchAngle;

	private int searchKey;

	private float nextClueUse;

	private readonly Dictionary<int, bool> carcassRolls = new Dictionary<int, bool>();

	private readonly Dictionary<int, float> ignored = new Dictionary<int, float>();

	private readonly List<int> expired = new List<int>();

	private float giveUpUntil;

	private float nextPrune;

	private int lastFeedState;

	private float feedStart;

	private float satedUntil;

	private bool raging;

	private float rageUntil;

	private bool walking;

	private int moveFailures;

	private float nextFailureCount;

	internal HuntState State { get; private set; }

	internal float StateSince { get; private set; }

	internal string Reason { get; private set; } = "";

	internal bool Dormant { get; private set; }

	internal SourceKind SourceKind { get; private set; }

	internal int SourceKey { get; private set; }

	internal Vector3 Estimate { get; private set; }

	internal Character Target { get; private set; }

	internal bool UsingPursuitAgent { get; private set; }

	internal int Leaps { get; private set; }

	internal Vector3 Position
	{
		get
		{
			//IL_002a: 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)
			if (!((Object)(object)Character != (Object)null))
			{
				return ((Component)Ai).transform.position;
			}
			return ((Component)Character).transform.position;
		}
	}

	internal string Name
	{
		get
		{
			ZDO val = (((Object)(object)NView != (Object)null && NView.IsValid()) ? NView.GetZDO() : null);
			return Prefab + "#" + ((val != null) ? (((ZDOID)(ref val.m_uid)).ID % 100000).ToString(CultureInfo.InvariantCulture) : "?");
		}
	}

	internal bool Hungry => !Sated(Time.time);

	internal bool IsOwner
	{
		get
		{
			if ((Object)(object)NView != (Object)null && NView.IsValid())
			{
				return NView.IsOwner();
			}
			return false;
		}
	}

	internal bool ModifiedAnything
	{
		get
		{
			if (!bursting && !UsingPursuitAgent && !raging)
			{
				return walking;
			}
			return true;
		}
	}

	internal Predator(MonsterAI ai, string prefab, float nose)
	{
		//IL_009a: Unknown result type (might be due to invalid IL or missing references)
		//IL_009f: Unknown result type (might be due to invalid IL or missing references)
		Ai = ai;
		Character = ((Component)ai).GetComponent<Character>();
		NView = Access.NView((BaseAI)(object)ai);
		Prefab = prefab;
		Nose = nose;
		CarcassEater = PhConfig.CarcassEaterSet.Matches(prefab);
		baseAgent = ((BaseAI)ai).m_pathAgentType;
		fleeIfLowHealth = ai.m_fleeIfLowHealth;
		fleeIfHurtUnreachable = ai.m_fleeIfHurtWhenTargetCantBeReached;
		fleeIfNotAlerted = ai.m_fleeIfNotAlerted;
		State = HuntState.Unaware;
		StateSince = Time.time;
		nextSample = Time.time + Random.Range(0f, PhConfig.SampleSeconds.Value);
	}

	internal bool Sated(float now)
	{
		return now < satedUntil;
	}

	internal bool IsIgnored(int key, float now)
	{
		if (ignored.TryGetValue(key, out var value))
		{
			return now < value;
		}
		return false;
	}

	internal unsafe bool WantsCarcass(int key, Vector3 center)
	{
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		if (carcassRolls.TryGetValue(key, out var value))
		{
			return value;
		}
		Biome biome = (Biome)((WorldGenerator.instance != null) ? ((int)WorldGenerator.instance.GetBiome(center)) : 0);
		float num = PhConfig.CarcassChance(biome);
		float num2 = Random.value * 100f;
		value = num2 < num;
		if (carcassRolls.Count > 128)
		{
			carcassRolls.Clear();
		}
		carcassRolls[key] = value;
		Note("smells a carcass in " + ((object)(*(Biome*)(&biome))/*cast due to .constrained prefix*/).ToString() + ": " + num.ToString("0", CultureInfo.InvariantCulture) + "% chance, rolled " + num2.ToString("0", CultureInfo.InvariantCulture) + (value ? ": goes to it" : ": leaves it alone"));
		return value;
	}

	internal bool IsIgnored(Character c, float now)
	{
		Player val = (Player)(object)((c is Player) ? c : null);
		if ((Object)(object)val != (Object)null)
		{
			return IsIgnored(ScentField.KeyOf(val), now);
		}
		return false;
	}

	private bool Eligible()
	{
		if ((Object)(object)Character == (Object)null || Character.IsDead())
		{
			return false;
		}
		if (!Character.IsTamed() && !((BaseAI)Ai).IsSleeping() && !Ai.IsEventCreature())
		{
			return !((BaseAI)Ai).HuntPlayer();
		}
		return false;
	}

	internal void Tick(float dt)
	{
		if (!IsOwner)
		{
			if (State != HuntState.Unaware || ModifiedAnything)
			{
				ReleaseLocal("not the owner");
			}
			return;
		}
		float time = Time.time;
		if (!Eligible())
		{
			if (State != HuntState.Unaware)
			{
				Enter(HuntState.Unaware, "vanilla AI (tamed, asleep, raid or dead)");
			}
			return;
		}
		Dormant = !PhConfig.MigrantsFollowScent.Value && MigrantLink.InHerd(NView.GetZDO());
		if (time >= nextPrune)
		{
			nextPrune = time + 2f;
			PruneIgnored(time);
		}
		UpdateRage(time);
		int num = DeadRemainLink.FeedState(NView.GetZDO());
		if (num != 0)
		{
			if (State != HuntState.Feeding)
			{
				feedStart = time;
				Enter(HuntState.Feeding, "The Dead Remain: " + FeedPhase(num));
			}
			else if (num != lastFeedState)
			{
				Note("The Dead Remain: " + FeedPhase(num));
			}
			lastFeedState = num;
			return;
		}
		if (State == HuntState.Feeding)
		{
			LeaveFeeding(time);
		}
		Character val = Access.TargetCreature(Ai);
		if (State == HuntState.Pursuit)
		{
			UpdatePursuit(time, val);
			return;
		}
		if ((Object)(object)val != (Object)null && IsHiddenPlayer(val))
		{
			Access.ClearTarget(Ai, 2f);
			val = null;
		}
		if ((Object)(object)val != (Object)null && !val.IsDead())
		{
			StartPursuit(val, time, "locked on (" + (((BaseAI)Ai).CanSeeTarget(val) ? "in sight" : "hurt or heard close") + ")");
			return;
		}
		if (Dormant)
		{
			hasClue = false;
			if (State != HuntState.Unaware && State != HuntState.GiveUp)
			{
				Enter(HuntState.Unaware, "travelling with its herd (Migrating Creatures)");
			}
			return;
		}
		switch (State)
		{
		case HuntState.Unaware:
		case HuntState.GiveUp:
			UpdateUnaware(time);
			break;
		case HuntState.Interested:
		case HuntState.Tracking:
			UpdateScentFollow(time);
			break;
		case HuntState.Searching:
			UpdateSearching(time);
			break;
		case HuntState.Pursuit:
			break;
		}
	}

	private static string FeedPhase(int state)
	{
		return state switch
		{
			1 => "approaching a carcass", 
			2 => "feeding", 
			3 => "waiting its turn at a carcass", 
			4 => "fighting over a carcass", 
			_ => "scavenging", 
		};
	}

	private void UpdateUnaware(float now)
	{
		if (State == HuntState.GiveUp && now >= giveUpUntil)
		{
			Enter(HuntState.Unaware, "cooldown over");
			return;
		}
		if (hasClue && State == HuntState.Unaware)
		{
			hasClue = false;
			AdoptClue(now, HuntState.Interested, "heard " + cluePlayer.GetPlayerName());
			return;
		}
		hasClue = false;
		if (!(now < nextSample))
		{
			nextSample = now + PhConfig.SampleSeconds.Value * Random.Range(0.8f, 1.2f);
			ScentFix fix = ScentField.Sample(this);
			if (fix.Valid)
			{
				Adopt(fix, now);
				Enter(HuntState.Interested, "smells " + Describe(fix));
			}
		}
	}

	private void AdoptClue(float now, HuntState state, string why)
	{
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		//IL_003e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0043: Unknown result type (might be due to invalid IL or missing references)
		//IL_0057: Unknown result type (might be due to invalid IL or missing references)
		//IL_005d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0080: Unknown result type (might be due to invalid IL or missing references)
		//IL_0091: Unknown result type (might be due to invalid IL or missing references)
		Player val = cluePlayer;
		if (!((Object)(object)val == (Object)null))
		{
			ScentFix fix = new ScentFix
			{
				Kind = SourceKind.Player,
				Key = ScentField.KeyOf(val),
				Source = clue,
				Goal = clue,
				Intensity = 0f,
				Distance = Vector3.Distance(Position, clue),
				Player = val
			};
			Adopt(fix, now);
			Estimate = Blur(clue, fix.Distance * 0.25f);
			Enter(state, why);
		}
	}

	private void Adopt(ScentFix fix, float now)
	{
		//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00be: Unknown result type (might be due to invalid IL or missing references)
		bool num = fix.Key != SourceKey || fix.Kind == SourceKind.None;
		Fix = fix;
		SourceKind = ((fix.Kind == SourceKind.Trail) ? SourceKind.Player : fix.Kind);
		SourceKey = fix.Key;
		sourcePlayer = fix.Player;
		lastScent = now;
		if (num)
		{
			strongSamples = 0;
			weakSamples = 0;
			arrivedAt = -1f;
			handOffSince = -1f;
			moveFailures = 0;
		}
		float num2 = ((State == HuntState.Tracking) ? PhConfig.TrackingErrorFraction.Value : PhConfig.InterestedErrorFraction.Value);
		Estimate = Blur(fix.Goal, Mathf.Max(1f, fix.Distance * num2));
	}

	private void UpdateScentFollow(float now)
	{
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
		//IL_0106: Unknown result type (might be due to invalid IL or missing references)
		//IL_021f: 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)
		if ((Object)(object)sourcePlayer != (Object)null && !PlayerScent.Hidden(sourcePlayer))
		{
			float num = Vector3.Distance(Position, ((Component)sourcePlayer).transform.position);
			if (num <= PhConfig.ContactRange.Value)
			{
				StartPursuit((Character)(object)sourcePlayer, now, "nose to nose at " + F(num) + " m");
				return;
			}
			if (CanSee((Character)(object)sourcePlayer, now))
			{
				StartPursuit((Character)(object)sourcePlayer, now, "sighted at " + F(num) + " m");
				return;
			}
		}
		if (hasClue)
		{
			hasClue = false;
			if ((Object)(object)cluePlayer != (Object)null && (SourceKind != SourceKind.Player || ScentField.KeyOf(cluePlayer) == SourceKey))
			{
				sourcePlayer = cluePlayer;
				Estimate = Blur(clue, Vector3.Distance(Position, clue) * 0.25f);
			}
		}
		if (lingerUntil > 0f)
		{
			if (now >= lingerUntil)
			{
				lingerUntil = 0f;
				Ignore(SourceKey, now + PhConfig.FoodSourceCooldownSeconds.Value);
				Enter(HuntState.Unaware, "done nosing around the " + KindName(SourceKind));
			}
		}
		else if (SourceKind == SourceKind.Carcass && handOffSince >= 0f && now - handOffSince > PhConfig.HandOffWaitSeconds.Value)
		{
			Ignore(SourceKey, now + PhConfig.FoodSourceCooldownSeconds.Value);
			Enter(HuntState.Unaware, "The Dead Remain never took it to the carcass (a player near it, or no free place)");
		}
		else
		{
			if (now < nextSample)
			{
				return;
			}
			nextSample = now + ((State == HuntState.Tracking) ? PhConfig.TrackingResampleSeconds.Value : PhConfig.InterestedResampleSeconds.Value);
			ScentFix fix = ScentField.Sample(this);
			if (!fix.Valid)
			{
				if (!(now - lastScent < PhConfig.ScentLostSeconds.Value))
				{
					if (State == HuntState.Tracking && SourceKind == SourceKind.Player)
					{
						StartSearch(Estimate, Vector3.zero, SourceKey, now);
						Enter(HuntState.Searching, "lost the scent");
					}
					else
					{
						Enter(HuntState.Unaware, "lost the scent");
					}
				}
				return;
			}
			int sourceKey = SourceKey;
			SourceKind sourceKind = SourceKind;
			Adopt(fix, now);
			if (fix.Key != sourceKey)
			{
				Note("now smells " + Describe(fix) + " (was " + KindName(sourceKind) + ")");
				if (State == HuntState.Tracking)
				{
					Enter(HuntState.Interested, "new scent: " + Describe(fix));
					return;
				}
			}
			bool flag = fix.Intensity >= PhConfig.TrackThreshold.Value;
			strongSamples = (flag ? (strongSamples + 1) : 0);
			weakSamples = ((fix.Intensity < PhConfig.TrackThreshold.Value * 0.5f) ? (weakSamples + 1) : 0);
			if (State == HuntState.Interested && strongSamples >= PhConfig.TrackSamples.Value)
			{
				Enter(HuntState.Tracking, "strong, steady scent: " + Describe(fix));
			}
			else if (State == HuntState.Tracking && weakSamples >= 2)
			{
				Enter(HuntState.Interested, "scent fading: " + Describe(fix));
			}
			if (SourceKind == SourceKind.Carcass && fix.Distance <= PhConfig.CarcassHandOffRange.Value && handOffSince < 0f)
			{
				handOffSince = now;
				Note("close to the carcass (" + F(fix.Distance) + " m): over to The Dead Remain");
			}
		}
	}

	private void StartPursuit(Character target, float now, string why)
	{
		//IL_0040: Unknown result type (might be due to invalid IL or missing references)
		//IL_0045: Unknown result type (might be due to invalid IL or missing references)
		//IL_004c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0051: Unknown result type (might be due to invalid IL or missing references)
		//IL_006a: Unknown result type (might be due to invalid IL or missing references)
		//IL_006f: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)Access.TargetCreature(Ai) != (Object)(object)target)
		{
			Access.SetTarget(Ai, target);
		}
		Access.SetAlerted((BaseAI)(object)Ai, alerted: true);
		Target = target;
		lastSeen = now;
		lastKnown = ((Component)target).transform.position;
		lastKnownVelocity = target.GetVelocity();
		nextBurst = Mathf.Min(nextBurst, now);
		stuckRef = Position;
		stuckSince = now;
		reachLogged = false;
		baseReachable = true;
		nextAgentCheck = 0f;
		Enter(HuntState.Pursuit, why + " -> " + NameOf(target));
	}

	private void UpdatePursuit(float now, Character vanillaTarget)
	{
		//IL_0126: Unknown result type (might be due to invalid IL or missing references)
		//IL_012b: Unknown result type (might be due to invalid IL or missing references)
		//IL_012d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0132: Unknown result type (might be due to invalid IL or missing references)
		//IL_0085: Unknown result type (might be due to invalid IL or missing references)
		//IL_008b: Unknown result type (might be due to invalid IL or missing references)
		//IL_015d: Unknown result type (might be due to invalid IL or missing references)
		//IL_015e: Unknown result type (might be due to invalid IL or missing references)
		//IL_016a: Unknown result type (might be due to invalid IL or missing references)
		//IL_016f: Unknown result type (might be due to invalid IL or missing references)
		//IL_021d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0225: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b4: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)vanillaTarget == (Object)null || vanillaTarget.IsDead() || IsHiddenPlayer(vanillaTarget))
		{
			if ((Object)(object)Target == (Object)null || Target.IsDead())
			{
				Access.ClearTarget(Ai, 2f);
				Enter(HuntState.Unaware, "target gone");
				return;
			}
			bool flag = (Object)(object)vanillaTarget != (Object)null && IsHiddenPlayer(vanillaTarget);
			Access.ClearTarget(Ai, 2f);
			Access.SetAlerted((BaseAI)(object)Ai, alerted: false);
			StartSearch(lastKnown, lastKnownVelocity, KeyOfTarget(Target), now);
			Enter(HuntState.Searching, flag ? ("lost " + NameOf(Target) + " (vanished from sight and scent)") : ("vanilla let go of " + NameOf(Target)));
			return;
		}
		if ((Object)(object)vanillaTarget != (Object)(object)Target)
		{
			Target = vanillaTarget;
			lastSeen = now;
			if (PhConfig.VerboseLogging.Value)
			{
				Note("switches target to " + NameOf(vanillaTarget));
			}
		}
		Vector3 position = ((Component)Target).transform.position;
		float num = Vector3.Distance(Position, position);
		if (CanSee(Target, now) || num <= PhConfig.ContactRange.Value)
		{
			lastSeen = now;
			lastKnown = position;
			lastKnownVelocity = Target.GetVelocity();
		}
		else if (now - lastSeen > PhConfig.LostContactSeconds.Value)
		{
			Access.ClearTarget(Ai, 2f);
			Access.SetAlerted((BaseAI)(object)Ai, alerted: false);
			StartSearch(lastKnown, lastKnownVelocity, KeyOfTarget(Target), now);
			Enter(HuntState.Searching, "lost sight of " + NameOf(Target) + " for " + F(now - lastSeen) + " s");
			return;
		}
		UpdateBurst(now, num);
		UpdateAgent(now, position);
		UpdateJump(now, position);
	}

	private void UpdateBurst(float now, float distance)
	{
		if (bursting)
		{
			if (!(now < burstUntil))
			{
				EndBurst();
				nextBurst = now + PhConfig.BurstCooldownSeconds.Value;
			}
		}
		else if (!(now < nextBurst) && !(distance < 5f) && !(PhConfig.BurstSeconds.Value <= 0f) && !(PhConfig.BurstSpeedMultiplier.Value <= 1f))
		{
			bursting = true;
			speedBeforeBurst = Character.m_runSpeed;
			Character.m_runSpeed = speedBeforeBurst * PhConfig.BurstSpeedMultiplier.Value;
			burstUntil = now + PhConfig.BurstSeconds.Value;
			if (PhConfig.VerboseLogging.Value)
			{
				Note("burst x" + PhConfig.BurstSpeedMultiplier.Value.ToString("0.00") + " for " + F(PhConfig.BurstSeconds.Value) + " s");
			}
		}
	}

	private void EndBurst()
	{
		if (bursting)
		{
			bursting = false;
			if ((Object)(object)Character != (Object)null)
			{
				Character.m_runSpeed = speedBeforeBurst;
			}
		}
	}

	private void UpdateAgent(float now, Vector3 targetPos)
	{
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_003d: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0055: Unknown result type (might be due to invalid IL or missing references)
		//IL_006f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0074: Unknown result type (might be due to invalid IL or missing references)
		//IL_0076: Unknown result type (might be due to invalid IL or missing references)
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		if (now < nextAgentCheck)
		{
			return;
		}
		nextAgentCheck = now + 1f;
		if ((Object)(object)Pathfinding.instance == (Object)null || Character.IsFlying())
		{
			return;
		}
		if ((baseReachable = Pathfinding.instance.HavePath(Position, targetPos, baseAgent)) || !PathAgents.HasPursuit(baseAgent))
		{
			SetAgent(pursuit: false);
			return;
		}
		bool num = Pathfinding.instance.HavePath(Position, targetPos, PathAgents.PursuitOf(baseAgent));
		SetAgent(pursuit: true);
		if (num && !reachLogged)
		{
			reachLogged = true;
			Note("pursuit navmesh reaches " + NameOf(Target) + " where the normal one does not (step " + F(PhConfig.PursuitStepHeight.Value) + " m)");
		}
	}

	private void SetAgent(bool pursuit)
	{
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_002d: Unknown result type (might be due to invalid IL or missing references)
		if (UsingPursuitAgent != pursuit)
		{
			UsingPursuitAgent = pursuit;
			((BaseAI)Ai).m_pathAgentType = (pursuit ? PathAgents.PursuitOf(baseAgent) : baseAgent);
		}
	}

	private void UpdateJump(float now, Vector3 targetPos)
	{
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		//IL_0047: Unknown result type (might be due to invalid IL or missing references)
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		//IL_0092: Unknown result type (might be due to invalid IL or missing references)
		//IL_0093: Unknown result type (might be due to invalid IL or missing references)
		//IL_0094: Unknown result type (might be due to invalid IL or missing references)
		//IL_0099: Unknown result type (might be due to invalid IL or missing references)
		//IL_009a: Unknown result type (might be due to invalid IL or missing references)
		//IL_012b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0130: Unknown result type (might be due to invalid IL or missing references)
		//IL_0169: Unknown result type (might be due to invalid IL or missing references)
		//IL_01dd: Unknown result type (might be due to invalid IL or missing references)
		//IL_01df: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e4: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ec: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f9: Unknown result type (might be due to invalid IL or missing references)
		//IL_0200: Unknown result type (might be due to invalid IL or missing references)
		//IL_0205: Unknown result type (might be due to invalid IL or missing references)
		//IL_020c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0211: Unknown result type (might be due to invalid IL or missing references)
		if (!PhConfig.JumpAssist.Value || now < nextJumpCheck)
		{
			return;
		}
		nextJumpCheck = now + 0.25f;
		Vector3 position = Position;
		Vector3 val = position - stuckRef;
		if (((Vector3)(ref val)).sqrMagnitude > 0.09f)
		{
			stuckRef = position;
			stuckSince = now;
		}
		if (now < nextJump || Character.IsFlying() || !Character.IsOnGround() || Character.InAttack() || Character.IsSwimming())
		{
			return;
		}
		Vector3 val2 = targetPos - position;
		float y = val2.y;
		val2.y = 0f;
		float magnitude = ((Vector3)(ref val2)).magnitude;
		float value = PhConfig.PursuitJumpDistance.Value;
		float value2 = PhConfig.PursuitJumpHeight.Value;
		if (!(value <= 0f) && !(magnitude < 0.5f) && !(magnitude > value + 2f) && !(y > value2 + 0.5f) && !(y < -3f))
		{
			int num;
			if (now - stuckSince >= 1f && magnitude > Mathf.Max(4f, AttackReach() + 1.5f))
			{
				val = Character.GetMoveDir();
				num = ((((Vector3)(ref val)).sqrMagnitude > 0.01f) ? 1 : 0);
			}
			else
			{
				num = 0;
			}
			bool flag = (byte)num != 0;
			bool flag2 = y > 0.6f && !baseReachable;
			if (flag || flag2)
			{
				float num2 = Mathf.Max(1f, 0f - Physics.gravity.y);
				float num3 = Mathf.Max(y, 0f) + 0.6f;
				float num4 = Mathf.Sqrt(2f * num2 * num3);
				float num5 = Mathf.Sqrt(2f * Mathf.Max(0.05f, num3 - y) / num2);
				float num6 = num4 / num2 + num5;
				float num7 = Mathf.Clamp(magnitude - 0.4f, 0.5f, value);
				Vector3 val3 = val2 / magnitude;
				((BaseAI)Ai).LookTowards(val3);
				Character.ForceJump(val3 * (num7 / num6) + Vector3.up * num4, true);
				nextJump = now + PhConfig.JumpCooldownSeconds.Value;
				stuckSince = now;
				Leaps++;
				Note("leaps " + F(num7) + " m across, " + F(y) + " m up toward " + NameOf(Target) + (flag ? " (stuck)" : " (ledge)"));
			}
		}
	}

	private float AttackReach()
	{
		Character character = Character;
		Humanoid val = (Humanoid)(object)((character is Humanoid) ? character : null);
		ItemData val2 = (((Object)(object)val != (Object)null) ? val.GetCurrentWeapon() : null);
		if (val2 == null || val2.m_shared == null)
		{
			return 2.5f;
		}
		return Mathf.Max(1.5f, val2.m_shared.m_aiAttackRange);
	}

	private void StartSearch(Vector3 center, Vector3 heading, int key, float now)
	{
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_007c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0082: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
		EndPursuitExtras();
		searchKey = key;
		searchUntil = now + PhConfig.SearchSeconds.Value;
		waypoints.Clear();
		waypoints.Add(center);
		waypointIndex = 0;
		dwellUntil = 0f;
		moveFailures = 0;
		heading.y = 0f;
		searchAngle = ((((Vector3)(ref heading)).sqrMagnitude > 0.25f) ? Mathf.Atan2(heading.x, heading.z) : Random.Range(0f, (float)Math.PI * 2f));
		searchRadius = PhConfig.SearchStartRadius.Value;
		AddSearchRing(center);
	}

	private void AddSearchRing(Vector3 center)
	{
		//IL_005e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0072: Unknown result type (might be due to invalid IL or missing references)
		//IL_007d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0082: Unknown result type (might be due to invalid IL or missing references)
		//IL_0087: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
		//IL_009b: Unknown result type (might be due to invalid IL or missing references)
		int num = Mathf.Max(1, PhConfig.SearchWaypointsMin.Value);
		int num2 = Mathf.Max(num, PhConfig.SearchWaypointsMax.Value);
		int num3 = Random.Range(num, num2 + 1);
		float value = PhConfig.SearchRadiusGrowth.Value;
		float num4 = (float)Math.PI * 2f / (float)num3;
		float y = default(float);
		for (int i = 0; i < num3; i++)
		{
			float num5 = searchAngle + Random.Range(-0.3f, 0.3f);
			Vector3 val = center + new Vector3(Mathf.Sin(num5), 0f, Mathf.Cos(num5)) * searchRadius;
			if ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetSolidHeight(val, ref y, 1000))
			{
				val.y = y;
			}
			waypoints.Add(val);
			searchRadius += PhConfig.SearchStartRadius.Value * value;
			searchAngle += num4 + Random.Range(0.2f, 0.6f);
		}
	}

	private void UpdateSearching(float now)
	{
		//IL_007a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0085: Unknown result type (might be due to invalid IL or missing references)
		//IL_011f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0125: Unknown result type (might be due to invalid IL or missing references)
		//IL_012b: Unknown result type (might be due to invalid IL or missing references)
		//IL_013b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0140: 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)
		//IL_015e: Unknown result type (might be due to invalid IL or missing references)
		if (now >= searchUntil)
		{
			GiveUpOn(searchKey, now, "searched " + F(PhConfig.SearchSeconds.Value) + " s without finding it");
			return;
		}
		Player val = PlayerByKey(searchKey);
		if ((Object)(object)val != (Object)null && !PlayerScent.Hidden(val) && !IsIgnored(searchKey, now))
		{
			if (CanSee((Character)(object)val, now))
			{
				StartPursuit((Character)(object)val, now, "found it again (in sight)");
				return;
			}
			if (Vector3.Distance(Position, ((Component)val).transform.position) <= PhConfig.ContactRange.Value)
			{
				StartPursuit((Character)(object)val, now, "found it again (nose to nose)");
				return;
			}
		}
		if (hasClue)
		{
			hasClue = false;
			if ((Object)(object)cluePlayer != (Object)null && ScentField.KeyOf(cluePlayer) == searchKey && now >= nextClueUse)
			{
				nextClueUse = now + 5f;
				waypoints.RemoveRange(waypointIndex, waypoints.Count - waypointIndex);
				Vector3 val2 = Blur(clue, Vector3.Distance(Position, clue) * 0.25f);
				waypoints.Add(val2);
				searchRadius = PhConfig.SearchStartRadius.Value;
				AddSearchRing(val2);
				dwellUntil = 0f;
				Note("heard it: searching around the noise");
			}
		}
		if (!(now < nextSample))
		{
			nextSample = now + PhConfig.TrackingResampleSeconds.Value;
			ScentFix fix = ScentField.Sample(this);
			if (fix.Valid && fix.Key == searchKey && fix.Intensity >= PhConfig.TrackThreshold.Value)
			{
				Adopt(fix, now);
				Enter(HuntState.Tracking, "picked the scent up again: " + Describe(fix));
			}
		}
	}

	private void GiveUpOn(int key, float now, string why)
	{
		float value = PhConfig.GiveUpCooldownSeconds.Value;
		if (key != 0)
		{
			Ignore(key, now + value);
		}
		giveUpUntil = now + Mathf.Max(10f, value);
		Access.SetAlerted((BaseAI)(object)Ai, alerted: false);
		Enter(HuntState.GiveUp, why + "; ignores it for " + F(value) + " s");
	}

	private void LeaveFeeding(float now)
	{
		Character val = Access.TargetCreature(Ai);
		if ((Object)(object)val != (Object)null && !val.IsDead())
		{
			StartRage(now);
			StartPursuit(val, now, "interrupted while feeding: attacks (no flight for " + F(PhConfig.FeedRageSeconds.Value) + " s)");
			return;
		}
		bool flag = lastFeedState == 2 && now - feedStart >= 20f;
		if (flag)
		{
			satedUntil = now + PhConfig.SatedMinutes.Value * 60f;
		}
		Enter(HuntState.Unaware, flag ? ("finished feeding, sated for " + F(PhConfig.SatedMinutes.Value) + " min") : "left the carcass");
	}

	private void StartRage(float now)
	{
		if (!(PhConfig.FeedRageSeconds.Value <= 0f))
		{
			raging = true;
			rageUntil = now + PhConfig.FeedRageSeconds.Value;
			ApplyRage();
		}
	}

	private void ApplyRage()
	{
		Ai.m_fleeIfLowHealth = 0f;
		Ai.m_fleeIfHurtWhenTargetCantBeReached = false;
		Ai.m_fleeIfNotAlerted = false;
	}

	private void UpdateRage(float now)
	{
		if (raging)
		{
			if (now < rageUntil)
			{
				ApplyRage();
			}
			else
			{
				EndRage();
			}
		}
	}

	private void EndRage()
	{
		if (raging)
		{
			raging = false;
			if (!((Object)(object)Ai == (Object)null))
			{
				Ai.m_fleeIfLowHealth = fleeIfLowHealth;
				Ai.m_fleeIfHurtWhenTargetCantBeReached = fleeIfHurtUnreachable;
				Ai.m_fleeIfNotAlerted = fleeIfNotAlerted;
			}
		}
	}

	internal bool Drive(float dt)
	{
		if (!IsOwner || Dormant)
		{
			return false;
		}
		float time = Time.time;
		switch (State)
		{
		case HuntState.Interested:
		case HuntState.Tracking:
			return DriveToScent(dt, time);
		case HuntState.Searching:
			return DriveSearch(dt, time);
		default:
			return false;
		}
	}

	private bool DriveToScent(float dt, float now)
	{
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
		//IL_0079: Unknown result type (might be due to invalid IL or missing references)
		//IL_007a: Unknown result type (might be due to invalid IL or missing references)
		//IL_008e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0144: 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)
		bool walk = State == HuntState.Interested;
		SetWalk(walk);
		Vector3 position = Position;
		Vector3 source = Fix.Source;
		if (lingerUntil > 0f)
		{
			SetWalk(walk: true);
			if (!Access.Circle((BaseAI)(object)Ai, dt, source, 4f, run: false))
			{
				Access.MoveTo((BaseAI)(object)Ai, dt, source, 4f, run: false);
			}
			return true;
		}
		if (SourceKind == SourceKind.Carcass && handOffSince >= 0f)
		{
			if (Utils.DistanceXZ(position, source) > 8f)
			{
				Access.MoveTo((BaseAI)(object)Ai, dt, source, 8f, run: false);
			}
			else
			{
				((BaseAI)Ai).StopMoving();
			}
			return true;
		}
		if (moveFailures >= 12)
		{
			Ignore(SourceKey, now + 60f);
			Enter(HuntState.Unaware, "cannot find a way to the " + KindName(SourceKind));
			return false;
		}
		Vector3 estimate = Estimate;
		bool flag = Step(dt, estimate, 2.5f, run: false);
		if (flag && arrivedAt < 0f)
		{
			arrivedAt = now;
			nextSample = Mathf.Min(nextSample, now + 0.5f);
			if ((SourceKind == SourceKind.Chest || SourceKind == SourceKind.Food) && Utils.DistanceXZ(position, source) < 6f)
			{
				lingerUntil = now + PhConfig.FoodLingerSeconds.Value;
				Note("reached the " + KindName(SourceKind) + "; noses around it for " + F(PhConfig.FoodLingerSeconds.Value) + " s");
			}
		}
		else if (!flag)
		{
			arrivedAt = -1f;
		}
		return true;
	}

	private bool DriveSearch(float dt, float now)
	{
		//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
		//IL_0060: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
		if (dwellUntil > 0f)
		{
			SetWalk(walk: true);
			if (now < dwellUntil)
			{
				((BaseAI)Ai).StopMoving();
				float num = (now * 70f + (float)(((Object)Character).GetInstanceID() % 360)) * ((float)Math.PI / 180f);
				((BaseAI)Ai).LookTowards(new Vector3(Mathf.Sin(num), 0f, Mathf.Cos(num)));
				return true;
			}
			dwellUntil = 0f;
			waypointIndex++;
			moveFailures = 0;
		}
		if (waypointIndex >= waypoints.Count)
		{
			Vector3 center = ((waypoints.Count > 0) ? waypoints[0] : Position);
			AddSearchRing(center);
		}
		bool flag = waypointIndex == 0;
		SetWalk(!flag);
		if (Step(dt, waypoints[waypointIndex], 2f, flag) || moveFailures >= 6)
		{
			dwellUntil = now + Random.Range(2f, 5f);
			moveFailures = 0;
			if (PhConfig.VerboseLogging.Value)
			{
				Note("search waypoint " + (waypointIndex + 1) + "/" + waypoints.Count);
			}
		}
		return true;
	}

	private bool Step(float dt, Vector3 goal, float arrive, bool run)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//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_0009: Unknown result type (might be due to invalid IL or missing references)
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
		//IL_007c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0081: Unknown result type (might be due to invalid IL or missing references)
		//IL_0083: Unknown result type (might be due to invalid IL or missing references)
		//IL_0088: Unknown result type (might be due to invalid IL or missing references)
		//IL_008d: 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)
		//IL_0090: Unknown result type (might be due to invalid IL or missing references)
		//IL_0097: Unknown result type (might be due to invalid IL or missing references)
		//IL_009c: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
		Vector3 position = Position;
		Vector3 val = goal - position;
		val.y = 0f;
		float magnitude = ((Vector3)(ref val)).magnitude;
		if (magnitude <= arrive)
		{
			return true;
		}
		Vector3 val2 = goal;
		if (magnitude > 25f)
		{
			float num = ((moveFailures == 0) ? 0f : (((moveFailures % 2 == 1) ? 1f : (-1f)) * 35f * (float)((moveFailures + 1) / 2)));
			Vector3 val3 = Quaternion.Euler(0f, num, 0f) * (val / magnitude);
			val2 = position + val3 * 20f;
			float y = default(float);
			if ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetSolidHeight(val2, ref y, 1000))
			{
				val2.y = y;
			}
		}
		bool flag = Access.MoveTo((BaseAI)(object)Ai, dt, val2, (magnitude > 25f) ? 1f : arrive, run);
		if (flag && Utils.DistanceXZ(Position, val2) > arrive + 1f && Time.time >= nextFailureCount)
		{
			nextFailureCount = Time.time + 1f;
			moveFailures++;
		}
		else if (!flag && moveFailures > 0 && Time.time >= nextFailureCount)
		{
			moveFailures = Mathf.Max(0, moveFailures - 1);
		}
		if (moveFailures >= 6 && magnitude <= 25f)
		{
			return true;
		}
		return false;
	}

	private void SetWalk(bool walk)
	{
		if (walking != walk)
		{
			walking = walk;
			if ((Object)(object)Character != (Object)null)
			{
				Character.SetWalk(walk);
			}
		}
	}

	private bool CanSee(Character target, float now)
	{
		if (now < nextSight)
		{
			return seenNow;
		}
		nextSight = now + 0.25f;
		seenNow = (Object)(object)target != (Object)null && ((BaseAI)Ai).CanSeeTarget(target);
		return seenNow;
	}

	internal bool FilterSense(Player player)
	{
		//IL_0025: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		//IL_0070: 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)
		float time = Time.time;
		if (!IsOwner || Dormant)
		{
			return true;
		}
		bool num = ((BaseAI)Ai).CanSeeTarget((Character)(object)player);
		float num2 = Vector3.Distance(Position, ((Component)player).transform.position);
		if (!num)
		{
			if (!PhConfig.HearingGivesClue.Value)
			{
				return true;
			}
			if (!IsIgnored((Character)(object)player, time) || num2 <= PhConfig.GiveUpSightRange.Value)
			{
				hasClue = true;
				clue = ((Component)player).transform.position;
				cluePlayer = player;
			}
			return false;
		}
		if (IsIgnored((Character)(object)player, time) && num2 > PhConfig.GiveUpSightRange.Value)
		{
			return false;
		}
		if (State == HuntState.Unaware || State == HuntState.GiveUp || State == HuntState.Feeding)
		{
			return num2 <= ((BaseAI)Ai).m_viewRange * PhConfig.UnawareSightFactor.Value;
		}
		return true;
	}

	private void Enter(HuntState state, string why)
	{
		HuntState state2 = State;
		if (state != HuntState.Pursuit)
		{
			EndPursuitExtras();
		}
		if (state != HuntState.Interested && state != HuntState.Tracking && state != HuntState.Searching)
		{
			SetWalk(walk: false);
		}
		if (state != HuntState.Interested && state != HuntState.Tracking)
		{
			lingerUntil = 0f;
			handOffSince = -1f;
		}
		if (state == HuntState.Unaware)
		{
			SourceKind = SourceKind.None;
			SourceKey = 0;
			sourcePlayer = null;
			Fix = default(ScentFix);
		}
		if (state != HuntState.Pursuit)
		{
			Target = null;
		}
		State = state;
		StateSince = Time.time;
		Reason = why;
		nextSample = Mathf.Min(nextSample, Time.time + 0.5f);
		if (PhConfig.LogStateChanges.Value)
		{
			Plugin.Event(Name + " " + state2.ToString() + " -> " + state.ToString() + ": " + why + Conditions());
		}
	}

	private void EndPursuitExtras()
	{
		EndBurst();
		SetAgent(pursuit: false);
	}

	private void Note(string text)
	{
		if (PhConfig.LogStateChanges.Value)
		{
			Plugin.Event(Name + " (" + State.ToString() + ") " + text);
		}
	}

	private void Ignore(int key, float until)
	{
		if (key != 0)
		{
			ignored[key] = until;
		}
	}

	private void PruneIgnored(float now)
	{
		if (ignored.Count == 0)
		{
			return;
		}
		expired.Clear();
		foreach (KeyValuePair<int, float> item in ignored)
		{
			if (now >= item.Value)
			{
				expired.Add(item.Key);
			}
		}
		for (int i = 0; i < expired.Count; i++)
		{
			ignored.Remove(expired[i]);
		}
	}

	internal void ReleaseLocal(string why)
	{
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		EndBurst();
		if (UsingPursuitAgent)
		{
			UsingPursuitAgent = false;
			if ((Object)(object)Ai != (Object)null)
			{
				((BaseAI)Ai).m_pathAgentType = baseAgent;
			}
		}
		EndRage();
		SetWalk(walk: false);
		if (State != HuntState.Unaware && PhConfig.LogStateChanges.Value)
		{
			Plugin.Event(Name + " " + State.ToString() + " -> Unaware: " + why);
		}
		State = HuntState.Unaware;
		StateSince = Time.time;
		Reason = why;
		Target = null;
		SourceKind = SourceKind.None;
		SourceKey = 0;
		sourcePlayer = null;
		lingerUntil = 0f;
		handOffSince = -1f;
	}

	private string Conditions()
	{
		return " [wind " + F(Air.WindIntensity) + " to " + Air.WindBearing.ToString("0") + " deg, rain " + F(Air.RainLevel) + "]";
	}

	internal string Describe(ScentFix fix)
	{
		if (!fix.Valid)
		{
			return "nothing";
		}
		string text = ((fix.Kind == SourceKind.Player || fix.Kind == SourceKind.Trail) ? (((fix.Kind == SourceKind.Trail) ? "the trail of " : "") + (((Object)(object)fix.Player != (Object)null) ? fix.Player.GetPlayerName() : "a player")) : KindName(fix.Kind));
		return text + " at " + F(fix.Distance) + " m, intensity " + fix.Intensity.ToString("0.00", CultureInfo.InvariantCulture) + " (strength " + F(fix.Strength) + ", range " + F(fix.Range) + " m, wind x" + fix.Wind.ToString("0.00", CultureInfo.InvariantCulture) + ", rain x" + Air.RainFactor.ToString("0.00", CultureInfo.InvariantCulture) + ")";
	}

	internal static string KindName(SourceKind kind)
	{
		return kind switch
		{
			SourceKind.Player => "player", 
			SourceKind.Trail => "trail", 
			SourceKind.Chest => "food chest", 
			SourceKind.Carcass => "carcass", 
			SourceKind.Food => "food on the ground", 
			_ => "nothing", 
		};
	}

	private static string NameOf(Character c)
	{
		if ((Object)(object)c == (Object)null)
		{
			return "nothing";
		}
		Player val = (Player)(object)((c is Player) ? c : null);
		if (!((Object)(object)val != (Object)null))
		{
			return Utils.GetPrefabName(((Component)c).gameObject);
		}
		return val.GetPlayerName();
	}

	private static bool IsHiddenPlayer(Character c)
	{
		Player val = (Player)(object)((c is Player) ? c : null);
		if ((Object)(object)val != (Object)null)
		{
			return PlayerScent.Hidden(val);
		}
		return false;
	}

	private static int KeyOfTarget(Character c)
	{
		Player val = (Player)(object)((c is Player) ? c : null);
		if (!((Object)(object)val != (Object)null))
		{
			if (!((Object)(object)c != (Object)null))
			{
				return 0;
			}
			return ((Object)c).GetInstanceID();
		}
		return ScentField.KeyOf(val);
	}

	private static Player PlayerByKey(int key)
	{
		if (key == 0)
		{
			return null;
		}
		List<Player> allPlayers = Player.GetAllPlayers();
		for (int i = 0; i < allPlayers.Count; i++)
		{
			if ((Object)(object)allPlayers[i] != (Object)null && ScentField.KeyOf(allPlayers[i]) == key)
			{
				return allPlayers[i];
			}
		}
		return null;
	}

	private static Vector3 Blur(Vector3 p, float radius)
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0025: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0031: 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_0066: 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)
		if (radius <= 0f)
		{
			return p;
		}
		Vector2 val = Random.insideUnitCircle * radius;
		Vector3 val2 = default(Vector3);
		((Vector3)(ref val2))..ctor(p.x + val.x, p.y, p.z + val.y);
		float y = default(float);
		if ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetSolidHeight(val2, ref y, 1000))
		{
			val2.y = y;
		}
		return val2;
	}

	internal static string F(float value)
	{
		return value.ToString("0.0", CultureInfo.InvariantCulture);
	}
}
internal static class Predators
{
	private const float PruneInterval = 5f;

	private static readonly Dictionary<BaseAI, Predator> ByAi = new Dictionary<BaseAI, Predator>();

	private static readonly List<BaseAI> Stale = new List<BaseAI>();

	private static int listVersion = -1;

	private static float nextPrune;

	internal static IEnumerable<Predator> All => ByAi.Values;

	internal static int Count => ByAi.Count;

	internal static Predator Get(BaseAI ai)
	{
		if (!((Object)(object)ai != (Object)null) || !ByAi.TryGetValue(ai, out var value))
		{
			return null;
		}
		return value;
	}

	internal static void OnAwake(BaseAI ai)
	{
		MonsterAI val = (MonsterAI)(object)((ai is MonsterAI) ? ai : null);
		if ((Object)(object)val != (Object)null)
		{
			Consider(val);
		}
	}

	private static void Consider(MonsterAI ai)
	{
		if ((Object)(object)ai == (Object)null || ByAi.ContainsKey((BaseAI)(object)ai))
		{
			return;
		}
		string prefabName = Utils.GetPrefabName(((Component)ai).gameObject);
		float num = PhConfig.NoseOf(prefabName);
		if (!(num <= 0f))
		{
			Character component = ((Component)ai).GetComponent<Character>();
			if (!((Object)(object)component == (Object)null) && !component.m_flying && !ai.m_avoidLand)
			{
				ByAi[(BaseAI)(object)ai] = new Predator(ai, prefabName, num);
			}
		}
	}

	internal static void Tick()
	{
		float time = Time.time;
		if (listVersion != PhConfig.ListVersion)
		{
			bool num = listVersion < 0;
			listVersion = PhConfig.ListVersion;
			if (!num)
			{
				Rescan();
			}
		}
		if (time < nextPrune)
		{
			return;
		}
		nextPrune = time + 5f;
		Stale.Clear();
		foreach (KeyValuePair<BaseAI, Predator> item in ByAi)
		{
			if ((Object)(object)item.Key == (Object)null)
			{
				Stale.Add(item.Key);
			}
		}
		for (int i = 0; i < Stale.Count; i++)
		{
			ByAi.Remove(Stale[i]);
		}
	}

	private static void Rescan()
	{
		Stale.Clear();
		foreach (KeyValuePair<BaseAI, Predator> item in ByAi)
		{
			if (!((Object)(object)item.Key != (Object)null) || !(PhConfig.NoseOf(item.Value.Prefab) > 0f))
			{
				item.Value.ReleaseLocal("no longer on the Predators list");
				Stale.Add(item.Key);
			}
		}
		for (int i = 0; i < Stale.Count; i++)
		{
			ByAi.Remove(Stale[i]);
		}
		List<BaseAI> baseAIInstances = BaseAI.BaseAIInstances;
		for (int j = 0; j < baseAIInstances.Count; j++)
		{
			BaseAI obj = baseAIInstances[j];
			MonsterAI val = (MonsterAI)(object)((obj is MonsterAI) ? obj : null);
			if ((Object)(object)val != (Object)null)
			{
				Consider(val);
			}
		}
	}

	internal static void ReleaseAll(string why)
	{
		foreach (Predator value in ByAi.Values)
		{
			value.ReleaseLocal(why);
		}
	}

	internal static void Clear()
	{
		ByAi.Clear();
		listVersion = -1;
	}

	internal static int CountIn(HuntState state)
	{
		int num = 0;
		foreach (Predator value in ByAi.Values)
		{
			if (value.State == state && value.IsOwner)
			{
				num++;
			}
		}
		return num;
	}
}
internal static class Access
{
	private static readonly FieldRef<MonsterAI, Character> TargetCreatureRef = Field<MonsterAI, Character>("m_targetCreature");

	private static readonly FieldRef<MonsterAI, StaticTarget> TargetStaticRef = Field<MonsterAI, StaticTarget>("m_targetStatic");

	private static readonly FieldRef<MonsterAI, Vector3> LastKnownPosRef = Field<MonsterAI, Vector3>("m_lastKnownTargetPos");

	private static readonly FieldRef<MonsterAI, bool> BeenAtLastPosRef = Field<MonsterAI, bool>("m_beenAtLastPos");

	private static readonly FieldRef<MonsterAI, float> TimeSinceSensedRef = Field<MonsterAI, float>("m_timeSinceSensedTargetCreature");

	private static readonly FieldRef<MonsterAI, float> UpdateTargetTimerRef = Field<MonsterAI, float>("m_updateTargetTimer");

	private static readonly FieldRef<BaseAI, ZNetView> NViewRef = Field<BaseAI, ZNetView>("m_nview");

	private static readonly FieldRef<BaseAI, float> TimeSinceHurtRef = Field<BaseAI, float>("m_timeSinceHurt");

	private static readonly Func<BaseAI, float, Vector3, float, bool, bool> MoveToFn = Method<Func<BaseAI, float, Vector3, float, bool, bool>>(typeof(BaseAI), "MoveTo", new Type[4]
	{
		typeof(float),
		typeof(Vector3),
		typeof(float),
		typeof(bool)
	});

	private static readonly Action<BaseAI, bool> SetAlertedFn = Method<Action<BaseAI, bool>>(typeof(BaseAI), "SetAlerted", new Type[1] { typeof(bool) });

	private static readonly Action<BaseAI, Vector3> LookAtFn = Method<Action<BaseAI, Vector3>>(typeof(BaseAI), "LookAt", new Type[1] { typeof(Vector3) });

	private static readonly Action<BaseAI, float, Vector3, float, bool> CircleFn = Method<Action<BaseAI, float, Vector3, float, bool>>(typeof(BaseAI), "RandomMovementArroundPoint", new Type[4]
	{
		typeof(float),
		typeof(Vector3),
		typeof(float),
		typeof(bool)
	});

	private static readonly FieldRef<List<ItemDrop>> ItemDropsRef = StaticField<ItemDrop, List<ItemDrop>>("s_instances");

	internal static bool Ready
	{
		get
		{
			if (MoveToFn != null && SetAlertedFn != null && TargetCreatureRef != null)
			{
				return NViewRef != null;
			}
			return false;
		}
	}

	internal static string Missing
	{
		get
		{
			string text = "";
			if (MoveToFn == null)
			{
				text += " MoveTo";
			}
			if (SetAlertedFn == null)
			{
				text += " SetAlerted";
			}
			if (TargetCreatureRef == null)
			{
				text += " m_targetCreature";
			}
			if (NViewRef == null)
			{
				text += " m_nview";
			}
			return text.Trim();
		}
	}

	private static FieldRef<TObject, TField> Field<TObject, TField>(string field) where TObject : class
	{
		try
		{
			return AccessTools.FieldRefAccess<TObject, TField>(field);
		}
		catch (Exception ex)
		{
			Plugin.LogOnce("field-" + typeof(TObject).Name + "." + field, "Predators Hunt: field " + typeof(TObject).Name + "." + field + " not found (" + ex.Message + ").");
			return null;
		}
	}

	private static FieldRef<TField> StaticField<TObject, TField>(string field)
	{
		try
		{
			return AccessTools.StaticFieldRefAccess<TField>(AccessTools.Field(typeof(TObject), field));
		}
		catch (Exception ex)
		{
			Plugin.LogOnce("sfield-" + typeof(TObject).Name + "." + field, "Predators Hunt: static field " + typeof(TObject).Name + "." + field + " not found (" + ex.Message + ").");
			return null;
		}
	}

	private static TDelegate Method<TDelegate>(Type type, string name, Type[] parameters) where TDelegate : Delegate
	{
		try
		{
			MethodInfo methodInfo = AccessTools.Method(type, name, parameters, (Type[])null);
			if (methodInfo != null)
			{
				return AccessTools.MethodDelegate<TDelegate>(methodInfo, (object)null, true);
			}
		}
		catch (Exception ex)
		{
			Plugin.LogOnce("method-" + type.Name + "." + name, "Predators Hunt: " + type.Name + "." + name + " could not be bound (" + ex.Message + ").");
			return null;
		}
		Plugin.LogOnce("method-" + type.Name + "." + name, "Predators Hunt: " + type.Name + "." + name + " not found in this Valheim build.");
		return null;
	}

	internal static ZNetView NView(BaseAI ai)
	{
		if (NViewRef == null)
		{
			return null;
		}
		return NViewRef.Invoke(ai);
	}

	internal static Character TargetCreature(MonsterAI ai)
	{
		if (TargetCreatureRef == null)
		{
			return null;
		}
		return TargetCreatureRef.Invoke(ai);
	}

	internal static StaticTarget TargetStatic(MonsterAI ai)
	{
		if (TargetStaticRef == null)
		{
			return null;
		}
		return TargetStaticRef.Invoke(ai);
	}

	internal static float TimeSinceHurt(BaseAI ai)
	{
		if (TimeSinceHurtRef == null)
		{
			return 9999f;
		}
		return TimeSinceHurtRef.Invoke(ai);
	}

	internal static List<ItemDrop> ItemDrops()
	{
		if (ItemDropsRef == null)
		{
			return null;
		}
		return ItemDropsRef.Invoke();
	}

	internal static void SetTarget(MonsterAI ai, Character target)
	{
		//IL_004b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0050: Unknown result type (might be due to invalid IL or missing references)
		if (TargetCreatureRef == null)
		{
			return;
		}
		TargetCreatureRef.Invoke(ai) = target;
		if (!((Object)(object)target == (Object)null))
		{
			if (TargetStaticRef != null)
			{
				TargetStaticRef.Invoke(ai) = null;
			}
			if (LastKnownPosRef != null)
			{
				LastKnownPosRef.Invoke(ai) = ((Component)target).transform.position;
			}
			if (BeenAtLastPosRef != null)
			{
				BeenAtLastPosRef.Invoke(ai) = false;
			}
			if (TimeSinceSensedRef != null)
			{
				TimeSinceSensedRef.Invoke(ai) = 0f;
			}
		}
	}

	internal static void ClearTarget(MonsterAI ai, float holdOff)
	{
		if (TargetCreatureRef != null)
		{
			TargetCreatureRef.Invoke(ai) = null;
		}
		if (UpdateTargetTimerRef != null)
		{
			UpdateTargetTimerRef.Invoke(ai) = Mathf.Max(UpdateTargetTimerRef.Invoke(ai), holdOff);
		}
	}

	internal static bool MoveTo(BaseAI ai, float dt, Vector3 point, float dist, bool run)
	{
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		if (MoveToFn != null)
		{
			return MoveToFn(ai, dt, point, dist, run);
		}
		return true;
	}

	internal static void SetAlerted(BaseAI ai, bool alerted)
	{
		if (SetAlertedFn != null)
		{
			SetAlertedFn(ai, alerted);
		}
	}

	internal static void LookAt(BaseAI ai, Vector3 point)
	{
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		if (LookAtFn != null)
		{
			LookAtFn(ai, point);
		}
	}

	internal static bool Circle(BaseAI ai, float dt, Vector3 point, float distance, bool run)
	{
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		if (CircleFn == null)
		{
			return false;
		}
		CircleFn(ai, dt, point, distance, run);
		return true;
	}
}
internal sealed class PatternSet
{
	private readonly HashSet<string> exact = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

	private readonly List<string> prefixes = new List<string>();

	private readonly List<string> suffixes = new List<string>();

	private readonly List<string> contains = new List<string>();

	private readonly List<string> globs = new List<string>();

	internal int Count => exact.Count + prefixes.Count + suffixes.Count + contains.Count + globs.Count;

	internal static PatternSet Parse(string text)
	{
		PatternSet patternSet = new PatternSet();
		if (string.IsNullOrEmpty(text))
		{
			return patternSet;
		}
		string[] array = text.Split(new char[1] { ',' });
		for (int i = 0; i < array.Length; i++)
		{
			string text2 = array[i].Trim();
			if (text2.Length == 0)
			{
				continue;
			}
			bool flag = text2[0] == '*';
			bool flag2 = text2[text2.Length - 1] == '*';
			string text3 = text2.Trim(new char[1] { '*' });
			if (text3.Length != 0)
			{
				if (text3.IndexOf('*') >= 0)
				{
					patternSet.globs.Add(text2);
				}
				else if (flag && flag2)
				{
					patternSet.contains.Add(text3);
				}
				else if (flag2)
				{
					patternSet.prefixes.Add(text3);
				}
				else if (flag)
				{
					patternSet.suffixes.Add(text3);
				}
				else
				{
					patternSet.exact.Add(text3);
				}
			}
		}
		return patternSet;
	}

	internal bool Matches(string name)
	{
		if (string.IsNullOrEmpty(name))
		{
			return false;
		}
		if (exact.Contains(name))
		{
			return true;
		}
		for (int i = 0; i < prefixes.Count; i++)
		{
			if (name.StartsWith(prefixes[i], StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
		}
		for (int j = 0; j < suffixes.Count; j++)
		{
			if (name.EndsWith(suffixes[j], StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
		}
		for (int k = 0; k < contains.Count; k++)
		{
			if (name.IndexOf(contains[k], StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return true;
			}
		}
		for (int l = 0; l < globs.Count; l++)
		{
			if (GlobMatch(globs[l], name))
			{
				return true;
			}
		}
		return false;
	}

	private static bool GlobMatch(string pattern, string name)
	{
		string[] array = pattern.Split(new char[1] { '*' });
		int num = 0;
		for (int i = 0; i < array.Length; i++)
		{
			string text = array[i];
			if (text.Length == 0)
			{
				continue;
			}
			if (i == 0)
			{
				if (!name.StartsWith(text, StringComparison.OrdinalIgnoreCase))
				{
					return false;
				}
				num = text.Length;
				continue;
			}
			if (i == array.Length - 1)
			{
				if (name.Length - text.Length >= num)
				{
					return name.EndsWith(text, StringComparison.OrdinalIgnoreCase);
				}
				return false;
			}
			int num2 = name.IndexOf(text, num, StringComparison.OrdinalIgnoreCase);
			if (num2 < 0)
			{
				return false;
			}
			num = num2 + text.Length;
		}
		if (array.Length <= 1)
		{
			return num == name.Length;
		}
		return true;
	}
}
internal static class PhConfig
{
	internal static ConfigEntry<bool> Enabled;

	internal static ConfigEntry<string> Predators;

	internal static ConfigEntry<string> CarcassEaters;

	internal static ConfigEntry<bool> MigrantsFollowScent;

	internal static ConfigEntry<float> UnawareSightFactor;

	internal static ConfigEntry<bool> HearingGivesClue;

	internal static ConfigEntry<float> BaseScentRange;

	internal static ConfigEntry<float> MaxScentRange;

	internal static ConfigEntry<float> BodyScent;

	internal static ConfigEntry<float> RawFoodScent;

	internal static ConfigEntry<float> CookedFoodScent;

	internal static ConfigEntry<float> StackGrowth;

	internal static ConfigEntry<float> MaxStackFactor;

	internal static ConfigEntry<string> RawFoodItems;

	internal static ConfigEntry<string> NotRawFood;

	internal static ConfigEntry<string> ScentlessFood;

	internal static ConfigEntry<float> BloodPerDamage;

	internal static ConfigEntry<float> MaxWoundBlood;

	internal static ConfigEntry<float> KillBlood;

	internal static ConfigEntry<float> BloodHalfLifeSeconds;

	internal static ConfigEntry<float> DroppedFoodScent;

	internal static ConfigEntry<float> DownwindMultiplier;

	internal static ConfigEntry<float> UpwindMultiplier;

	internal static ConfigEntry<float> WindConeSharpness;

	internal static ConfigEntry<float> FullEffectWindIntensity;

	internal static ConfigEntry<float> RainRangeMultiplier;

	internal static ConfigEntry<float> RainRampSeconds;

	internal static ConfigEntry<bool> EnableTrails;

	internal static ConfigEntry<float> TrailSpacing;

	internal static ConfigEntry<float> TrailStrength;

	internal static ConfigEntry<float> TrailHalfLifeSeconds;

	internal static ConfigEntry<float> RainTrailWashout;

	internal static ConfigEntry<int> TrailMaxPoints;

	internal static ConfigEntry<float> TrailLookAhead;

	internal static ConfigEntry<float> StoredFoodScent;

	internal static ConfigEntry<float> CacheMinHeight;

	internal static ConfigEntry<float> CacheClearRadius;

	internal static ConfigEntry<float> CacheLevelTolerance;

	internal static ConfigEntry<bool> CacheUseNavmesh;

	internal static ConfigEntry<float> CacheRecheckSeconds;

	internal static ConfigEntry<float> ProtectedCacheScent;

	internal static ConfigEntry<float> FoodLingerSeconds;

	internal static ConfigEntry<float> FoodSourceCooldownSeconds;

	internal static ConfigEntry<bool> UseDeadRemain;

	internal static ConfigEntry<float> CarcassScent;

	internal static ConfigEntry<string> CarcassChanceByBiome;

	internal static ConfigEntry<float> HungryCarcassPreference;

	internal static ConfigEntry<float> SatedFoodInterest;

	internal static ConfigEntry<float> SatedMinutes;

	internal static ConfigEntry<float> CarcassHandOffRange;

	internal static ConfigEntry<float> HandOffWaitSeconds;

	internal static ConfigEntry<float> FeedRageSeconds;

	internal static ConfigEntry<float> SampleSeconds;

	internal static ConfigEntry<float> InterestedResampleSeconds;

	internal static ConfigEntry<float> TrackingResampleSeconds;

	internal static ConfigEntry<float> TrackThreshold;

	internal static ConfigEntry<int> TrackSamples;

	internal static ConfigEntry<float> InterestedErrorFraction;

	internal static ConfigEntry<float> TrackingErrorFraction;

	internal static ConfigEntry<float> ScentLostSeconds;

	internal static ConfigEntry<float> ContactRange;

	internal static ConfigEntry<float> LostContactSeconds;

	internal static ConfigEntry<float> SearchSeconds;

	internal static ConfigEntry<int> SearchWaypointsMin;

	internal static ConfigEntry<int> SearchWaypointsMax;

	internal static ConfigEntry<float> SearchStartRadius;

	internal static ConfigEntry<float> SearchRadiusGrowth;

	internal static ConfigEntry<float> GiveUpCooldownSeconds;

	internal static ConfigEntry<float> GiveUpSightRange;

	internal static ConfigEntry<float> BurstSpeedMultiplier;

	internal static ConfigEntry<float> BurstSeconds;

	internal static ConfigEntry<float> BurstCooldownSeconds;

	internal static ConfigEntry<bool> PursuitNavmesh;

	internal static ConfigEntry<float> PursuitStepHeight;

	internal static ConfigEntry<float> PursuitSlopeLimit;

	internal static ConfigEntry<float> PursuitJumpDistance;

	internal static ConfigEntry<float> PursuitJumpHeight;

	internal static ConfigEntry<bool> JumpAssist;

	internal static ConfigEntry<float> JumpCooldownSeconds;

	internal static ConfigEntry<bool> LogStateChanges;

	internal static ConfigEntry<bool> VerboseLogging;

	private static readonly List<KeyValuePair<PatternSet, float>> PredatorTable = new List<KeyValuePair<PatternSet, float>>();

	internal static PatternSet CarcassEaterSet = new PatternSet();

	internal static PatternSet RawFoodSet = new PatternSet();

	internal static PatternSet NotRawFoodSet = new PatternSet();

	internal static PatternSet ScentlessFoodSet = new PatternSet();

	private static readonly Dictionary<Biome, float> CarcassChances = new Dictionary<Biome, float>();

	internal static int ListVersion { get; private set; }

	internal static int PredatorEntries => PredatorTable.Count;

	internal static void Bind(ConfigFile config)
	{
		//IL_054b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0555: Expected O, but got Unknown
		//IL_0965: Unknown result type (might be due to invalid IL or missing references)
		//IL_096f: Expected O, but got Unknown
		//IL_0aa6: Unknown result type (might be due to invalid IL or missing references)
		//IL_0ab0: Expected O, but got Unknown
		//IL_0ad3: Unknown result type (might be due to invalid IL or missing references)
		//IL_0add: Expected O, but got Unknown
		Enabled = config.Bind<bool>("1 - General", "Enabled", true, "Predators find you (and food) by scent carried on the wind, and hunt through Unaware, Interested, Tracking, Pursuit, Searching and GiveUp. Off = vanilla AI.");
		Predators = config.Bind<string>("2 - Predators", "Predators", "Wolf:1.4, Ulv:1.4, Fenring:1.2, Bjorn:1.3, Unbjorn:1.3, Troll:0.8, Greydwarf:0.8, Greydwarf_Elite:0.8, Greydwarf_Shaman:0.6, Asksvin:1.2, Seeker:1.0, SeekerBrute:1.0, Morgen:1.0", "Creature prefabs that hunt by scent, each with a nose: its scent ranges are multiplied by that number. Wildcards: Prefix*, *Suffix, *part*. Flying, swimming-only, tamed, sleeping and raid creatures always stay vanilla.");
		CarcassEaters = config.Bind<string>("2 - Predators", "CarcassEaters", "Wolf, Ulv, Bjorn, Unbjorn, Asksvin", "Predators that follow the scent of a carcass (The Dead Remain) and feed on it. To actually feed they must also be in The Dead Remain's ScavengerRanks.");
		MigrantsFollowScent = config.Bind<bool>("2 - Predators", "MigrantsFollowScent", false, "Creatures travelling in a Migrating Creatures herd keep to their route while they only smell you; they still hunt on sight (and then leave the herd, as Migrating Creatures does). True = they break off for a scent too.");
		UnawareSightFactor = config.Bind<float>("2 - Predators", "UnawareSightFactor", 0.4f, Range("An unaware predator only spots a player by eye within its view range times this. It finds you by scent instead; once it is interested its eyes are back to full range.", 0f, 1f));
		HearingGivesClue = config.Bind<bool>("2 - Predators", "HearingGivesClue", true, "A predator that hears a player but cannot see it gets a rough idea where the noise came from (it becomes Interested and walks over), instead of vanilla's instant lock-on.");
		BaseScentRange = config.Bind<float>("3 - Scent", "BaseScentRange", 20f, Range("Metres a scent of strength 1 carries in still, dry air. Every source's range is this times its strength, times the wind and rain factors and the predator's nose.", 1f, 200f));
		MaxScentRange = config.Bind<float>("3 - Scent", "MaxScentRange", 250f, Range("No scent carries further than this. Creatures only run their AI near a player (about 100 m), so very long ranges only matter there.", 10f, 1000f));
		BodyScent = config.Bind<float>("3 - Scent", "BodyScent", 1f, Range("A player's own scent, carrying nothing.", 0f, 10f));
		RawFoodScent = config.Bind<float>("3 - Scent", "RawFoodScent", 2f, Range("Added for raw meat and fish carried: the strongest signal.", 0f, 20f));
		CookedFoodScent = config.Bind<float>("3 - Scent", "CookedFoodScent", 0.5f, Range("Added for cooked or prepared food carried: weaker.", 0f, 20f));
		StackGrowth = config.Bind<float>("3 - Scent", "StackGrowth", 0.25f, Range("How much more a bigger load smells: each doubling of the amount carried adds this fraction (1 raw meat x1, 2 x1.25, 4 x1.5 ...).", 0f, 2f));
		MaxStackFactor = config.Bind<float>("3 - Scent", "MaxStackFactor", 2f, Range("A load never smells more than this many times one item.", 1f, 10f));
		RawFoodItems = config.Bind<string>("3 - Scent", "RawFoodItems", "*Meat, *Meat_*, NeckTail, Entrails, FishRaw, Fish1, Fish2, Fish3, Fish4_cave, Fish5, Fish6, Fish7, Fish8, Fish9, Fish10, Fish11, Fish12, *_Raw, Raw*, *_Fillet, Lobster, MF_Lobster, Crab, MF_Crab*, BloodPudding_raw", "Item prefabs that count as raw meat or fish (Coastal Fishing's lobster, crab and fillets included).");
		NotRawFood = config.Bind<string>("3 - Scent", "NotRawFood", "Cooked*, *Cooked*, *Grilled*, *Boiled*, Fishing*, FishAndBread, FishWraps, *Trophy*", "Item prefabs that match RawFoodItems but are not raw (cooked meat, fishing gear).");
		ScentlessFood = config.Bind<string>("3 - Scent", "ScentlessFood", "Raspberry, Blueberries, Cloudberry, Mushroom*, Honey, Onion, Carrot, Turnip, Fiddleheadfern, Vineberry, Pukeberries, Thistle, Dandelion", "Edible items that do not count as cooked food (berries, mushrooms, raw vegetables). Every other food item counts as cooked.");
		BloodPerDamage = config.Bind<float>("3 - Scent", "BloodPerDamage", 0.03f, Range("Fresh blood: scent added per point of damage you take (a 30 HP hit adds 0.9).", 0f, 1f));
		MaxWoundBlood = config.Bind<float>("3 - Scent", "MaxWoundBlood", 3f, Range("Your blood scent never goes above this.", 0f, 20f));
		KillBlood = config.Bind<float>("3 - Scent", "KillBlood", 1.5f, Range("Fresh blood added when you kill a creature (hands and blade).", 0f, 20f));
		BloodHalfLifeSeconds = config.Bind<float>("3 - Scent", "BloodHalfLifeSeconds", 60f, Range("Blood scent halves every this many seconds.", 1f, 3600f));
		DroppedFoodScent = config.Bind<float>("3 - Scent", "DroppedFoodScent", 1f, Range("Raw or cooked food lying on the ground smells like the same food carried, times this. 0 = it does not draw predators.", 0f, 10f));
		DownwindMultiplier = config.Bind<float>("4 - Wind and rain", "DownwindMultiplier", 3f, Range("Range multiplier for a predator straight downwind of the scent in a full wind (several times normal range).", 1f, 20f));
		UpwindMultiplier = config.Bind<float>("4 - Wind and rain", "UpwindMultiplier", 0.15f, Range("Range multiplier for a predator straight upwind in a full wind: you are nearly invisible.", 0f, 1f));
		WindConeSharpness = config.Bind<float>("4 - Wind and rain", "WindConeSharpness", 2f, Range("How narrow the downwind cone is: 1 = broad, 2 = full effect only near straight downwind (half at 45 degrees).", 0.5f, 8f));
		FullEffectWindIntensity = config.Bind<float>("4 - Wind and rain", "FullEffectWindIntensity", 0.5f, Range("The game's wind intensity (0 to 1) at which the wind has its full effect; calmer air spreads scent more evenly.", 0.05f, 1f));
		RainRangeMultiplier = config.Bind<float>("4 - Wind and rain", "RainRangeMultiplier", 0.4f, Range("Every scent's range is multiplied by this in rain.", 0f, 1f));
		RainRampSeconds = config.Bind<float>("4 - Wind and rain", "RainRampSeconds", 30f, Range("Seconds the rain takes to reach its full effect (and to wear off).", 0f, 600f));
		EnableTrails = config.Bind<bool>("5 - Trails", "EnableTrails", true, "Players leave a fading scent trail that predators can follow to them.");
		TrailSpacing = config.Bind<float>("5 - Trails", "TrailSpacing", 3f, Range("Metres between trail points.", 0.5f, 20f));
		TrailStrength = config.Bind<float>("5 - Trails", "TrailStrength", 0.5f, Range("A fresh trail point smells this fraction of the player who left it.", 0f, 2f));
		TrailHalfLifeSeconds = config.Bind<float>("5 - Trails", "TrailHalfLifeSeconds", 120f, Range("A trail halves in strength every this many seconds in dry weather.", 1f, 3600f));
		RainTrailWashout = config.Bind<float>("5 - Trails", "RainTrailWashout", 8f, Range("Rain makes trails fade this many times faster: rain washes the trail out.", 1f, 100f));
		TrailMaxPoints = config.Bind<int>("5 - Trails", "TrailMaxPoints", 80, new ConfigDescription("Trail points kept per player.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 1000), Array.Empty<object>()));
		TrailLookAhead = config.Bind<float>("5 - Trails", "TrailLookAhead", 12f, Range("A predator on a trail heads for the freshest point within this many metres of the one it smells, so it follows the trail forwards.", 0f, 50f));
		StoredFoodScent = config.Bind<float>("6 - Stored food and caches", "StoredFoodScent", 1f, Range("A chest holding raw or cooked food smells like the same food carried, times this: it draws predators to your base. 0 = chests never smell.", 0f, 10f));
		CacheMinHeight = config.Bind<float>("6 - Stored food and caches", "CacheMinHeight", 3.5f, Range("An elevated cache: the chest's base must sit at least this many metres above the ground beneath it.", 0f, 50f));
		CacheClearRadius = config.Bind<float>("6 - Stored food and caches", "CacheClearRadius", 2.5f, Range("...and no walkable ground may lie within this many metres horizontally at that height (terrain, rock, or a piece a creature can walk up to).", 0f, 20f));
		CacheLevelTolerance = config.Bind<float>("6 - Stored food and caches", "CacheLevelTolerance", 1f, Range("Ground up to this far below the chest's base still counts as 'at that height'.", 0f, 10f));
		CacheUseNavmesh = config.Bind<bool>("6 - Stored food and caches", "CacheUseNavmesh", true, "Building pieces near the cache count as walkable ground when a creature could walk onto them from the ground (the game's navmesh). So a ramp or stairs built later ends the protection.");
		CacheRecheckSeconds = config.Bind<float>("6 - Stored food and caches", "CacheRecheckSeconds", 15f, Range("How often a food chest's protection is checked again (also a few seconds after building nearby).", 2f, 600f));
		ProtectedCacheScent = config.Bind<float>("6 - Stored food and caches", "ProtectedCacheScent", 0f, Range("A protected cache still smells this fraction as much. 0 = it draws nothing.", 0f, 1f));
		FoodLingerSeconds = config.Bind<float>("6 - Stored food and caches", "FoodLingerSeconds", 45f, Range("A predator that reached a food chest or food on the ground noses around it this long.", 0f, 600f));
		FoodSourceCooldownSeconds = config.Bind<float>("6 - Stored food and caches", "FoodSourceCooldownSeconds", 300f, Range("...and then ignores that food source this long before it can be drawn back.", 0f, 7200f));
		UseDeadRemain = config.Bind<bool>("7 - Carcasses", "UseDeadRemain", true, "Carcasses from The Dead Remain are scent emitters (only when that mod is installed).");
		CarcassScent = config.Bind<float>("7 - Carcasses", "CarcassScent", 4f, Range("Scent of a fresh carcass, times The Dead Remain's freshness (Fresh 1 to 0.6, Picked 0.5 to 0.2, Bones 0).", 0f, 50f));
		CarcassChanceByBiome = config.Bind<string>("7 - Carcasses", "CarcassChanceByBiome", "Meadows:40, BlackForest:50, Swamp:30, Plains:35, Mistlands:10, AshLands:0, DeepNorth:60, Mountain:50, Ocean:0", "Not every predator goes to every carcass (Marc, 2026-10-24): the percent chance, by the biome the carcass lies in, that a predator smelling it goes to it. Rolled once per predator and carcass; a failed roll means it leaves that carcass alone. Biomes not listed: 100.");
		HungryCarcassPreference = config.Bind<float>("7 - Carcasses", "HungryCarcassPreference", 2f, Range("A hungry carcass eater weighs a carcass's scent this many times a player's, so a nearby kill beats a distant player.", 0f, 20f));
		SatedFoodInterest = config.Bind<float>("7 - Carcasses", "SatedFoodInterest", 0.2f, Range("A predator that has just fed weighs food (carcasses, chests, food on the ground) by this.", 0f, 1f));
		SatedMinutes = config.Bind<float>("7 - Carcasses", "SatedMinutes", 10f, Range("Minutes a predator stays sated after feeding.", 0f, 600f));
		CarcassHandOffRange = config.Bind<float>("7 - Carcasses", "CarcassHandOffRange", 30f, Range("Within this many metres of the carcass it smelled, the predator is handed to The Dead Remain's scavenger AI (keep it under that mod's ScavengeRange, 40).", 2f, 200f));
		HandOffWaitSeconds = config.Bind<float>("7 - Carcasses", "HandOffWaitSeconds", 20f, Range("If The Dead Remain has not taken it on by then (a player is near the kill, it is full), it gives up on that carcass.", 1f, 600f));
		FeedRageSeconds = config.Bind<float>("7 - Carcasses", "FeedRageSeconds", 30f, Range("A predator interrupted while feeding attacks rather than flees: for this many seconds it never runs (low health, unreachable target, unaware flight).", 0f, 600f));
		SampleSeconds = config.Bind<float>("8 - States", "SampleSeconds", 2f, Range("How often an unaware predator tests the air.", 0.2f, 30f));
		InterestedResampleSeconds = config.Bind<float>("8 - States", "InterestedResampleSeconds", 4f, Range("Interested: it re-samples the scent this often as it walks toward it.", 0.5f, 60f));
		TrackingResampleSeconds = config.Bind<float>("8 - States", "TrackingResampleSeconds", 2f, Range("Tracking: re-sample interval.", 0.5f, 60f));
		TrackThreshold = config.Bind<float>("8 - States", "TrackThreshold", 0.35f, Range("Scent intensity (0 at the edge of its range, 1 at the source) needed for Tracking.", 0.01f, 1f));
		TrackSamples = config.Bind<int>("8 - States", "TrackSamples", 3, new ConfigDescription("...for this many samples in a row from the same source (strong and consistent).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 50), Array.Empty<object>()));
		InterestedErrorFraction = config.Bind<float>("8 - States", "InterestedErrorFraction", 0.35f, Range("Interested: it has no idea where you actually are. Each sample it walks toward a point up to this fraction of the distance away from the real source.", 0f, 1f));
		TrackingErrorFraction = config.Bind<float>("8 - States", "TrackingErrorFraction", 0.12f, Range("Tracking: the same error, smaller.", 0f, 1f));
		ScentLostSeconds = config.Bind<float>("8 - States", "ScentLostSeconds", 12f, Range("Interested loses interest, and Tracking starts Searching, after smelling nothing this long.", 1f, 600f));
		ContactRange = config.Bind<float>("8 - States", "ContactRange", 3f, Range("Nose to the source: a scent this close counts as contact (Pursuit) even without sight.", 0f, 20f));
		LostContactSeconds = config.Bind<float>("8 - States", "LostContactSeconds", 4f, Range("Pursuit turns to Searching after this long without seeing the target.", 0.5f, 120f));
		SearchSeconds = config.Bind<float>("8 - States", "SearchSeconds", 90f, Range("Searching lasts about this long before it gives up.", 5f, 1800f));
		SearchWaypointsMin = config.Bind<int>("8 - States", "SearchWaypointsMin", 3, new ConfigDescription("Waypoints in the expanding search pattern: at least...", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 20), Array.Empty<object>()));
		SearchWaypointsMax = config.Bind<int>("8 - States", "SearchWaypointsMax", 5, new ConfigDescription("...and at most.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 20), Array.Empty<object>()));
		SearchStartRadius = config.Bind<float>("8 - States", "SearchStartRadius", 8f, Range("Distance of the first search waypoint from the last known position.", 1f, 100f));
		SearchRadiusGrowth = config.Bind<float>("8 - States", "SearchRadiusGrowth", 0.6f, Range("Each further waypoint lies this fraction further out (expanding pattern).", 0f, 5f));
		GiveUpCooldownSeconds = config.Bind<float>("8 - States", "GiveUpCooldownSeconds", 180f, Range("After giving up, it cannot re-acquire the same target for this long.", 0f, 7200f));
		GiveUpSightRange = config.Bind<float>("8 - States", "GiveUpSightRange", 8f, Range("...unless that target comes this close in plain sight (or attacks it).", 0f, 100f));
		BurstSpeedMultiplier = config.Bind<float>("9 - Pursuit", "BurstSpeedMultiplier", 1.35f, Range("Pursuit: run speed times this during a burst.", 1f, 5f));
		BurstSeconds = config.Bind<float>("9 - Pursuit", "BurstSeconds", 4f, Range("Length of a burst.", 0f, 60f));
		BurstCooldownSeconds = config.Bind<float>("9 - Pursuit", "BurstCooldownSeconds", 15f, Range("Seconds between bursts in one chase.", 0f, 600f));
		PursuitNavmesh = config.Bind<bool>("9 - Pursuit", "PursuitNavmesh", true, "Pursuit paths on navmesh built for a more agile body (higher step and slope), so a rock or ledge that shook it off no longer does. Restart the world after changing.");
		PursuitStepHeight = config.Bind<float>("9 - Pursuit", "PursuitStepHeight", 1f, Range("Pursuit navmesh step height in metres (vanilla 0.3; trolls 0.6). Restart the world after changing.", 0.1f, 3f));
		PursuitSlopeLimit = config.Bind<float>("9 - Pursuit", "PursuitSlopeLimit", 75f, Range("Pursuit navmesh slope limit in degrees. Unity only walks a slope as steep as the step height allows: vanilla's 85 at a 0.3 m step really walks about 48 degrees; at a 1 m step it walks up to 75 (higher only adds navmesh artifacts). Restart the world after changing.", 1f, 89f));
		PursuitJumpDistance = config.Bind<float>("9 - Pursuit", "PursuitJumpDistance", 4f, Range("Pursuit: it leaps gaps and ledges up to this many metres across.", 0f, 20f));
		PursuitJumpHeight = config.Bind<float>("9 - Pursuit", "PursuitJumpHeight", 2f, Range("...and up to this many metres up.", 0f, 10f));
		JumpAssist = config.Bind<bool>("9 - Pursuit", "JumpAssist", true, "Pursuit: a predator stuck short of its target, or facing a ledge, leaps.");
		JumpCooldownSeconds = config.Bind<float>("9 - Pursuit", "JumpCooldownSeconds", 2.5f, Range("Seconds between leaps.", 0.2f, 60f));
		LogStateChanges = config.Bind<bool>("10 - Debug", "LogStateChanges", true, "Write a [ph] line to the log for every predator state change, with the scent numbers behind it.");
		VerboseLogging = config.Bind<bool>("10 - Debug", "VerboseLogging", false, "More [ph] lines (cache checks, samples, jumps).");
		ParseLists();
		Predators.SettingChanged += delegate
		{
			ParseLists();
		};
		CarcassEaters.SettingChanged += delegate
		{
			ParseLists();
		};
		RawFoodItems.SettingChanged += delegate
		{
			ParseLists();
		};
		NotRawFood.SettingChanged += delegate
		{
			ParseLists();
		};
		ScentlessFood.SettingChanged += delegate
		{
			ParseLists();
		};
		CarcassChanceByBiome.SettingChanged += delegate
		{
			ParseLists();
		};
	}

	private static ConfigDescription Range(string text, float min, float max)
	{
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0013: Expected O, but got Unknown
		return new ConfigDescription(text, (AcceptableValueBase)(object)new AcceptableValueRange<float>(min, max), Array.Empty<object>());
	}

	internal static void ParseLists()
	{
		//IL_017c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0181: 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)
		PredatorTable.Clear();
		string[] array = (Predators.Value ?? "").Split(new char[1] { ',' });
		for (int i = 0; i < array.Length; i++)
		{
			string text = array[i].Trim();
			if (text.Length == 0)
			{
				continue;
			}
			float num = 1f;
			int num2 = text.LastIndexOf(':');
			if (num2 > 0)
			{
				if (float.TryParse(text.Substring(num2 + 1).Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
				{
					num = result;
				}
				text = text.Substring(0, num2).Trim();
			}
			if (text.Length != 0 && !(num <= 0f))
			{
				PredatorTable.Add(new KeyValuePair<PatternSet, float>(PatternSet.Parse(text), num));
			}
		}
		CarcassEaterSet = PatternSet.Parse(CarcassEaters.Value);
		RawFoodSet = PatternSet.Parse(RawFoodItems.Value);
		NotRawFoodSet = PatternSet.Parse(NotRawFood.Value);
		ScentlessFoodSet = PatternSet.Parse(ScentlessFood.Value);
		CarcassChances.Clear();
		array = (CarcassChanceByBiome.Value ?? "").Split(new char[1] { ',' });
		foreach (string text2 in array)
		{
			int num3 = text2.IndexOf(':');
			if (num3 > 0)
			{
				Biome key;
				try
				{
					key = (Biome)Enum.Parse(typeof(Biome), text2.Substring(0, num3).Trim(), ignoreCase: true);
				}
				catch (ArgumentException)
				{
					continue;
				}
				if (float.TryParse(text2.Substring(num3 + 1).Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result2))
				{
					CarcassChances[key] = Math.Max(0f, Math.Min(100f, result2));
				}
			}
		}
		ListVersion++;
	}

	internal static float NoseOf(string prefab)
	{
		for (int i = 0; i < PredatorTable.Count; i++)
		{
			if (PredatorTable[i].Key.Matches(prefab))
			{
				return PredatorTable[i].Value;
			}
		}
		return 0f;
	}

	internal static float CarcassChance(Biome biome)
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		if (!CarcassChances.TryGetValue(biome, out var value))
		{
			return 100f;
		}
		return value;
	}
}
[BepInPlugin("marc.valheim.predatorshunt", "Predators Hunt", "0.1.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInProcess("valheim.exe")]
[BepInProcess("valheim_server.exe")]
public sealed class Plugin : BaseUnityPlugin
{
	public const string Guid = "marc.valheim.predatorshunt";

	public const string Name = "Predators Hunt";

	public const string Version = "0.1.0";

	internal static Plugin Instance;

	internal static ManualLogSource Log;

	private static readonly HashSet<string> LoggedOnce = new HashSet<string>();

	private static readonly KeyValuePair<string, Action>[] Ticks = new KeyValuePair<string, Action>[6]
	{
		new KeyValuePair<string, Action>("air", delegate
		{
			Air.Tick(Time.deltaTime);
		}),
		new KeyValuePair<string, Action>("player scent", PlayerScent.Tick),
		new KeyValuePair<string, Action>("stored food", StoredFood.Tick),
		new KeyValuePair<string, Action>("dropped food", ScentField.Tick),
		new KeyValuePair<string, Action>("carcasses", DeadRemainLink.Tick),
		new KeyValuePair<string, Action>("predators", Predators.Tick)
	};

	private Harmony harmony;