plugins\Heel.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Heel.Commands;
using Heel.Config;
using Heel.Follow;
using Heel.Safety;
using Heel.Server;
using Heel.Util;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Heel")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0")]
[assembly: AssemblyProduct("Heel")]
[assembly: AssemblyTitle("Heel")]
[assembly: AssemblyVersion("1.1.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Heel
{
	internal static class ModInfo
	{
		internal const string Guid = "dafini.heel";

		internal const string Name = "Heel";

		internal const string Version = "2.1.0";
	}
	[BepInPlugin("dafini.heel", "Heel", "2.1.0")]
	[BepInProcess("valheim.exe")]
	[BepInProcess("valheim_server.exe")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "dafini.heel";

		public const string PluginName = "Heel";

		public const string PluginVersion = "2.1.0";

		private Harmony _harmony;

		private bool _driverChosen;

		private void Update()
		{
			if (_driverChosen)
			{
				Warden.Tick();
				return;
			}
			ZNet instance = ZNet.instance;
			if (!((Object)(object)instance == (Object)null))
			{
				_driverChosen = true;
				if (Warden.ShouldRun(instance.IsDedicated(), HeelConfig.ServerSide.Value))
				{
					Warden.Begin();
				}
			}
		}

		private void Awake()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			Log.Bind(((BaseUnityPlugin)this).Logger);
			HeelConfig.Bind(((BaseUnityPlugin)this).Config);
			try
			{
				_harmony = new Harmony("dafini.heel");
				_harmony.PatchAll();
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Heel failed to patch and is disabled for this session: " + ex));
				return;
			}
			HeelCommand.Register();
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Heel 2.1.0 loaded. Followers adrift beyond " + $"{HeelConfig.AdriftDistance.Value}m, or stalled, are brought to you after " + $"{HeelConfig.CatchUpDelay.Value}s."));
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			Recall.ResetForTests();
		}
	}
}
namespace Heel.Util
{
	internal static class Log
	{
		private static ManualLogSource _source;

		internal static void Bind(ManualLogSource source)
		{
			_source = source;
		}

		internal static void Info(string message)
		{
			ManualLogSource source = _source;
			if (source != null)
			{
				source.LogInfo((object)message);
			}
		}

		internal static void Warn(string message)
		{
			ManualLogSource source = _source;
			if (source != null)
			{
				source.LogWarning((object)message);
			}
		}

		internal static void Error(string message)
		{
			ManualLogSource source = _source;
			if (source != null)
			{
				source.LogError((object)message);
			}
		}

		internal static void Debug(string message)
		{
			if (HeelConfig.Verbose != null && HeelConfig.Verbose.Value)
			{
				ManualLogSource source = _source;
				if (source != null)
				{
					source.LogInfo((object)message);
				}
			}
		}
	}
}
namespace Heel.Server
{
	internal sealed class Bearing
	{
		private sealed class Track
		{
			internal Vector3 Where;

			internal Vector3 Facing;

			internal float Seen;
		}

		internal const float Meaningful = 1f;

		private readonly Dictionary<string, Track> _track = new Dictionary<string, Track>(StringComparer.Ordinal);

		internal int Count => _track.Count;

		internal void Clear()
		{
			_track.Clear();
		}

		internal static Vector3 From(Vector3 previous, Vector3 current, Vector3 lastKnown)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: 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_0049: 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_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = default(Vector3);
			((Vector3)(ref val))..ctor(current.x - previous.x, 0f, current.z - previous.z);
			if (((Vector3)(ref val)).sqrMagnitude >= 1f)
			{
				return ((Vector3)(ref val)).normalized;
			}
			Vector3 val2 = default(Vector3);
			((Vector3)(ref val2))..ctor(lastKnown.x, 0f, lastKnown.z);
			if (!(((Vector3)(ref val2)).sqrMagnitude < 0.0001f))
			{
				return ((Vector3)(ref val2)).normalized;
			}
			return Vector3.forward;
		}

		internal Vector3 Of(string player, Vector3 where, float now)
		{
			//IL_0008: 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)
			//IL_0055: 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_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: 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)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: 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_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: 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)
			if (string.IsNullOrEmpty(player))
			{
				return Vector3.forward;
			}
			if (!_track.TryGetValue(player, out var value))
			{
				_track[player] = new Track
				{
					Where = where,
					Facing = Vector3.forward,
					Seen = now
				};
				return Vector3.forward;
			}
			value.Facing = From(value.Where, where, value.Facing);
			value.Where = where;
			value.Seen = now;
			return value.Facing;
		}

		internal int Forget(float now, float after)
		{
			List<string> list = new List<string>();
			foreach (KeyValuePair<string, Track> item in _track)
			{
				if (now - item.Value.Seen > after)
				{
					list.Add(item.Key);
				}
			}
			foreach (string item2 in list)
			{
				_track.Remove(item2);
			}
			return list.Count;
		}
	}
	internal sealed class Berth
	{
		private sealed class Landing
		{
			internal int Next;

			internal float Last;
		}

		private readonly Dictionary<string, Landing> _by = new Dictionary<string, Landing>(StringComparer.Ordinal);

		internal int Count => _by.Count;

		internal void Clear()
		{
			_by.Clear();
		}

		internal bool Ready(string player, float now, float spacing, float quiet, out int slot)
		{
			slot = 0;
			if (string.IsNullOrEmpty(player))
			{
				return false;
			}
			if (!_by.TryGetValue(player, out var value))
			{
				value = new Landing();
				_by[player] = value;
			}
			else if (now - value.Last > quiet)
			{
				value.Next = 0;
			}
			else if (now - value.Last < spacing)
			{
				return false;
			}
			slot = value.Next;
			value.Next++;
			value.Last = now;
			return true;
		}

		internal int Forget(float now, float after)
		{
			List<string> list = new List<string>();
			foreach (KeyValuePair<string, Landing> item in _by)
			{
				if (now - item.Value.Last > after)
				{
					list.Add(item.Key);
				}
			}
			foreach (string item2 in list)
			{
				_by.Remove(item2);
			}
			return list.Count;
		}
	}
	internal static class Claim
	{
		internal enum Move
		{
			Take,
			Mine,
			Busy
		}

		internal static Move Decide(bool ownedByUs, bool ownedByAnother)
		{
			if (ownedByUs)
			{
				return Move.Mine;
			}
			if (!ownedByAnother)
			{
				return Move.Take;
			}
			return Move.Busy;
		}

		internal static bool MayWrite(Move move)
		{
			return move != Move.Busy;
		}
	}
	internal static class Drop
	{
		internal static Vector3 Spot(Vector3 standingOn, Vector3 forward, int index, int count, float minRadius, float ringStep, float halfWidthDeg)
		{
			//IL_0002: 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_000f: 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_001c: 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_0028: 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)
			Vector3 val = Arc.Slot(index, count, forward, minRadius, ringStep, halfWidthDeg);
			return new Vector3(standingOn.x + val.x, standingOn.y, standingOn.z + val.z);
		}

		internal static Vector3 Spot(Vector3 standingOn, Vector3 forward, int arrival, float minRadius, float ringStep, float halfWidthDeg)
		{
			//IL_0001: 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_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: 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)
			Vector3 val = Arc.OpenSlot(arrival, forward, minRadius, ringStep, halfWidthDeg);
			return new Vector3(standingOn.x + val.x, standingOn.y, standingOn.z + val.z);
		}

		internal static Verdict Judge(Vector3 standingOn, float waterLevel, float clearance)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			return Landing.Judge(floorFound: true, standingOn.y, waterLevel, clearance, warded: false);
		}
	}
	internal static class Herd
	{
		internal enum Verdict
		{
			Follower,
			Wild,
			Staying,
			OwnerAway
		}

		internal static Verdict Judge(bool tamed, string followName, bool connected)
		{
			if (!tamed)
			{
				return Verdict.Wild;
			}
			if (string.IsNullOrEmpty(followName))
			{
				return Verdict.Staying;
			}
			if (!connected)
			{
				return Verdict.OwnerAway;
			}
			return Verdict.Follower;
		}

		internal static bool IsFollower(Verdict verdict)
		{
			return verdict == Verdict.Follower;
		}

		internal static bool SamePlayer(string a, string b)
		{
			if (!string.IsNullOrEmpty(a))
			{
				return string.Equals(a, b, StringComparison.Ordinal);
			}
			return false;
		}
	}
	internal sealed class Peers
	{
		internal struct Who
		{
			internal long Uid;

			internal string Name;

			internal Vector3 Where;

			internal Vector3 Facing;
		}

		internal enum Holder
		{
			Nobody,
			ItsOwnPlayer,
			Another
		}

		private readonly List<Who> _who = new List<Who>();

		internal IList<Who> All => _who;

		internal int Count => _who.Count;

		internal void Clear()
		{
			_who.Clear();
		}

		internal void Add(Who who)
		{
			_who.Add(who);
		}

		internal bool Find(string name, out Who found)
		{
			found = default(Who);
			foreach (Who item in _who)
			{
				if (Herd.SamePlayer(item.Name, name))
				{
					found = item;
					return true;
				}
			}
			return false;
		}

		internal Holder WhoHolds(long owner, string followName)
		{
			if (owner == 0L)
			{
				return Holder.Nobody;
			}
			foreach (Who item in _who)
			{
				if (item.Uid == owner)
				{
					return Herd.SamePlayer(item.Name, followName) ? Holder.ItsOwnPlayer : Holder.Another;
				}
			}
			return Holder.Nobody;
		}

		internal float NearestTo(Vector3 point)
		{
			//IL_001c: 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)
			float num = float.PositiveInfinity;
			foreach (Who item in _who)
			{
				float num2 = Vector3.Distance(point, item.Where);
				if (num2 < num)
				{
					num = num2;
				}
			}
			return num;
		}
	}
	internal static class Sweep
	{
		internal struct Cursor
		{
			internal int Prefab;

			internal int Index;

			internal int Passes;
		}

		internal const int Slice = 128;

		internal static Cursor Next(Cursor at, int prefabCount)
		{
			if (prefabCount <= 0)
			{
				return default(Cursor);
			}
			int num = at.Prefab + 1;
			int num2 = at.Passes;
			if (num >= prefabCount)
			{
				num = 0;
				num2++;
			}
			return new Cursor
			{
				Prefab = num,
				Index = 0,
				Passes = num2
			};
		}

		internal static Cursor Continued(Cursor at, int reachedIndex)
		{
			return new Cursor
			{
				Prefab = at.Prefab,
				Index = reachedIndex,
				Passes = at.Passes
			};
		}

		internal static bool Valid(Cursor at, int prefabCount)
		{
			if (prefabCount > 0 && at.Prefab >= 0 && at.Prefab < prefabCount)
			{
				return at.Index >= 0;
			}
			return false;
		}

		internal static List<string> Tameables(IEnumerable<GameObject> prefabs)
		{
			List<string> list = new List<string>();
			if (prefabs == null)
			{
				return list;
			}
			foreach (GameObject prefab in prefabs)
			{
				if (!((Object)(object)prefab == (Object)null) && !((Object)(object)prefab.GetComponent<Tameable>() == (Object)null))
				{
					list.Add(((Object)prefab).name);
				}
			}
			return list;
		}
	}
	internal static class Warden
	{
		internal const float Interval = 1f;

		private static float _next;

		private static Sweep.Cursor _cursor;

		private static List<string> _tameables;

		private static readonly Watch Clock = new Watch();

		private static readonly Peers Present = new Peers();

		private static readonly Berth Arrivals = new Berth();

		private static readonly Bearing Headings = new Bearing();

		private static readonly List<ZDO> Batch = new List<ZDO>();

		internal static bool Running { get; private set; }

		internal static int Lifted { get; private set; }

		internal static int Considered { get; private set; }

		internal static int LeftBusy { get; private set; }

		internal static int HeldForSpacing { get; private set; }

		internal static void ResetForTests()
		{
			Running = false;
			Lifted = 0;
			Considered = 0;
			LeftBusy = 0;
			HeldForSpacing = 0;
			_next = 0f;
			_cursor = default(Sweep.Cursor);
			_tameables = null;
			Clock.Clear();
			Present.Clear();
			Arrivals.Clear();
			Headings.Clear();
		}

		internal static bool ShouldRun(bool dedicated, bool enabled)
		{
			return dedicated && enabled;
		}

		internal static void Begin()
		{
			Running = true;
			Log.Info("Heel is running server-side. Clients do not need this mod installed. Hazard avoidance is client-only and is not active.");
		}

		internal static void Tick()
		{
			if (Running && !(Time.time < _next))
			{
				_next = Time.time + 1f;
				ZDOMan instance = ZDOMan.instance;
				ZNet instance2 = ZNet.instance;
				if (instance != null && !((Object)(object)instance2 == (Object)null) && Gather(instance2) && Prefabs())
				{
					Slice(instance);
					float after = HeelConfig.ForgetAfter.Value + 60f;
					Clock.Forget(Time.time, after);
					Arrivals.Forget(Time.time, after);
					Headings.Forget(Time.time, after);
				}
			}
		}

		private static bool Gather(ZNet net)
		{
			//IL_0061: 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_0079: 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)
			Present.Clear();
			List<ZNetPeer> peers = net.GetPeers();
			if (peers == null)
			{
				return false;
			}
			foreach (ZNetPeer item in peers)
			{
				if (item != null && !string.IsNullOrEmpty(item.m_playerName))
				{
					Present.Add(new Peers.Who
					{
						Uid = item.m_uid,
						Name = item.m_playerName,
						Where = item.m_refPos,
						Facing = Headings.Of(item.m_playerName, item.m_refPos, Time.time)
					});
				}
			}
			return Present.Count > 0;
		}

		private static bool Prefabs()
		{
			if (_tameables != null)
			{
				return _tameables.Count > 0;
			}
			ZNetScene instance = ZNetScene.instance;
			if ((Object)(object)instance == (Object)null || instance.m_prefabs == null)
			{
				return false;
			}
			_tameables = Sweep.Tameables(instance.m_prefabs);
			Log.Info("Heel found " + _tameables.Count + " tameable prefab(s) to watch.");
			return _tameables.Count > 0;
		}

		private static void Slice(ZDOMan zdoMan)
		{
			if (!Sweep.Valid(_cursor, _tameables.Count))
			{
				_cursor = default(Sweep.Cursor);
			}
			string text = _tameables[_cursor.Prefab];
			Batch.Clear();
			int index = _cursor.Index;
			bool allZDOsWithPrefabIterative = zdoMan.GetAllZDOsWithPrefabIterative(text, Batch, ref index);
			foreach (ZDO item in Batch)
			{
				Consider(zdoMan, item);
			}
			_cursor = (allZDOsWithPrefabIterative ? Sweep.Next(_cursor, _tameables.Count) : Sweep.Continued(_cursor, index));
		}

		private unsafe static void Consider(ZDOMan zdoMan, ZDO zdo)
		{
			//IL_004b: 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_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0161: Unknown result type (might be due to invalid IL or missing references)
			//IL_0186: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0199: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0241: Unknown result type (might be due to invalid IL or missing references)
			//IL_0243: Unknown result type (might be due to invalid IL or missing references)
			if (zdo == null)
			{
				return;
			}
			string text = zdo.GetString(ZDOVars.s_follow, string.Empty);
			bool tamed = zdo.GetBool(ZDOVars.s_tamed, false);
			Peers.Who found;
			bool connected = Present.Find(text, out found);
			if (!Herd.IsFollower(Herd.Judge(tamed, text, connected)))
			{
				return;
			}
			Considered++;
			float distance = Vector3.Distance(zdo.GetPosition(), found.Where);
			if (!Clock.Due(zdo.m_uid, distance, Time.time, HeelConfig.WithYouDistance.Value, HeelConfig.AdriftDistance.Value, HeelConfig.ClosingBy.Value, HeelConfig.CatchUpDelay.Value, out var standing))
			{
				return;
			}
			Claim.Move move = Claim.Decide(zdo.GetOwner() == ZDOMan.GetSessionID(), Present.WhoHolds(zdo.GetOwner(), text) == Peers.Holder.Another);
			if (!Claim.MayWrite(move))
			{
				LeftBusy++;
				return;
			}
			Verdict verdict = Drop.Judge(found.Where, ZoneSystem.instance.m_waterLevel, HeelConfig.WaterClearance.Value);
			if (verdict != Verdict.Ok)
			{
				Log.Debug("heel/server: not lifting " + text + "'s follower - " + verdict);
				return;
			}
			if (!Arrivals.Ready(text, Time.time, HeelConfig.ArrivalSpacing.Value, HeelConfig.CatchUpDelay.Value, out var slot))
			{
				HeldForSpacing++;
				return;
			}
			Vector3 val = Drop.Spot(found.Where, found.Facing, slot, HeelConfig.MinRadius.Value, HeelConfig.RingStep.Value, HeelConfig.ArcHalfWidth.Value);
			zdo.SetOwner(ZDOMan.GetSessionID());
			zdo.SetPosition(val);
			zdoMan.ForceSendZDO(zdo.m_uid);
			Clock.Settled(zdo.m_uid);
			Lifted++;
			string[] obj = new string[12]
			{
				"heel/server: lifted a follower of ",
				text,
				" (",
				standing.ToString(),
				", ",
				distance.ToString("0"),
				"m, ",
				move.ToString(),
				", slot ",
				slot.ToString(),
				") to ",
				null
			};
			Vector3 val2 = val;
			obj[11] = ((object)(*(Vector3*)(&val2))/*cast due to .constrained prefix*/).ToString();
			Log.Debug(string.Concat(obj));
		}

		internal static List<string> Report()
		{
			List<string> list = new List<string> { "--- heel (server) ---" };
			if (!Running)
			{
				list.Add("  not running - this machine is not a dedicated server, so the client path is in charge");
				return list;
			}
			list.Add("  watching   " + ((_tameables != null) ? _tameables.Count : 0) + " tameable prefab(s), cursor at " + _cursor.Prefab + "/" + _cursor.Index + ", " + _cursor.Passes + " full pass(es)");
			list.Add("  players    " + Present.Count + " connected");
			list.Add("  followers  " + Considered + " considered, " + Lifted + " lifted, " + LeftBusy + " left in somebody else's world, " + HeldForSpacing + " held back a tick for spacing");
			list.Add("  clocks     " + Clock.Count + " follower(s) being timed");
			list.Add("  arrivals   " + Arrivals.Count + " player(s) mid-landing, " + Headings.Count + " heading(s) tracked");
			list.Add("  NOTE       hazard avoidance is client-only and is not running here");
			return list;
		}
	}
	internal sealed class Watch
	{
		private sealed class Since
		{
			internal float Waiting;

			internal float LastDistance = float.PositiveInfinity;

			internal float Seen;
		}

		private readonly Dictionary<ZDOID, Since> _since = new Dictionary<ZDOID, Since>();

		internal int Count => _since.Count;

		internal void Clear()
		{
			_since.Clear();
		}

		internal bool Due(ZDOID id, float distance, float now, float withYou, float adrift, float closingBy, float delay, out Standing standing)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			if (!_since.TryGetValue(id, out var value))
			{
				value = new Since();
				_since[id] = value;
			}
			standing = Progress.Judge(distance, value.LastDistance, withYou, adrift, closingBy);
			value.LastDistance = distance;
			value.Seen = now;
			if (!Progress.Waiting(standing))
			{
				value.Waiting = 0f;
				return false;
			}
			if (value.Waiting <= 0f)
			{
				value.Waiting = now;
			}
			return Progress.Elapsed(value.Waiting, now, delay);
		}

		internal void Settled(ZDOID id)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			if (_since.TryGetValue(id, out var value))
			{
				value.Waiting = 0f;
				value.LastDistance = float.PositiveInfinity;
			}
		}

		internal int Forget(float now, float after)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			List<ZDOID> list = new List<ZDOID>();
			foreach (KeyValuePair<ZDOID, Since> item in _since)
			{
				if (now - item.Value.Seen > after)
				{
					list.Add(item.Key);
				}
			}
			foreach (ZDOID item2 in list)
			{
				_since.Remove(item2);
			}
			return list.Count;
		}
	}
}
namespace Heel.Safety
{
	internal static class Ground
	{
		private static int _smokeMask = -1;

