Decompiled source of Husbandry v1.1.0

plugins\Husbandry.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 BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Husbandry.Commands;
using Husbandry.Config;
using Husbandry.Herd;
using Husbandry.Idle;
using Husbandry.Offline;
using Husbandry.Patches;
using Husbandry.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("Husbandry")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0")]
[assembly: AssemblyProduct("Husbandry")]
[assembly: AssemblyTitle("Husbandry")]
[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 Husbandry
{
	internal static class ModInfo
	{
		internal const string Guid = "dafini.husbandry";

		internal const string Name = "Husbandry";

		internal const string Version = "1.1.0";
	}
	[BepInPlugin("dafini.husbandry", "Husbandry", "1.1.0")]
	[BepInProcess("valheim.exe")]
	[BepInProcess("valheim_server.exe")]
	internal sealed class Plugin : BaseUnityPlugin
	{
		private Harmony _harmony;

		private bool _started;

		private void Awake()
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected O, but got Unknown
			Log.Source = ((BaseUnityPlugin)this).Logger;
			HusbandryConfig.Bind(((BaseUnityPlugin)this).Config);
			if (!HusbandryConfig.Enabled.Value)
			{
				Log.Info("Husbandry is switched off in config and is doing nothing.");
				return;
			}
			_harmony = new Harmony("dafini.husbandry");
			_harmony.PatchAll();
			HusbandryCommand.Register();
		}

		private void Update()
		{
			if (!_started)
			{
				ZNet instance = ZNet.instance;
				if ((Object)(object)instance == (Object)null)
				{
					return;
				}
				_started = true;
				if (Steward.ShouldRun(instance.IsServer(), HusbandryConfig.OfflineUpkeep.Value))
				{
					Steward.Begin();
				}
				else
				{
					Log.Info("Husbandry: this machine does not hold the world, so the offline upkeep is not running here. The throttle still is.");
				}
			}
			Steward.Tick();
			Shoving.Tick(Time.time);
		}

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

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

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

		internal static void Debug(string what)
		{
			if (HusbandryConfig.Verbose == null || HusbandryConfig.Verbose.Value)
			{
				ManualLogSource source = Source;
				if (source != null)
				{
					source.LogInfo((object)what);
				}
			}
		}
	}
}
namespace Husbandry.Patches
{
	[HarmonyPatch(typeof(Procreation), "Procreate")]
	internal static class BreedPatch
	{
		internal static int Skipped { get; private set; }

		internal static void ResetForTests()
		{
			Skipped = 0;
		}

		private static bool Prepare()
		{
			if (HusbandryConfig.Enabled == null || HusbandryConfig.Enabled.Value)
			{
				if (HusbandryConfig.CheapCensus != null)
				{
					return HusbandryConfig.CheapCensus.Value;
				}
				return true;
			}
			return false;
		}

		[HarmonyPrefix]
		private static bool Prefix(Procreation __instance)
		{
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)__instance == (Object)null)
			{
				return true;
			}
			ZNetView component = ((Component)__instance).GetComponent<ZNetView>();
			if ((Object)(object)component == (Object)null || !component.IsValid() || !component.IsOwner())
			{
				return true;
			}
			if (Quickening.Carrying(component.GetZDO().GetLong(ZDOVars.s_pregnant, 0L)))
			{
				return true;
			}
			int num = Cap.Effective(__instance.m_maxCreatures, HusbandryConfig.SoftCap.Value);
			if (num <= 0)
			{
				return true;
			}
			int prefab = component.GetZDO().GetPrefab();
			Census census = Muster.Current(Time.time);
			if (Cap.CertainlyUnder(census.Total(prefab), num))
			{
				return true;
			}
			if (!Cap.Reached(census.Within(prefab, ((Component)__instance).transform.position, __instance.m_totalCheckRange), num))
			{
				return true;
			}
			Skipped++;
			return false;
		}
	}
	[HarmonyPatch(typeof(Tameable), "Interact")]
	internal static class ShoveePatch
	{
		internal static bool Armed { get; private set; }

		private static bool Prepare()
		{
			if (HusbandryConfig.Enabled != null)
			{
				return HusbandryConfig.Enabled.Value;
			}
			return true;
		}

		internal static void ResetForTests()
		{
			Armed = false;
		}

		private static void Postfix()
		{
			Armed = true;
		}

		[HarmonyPrefix]
		private static bool Prefix(Tameable __instance, Humanoid user, bool hold, bool alt, ref bool __result)
		{
			if (!hold || alt)
			{
				return true;
			}
			Armed = true;
			Character val = (((Object)(object)__instance == (Object)null) ? null : ((Component)__instance).GetComponent<Character>());
			if ((Object)(object)val == (Object)null)
			{
				return true;
			}
			if (!Bumping.Mine(val.IsTamed()))
			{
				return true;
			}
			if (!Shoving.Toggle(val))
			{
				return true;
			}
			if ((Object)(object)user != (Object)null)
			{
				((Character)user).Message((MessageType)2, val.m_name + " " + Bumping.Plain(Shoving.IsSolid(val)), 0, (Sprite)null, false);
			}
			__result = true;
			return false;
		}
	}
	[HarmonyPatch(typeof(BaseAI), "UpdateAI")]
	internal static class TickPatch
	{
		private static bool Prepare()
		{
			if (HusbandryConfig.Enabled != null)
			{
				return HusbandryConfig.Enabled.Value;
			}
			return true;
		}

		[HarmonyPrefix]
		[HarmonyPriority(200)]
		private static bool Prefix(BaseAI __instance, ref float dt, ref bool __result)
		{
			if (!Drowse.Skip(__instance, ref dt))
			{
				return true;
			}
			__result = false;
			return false;
		}
	}
}
namespace Husbandry.Offline
{
	internal static class Clocks
	{
		internal const long PerSecond = 10000000L;

