Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of PredatorsHunt v0.1.0
PredatorsHunt.dll
Decompiled 21 hours ago
The result has been truncated due to the large size, download it to view full contents!
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;