		private static int SmokeMask
		{
			get
			{
				if (_smokeMask == -1)
				{
					_smokeMask = LayerMask.GetMask(new string[1] { "smoke" });
				}
				return _smokeMask;
			}
		}

		internal static void ResetForTests()
		{
			_smokeMask = -1;
		}

		internal static Peril At(Vector3 point)
		{
			//IL_000c: 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)
			if (HeelConfig.AvoidSmoke.Value && InSmoke(point))
			{
				return Peril.Smoke;
			}
			if (HeelConfig.AvoidTraps.Value && InTrap(point))
			{
				return Peril.Trap;
			}
			return Peril.None;
		}

		internal static bool InSmoke(Vector3 point)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: 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)
			try
			{
				return Physics.CheckSphere(point + Vector3.up * 0.5f, 0.5f, SmokeMask);
			}
			catch (Exception)
			{
				return false;
			}
		}

		internal static bool InTrap(Vector3 point)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			float value = HeelConfig.TrapMargin.Value;
			Collider[] array = Physics.OverlapSphere(point, value);
			foreach (Collider val in array)
			{
				if (!((Object)(object)val == (Object)null))
				{
					Aoe componentInParent = ((Component)val).GetComponentInParent<Aoe>();
					if (!((Object)(object)componentInParent == (Object)null) && Hazard.Harmful(componentInParent.m_hitCharacters, componentInParent.m_hitFriendly, Damage(componentInParent)) && Hazard.Reaches(Vector3.Distance(point, ((Component)componentInParent).transform.position), componentInParent.m_radius, value))
					{
						return true;
					}
				}
			}
			return false;
		}

		internal static float Damage(Aoe aoe)
		{
			//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_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: 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_002a: 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_0038: 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_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)aoe == (Object)null)
			{
				return 0f;
			}
			DamageTypes damage = aoe.m_damage;
			return damage.m_damage + damage.m_blunt + damage.m_slash + damage.m_pierce + damage.m_fire + damage.m_frost + damage.m_lightning + damage.m_poison + damage.m_spirit;
		}

		internal static IEnumerable<Peril> All(Vector3 point)
		{
			//IL_0006: 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)
			List<Peril> list = new List<Peril>();
			if (InSmoke(point))
			{
				list.Add(Peril.Smoke);
			}
			if (InTrap(point))
			{
				list.Add(Peril.Trap);
			}
			return list;
		}
	}
	internal enum Peril
	{
		None,
		Fire,
		Lava,
		Water,
		Smoke,
		Trap
	}
	internal static class Hazard
	{
		internal const float Margin = 1.5f;

		internal static bool Harmful(bool hitsCharacters, bool hitsFriendly, float damage)
		{
			if (hitsCharacters && hitsFriendly)
			{
				return damage > 0f;
			}
			return false;
		}

		internal static bool Reaches(float distance, float hazardRadius, float margin)
		{
			return distance <= hazardRadius + margin;
		}

		internal static void Switches(bool fire, bool lava, bool water, out bool avoidFire, out bool avoidLava, out bool avoidWater)
		{
			avoidFire = fire;
			avoidLava = lava;
			avoidWater = water;
		}

		internal static bool Guards(bool tamed, bool alive, bool hasFollowTarget)
		{
			if (tamed && alive)
			{
				return !hasFollowTarget;
			}
			return false;
		}
	}
	internal static class Minding
	{
		[HarmonyPatch(typeof(BaseAI), "IsValidRandomMovePoint")]
		internal static class Validate
		{
			[HarmonyPostfix]
			private static void Postfix(BaseAI __instance, Vector3 point, ref bool __result)
			{
				//IL_0031: Unknown result type (might be due to invalid IL or missing references)
				if (!__result || HeelConfig.Enabled == null || !HeelConfig.Enabled.Value || !HeelConfig.MindHazards.Value)
				{
					return;
				}
				try
				{
					if (Applies(__instance) && Ground.At(point) != Peril.None)
					{
						__result = false;
						Refused++;
					}
				}
				catch (Exception ex)
				{
					Log.Error("Heel hit an error judging a wander point: " + ex);
				}
			}
		}

		internal const float SweepInterval = 2f;

		private static float _nextSweep;

		internal static int Guarded { get; private set; }

		internal static int Nudged { get; private set; }

		internal static int Refused { get; private set; }

		internal static void ResetForTests()
		{
			_nextSweep = 0f;
			Guarded = 0;
			Nudged = 0;
			Refused = 0;
			Ground.ResetForTests();
		}

		internal static void Tick(float now)
		{
			if (HeelConfig.Enabled == null || !HeelConfig.Enabled.Value || !HeelConfig.MindHazards.Value || now < _nextSweep)
			{
				return;
			}
			_nextSweep = now + 2f;
			List<Character> allCharacters = Character.GetAllCharacters();
			if (allCharacters == null)
			{
				return;
			}
			int num = 0;
			foreach (Character item in allCharacters)
			{
				if (!((Object)(object)item == (Object)null))
				{
					MonsterAI component = ((Component)item).GetComponent<MonsterAI>();
					if (!((Object)(object)component == (Object)null) && Hazard.Guards(item.IsTamed(), !item.IsDead(), (Object)(object)component.GetFollowTarget() != (Object)null))
					{
						num++;
						Arm(component);
						Rescue(item, component);
					}
				}
			}
			Guarded = num;
		}

		internal static void Arm(MonsterAI ai)
		{
			Hazard.Switches(HeelConfig.AvoidFire.Value, HeelConfig.AvoidLava.Value, HeelConfig.AvoidWater.Value, out var avoidFire, out var avoidLava, out var avoidWater);
			if (avoidFire)
			{
				((BaseAI)ai).m_avoidFire = true;
			}
			if (avoidLava)
			{
				((BaseAI)ai).m_avoidLava = true;
			}
			if (avoidWater)
			{
				((BaseAI)ai).m_avoidWater = true;
			}
		}

		internal static void Rescue(Character character, MonsterAI ai)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			Peril peril = Ground.At(((Component)character).transform.position);
			if (peril != Peril.None)
			{
				((BaseAI)ai).ResetRandomMovement();
				Nudged++;
				Log.Debug($"heel: {character.m_name} is standing in {peril}, sending it off");
			}
		}

		internal static bool Applies(BaseAI ai)
		{
			MonsterAI val = (MonsterAI)(object)((ai is MonsterAI) ? ai : null);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			Character component = ((Component)ai).GetComponent<Character>();
			if ((Object)(object)component == (Object)null)
			{
				return false;
			}
			return Hazard.Guards(component.IsTamed(), !component.IsDead(), (Object)(object)val.GetFollowTarget() != (Object)null);
		}
	}
}
namespace Heel.Patches
{
	[HarmonyPatch(typeof(Player), "Update")]
	internal static class PlayerUpdatePatch
	{
		private static bool _shouted;