		internal static double Between(long then, long now)
		{
			return (double)(now - then) / 10000000.0;
		}

		internal static bool Elapsed(long stamp, long now, double span)
		{
			return Between(stamp, now) > span;
		}

		internal static long Ago(long now, double seconds)
		{
			return now - (long)(seconds * 10000000.0);
		}
	}
	internal sealed class Courtship
	{
		private readonly Dictionary<ZDOID, float> _last = new Dictionary<ZDOID, float>();

		internal int Count => _last.Count;

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

		internal bool Ready(ZDOID id, float now, float interval)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			if (!_last.TryGetValue(id, out var value))
			{
				return true;
			}
			return now - value >= interval;
		}

		internal void Courted(ZDOID id, float now)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			_last[id] = now;
		}

		internal int Forget(float now, float after)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: 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_0066: Unknown result type (might be due to invalid IL or missing references)
			List<ZDOID> list = new List<ZDOID>();
			foreach (KeyValuePair<ZDOID, float> item in _last)
			{
				if (now - item.Value > after)
				{
					list.Add(item.Key);
				}
			}
			foreach (ZDOID item2 in list)
			{
				_last.Remove(item2);
			}
			return list.Count;
		}
	}
	internal static class Mending
	{
		internal static float Restored(float health, float max, float perSecond, double seconds)
		{
			if (health <= 0f)
			{
				return health;
			}
			if (health >= max)
			{
				return health;
			}
			if (perSecond <= 0f)
			{
				return health;
			}
			if (seconds <= 0.0)
			{
				return health;
			}
			float num = health + (float)((double)perSecond * seconds);
			if (!(num > max))
			{
				return num;
			}
			return max;
		}

		internal static bool Worth(float before, float after)
		{
			return after - before > 0.01f;
		}
	}
	internal static class Pasture
	{
		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 reached)
		{
			return new Cursor
			{
				Prefab = at.Prefab,
				Index = reached,
				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 bool Wrapped(Cursor before, Cursor after)
		{
			return after.Passes > before.Passes;
		}

		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 enum Verdict
	{
		NotOurs,
		Expecting,
		Alone,
		Hungry,
		Capped,
		Courting,
		Conceives
	}
	internal static class Quickening
	{
		internal static Verdict Judge(bool tamed, bool pregnant, bool havePartner, bool hungry, bool underCap, int lovePoints, int required)
		{
			if (!tamed)
			{
				return Verdict.NotOurs;
			}
			if (pregnant)
			{
				return Verdict.Expecting;
			}
			if (!havePartner)
			{
				return Verdict.Alone;
			}
			if (hungry)
			{
				return Verdict.Hungry;
			}
			if (!underCap)
			{
				return Verdict.Capped;
			}
			if (required < 1)
			{
				required = 1;
			}
			if (lovePoints + 1 < required)
			{
				return Verdict.Courting;
			}
			return Verdict.Conceives;
		}

		internal static bool Carrying(long stamp)
		{
			return stamp != 0;
		}

		internal static bool Courts(Verdict verdict)
		{
			return verdict == Verdict.Courting;
		}

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

		internal static bool Company(int sameKindNearby)
		{
			return sameKindNearby >= 2;
		}
	}
	internal static class Steward
	{
		internal const float Interval = 1f;

		private static float _next;

		private static Pasture.Cursor _cursor;

		private static List<string> _tameables;

		private static readonly Kinds Species = new Kinds();

		private static readonly Courtship Courting = new Courtship();

		private static readonly Census Settled = new Census();

		private static readonly List<Census.Head> Building = new List<Census.Head>();

		private static readonly HashSet<long> Present = new HashSet<long>();

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

		internal static bool Running { get; private set; }

		internal static int Considered { get; private set; }

		internal static int Attended { get; private set; }

		internal static int Healed { get; private set; }

		internal static int Courted { get; private set; }

		internal static int Conceived { get; private set; }

		internal static int Capped { get; private set; }

		internal static void ResetForTests()
		{
			Running = false;
			Considered = (Attended = (Healed = (Courted = (Conceived = (Capped = 0)))));
			_next = 0f;
			_cursor = default(Pasture.Cursor);
			_tameables = null;
			Species.Clear();
			Courting.Clear();
			Settled.Clear();
			Building.Clear();
			Present.Clear();
		}

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

		internal static void Begin()
		{
			Running = true;
			Log.Info("Husbandry is looking after unattended tames on this machine.");
		}

		internal static void Tick()
		{
			if (!Running || Time.time < _next)
			{
				return;
			}
			_next = Time.time + 1f;
			ZDOMan instance = ZDOMan.instance;
			ZNet instance2 = ZNet.instance;
			if (instance != null && !((Object)(object)instance2 == (Object)null))
			{
				Gather(instance2);
				if (Prefabs())
				{
					Slice(instance);
					Courting.Forget(Time.time, HusbandryConfig.ForgetAfter.Value);
				}
			}
		}

		private static void Gather(ZNet net)
		{
			Present.Clear();
			Present.Add(ZDOMan.GetSessionID());
			List<ZNetPeer> peers = net.GetPeers();
			if (peers == null)
			{
				return;
			}
			foreach (ZNetPeer item in peers)
			{
				if (item != null)
				{
					Present.Add(item.m_uid);
				}
			}
		}

		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 = Pasture.Tameables(instance.m_prefabs);
			Species.Load(Kinds.Read(instance.m_prefabs));
			Log.Info("Husbandry found " + _tameables.Count + " tameable prefab(s), " + Species.Count + " with numbers read off the prefab.");
			return _tameables.Count > 0;
		}

		private static void Slice(ZDOMan zdoMan)
		{
			if (!Pasture.Valid(_cursor, _tameables.Count))
			{
				_cursor = default(Pasture.Cursor);
			}
			string text = _tameables[_cursor.Prefab];
			Batch.Clear();
			int index = _cursor.Index;
			bool allZDOsWithPrefabIterative = zdoMan.GetAllZDOsWithPrefabIterative(text, Batch, ref index);
			Species.Find(StringExtensionMethods.GetStableHashCode(text), out var kind);
			foreach (ZDO item in Batch)
			{
				Consider(item, kind);
			}
			Pasture.Cursor cursor = (allZDOsWithPrefabIterative ? Pasture.Next(_cursor, _tameables.Count) : Pasture.Continued(_cursor, index));
			if (Pasture.Wrapped(_cursor, cursor))
			{
				Settled.Take(Building, Time.time);
				Building.Clear();
			}
			_cursor = cursor;
		}

		private static void Consider(ZDO zdo, Kinds.Kind kind)
		{
			//IL_0014: 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_0042: 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_00bc: Unknown result type (might be due to invalid IL or missing references)
			if (zdo == null || !zdo.GetBool(ZDOVars.s_tamed, false))
			{
				return;
			}
			Vector3 position = zdo.GetPosition();
			Considered++;
			Building.Add(new Census.Head
			{
				Prefab = kind.Hash,
				Where = position,
				Tamed = true
			});
			if (!Untended.Yes(zdo.HasOwner(), Present.Contains(zdo.GetOwner())))
			{
				Attended++;
				return;
			}
			long ticks = ZNet.instance.GetTime().Ticks;
			if (HusbandryConfig.OfflineHealing.Value)
			{
				Mend(zdo, kind);
			}
			if (HusbandryConfig.OfflineBreeding.Value && kind.Breeds)
			{
				Breed(zdo, kind, position, ticks);
			}
		}

		private static void Mend(ZDO zdo, Kinds.Kind kind)
		{
			float num = zdo.GetFloat(ZDOVars.s_health, -1f);
			if (!(num < 0f))
			{
				float max = zdo.GetFloat(ZDOVars.s_maxHealth, kind.MaxHealth);
				float num2 = Mending.Restored(num, max, HusbandryConfig.HealPerSecond.Value, 1.0);
				if (Mending.Worth(num, num2))
				{
					zdo.Set(ZDOVars.s_health, num2);
					Healed++;
				}
			}
		}

		private static void Breed(ZDO zdo, Kinds.Kind kind, Vector3 where, long now)
		{
			//IL_0006: 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_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			if (!Courting.Ready(zdo.m_uid, Time.time, HusbandryConfig.CourtingInterval.Value))
			{
				return;
			}
			bool pregnant = Quickening.Carrying(zdo.GetLong(ZDOVars.s_pregnant, 0L));
			bool hungry = HusbandryConfig.OfflineHunger.Value && Clocks.Elapsed(zdo.GetLong(ZDOVars.s_tameLastFeeding, 0L), now, kind.FedDuration);
			bool havePartner = Quickening.Company(Settled.Within(kind.Hash, where, kind.PartnerCheckRange));
			int cap = Cap.Effective(kind.MaxCreatures, HusbandryConfig.SoftCap.Value);
			bool underCap = Cap.CertainlyUnder(Settled.Total(kind.Hash), cap) || !Cap.Reached(Settled.Within(kind.Hash, where, kind.TotalCheckRange), cap);
			Verdict verdict = Quickening.Judge(tamed: true, pregnant, havePartner, hungry, underCap, zdo.GetInt(ZDOVars.s_lovePoints, 0), kind.RequiredLovePoints);
			Courting.Courted(zdo.m_uid, Time.time);
			if (Quickening.Courts(verdict))
			{
				zdo.Set(ZDOVars.s_lovePoints, zdo.GetInt(ZDOVars.s_lovePoints, 0) + 1, false);
				Courted++;
			}
			else if (!Quickening.Conceives(verdict))
			{
				if (verdict == Verdict.Capped)
				{
					Capped++;
				}
			}
			else
			{
				zdo.Set(ZDOVars.s_pregnant, now);
				zdo.Set(ZDOVars.s_lovePoints, 0, false);
				Conceived++;
				Log.Debug("husbandry: an unattended " + kind.Name + " is expecting");
			}
		}

		internal static List<string> Report()
		{
			List<string> list = new List<string> { "--- husbandry (offline) ---" };
			if (!Running)
			{
				list.Add("  not running - this machine does not hold the world");
				return list;
			}
			list.Add("  watching   " + ((_tameables != null) ? _tameables.Count : 0) + " tameable prefab(s), cursor " + _cursor.Prefab + "/" + _cursor.Index + ", " + _cursor.Passes + " full pass(es)");
			list.Add("  census     " + Settled.Counted + " tame(s) across " + Settled.Species + " species");
			list.Add("  tames      " + Considered + " considered, " + Attended + " left to the client holding them");
			list.Add("  upkeep     " + Healed + " healed, " + Courted + " courting, " + Conceived + " expecting, " + Capped + " held at the cap");
			list.Add("  clocks     " + Courting.Count + " courting clock(s)");
			return list;
		}
	}
	internal static class Untended
	{
		internal static bool Yes(bool hasOwner, bool ownerIsPresent)
		{
			if (hasOwner)
			{
				return !ownerIsPresent;
			}
			return true;
		}
	}
}
namespace Husbandry.Idle
{
	internal static class Cadence
	{
		internal static int Every(float distance, float near, float far, int midSkip, int farSkip)
		{
			if (midSkip < 1)
			{
				midSkip = 1;
			}
			if (farSkip < 1)
			{
				farSkip = 1;
			}
			if (distance < 0f || float.IsNaN(distance))
			{
				return 1;
			}
			if (distance <= near)
			{
				return 1;
			}
			if (distance <= far)
			{
				return midSkip;
			}
			return farSkip;
		}

