Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Husbandry v1.1.0
plugins\Husbandry.dll
Decompiled a week agousing 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; } } }