		[HarmonyPostfix]
		private static void Postfix(Player __instance)
		{
			if ((Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer)
			{
				return;
			}
			try
			{
				Recall.Tick(__instance, Time.time);
				Minding.Tick(Time.time);
			}
			catch (Exception ex)
			{
				if (!_shouted)
				{
					_shouted = true;
					Log.Error("Heel hit an error and will stay quiet about repeats: " + ex);
				}
			}
		}
	}
}
namespace Heel.Follow
{
	internal static class Arc
	{
		internal const int PerRing = 5;

		internal static Vector3 Slot(int index, int count, Vector3 forward, float minRadius, float ringStep, float halfWidthDeg)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			if (index < 0)
			{
				index = 0;
			}
			if (count < 1)
			{
				count = 1;
			}
			Vector3 flat = Flatten(forward);
			int num = index / 5;
			int index2 = index % 5;
			int num2 = count - num * 5;
			if (num2 > 5)
			{
				num2 = 5;
			}
			if (num2 < 1)
			{
				num2 = 1;
			}
			float angle = Spread(index2, num2, halfWidthDeg);
			float radius = minRadius + (float)num * ringStep;
			return Place(flat, angle, radius);
		}

		internal static Vector3 OpenSlot(int arrival, Vector3 forward, float minRadius, float ringStep, float halfWidthDeg)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			if (arrival < 0)
			{
				arrival = 0;
			}
			int num = arrival / 5;
			float angle = Spread(CentreOut(arrival % 5), 5, halfWidthDeg);
			float radius = minRadius + (float)num * ringStep;
			return Place(Flatten(forward), angle, radius);
		}