		internal static float FromCamera(Vector3 where, bool havePlayer, Vector3 player)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			if (!havePlayer)
			{
				return -1f;
			}
			return Vector3.Distance(where, player);
		}
	}
	internal static class Drowse
	{
		private sealed class Holder
		{
			internal Nap Nap;
		}

		private static readonly ConditionalWeakTable<BaseAI, Holder> Banks = new ConditionalWeakTable<BaseAI, Holder>();

		internal static int Skipped { get; private set; }

		internal static int Thought { get; private set; }

		internal static int Dozing { get; private set; }

		internal static void ResetForTests()
		{
			Skipped = 0;
			Thought = 0;
			Dozing = 0;
		}

		internal static bool Skip(BaseAI ai, ref float dt)
		{
			if ((Object)(object)ai == (Object)null)
			{
				return false;
			}
			if (HusbandryConfig.ThrottleIdle == null || !HusbandryConfig.ThrottleIdle.Value)
			{
				return false;
			}
			Holder orCreateValue = Banks.GetOrCreateValue(ai);
			if (!Dozy(ai))
			{
				dt = Nap.Wake(ref orCreateValue.Nap, dt);
				Thought++;
				return false;
			}
			Dozing++;
			int every = Cadence.Every(Distance(ai), HusbandryConfig.NearRange.Value, HusbandryConfig.FarRange.Value, HusbandryConfig.MidSkip.Value, HusbandryConfig.FarSkip.Value);
			if (Nap.Think(ref orCreateValue.Nap, every, dt, out var give))
			{
				dt = give;
				Thought++;
				return false;
			}
			Skipped++;
			return true;
		}

		private static bool Dozy(BaseAI ai)
		{
			Character component = ((Component)ai).GetComponent<Character>();
			ZNetView component2 = ((Component)ai).GetComponent<ZNetView>();
			bool ours = Beast.Ours((Object)(object)component != (Object)null && component.IsTamed(), (Object)(object)component2 != (Object)null && component2.IsValid(), (Object)(object)component2 != (Object)null && component2.IsOwner());
			MonsterAI val = (MonsterAI)(object)((ai is MonsterAI) ? ai : null);
			return Beast.Dozy(following: (Object)(object)val != (Object)null && (Object)(object)val.GetFollowTarget() != (Object)null, ours: ours, alerted: ai.IsAlerted(), hasTarget: (Object)(object)ai.GetTargetCreature() != (Object)null);
		}

		private static float Distance(BaseAI ai)
		{
			//IL_001b: 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)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return -1f;
			}
			return Cadence.FromCamera(((Component)ai).transform.position, havePlayer: true, ((Component)localPlayer).transform.position);
		}
	}
	internal struct Nap
	{
		internal float Banked;

		internal int Missed;

		internal static bool Think(ref Nap nap, int every, float dt, out float give)
		{
			if (every <= 1)
			{
				give = dt + nap.Banked;
				nap = default(Nap);
				return true;
			}
			nap.Banked += dt;
			nap.Missed++;
			if (nap.Missed < every)
			{
				give = 0f;
				return false;
			}
			give = nap.Banked;
			nap = default(Nap);
			return true;
		}

		internal static float Wake(ref Nap nap, float dt)
		{
			float result = dt + nap.Banked;
			nap = default(Nap);
			return result;
		}
	}
}
namespace Husbandry.Herd
{
	internal static class Beast
	{
		internal static bool Ours(bool tamed, bool hasNetView, bool owned)
		{
			return tamed && hasNetView && owned;
		}

		internal static bool Dozy(bool ours, bool alerted, bool hasTarget, bool following)
		{
			if (ours && !alerted && !hasTarget)
			{
				return !following;
			}
			return false;
		}
	}
	internal static class Bumping
	{
		internal const string Key = "husbandry_solid";

		internal static bool Phased(bool tamed, bool solid)
		{
			if (tamed)
			{
				return !solid;
			}
			return false;
		}

		internal static bool Toggled(bool tamed, bool solid)
		{
			if (!tamed)
			{
				return solid;
			}
			return !solid;
		}

		internal static bool Mine(bool tamed)
		{
			return tamed;
		}

		internal static string Says(bool solid)
		{
			if (!solid)
			{
				return "$msg_husbandry_phased";
			}
			return "$msg_husbandry_solid";
		}

		internal static string Plain(bool solid)
		{
			if (!solid)
			{
				return "will stay out of your way";
			}
			return "will get in your way again";
		}
	}
	internal static class Cap
	{
		internal static int Effective(int vanilla, int configured)
		{
			if (vanilla <= 0)
			{
				if (configured <= 0)
				{
					return 0;
				}
				return configured;
			}
			if (configured <= 0)
			{
				return vanilla;
			}
			if (vanilla >= configured)
			{
				return configured;
			}
			return vanilla;
		}