		internal static int CentreOut(int within)
		{
			if (within < 0)
			{
				within = 0;
			}
			if (within >= 5)
			{
				within = 4;
			}
			int num = 2;
			int num2 = (within + 1) / 2;
			if (within % 2 != 1)
			{
				return num + num2;
			}
			return num - num2;
		}

		private static Vector3 Flatten(Vector3 forward)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = default(Vector3);
			((Vector3)(ref val))..ctor(forward.x, 0f, forward.z);
			if (((Vector3)(ref val)).sqrMagnitude < 0.0001f)
			{
				((Vector3)(ref val))..ctor(0f, 0f, 1f);
			}
			return ((Vector3)(ref val)).normalized;
		}

		private static Vector3 Place(Vector3 flat, float angle, float radius)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			return RotateY(new Vector3(0f - flat.x, 0f, 0f - flat.z), angle) * radius;
		}

		internal static float Spread(int index, int count, float halfWidthDeg)
		{
			if (count <= 1)
			{
				return 0f;
			}
			return ((float)index / (float)(count - 1) - 0.5f) * 2f * halfWidthDeg;
		}

		internal static Vector3 RotateY(Vector3 v, float degrees)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: 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_0034: 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)
			float num = degrees * (MathF.PI / 180f);
			float num2 = Mathf.Cos(num);
			float num3 = Mathf.Sin(num);
			return new Vector3(v.x * num2 + v.z * num3, v.y, (0f - v.x) * num3 + v.z * num2);
		}

		internal static float OuterRadius(int count, float minRadius, float ringStep)
		{
			if (count < 1)
			{
				count = 1;
			}
			int num = (count - 1) / 5;
			return minRadius + (float)num * ringStep;
		}
	}
	internal enum Claim
	{
		Yours,
		Untamed,
		SomeoneElses,
		NotFollowing,
		Gone
	}
	internal static class Followers
	{
		internal static Claim Judge(bool hasAi, bool alive, bool tamed, bool followingMe)
		{
			if (!hasAi || !alive)
			{
				return Claim.Gone;
			}
			if (!tamed)
			{
				return Claim.Untamed;
			}
			if (!followingMe)
			{
				return Claim.SomeoneElses;
			}
			return Claim.Yours;
		}

		internal static Claim Judge(bool hasAi, bool alive, bool tamed, bool hasFollowTarget, bool followingMe)
		{
			if (!hasAi || !alive)
			{
				return Claim.Gone;
			}
			if (!tamed)
			{
				return Claim.Untamed;
			}
			if (followingMe)
			{
				return Claim.Yours;
			}
			if (!hasFollowTarget)
			{
				return Claim.NotFollowing;
			}
			return Claim.SomeoneElses;
		}

		internal static Claim Of(Character character, Player player)
		{
			if ((Object)(object)character == (Object)null || (Object)(object)player == (Object)null)
			{
				return Claim.Gone;
			}
			MonsterAI component = ((Component)character).GetComponent<MonsterAI>();
			if ((Object)(object)component == (Object)null)
			{
				return Claim.Gone;
			}
			GameObject followTarget = component.GetFollowTarget();
			bool followingMe = (Object)(object)followTarget != (Object)null && followTarget == ((Component)player).gameObject;
			return Judge(hasAi: true, !character.IsDead(), character.IsTamed(), (Object)(object)followTarget != (Object)null, followingMe);
		}
	}
	internal enum Verdict
	{
		Ok,
		NoGround,
		Water,
		Warded
	}
	internal static class Landing
	{
		internal static bool Footing(bool floorFound, float floorHeight, float waterLevel, float clearance)
		{
			if (floorFound)
			{
				return floorHeight >= waterLevel + clearance;
			}
			return false;
		}

		internal static Verdict Judge(bool floorFound, float floorHeight, float waterLevel, float clearance, bool warded)
		{
			if (!floorFound)
			{
				return Verdict.NoGround;
			}
			if (!Footing(floorFound: true, floorHeight, waterLevel, clearance))
			{
				return Verdict.Water;
			}
			if (warded)
			{
				return Verdict.Warded;
			}
			return Verdict.Ok;
		}

		internal static Vector3 OnFloor(Vector3 candidate, float floorHeight, float lift)
		{
			//IL_0000: 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_000f: Unknown result type (might be due to invalid IL or missing references)
			return new Vector3(candidate.x, floorHeight + lift, candidate.z);
		}
	}
	internal enum Standing
	{
		WithYou,
		Closing,
		Stalled,
		Adrift
	}
	internal static class Progress
	{
		internal static Standing Judge(float distance, float previousDistance, float withYou, float adrift, float closingBy)
		{
			if (distance >= adrift)
			{
				return Standing.Adrift;
			}
			if (distance <= withYou)
			{
				return Standing.WithYou;
			}
			if (previousDistance - distance >= closingBy)
			{
				return Standing.Closing;
			}
			return Standing.Stalled;
		}

		internal static bool Waiting(Standing standing)
		{
			if (standing != Standing.Adrift)
			{
				return standing == Standing.Stalled;
			}
			return true;
		}

		internal static bool Elapsed(float waitingSince, float now, float delay)
		{
			if (waitingSince <= 0f)
			{
				return false;
			}
			return now - waitingSince >= delay;
		}
	}
	internal static class Recall
	{
		internal const float SampleInterval = 1f;

		private static float _nextSample;

		private static float _nextRelease;

		private static readonly List<Enrolled> Ready = new List<Enrolled>();

		private static int _slot;

		private static int _batch;

		internal static int Waiting { get; private set; }

		internal static int Arrived { get; private set; }

		internal static void ResetForTests()
		{
			_nextSample = 0f;
			_nextRelease = 0f;
			_slot = 0;
			_batch = 0;
			Ready.Clear();
			Roster.Clear();
			Waiting = 0;
			Arrived = 0;
		}

		internal static void Tick(Player player, float now)
		{
			if (!((Object)(object)player == (Object)null) && HeelConfig.Enabled != null && HeelConfig.Enabled.Value)
			{
				if (now >= _nextSample)
				{
					_nextSample = now + 1f;
					Sample(player, now);
				}
				Release(player, now);
			}
		}

		private static void Sample(Player player, float now)
		{
			//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_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: 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_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			List<Character> allCharacters = Character.GetAllCharacters();
			if (allCharacters == null)
			{
				return;
			}
			Vector3 position = ((Component)player).transform.position;
			foreach (Character item in allCharacters)
			{
				if ((Object)(object)item == (Object)null)
				{
					continue;
				}
				ZNetView component = ((Component)item).GetComponent<ZNetView>();
				if ((Object)(object)component == (Object)null || !component.IsValid())
				{
					continue;
				}
				ZDOID uid = component.GetZDO().m_uid;
				if (Followers.Of(item, player) != Claim.Yours)
				{
					if (Roster.Get(uid) != null)
					{
						Roster.Strike(uid);
					}
				}
				else
				{
					Enrolled entry = Roster.Enrol(uid, item.m_name, now);
					float distance = Vector3.Distance(position, ((Component)item).transform.position);
					Consider(entry, distance, now, Fighting(item));
				}
			}
			Roster.Forget(now, HeelConfig.ForgetAfter.Value);
			Sweep(now);
		}

		internal static void Consider(Enrolled entry, float distance, float now, bool fighting)
		{
			Standing standing = Progress.Judge(distance, entry.LastDistance, HeelConfig.WithYouDistance.Value, HeelConfig.AdriftDistance.Value, HeelConfig.ClosingBy.Value);
			entry.LastDistance = distance;
			bool flag = fighting && HeelConfig.HoldWhileFighting.Value;
			if (!Progress.Waiting(standing) || flag)
			{
				entry.WaitingSince = 0f;
				entry.Queued = false;
			}
			else if (entry.WaitingSince <= 0f)
			{
				entry.WaitingSince = now;
			}
		}

		private static void Sweep(float now)
		{
			int num = 0;
			foreach (Enrolled item in Roster.All)
			{
				if (item.WaitingSince > 0f)
				{
					num++;
				}
				if (!item.Queued && Progress.Elapsed(item.WaitingSince, now, HeelConfig.CatchUpDelay.Value))
				{
					item.Queued = true;
					Ready.Add(item);
				}
			}
			Waiting = num;
		}

		private static bool Fighting(Character character)
		{
			MonsterAI component = ((Component)character).GetComponent<MonsterAI>();
			if ((Object)(object)component != (Object)null)
			{
				return ((BaseAI)component).IsAlerted();
			}
			return false;
		}

		private static void Release(Player player, float now)
		{
			if (Ready.Count == 0)
			{
				_slot = 0;
				_batch = 0;
			}
			else if (!(now < _nextRelease))
			{
				_nextRelease = now + HeelConfig.ArrivalSpacing.Value;
				if (_batch < Ready.Count + _slot)
				{
					_batch = Ready.Count + _slot;
				}
				Enrolled enrolled = Ready[0];
				Ready.RemoveAt(0);
				if (Land(player, enrolled, _slot, _batch))
				{
					_slot++;
					enrolled.WaitingSince = 0f;
					enrolled.Queued = false;
					enrolled.LastDistance = float.MaxValue;
					Arrived++;
				}
				else
				{
					enrolled.Queued = true;
					Ready.Add(enrolled);
				}
			}
		}

		private static bool Land(Player player, Enrolled entry, int slot, int batch)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: 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_0028: 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_005d: 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_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: 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)
			//IL_006c: 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_007a: 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_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			ZoneSystem instance = ZoneSystem.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return false;
			}
			Vector3 position = ((Component)player).transform.position;
			Vector3 forward = ((Component)player).transform.forward;
			float floorHeight = default(float);
			for (int i = 0; i < 6; i++)
			{
				int num = slot + i * 5;
				Vector3 val = Arc.Slot(num, batch + i * 5, forward, HeelConfig.MinRadius.Value, HeelConfig.RingStep.Value, HeelConfig.ArcHalfWidth.Value);
				Vector3 val2 = position + val;
				if (instance.FindFloor(val2, ref floorHeight))
				{
					bool warded = !PrivateArea.CheckAccess(val2, 0f, false, true);
					Verdict verdict = Landing.Judge(floorFound: true, floorHeight, instance.m_waterLevel, HeelConfig.WaterClearance.Value, warded);
					if (verdict == Verdict.Ok)
					{
						return Relocate.Move(entry.Id, Landing.OnFloor(val2, floorHeight, 0.2f), entry.Name);
					}
					Log.Debug($"heel: slot {num} for {entry.Name} rejected - {verdict}");
				}
			}
			Log.Debug("heel: nowhere safe behind you for " + entry.Name + " yet");
			return false;
		}
	}
	internal enum Route
	{
		Loaded,
		ByRecord,
		Unreachable
	}
	internal static class Relocate
	{
		internal static int Moved { get; private set; }

		internal static int Refused { get; private set; }

		internal static void ResetForTests()
		{
			Moved = 0;
			Refused = 0;
		}

		internal static Route Choose(bool objectLoaded, bool recordFound)
		{
			if (objectLoaded)
			{
				return Route.Loaded;
			}
			if (recordFound)
			{
				return Route.ByRecord;
			}
			return Route.Unreachable;
		}

		internal static bool Move(ZDOID id, Vector3 to, string name)
		{
			//IL_0025: 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)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			ZNetScene instance = ZNetScene.instance;
			ZDOMan instance2 = ZDOMan.instance;
			if ((Object)(object)instance == (Object)null || instance2 == null)
			{
				return Refuse(name, "the world is not up");
			}
			GameObject val = instance.FindInstance(id);
			ZDO zDO = instance2.GetZDO(id);
			return Choose((Object)(object)val != (Object)null, zDO != null) switch
			{
				Route.Loaded => MoveObject(val, to, name), 
				Route.ByRecord => MoveRecord(zDO, to, name), 
				_ => Refuse(name, "neither loaded nor in the record table"), 
			};
		}

		private static bool MoveObject(GameObject instance, Vector3 to, string name)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: 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_0052: Unknown result type (might be due to invalid IL or missing references)
			ZNetView component = instance.GetComponent<ZNetView>();
			if ((Object)(object)component == (Object)null || !component.IsValid())
			{
				return Refuse(name, "no valid network view");
			}
			component.ClaimOwnership();
			instance.transform.position = to;
			Rigidbody component2 = instance.GetComponent<Rigidbody>();
			if ((Object)(object)component2 != (Object)null)
			{
				component2.velocity = Vector3.zero;
				component2.angularVelocity = Vector3.zero;
			}
			Moved++;
			Log.Debug($"heel: moved {name} (loaded) to {to}");
			return true;
		}

		private static bool MoveRecord(ZDO record, Vector3 to, string name)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			record.SetOwner(ZDOMan.GetSessionID());
			record.SetPosition(to);
			Moved++;
			Log.Debug($"heel: moved {name} (by record) to {to}");
			return true;
		}

		private static bool Refuse(string name, string why)
		{
			Refused++;
			Log.Debug("heel: left " + name + " alone - " + why);
			return false;
		}
	}
	internal sealed class Enrolled
	{
		internal ZDOID Id;

		internal string Name = string.Empty;

		internal float LastDistance = float.MaxValue;

		internal float WaitingSince;

		internal float LastSeen;

		internal bool Queued;
	}
	internal static class Roster
	{
		private static readonly Dictionary<ZDOID, Enrolled> Book = new Dictionary<ZDOID, Enrolled>();

		internal static int Count => Book.Count;

		internal static IEnumerable<Enrolled> All => Book.Values;

		internal static void Clear()
		{
			Book.Clear();
		}

		internal static Enrolled Get(ZDOID id)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			if (!Book.TryGetValue(id, out var value))
			{
				return null;
			}
			return value;
		}

		internal static Enrolled Enrol(ZDOID id, string name, float now)
		{
			//IL_0005: 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_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			if (!Book.TryGetValue(id, out var value))
			{
				value = new Enrolled
				{
					Id = id
				};
				Book[id] = value;
			}
			if (!string.IsNullOrEmpty(name))
			{
				value.Name = name;
			}
			value.LastSeen = now;
			return value;
		}

		internal static void Strike(ZDOID id)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			Book.Remove(id);
		}

		internal static int Forget(float now, float after)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: 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)
			List<ZDOID> list = null;
			foreach (KeyValuePair<ZDOID, Enrolled> item in Book)
			{
				if (!(now - item.Value.LastSeen < after))
				{
					list = list ?? new List<ZDOID>();
					list.Add(item.Key);
				}
			}
			if (list == null)
			{
				return 0;
			}
			foreach (ZDOID item2 in list)
			{
				Book.Remove(item2);
			}
			return list.Count;
		}

		internal static bool Stale(float lastSeen, float now, float after)
		{
			return now - lastSeen >= after;
		}
	}
}
namespace Heel.Config
{
	internal static class HeelConfig
	{
		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<float> CatchUpDelay;