		internal static bool Reached(int nearby, int cap)
		{
			if (cap > 0)
			{
				return nearby >= cap;
			}
			return false;
		}

		internal static bool CertainlyUnder(int worldTotal, int cap)
		{
			if (cap > 0)
			{
				return worldTotal < cap;
			}
			return false;
		}
	}
	internal sealed class Census
	{
		internal struct Head
		{
			internal int Prefab;

			internal Vector3 Where;

			internal bool Tamed;
		}

		private readonly Dictionary<int, List<Vector3>> _by = new Dictionary<int, List<Vector3>>();

		internal float TakenAt { get; private set; } = float.NegativeInfinity;

		internal int Counted { get; private set; }

		internal int Species => _by.Count;

		internal void Clear()
		{
			_by.Clear();
			Counted = 0;
			TakenAt = float.NegativeInfinity;
		}

		internal bool Stale(float now, float interval)
		{
			return now - TakenAt >= interval;
		}

		internal void Take(IEnumerable<Head> heads, float now)
		{
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			_by.Clear();
			Counted = 0;
			TakenAt = now;
			if (heads == null)
			{
				return;
			}
			foreach (Head head in heads)
			{
				if (head.Tamed)
				{
					if (!_by.TryGetValue(head.Prefab, out var value))
					{
						value = new List<Vector3>();
						_by[head.Prefab] = value;
					}
					value.Add(head.Where);
					Counted++;
				}
			}
		}

		internal int Total(int prefab)
		{
			if (!_by.TryGetValue(prefab, out var value))
			{
				return 0;
			}
			return value.Count;
		}

		internal int Within(int prefab, Vector3 point, float radius)
		{
			//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_003d: 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_004c: 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)
			if (!_by.TryGetValue(prefab, out var value))
			{
				return 0;
			}
			if (radius <= 0f)
			{
				return value.Count;
			}
			float num = radius * radius;
			int num2 = 0;
			foreach (Vector3 item in value)
			{
				float num3 = item.x - point.x;
				float num4 = item.y - point.y;
				float num5 = item.z - point.z;
				if (num3 * num3 + num4 * num4 + num5 * num5 <= num)
				{
					num2++;
				}
			}
			return num2;
		}
	}
	internal sealed class Kinds
	{
		internal struct Kind
		{
			internal string Name;

			internal int Hash;

			internal bool Breeds;

			internal float FedDuration;

			internal float PregnancyDuration;

			internal int RequiredLovePoints;

			internal int MaxCreatures;

			internal float TotalCheckRange;

			internal float PartnerCheckRange;

			internal float MaxHealth;
		}

		private readonly Dictionary<int, Kind> _by = new Dictionary<int, Kind>();

		internal int Count => _by.Count;

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

		internal void Load(IEnumerable<Kind> kinds)
		{
			_by.Clear();
			if (kinds == null)
			{
				return;
			}
			foreach (Kind kind in kinds)
			{
				_by[kind.Hash] = kind;
			}
		}

		internal bool Find(int hash, out Kind kind)
		{
			return _by.TryGetValue(hash, out kind);
		}