		internal static ConfigEntry<float> AdriftDistance;

		internal static ConfigEntry<float> WithYouDistance;

		internal static ConfigEntry<float> ClosingBy;

		internal static ConfigEntry<float> ArrivalSpacing;

		internal static ConfigEntry<float> MinRadius;

		internal static ConfigEntry<float> RingStep;

		internal static ConfigEntry<float> ArcHalfWidth;

		internal static ConfigEntry<float> WaterClearance;

		internal static ConfigEntry<bool> HoldWhileFighting;

		internal static ConfigEntry<float> ForgetAfter;

		internal static ConfigEntry<bool> MindHazards;

		internal static ConfigEntry<bool> AvoidFire;

		internal static ConfigEntry<bool> AvoidLava;

		internal static ConfigEntry<bool> AvoidWater;

		internal static ConfigEntry<bool> AvoidSmoke;

		internal static ConfigEntry<bool> AvoidTraps;

		internal static ConfigEntry<float> TrapMargin;

		internal static ConfigEntry<bool> Verbose;

		internal static ConfigEntry<bool> ServerSide;

		internal static void Bind(ConfigFile cfg)
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Expected O, but got Unknown
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Expected O, but got Unknown
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Expected O, but got Unknown
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Expected O, but got Unknown
			//IL_015c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0166: Expected O, but got Unknown
			//IL_0194: Unknown result type (might be due to invalid IL or missing references)
			//IL_019e: Expected O, but got Unknown
			//IL_01cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d6: Expected O, but got Unknown
			//IL_0204: Unknown result type (might be due to invalid IL or missing references)
			//IL_020e: Expected O, but got Unknown
			//IL_02f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0303: Expected O, but got Unknown
			//IL_0331: Unknown result type (might be due to invalid IL or missing references)
			//IL_033b: Expected O, but got Unknown
			Enabled = cfg.Bind<bool>("General", "Enabled", true, "Master switch. Off means this mod does nothing at all.");
			CatchUpDelay = cfg.Bind<float>("Timing", "CatchUpDelay", 25f, new ConfigDescription("Seconds a follower must be adrift or stalled before it is brought to you. The delay is the point: it gives them a chance to arrive under their own steam, and it keeps a portal exit clear while you walk away from it.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 300f), Array.Empty<object>()));
			ArrivalSpacing = cfg.Bind<float>("Timing", "ArrivalSpacing", 0.5f, new ConfigDescription("Seconds between one arrival and the next. With a large pack this is what stops twelve animals materialising in the same instant. Applies to the server path too as of 2.1.0, which is where a large pack actually shows up.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>()));
			AdriftDistance = cfg.Bind<float>("Distance", "AdriftDistance", 40f, new ConfigDescription("Beyond this many metres a follower is considered unable to catch up, and the clock runs no matter what it is doing.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 500f), Array.Empty<object>()));
			WithYouDistance = cfg.Bind<float>("Distance", "WithYouDistance", 12f, new ConfigDescription("Within this many metres a follower counts as being with you and the clock is cleared. Normal following sits well inside it.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 40f), Array.Empty<object>()));
			ClosingBy = cfg.Bind<float>("Distance", "ClosingBy", 0.5f, new ConfigDescription("Metres a follower must close per sample to count as making progress. This is what tells a wolf jogging up behind you from a wolf in a pit: both are at the same distance, only one of them is getting nearer.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
			MinRadius = cfg.Bind<float>("Placement", "MinRadius", 5f, new ConfigDescription("How far behind you the first ring of arrivals lands.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 30f), Array.Empty<object>()));
			RingStep = cfg.Bind<float>("Placement", "RingStep", 3.5f, new ConfigDescription("How much further out each additional ring sits. A ring holds five.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 20f), Array.Empty<object>()));
			ArcHalfWidth = cfg.Bind<float>("Placement", "ArcHalfWidth", 65f, new ConfigDescription("Half the width of the arrival arc, in degrees either side of dead-behind. Must stay under 90 or arrivals start appearing beside and then in front of you, which is the thing the arc exists to prevent.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 89f), Array.Empty<object>()));
			WaterClearance = cfg.Bind<float>("Safety", "WaterClearance", 0.5f, new ConfigDescription("Metres of dry ground a landing spot must have above the water line. The shoreline is exactly level with the sea, so zero here means chest deep.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
			HoldWhileFighting = cfg.Bind<bool>("Safety", "HoldWhileFighting", false, "Hold a follower in place while it is in a fight instead of pulling it out. Off by default, because the wolf you most want rescued is usually the one stuck in a pit WITH something. Turn it on if you would rather your tames finished their fights.");
			MindHazards = cfg.Bind<bool>("Minding", "MindHazards", true, "Keep tames that are NOT following anybody out of things that would kill them. The game already has this and switches it off for tamed creatures - BaseAI.AvoidFire literally opens by returning early if IsTamed() - so this turns it back on for the animals you parked rather than the ones at heel.");
			AvoidFire = cfg.Bind<bool>("Minding", "AvoidFire", true, "Open flames. The one everybody loses a boar to.");
			AvoidLava = cfg.Bind<bool>("Minding", "AvoidLava", true, "Lava. The game has the switch; it is simply off for tames.");
			AvoidWater = cfg.Bind<bool>("Minding", "AvoidWater", true, "Deep water. Boar and lox do not swim well and drown quietly offscreen.");
			AvoidSmoke = cfg.Bind<bool>("Minding", "AvoidSmoke", true, "Smoke, which does damage over time. NOTE: this also keeps tames out of a working smokehouse, which is either exactly what you want or exactly what you don't - turn it off if you are curing meat around your animals.");
			AvoidTraps = cfg.Bind<bool>("Minding", "AvoidTraps", true, "Anything built that would hurt them: sharp stakes, spikes, any trap. Derived from the game rather than a list of names, so modded traps count too - a trap already set to spare friendly targets is left alone, because it was never going to hurt them.");
			TrapMargin = cfg.Bind<float>("Minding", "TrapMargin", 1.5f, new ConfigDescription("How wide a berth to give a trap, in metres. The game's own fire test uses zero, which stops an animal standing IN something and not against it - and a lox is wider than the point the AI steers.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
			ForgetAfter = cfg.Bind<float>("Advanced", "ForgetAfter", 300f, new ConfigDescription("Seconds after last seeing a follower before this mod stops considering it yours. This is what lets a follower be recovered after its part of the world has unloaded - which is the entire portal case - without remembering an animal you released an hour ago.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(30f, 3600f), Array.Empty<object>()));
			ServerSide = cfg.Bind<bool>("Server", "ServerSide", true, "On a DEDICATED server, run the catch-up from the server so that players do not have to install Heel at all. It works entirely from ZDOs - which is also why hazard avoidance does not come across: m_avoidFire and friends are fields on a live AI component, and a server never runs one. Ignored everywhere but a dedicated server; a host playing on their own machine gets the client path, which is strictly better because it can see the ground.");
			Verbose = cfg.Bind<bool>("Advanced", "Verbose", false, "Log every decision. Useful for a bug report, noisy otherwise.");
		}
	}
}
namespace Heel.Commands
{
	internal static class HeelCommand
	{
		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static ConsoleEvent <>9__0_0;

			internal void <Register>b__0_0(ConsoleEventArgs args)
			{
				args.Context.AddString(Report());
			}
		}

		internal static void Register()
		{
			//IL_0033: 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_0029: Expected O, but got Unknown
			object obj = <>c.<>9__0_0;
			if (obj == null)
			{
				ConsoleEvent val = delegate(ConsoleEventArgs args)
				{
					args.Context.AddString(Report());
				};
				<>c.<>9__0_0 = val;
				obj = (object)val;
			}
			new ConsoleCommand("heel", "what Heel is tracking and waiting on", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		}

		internal static string Report()
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine("Heel 2.1.0 - " + (HeelConfig.Enabled.Value ? "on" : "OFF"));
			if (Warden.Running)
			{
				foreach (string item in Warden.Report())
				{
					stringBuilder.AppendLine(item);
				}
				return stringBuilder.ToString();
			}
			stringBuilder.AppendLine($"  adrift beyond {HeelConfig.AdriftDistance.Value:0}m, " + $"with you inside {HeelConfig.WithYouDistance.Value:0}m, " + $"delay {HeelConfig.CatchUpDelay.Value:0}s");
			stringBuilder.AppendLine($"  following you: {Roster.Count}    " + $"clock running: {Recall.Waiting}    " + $"brought in: {Recall.Arrived}");
			stringBuilder.AppendLine($"  moves {Relocate.Moved}, refused {Relocate.Refused}");
			stringBuilder.AppendLine($"  minding {Minding.Guarded} parked tame(s): " + $"{Minding.Nudged} sent out of harm, " + $"{Minding.Refused} bad spot(s) refused");
			if (Roster.Count == 0)
			{
				stringBuilder.AppendLine("  Nothing is following you. This mod only ever moves a tame that is tamed, alive, and following YOU - a penned or parked animal is left exactly where it is.");
				return stringBuilder.ToString();
			}
			foreach (Enrolled item2 in Roster.All)
			{
				string value = (item2.Queued ? "queued" : ((item2.WaitingSince > 0f) ? "waiting" : "keeping up"));
				string text = ((item2.LastDistance >= float.MaxValue) ? "?" : $"{item2.LastDistance:0}m");
				stringBuilder.AppendLine("  " + Pad(item2.Name, 22) + " " + Pad(value, 11) + " " + text);
			}
			return stringBuilder.ToString();
		}

		internal static string Pad(string value, int width)
		{
			value = value ?? string.Empty;
			if (value.Length < width)
			{
				return value + new string(' ', width - value.Length);
			}
			return value;
		}
	}
}