		internal static List<Kind> Read(IEnumerable<GameObject> prefabs)
		{
			List<Kind> list = new List<Kind>();
			if (prefabs == null)
			{
				return list;
			}
			foreach (GameObject prefab in prefabs)
			{
				if (!((Object)(object)prefab == (Object)null))
				{
					Tameable component = prefab.GetComponent<Tameable>();
					if (!((Object)(object)component == (Object)null))
					{
						Procreation component2 = prefab.GetComponent<Procreation>();
						Character component3 = prefab.GetComponent<Character>();
						list.Add(new Kind
						{
							Name = ((Object)prefab).name,
							Hash = StringExtensionMethods.GetStableHashCode(((Object)prefab).name),
							Breeds = ((Object)(object)component2 != (Object)null),
							FedDuration = component.m_fedDuration,
							PregnancyDuration = (((Object)(object)component2 == (Object)null) ? 0f : component2.m_pregnancyDuration),
							RequiredLovePoints = ((!((Object)(object)component2 == (Object)null)) ? component2.m_requiredLovePoints : 0),
							MaxCreatures = ((!((Object)(object)component2 == (Object)null)) ? component2.m_maxCreatures : 0),
							TotalCheckRange = (((Object)(object)component2 == (Object)null) ? 0f : component2.m_totalCheckRange),
							PartnerCheckRange = (((Object)(object)component2 == (Object)null) ? 0f : component2.m_partnerCheckRange),
							MaxHealth = (((Object)(object)component3 == (Object)null) ? 0f : component3.m_health)
						});
					}
				}
			}
			return list;
		}
	}
	internal static class Muster
	{
		private static readonly Census Taken = new Census();

		private static readonly List<Census.Head> Heads = new List<Census.Head>();

		internal static int Counted => Taken.Counted;

		internal static int Species => Taken.Species;

		internal static int Takings { get; private set; }

		internal static void ResetForTests()
		{
			Taken.Clear();
			Heads.Clear();
			Takings = 0;
		}

		internal static Census Current(float now)
		{
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			if (!Taken.Stale(now, HusbandryConfig.CensusInterval.Value))
			{
				return Taken;
			}
			Heads.Clear();
			List<BaseAI> baseAIInstances = BaseAI.BaseAIInstances;
			if (baseAIInstances != null)
			{
				foreach (BaseAI item in baseAIInstances)
				{
					if ((Object)(object)item == (Object)null)
					{
						continue;
					}
					Character component = ((Component)item).GetComponent<Character>();
					if (!((Object)(object)component == (Object)null) && component.IsTamed())
					{
						ZNetView component2 = ((Component)item).GetComponent<ZNetView>();
						if (!((Object)(object)component2 == (Object)null) && component2.IsValid())
						{
							Heads.Add(new Census.Head
							{
								Prefab = component2.GetZDO().GetPrefab(),
								Where = ((Component)item).transform.position,
								Tamed = true
							});
						}
					}
				}
			}
			Taken.Take(Heads, now);
			Takings++;
			return Taken;
		}
	}
	internal static class Shoving
	{
		internal const float Every = 0.5f;

		private static float _next;

		private static readonly Dictionary<long, bool> Known = new Dictionary<long, bool>();

		internal static int Phased { get; private set; }

		internal static int Solid { get; private set; }

		internal static int Sweeps { get; private set; }

		internal static bool Wanted
		{
			get
			{
				if (HusbandryConfig.Shoving != null)
				{
					return !HusbandryConfig.Shoving.Value;
				}
				return true;
			}
		}

		internal static void ResetForTests()
		{
			Phased = 0;
			Solid = 0;
			Sweeps = 0;
			_next = 0f;
			Known.Clear();
		}

		internal static long Pair(int one, int two)
		{
			int num = ((one < two) ? one : two);
			int num2 = ((one < two) ? two : one);
			return ((long)num << 32) ^ (uint)num2;
		}

		internal static void Tick(float now)
		{
			if (now < _next)
			{
				return;
			}
			_next = now + 0.5f;
			if (!Wanted)
			{
				if (Known.Count > 0)
				{
					Restore();
				}
			}
			else
			{
				Sweep();
			}
		}

		private static void Restore()
		{
			List<Character> allCharacters = Character.GetAllCharacters();
			if (allCharacters == null)
			{
				Known.Clear();
				return;
			}
			List<Character> list = new List<Character>();
			List<Character> list2 = new List<Character>();
			foreach (Character item in allCharacters)
			{
				if (!((Object)(object)item == (Object)null))
				{
					if (item.IsPlayer())
					{
						list.Add(item);
					}
					else if (item.IsTamed())
					{
						list2.Add(item);
					}
				}
			}
			foreach (Character item2 in list2)
			{
				CapsuleCollider collider = item2.GetCollider();
				if ((Object)(object)collider == (Object)null)
				{
					continue;
				}
				foreach (Character item3 in list)
				{
					CapsuleCollider collider2 = item3.GetCollider();
					if (!((Object)(object)collider2 == (Object)null))
					{
						Set((Collider)(object)collider, (Collider)(object)collider2, phase: false);
					}
				}
			}
			Known.Clear();
		}

		private static void Sweep()
		{
			List<Character> allCharacters = Character.GetAllCharacters();
			if (allCharacters == null)
			{
				return;
			}
			Sweeps++;
			List<Character> list = new List<Character>();
			List<Character> list2 = new List<Character>();
			foreach (Character item in allCharacters)
			{
				if (!((Object)(object)item == (Object)null))
				{
					if (item.IsPlayer())
					{
						list.Add(item);
					}
					else if (item.IsTamed())
					{
						list2.Add(item);
					}
				}
			}
			if (list.Count == 0 || list2.Count == 0)
			{
				return;
			}
			foreach (Character item2 in list2)
			{
				CapsuleCollider collider = item2.GetCollider();
				if ((Object)(object)collider == (Object)null)
				{
					continue;
				}
				bool phase = Bumping.Phased(tamed: true, IsSolid(item2));
				foreach (Character item3 in list)
				{
					CapsuleCollider collider2 = item3.GetCollider();
					if (!((Object)(object)collider2 == (Object)null))
					{
						Set((Collider)(object)collider, (Collider)(object)collider2, phase);
					}
				}
			}
		}

		private static void Set(Collider beast, Collider person, bool phase)
		{
			long key = Pair(((Object)beast).GetInstanceID(), ((Object)person).GetInstanceID());
			if (!Known.TryGetValue(key, out var value) || value != phase)
			{
				Physics.IgnoreCollision(beast, person, phase);
				Known[key] = phase;
				if (phase)
				{
					Phased++;
				}
				else
				{
					Solid++;
				}
			}
		}

		internal static bool IsSolid(Character beast)
		{
			ZNetView val = (((Object)(object)beast == (Object)null) ? null : ((Component)beast).GetComponent<ZNetView>());
			if ((Object)(object)val == (Object)null || !val.IsValid())
			{
				return false;
			}
			return val.GetZDO().GetBool(StringExtensionMethods.GetStableHashCode("husbandry_solid"), false);
		}

		internal static bool Toggle(Character beast)
		{
			if ((Object)(object)beast == (Object)null || !Bumping.Mine(beast.IsTamed()))
			{
				return false;
			}
			ZNetView component = ((Component)beast).GetComponent<ZNetView>();
			if ((Object)(object)component == (Object)null || !component.IsValid())
			{
				return false;
			}
			component.ClaimOwnership();
			bool flag = Bumping.Toggled(tamed: true, IsSolid(beast));
			component.GetZDO().Set(StringExtensionMethods.GetStableHashCode("husbandry_solid"), flag);
			Known.Clear();
			Sweep();
			Log.Debug("Husbandry: " + beast.m_name + " " + Bumping.Plain(flag));
			return true;
		}
	}
}
namespace Husbandry.Config
{
	internal static class HusbandryConfig
	{
		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<bool> ThrottleIdle;

		internal static ConfigEntry<float> NearRange;

		internal static ConfigEntry<float> FarRange;

		internal static ConfigEntry<int> MidSkip;

		internal static ConfigEntry<int> FarSkip;

		internal static ConfigEntry<bool> CheapCensus;

		internal static ConfigEntry<float> CensusInterval;

		internal static ConfigEntry<int> SoftCap;

		internal static ConfigEntry<bool> Shoving;

		internal static ConfigEntry<bool> OfflineUpkeep;

		internal static ConfigEntry<bool> OfflineHealing;

		internal static ConfigEntry<float> HealPerSecond;

		internal static ConfigEntry<bool> OfflineBreeding;

		internal static ConfigEntry<float> CourtingInterval;

		internal static ConfigEntry<bool> OfflineHunger;

		internal static ConfigEntry<float> ForgetAfter;

		internal static ConfigEntry<bool> Verbose;

		internal static void Bind(ConfigFile cfg)
		{
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Expected O, but got Unknown
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Expected O, but got Unknown
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Expected O, but got Unknown
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Expected O, but got Unknown
			//IL_0145: Unknown result type (might be due to invalid IL or missing references)
			//IL_014f: Expected O, but got Unknown
			//IL_0175: Unknown result type (might be due to invalid IL or missing references)
			//IL_017f: Expected O, but got Unknown
			//IL_01fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0208: Expected O, but got Unknown
			//IL_0251: Unknown result type (might be due to invalid IL or missing references)
			//IL_025b: Expected O, but got Unknown
			//IL_02a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ae: Expected O, but got Unknown
			Enabled = cfg.Bind<bool>("General", "Enabled", true, "Master switch. Off means this mod does nothing at all.");
			ThrottleIdle = cfg.Bind<bool>("Throttle", "ThrottleIdle", true, "Let a tame that is standing around doing nothing think less often. It only ever applies to a TAMED creature that is not alerted, has nothing targeted and is following nobody - so it never touches anything hostile, anything in a fight, or anything at your heel. The time a skipped tick would have had is handed to the next one, so every timer inside the AI still lands where it would have.");
			NearRange = cfg.Bind<float>("Throttle", "NearRange", 24f, new ConfigDescription("Inside this many metres nothing is throttled at all, so the animals you are actually looking at behave exactly as they always did.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 128f), Array.Empty<object>()));
			FarRange = cfg.Bind<float>("Throttle", "FarRange", 48f, new ConfigDescription("Past this, the far band applies. Between the two, the mid band does.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 256f), Array.Empty<object>()));
			MidSkip = cfg.Bind<int>("Throttle", "MidSkip", 4, new ConfigDescription("An idle tame in the middle band thinks one physics tick in this many. At 4 that is about twelve times a second instead of fifty.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 25), Array.Empty<object>()));
			FarSkip = cfg.Bind<int>("Throttle", "FarSkip", 10, new ConfigDescription("The same, for idle tames past FarRange. This is the number that does the work in a big pen. Worst case it costs an animal a fifth of a second noticing something, and only ever an animal that was standing still, unalerted, with nothing targeted, a long way off.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 50), Array.Empty<object>()));
			CheapCensus = cfg.Bind<bool>("Breeding", "CheapCensus", true, "Replace the breeding system's population count with one shared count. Vanilla's runs per animal, walks EVERY ai in the world, compares object names as strings and does a GetComponent per match - four times per breeding round, per animal. That is the one cost that grows with the SQUARE of the herd. This changes no behaviour; it only stops asking the same question sixty times.");
			CensusInterval = cfg.Bind<float>("Breeding", "CensusInterval", 3f, new ConfigDescription("Seconds between shared population counts. Breeding runs on a ten-second invoke and pregnancies take minutes, so a count a few seconds old cannot change any decision that depends on it.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 60f), Array.Empty<object>()));
			SoftCap = cfg.Bind<int>("Breeding", "SoftCap", 0, new ConfigDescription("Stop breeding once this many of a species are within the prefab's own check range. 0 uses the game's own cap unchanged. This can only ever make breeding stop SOONER than vanilla, never later - and it never removes an animal, only births.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 200), Array.Empty<object>()));
			Shoving = cfg.Bind<bool>("Herd", "Shoving", false, "Whether your own tamed animals can push you around. Off by default, which is the point: one wolf is a companion and forty are a moving wall that puts you in the water. Hostile creatures are never affected - walking through something that wants to eat you is a cheat rather than a convenience. Hold E on any tame to make that one solid again; the choice is stored on the animal and survives a relog.");
			OfflineUpkeep = cfg.Bind<bool>("Offline", "OfflineUpkeep", true, "Look after tames in zones nobody has loaded. Runs only where the whole ZDO table lives - a dedicated server, a host, or single-player - and only on animals no connected machine is simulating.");
			OfflineHealing = cfg.Bind<bool>("Offline", "OfflineHealing", true, "Let an unattended tame slowly heal. It never raises one that is dying or dead, and never takes one above its own maximum.");
			HealPerSecond = cfg.Bind<float>("Offline", "HealPerSecond", 0.25f, new ConfigDescription("Health per real second for an unattended tame. A wolf left in a corner of the map comes back whole in a few minutes rather than never.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 20f), Array.Empty<object>()));
			OfflineBreeding = cfg.Bind<bool>("Offline", "OfflineBreeding", true, "Let unattended pairs get on with it, slowly. This does NOT create an animal: it sets the same pregnancy stamp the game sets, and the game itself gives birth - with the right level, the right tamed flag and its own cap check - on the first frame somebody loads the zone.");
			CourtingInterval = cfg.Bind<float>("Offline", "CourtingInterval", 180f, new ConfigDescription("Seconds between one step of courting and the next, for an unattended animal. Loaded, the game grants these every few seconds; this is the setting that makes away-from-home breeding a trickle rather than a population you come home to.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 3600f), Array.Empty<object>()));
			OfflineHunger = cfg.Bind<bool>("Offline", "OfflineHunger", true, "Honour hunger when deciding whether an unattended pair may breed. Worth knowing: unloaded tames ALREADY get hungry, in vanilla, because the feeding timer is a world-time stamp rather than a loaded timer. Turning this off does not feed them; it only stops hunger holding up offline breeding.");
			ForgetAfter = cfg.Bind<float>("Advanced", "ForgetAfter", 600f, new ConfigDescription("Seconds before this mod stops remembering an animal it has not seen. Only affects in-memory clocks; nothing is stored in your save.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(60f, 7200f), Array.Empty<object>()));
			Verbose = cfg.Bind<bool>("Advanced", "Verbose", false, "Log every decision. Useful for a bug report, noisy otherwise.");
		}
	}
}
namespace Husbandry.Commands
{
	internal static class HusbandryCommand
	{
		[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)
			{
				foreach (string item in Report())
				{
					args.Context.AddString(item);
				}
			}
		}

		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)
				{
					foreach (string item in Report())
					{
						args.Context.AddString(item);
					}
				};
				<>c.<>9__0_0 = val;
				obj = (object)val;
			}
			new ConsoleCommand("husbandry", "what Husbandry is doing", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		}

		internal static List<string> Report()
		{
			List<string> list = new List<string> { "--- husbandry 1.1.0 ---" };
			if (HusbandryConfig.ThrottleIdle.Value)
			{
				list.Add("  throttle   on: nothing inside " + HusbandryConfig.NearRange.Value + "m, one tick in " + HusbandryConfig.MidSkip.Value + " out to " + HusbandryConfig.FarRange.Value + "m, one in " + HusbandryConfig.FarSkip.Value + " beyond");
				list.Add("  ticks      " + Drowse.Thought + " thought, " + Drowse.Skipped + " skipped, " + Drowse.Dozing + " idle-tame frame(s) seen");
			}
			else
			{
				list.Add("  throttle   off");
			}
			list.Add("  census     " + Muster.Counted + " tame(s) across " + Muster.Species + " species, taken " + Muster.Takings + " time(s)");
			list.Add("  breeding   " + BreedPatch.Skipped + " round(s) skipped at the cap");
			list.AddRange(Steward.Report());
			return list;
		}
	}
}