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 IdleBeasts v1.0.2
plugins/IdleBeasts.dll
Decompiled 19 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyVersion("0.0.0.0")] namespace IdleBeasts; internal sealed class SameObject : IEqualityComparer<object> { internal static readonly SameObject Instance = new SameObject(); public new bool Equals(object a, object b) { return a == b; } public int GetHashCode(object o) { return RuntimeHelpers.GetHashCode(o); } } internal sealed class BeastState { public Character Character; public BaseAI AI; public MonsterAI Monster; public ZNetView View; public Animator Animator; public Renderer[] Renderers; public Rigidbody Body; public readonly List<object> Updaters = new List<object>(); public string Prefab; public int Id; public Mode Mode; public float QuietSince = -1f; public float StillSince = -1f; public float LastHurt = -999f; public Owed Owed; public bool Frozen; private bool _savedKeep; private bool _savedWarnings; public float NextAnim; public float AnimOwed; public bool Alive { get { if ((Object)(object)Character != (Object)null && (Object)(object)View != (Object)null) { return View.IsValid(); } return false; } } public bool Visible() { if (Renderers == null) { return false; } Renderer[] renderers = Renderers; foreach (Renderer val in renderers) { if ((Object)(object)val != (Object)null && val.isVisible) { return true; } } return false; } public void Freeze() { if (!Frozen && !((Object)(object)Animator == (Object)null)) { _savedKeep = Animator.keepAnimatorStateOnDisable; _savedWarnings = Animator.logWarnings; Animator.keepAnimatorStateOnDisable = true; Animator.logWarnings = false; ((Behaviour)Animator).enabled = false; Frozen = true; AnimOwed = 0f; } } public void Thaw() { if (Frozen) { Frozen = false; if (!((Object)(object)Animator == (Object)null)) { ((Behaviour)Animator).enabled = true; Animator.logWarnings = _savedWarnings; Animator.keepAnimatorStateOnDisable = _savedKeep; } } } } internal static class Beasts { internal const string RestKey = "anwo_ib_rest"; internal const float OwedCap = 2f; private static readonly Dictionary<object, BeastState> s_byUpdater = new Dictionary<object, BeastState>(SameObject.Instance); private static readonly Dictionary<Character, BeastState> s_byCharacter = new Dictionary<Character, BeastState>(SameObject.Instance); internal static readonly List<BeastState> All = new List<BeastState>(); internal static readonly RuleSettings Settings = new RuleSettings(); internal static int RestPhysics = 50; internal static int RestAI = 20; internal static int FarPhysics = 2; internal static int FarAI = 5; internal static int FarDetails = 2; private static readonly FieldRef<Character, Animator> CharacterAnimator = AccessTools.FieldRefAccess<Character, Animator>("m_animator"); private static readonly FieldRef<MonsterAI, ItemDrop> ConsumeTarget = AccessTools.FieldRefAccess<MonsterAI, ItemDrop>("m_consumeTarget"); private static float s_nextScan; private static readonly List<Vector3> s_players = new List<Vector3>(); private static readonly List<BeastState> s_wild = new List<BeastState>(); private static readonly List<BeastState> s_remove = new List<BeastState>(); private const float LookEvery = 0.25f; private static int s_decideIndex; internal static bool TryGet(object updater, out BeastState st) { return s_byUpdater.TryGetValue(updater, out st); } internal static BeastState Of(Character c) { if (!((Object)(object)c != (Object)null) || !s_byCharacter.TryGetValue(c, out var value)) { return null; } return value; } private static void Track(Character c) { if ((Object)(object)c == (Object)null || c.IsPlayer() || s_byCharacter.ContainsKey(c)) { return; } BaseAI component = ((Component)c).GetComponent<BaseAI>(); ZNetView component2 = ((Component)c).GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || (Object)(object)component2 == (Object)null) { return; } BeastState obj = new BeastState { Character = c, AI = component, Monster = (MonsterAI)(object)((component is MonsterAI) ? component : null), View = component2, Animator = CharacterAnimator.Invoke(c) }; Renderer[] componentsInChildren = (Renderer[])(object)((Component)c).GetComponentsInChildren<SkinnedMeshRenderer>(true); obj.Renderers = componentsInChildren; obj.Body = ((Component)c).GetComponent<Rigidbody>(); obj.Prefab = Utils.GetPrefabName(((Component)c).gameObject); obj.Id = ((Object)c).GetInstanceID(); BeastState beastState = obj; beastState.Updaters.Add(c); beastState.Updaters.Add(component); AddUpdater(beastState, ((Component)c).GetComponent<ZSyncTransform>()); AddUpdater(beastState, ((Component)c).GetComponent<ZSyncAnimation>()); CharacterAnimEvent[] componentsInChildren2 = ((Component)c).GetComponentsInChildren<CharacterAnimEvent>(true); foreach (CharacterAnimEvent u in componentsInChildren2) { AddUpdater(beastState, u); } FootStep[] componentsInChildren3 = ((Component)c).GetComponentsInChildren<FootStep>(true); foreach (FootStep u2 in componentsInChildren3) { AddUpdater(beastState, u2); } Tail[] componentsInChildren4 = ((Component)c).GetComponentsInChildren<Tail>(true); foreach (Tail u3 in componentsInChildren4) { AddUpdater(beastState, u3); } foreach (object updater in beastState.Updaters) { Register(updater, beastState); } s_byCharacter[c] = beastState; All.Add(beastState); Report.Seen(beastState); } internal static void Register(object updater, BeastState st) { if (updater != null) { s_byUpdater[updater] = st; } } private static void AddUpdater(BeastState st, object u) { Object val = (Object)((u is Object) ? u : null); if (val != null && val != (Object)null && !st.Updaters.Contains(u)) { st.Updaters.Add(u); } } private static void Untrack(BeastState st) { st.Thaw(); foreach (object updater in st.Updaters) { s_byUpdater.Remove(updater); } if (st.Character != null) { s_byCharacter.Remove(st.Character); } All.Remove(st); } internal static void Clear() { BeastState[] array = All.ToArray(); foreach (BeastState st in array) { WakeUp(st); Untrack(st); } s_byUpdater.Clear(); s_byCharacter.Clear(); All.Clear(); } internal static void Tick() { //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_0247: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsDedicated()) { return; } float time = Time.time; if (time >= s_nextScan) { s_nextScan = time + 0.25f; foreach (Character allCharacter in Character.GetAllCharacters()) { if ((Object)(object)allCharacter != (Object)null && !allCharacter.IsPlayer() && !s_byCharacter.ContainsKey(allCharacter)) { Track(allCharacter); } } s_players.Clear(); foreach (Player allPlayer in Player.GetAllPlayers()) { if ((Object)(object)allPlayer != (Object)null && !((Character)allPlayer).IsDead()) { s_players.Add(((Component)allPlayer).transform.position); } } s_wild.Clear(); s_remove.Clear(); foreach (BeastState item in All) { if (!item.Alive) { s_remove.Add(item); } else if (!item.Character.IsDead() && !item.Character.IsTamed()) { s_wild.Add(item); } } foreach (BeastState item2 in s_remove) { Untrack(item2); } } if (All.Count == 0) { return; } Player localPlayer = Player.m_localPlayer; Vector3 localPos = (((Object)(object)localPlayer != (Object)null) ? ((Component)localPlayer).transform.position : Vector3.zero); int num = Mathf.Clamp(Mathf.CeilToInt((float)All.Count * Time.deltaTime / 0.25f), 1, All.Count); for (int i = 0; i < num; i++) { if (s_decideIndex >= All.Count) { s_decideIndex = 0; } BeastState beastState = All[s_decideIndex++]; if (beastState.Alive) { try { Decide(beastState, time, localPlayer, localPos); } catch (Exception ex) { IdleBeastsPlugin.Log.LogDebug((object)("Deciding for " + beastState.Prefab + " failed: " + ex.Message)); } } } } private static void Decide(BeastState st, float now, Player local, Vector3 localPos) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) BeastRule r = IdleBeastsPlugin.RuleFor(st.Prefab, st.Character); bool flag = st.View.IsOwner(); Look l = Observe(st, flag, local, localPos, now); Mode next; if (flag) { bool flag2 = st.Mode == Mode.Resting || st.Mode == Mode.Settling; if (Rules.Quiet(l, r, Settings, flag2 ? 0f : 2f)) { if (st.QuietSince < 0f) { st.QuietSince = now; } } else { st.QuietSince = -1f; } if (l.Speed < 0.15f && !l.Moving) { if (st.StillSince < 0f) { st.StillSince = now; } } else { st.StillSince = -1f; } next = Rules.Next(st.Mode, l, r, Settings, (st.QuietSince < 0f) ? 0f : (now - st.QuietSince), (st.StillSince < 0f) ? 0f : (now - st.StillSince)); } else { ZDO zDO = st.View.GetZDO(); long num = zDO.GetLong("anwo_ib_rest", 0L); next = Rules.NextNotOwned(num != 0L && num == zDO.GetOwner(), l, r, Settings); } SetMode(st, next, flag); } private static Look Observe(BeastState st, bool owner, Player local, Vector3 localPos, float now) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_02cb: Unknown result type (might be due to invalid IL or missing references) //IL_02cc: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_02b3: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Unknown result type (might be due to invalid IL or missing references) //IL_024b: Unknown result type (might be due to invalid IL or missing references) //IL_0250: Unknown result type (might be due to invalid IL or missing references) Character character = st.Character; Vector3 position = ((Component)character).transform.position; Look look = new Look { Owner = owner, Tame = character.IsTamed(), Dead = character.IsDead(), Rider = character.HaveRider(), Alerted = st.AI.IsAlerted(), Attacking = character.InAttack(), Grounded = character.IsOnGround(), Swimming = character.IsSwimming(), Flying = character.IsFlying(), OnShip = ((Object)(object)character.GetStandingOnShip() != (Object)null || character.InNumShipVolumes > 0 || character.IsAttachedToShip()) }; Vector3 val = character.GetVelocity(); look.Speed = ((Vector3)(ref val)).magnitude; val = character.GetMoveDir(); look.Moving = ((Vector3)(ref val)).sqrMagnitude > 0.01f; look.SinceHurt = now - st.LastHurt; look.NearestEnemy = float.MaxValue; look.NearestPlayer = float.MaxValue; Look result = look; if ((Object)(object)st.Monster != (Object)null) { result.Following = (Object)(object)st.Monster.GetFollowTarget() != (Object)null; } if (owner) { result.Target = st.AI.HaveTarget(); result.Hunting = st.AI.HuntPlayer(); if ((Object)(object)st.Monster != (Object)null) { result.Eating = (Object)(object)ConsumeTarget.Invoke(st.Monster) != (Object)null; } foreach (Vector3 s_player in s_players) { result.NearestPlayer = Mathf.Min(result.NearestPlayer, Vector3.Distance(s_player, position)); } if (result.Tame && Settings.Rest) { float num = Settings.EnemyDistance + 2f + 1f; float num2 = num * num; foreach (BeastState item in s_wild) { if (item == st || (Object)(object)item.Character == (Object)null) { continue; } val = ((Component)item.Character).transform.position - position; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (!(sqrMagnitude > num2)) { float num3 = Mathf.Sqrt(sqrMagnitude); if (num3 < result.NearestEnemy && BaseAI.IsEnemy(character, item.Character)) { result.NearestEnemy = num3; } } } } if (Settings.OnlyInPens) { result.InPen = PenLink.InPen(position); } } else if ((Object)(object)local != (Object)null) { result.NearestPlayer = Vector3.Distance(localPos, position); } return result; } internal static void SetMode(BeastState st, Mode next, bool owner) { Mode mode = st.Mode; if (mode == next) { return; } st.Mode = next; if (next == Mode.Resting) { if (IdleBeastsPlugin.RestingLook.Value != RestLook.Normal) { st.Freeze(); } st.NextAnim = Time.time + 0.1f + (float)(st.Id & 7) * 0.03f; if (owner) { st.AI.StopMoving(); if ((Object)(object)st.Body != (Object)null && !st.Body.isKinematic) { st.Body.Sleep(); } st.View.GetZDO().Set("anwo_ib_rest", ZDOMan.GetSessionID()); } Report.Count(st, next); } else if (mode == Mode.Resting) { st.Thaw(); if (owner && st.View.IsValid() && st.View.GetZDO().GetLong("anwo_ib_rest", 0L) != 0L) { st.View.GetZDO().Set("anwo_ib_rest", 0L); } } if (next == Mode.Settling && owner) { st.AI.StopMoving(); } if (next == Mode.Far) { Report.Count(st, next); } } internal static void WakeUp(BeastState st) { if (st != null) { bool owner = (Object)(object)st.View != (Object)null && st.View.IsValid() && st.View.IsOwner(); st.QuietSince = -1f; if (st.Mode != Mode.Normal) { SetMode(st, Mode.Normal, owner); } } } internal static void WakeAll() { foreach (BeastState item in All) { WakeUp(item); } } internal static void AfterRestingTurn(BeastState st) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) if (st.Mode == Mode.Resting && !((Object)(object)st.View == (Object)null) && st.View.IsOwner()) { Vector3 moveDir = st.Character.GetMoveDir(); if (((Vector3)(ref moveDir)).sqrMagnitude > 0.01f || st.AI.HaveTarget() || st.AI.IsAlerted() || ((Object)(object)st.Monster != (Object)null && ((Object)(object)st.Monster.GetFollowTarget() != (Object)null || (Object)(object)ConsumeTarget.Invoke(st.Monster) != (Object)null))) { st.QuietSince = -1f; SetMode(st, Mode.Normal, owner: true); } } } internal static bool HoldStill(BaseAI ai) { if (!s_byUpdater.TryGetValue(ai, out var value)) { return false; } if (value.Mode != Mode.Settling) { return value.Mode == Mode.Resting; } return true; } internal static void Hurt(Character c) { BeastState beastState = Of(c); if (beastState != null) { beastState.LastHurt = Time.time; beastState.QuietSince = -1f; if (beastState.Mode == Mode.Resting || beastState.Mode == Mode.Settling) { SetMode(beastState, Mode.Normal, beastState.View.IsOwner()); } } } internal static void AnimateResting() { if (IdleBeastsPlugin.RestingLook.Value != RestLook.Slow) { return; } float time = Time.time; float num = 1f / Mathf.Max(1f, IdleBeastsPlugin.SlowRate.Value); foreach (BeastState item in All) { if (!item.Frozen || (Object)(object)item.Animator == (Object)null) { continue; } item.AnimOwed += Time.deltaTime; if (time < item.NextAnim) { continue; } item.NextAnim = time + num; if (!item.Visible()) { item.AnimOwed = 0f; continue; } float num2 = Mathf.Min(item.AnimOwed, 0.5f); item.AnimOwed = 0f; long t = Stats.Now(); try { item.Animator.Update(num2); } catch (Exception ex) { IdleBeastsPlugin.Log.LogDebug((object)("Slow animation failed on " + item.Prefab + ": " + ex.Message)); } Stats.AddTime(Stats.Bucket.RestAnimation, t); } } internal static void ApplyLook() { foreach (BeastState item in All) { if (item.Mode == Mode.Resting) { if (IdleBeastsPlugin.RestingLook.Value == RestLook.Normal) { item.Thaw(); } else { item.Freeze(); } } } } } internal static class PenLink { private static bool s_looked; private static MethodInfo s_find; internal static bool InPen(Vector3 pos) { //IL_0089: Unknown result type (might be due to invalid IL or missing references) if (!s_looked) { s_looked = true; Type type = AccessTools.TypeByName("PenPosts.PenRegistry"); s_find = ((type != null) ? AccessTools.Method(type, "FindPen", new Type[1] { typeof(Vector3) }, (Type[])null) : null); IdleBeastsPlugin.Log.LogInfo((object)((s_find != null) ? "PenPosts found: 'Only in pens' uses its pens." : "PenPosts not found: 'Only in pens' has no pens to go by, so no animal rests.")); } if (s_find == null) { return false; } try { return s_find.Invoke(null, new object[1] { pos }) != null; } catch { return false; } } } internal static class Loops { internal enum Kind { None, Body, SyncFixed, AnimSync, AnimEventFixed, AI, FootStep, SyncLate, AnimEventLate, Tail } internal static Func<bool> IsActive = () => IdleBeastsPlugin.Enabled.Value || IdleBeastsPlugin.ShowStats.Value || Report.Measuring; internal static Func<long> FixedStepNow = () => (long)Math.Round(Time.fixedTime / Math.Max(0.001f, Time.fixedDeltaTime)); internal static Func<int> FrameNow = () => Time.frameCount; internal static Action<Exception> LogError = delegate(Exception e) { Debug.LogException(e); }; private static long s_aiStep; private static bool Active => IsActive(); private static Kind FixedKind(string scope) { return scope switch { "MonoUpdaters.FixedUpdate.Character" => Kind.Body, "MonoUpdaters.FixedUpdate.ZSyncTransform" => Kind.SyncFixed, "MonoUpdaters.FixedUpdate.ZSyncAnimation" => Kind.AnimSync, "MonoUpdaters.FixedUpdate.CharacterAnimEvent" => Kind.AnimEventFixed, _ => Kind.None, }; } private static Kind LateKind(string scope) { return scope switch { "MonoUpdaters.LateUpdate.ZSyncTransform" => Kind.SyncLate, "MonoUpdaters.LateUpdate.CharacterAnimEvent" => Kind.AnimEventLate, "MonoUpdaters.LateUpdate.Tail" => Kind.Tail, _ => Kind.None, }; } [HarmonyPrefix] [HarmonyPatch(typeof(MonoUpdatersExtra), "CustomFixedUpdate")] private static bool FixedLoop(List<IMonoUpdater> container, List<IMonoUpdater> source, string profileScope, float deltaTime) { Kind kind = FixedKind(profileScope); if (kind == Kind.None || !Active) { return true; } long t = Stats.Now(); long step = FixedStepNow(); container.AddRange(source); try { for (int i = 0; i < container.Count; i++) { IMonoUpdater val = container[i]; if (Beasts.TryGet(val, out var st)) { Stats.Creature(kind); if (SkipFixed(st, kind, step)) { Stats.Skipped(kind); continue; } } try { val.CustomFixedUpdate(deltaTime); } catch (Exception obj) { LogError(obj); } } } finally { container.Clear(); Stats.AddTime(Stats.BucketOf(kind), t); } return false; } private static bool SkipFixed(BeastState st, Kind kind, long step) { switch (st.Mode) { case Mode.Resting: if (kind == Kind.Body || kind == Kind.SyncFixed) { return !Schedule.Due(step, Schedule.Phase(st.Id, Beasts.RestPhysics), Beasts.RestPhysics); } return true; case Mode.Far: return !Schedule.Due(step, Schedule.Phase(st.Id, Beasts.FarPhysics), Beasts.FarPhysics); default: return false; } } [HarmonyPrefix] [HarmonyPatch(typeof(MonoUpdatersExtra), "UpdateAI")] private static bool AILoop(List<IUpdateAI> container, List<IUpdateAI> source, string profileScope, float deltaTime) { if (!Active) { return true; } long t = Stats.Now(); s_aiStep++; container.AddRange(source); try { for (int i = 0; i < container.Count; i++) { IUpdateAI val = container[i]; float num = deltaTime; BeastState st = null; if (Beasts.TryGet(val, out st)) { Stats.Creature(Kind.AI); int num2 = ((st.Mode == Mode.Resting) ? Beasts.RestAI : ((st.Mode != Mode.Far) ? 1 : Beasts.FarAI)); st.Owed.Add(deltaTime); if (num2 > 1 && !Schedule.Due(s_aiStep, Schedule.Phase(st.Id, num2), num2)) { Stats.Skipped(Kind.AI); continue; } num = st.Owed.Take(2f); } try { val.UpdateAI(num); } catch (Exception obj) { LogError(obj); } if (st != null && st.Mode == Mode.Resting) { try { Beasts.AfterRestingTurn(st); } catch (Exception ex) { IdleBeastsPlugin.Log.LogDebug((object)("Waking check failed: " + ex.Message)); } } } } finally { container.Clear(); Stats.AddTime(Stats.Bucket.AI, t); } return false; } [HarmonyPrefix] [HarmonyPatch(typeof(MonoUpdatersExtra), "CustomUpdate")] private static bool UpdateLoop(List<IMonoUpdater> container, List<IMonoUpdater> source, string profileScope, float deltaTime, float time) { if (profileScope != "MonoUpdaters.Update.FootStep" || !Active) { return true; } long t = Stats.Now(); int frame = FrameNow(); container.AddRange(source); try { for (int i = 0; i < container.Count; i++) { IMonoUpdater val = container[i]; float dt = deltaTime; if (Beasts.TryGet(val, out var st)) { Stats.Creature(Kind.FootStep); if (SkipFrame(st, frame, ref dt)) { Stats.Skipped(Kind.FootStep); continue; } } try { val.CustomUpdate(dt, time); } catch (Exception obj) { LogError(obj); } } } finally { container.Clear(); Stats.AddTime(Stats.Bucket.Details, t); } return false; } [HarmonyPrefix] [HarmonyPatch(typeof(MonoUpdatersExtra), "CustomLateUpdate")] private static bool LateLoop(List<IMonoUpdater> container, List<IMonoUpdater> source, string profileScope, float deltaTime) { Kind kind = LateKind(profileScope); if (kind == Kind.None || !Active) { return true; } long t = Stats.Now(); int frame = FrameNow(); container.AddRange(source); try { for (int i = 0; i < container.Count; i++) { IMonoUpdater val = container[i]; float dt = deltaTime; if (Beasts.TryGet(val, out var st)) { Stats.Creature(kind); if (SkipFrame(st, frame, ref dt)) { Stats.Skipped(kind); continue; } } try { val.CustomLateUpdate(dt); } catch (Exception obj) { LogError(obj); } } } finally { container.Clear(); Stats.AddTime((kind == Kind.SyncLate) ? Stats.Bucket.Sync : Stats.Bucket.Details, t); } return false; } private static bool SkipFrame(BeastState st, int frame, ref float dt) { switch (st.Mode) { case Mode.Resting: return true; case Mode.Far: { int farDetails = Beasts.FarDetails; if (farDetails <= 1) { return false; } if (!Schedule.Due(frame, Schedule.Phase(st.Id, farDetails), farDetails)) { return true; } dt *= farDetails; return false; } default: return false; } } [HarmonyPrefix] [HarmonyPatch(typeof(BaseAI), "IdleMovement")] private static bool IdleMovement(BaseAI __instance) { return Wander(__instance); } [HarmonyPrefix] [HarmonyPatch(typeof(BaseAI), "RandomMovement")] private static bool RandomMovement(BaseAI __instance) { return Wander(__instance); } [HarmonyPrefix] [HarmonyPatch(typeof(BaseAI), "RandomMovementArroundPoint")] private static bool RandomMovementAroundPoint(BaseAI __instance) { return Wander(__instance); } private static bool Wander(BaseAI ai) { try { if (!IdleBeastsPlugin.Enabled.Value || !Beasts.HoldStill(ai)) { return true; } ai.StopMoving(); return false; } catch { return true; } } [HarmonyPrefix] [HarmonyPatch(typeof(Character), "RPC_Damage")] private static void Damaged(Character __instance) { try { Beasts.Hurt(__instance); } catch (Exception ex) { IdleBeastsPlugin.Log.LogDebug((object)("Waking on damage failed: " + ex.Message)); } } [HarmonyPostfix] [HarmonyPatch(typeof(ZNetScene), "Awake")] private static void SceneReady(ZNetScene __instance) { try { IdleBeastsPlugin.BindCreatures(__instance); } catch (Exception ex) { IdleBeastsPlugin.Log.LogWarning((object)("Adding creature settings failed: " + ex.Message)); } } [HarmonyPostfix] [HarmonyPatch(typeof(ZNetScene), "OnDestroy")] private static void SceneGone() { Beasts.Clear(); } } [BepInPlugin("anwo.IdleBeasts", "IdleBeasts", "1.0.2")] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public class IdleBeastsPlugin : BaseUnityPlugin { public const string Guid = "anwo.IdleBeasts"; public const string ModName = "IdleBeasts"; public const string Version = "1.0.2"; internal static ManualLogSource Log; internal static IdleBeastsPlugin Instance; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<bool> TamedOnly; internal static ConfigEntry<bool> RestWhenIdle; internal static ConfigEntry<float> WakeDistance; internal static ConfigEntry<float> EnemyDistance; internal static ConfigEntry<float> IdleSeconds; internal static ConfigEntry<bool> OnlyInPens; internal static ConfigEntry<float> RestTurn; internal static ConfigEntry<bool> SlowFar; internal static ConfigEntry<float> FarDistance; internal static ConfigEntry<float> FarAIEvery; internal static ConfigEntry<int> FarPhysicsEvery; internal static ConfigEntry<int> FarDetailsEvery; internal static ConfigEntry<RestLook> RestingLook; internal static ConfigEntry<float> SlowRate; internal static ConfigEntry<bool> ShowStats; private const string CreatureSection = "5 - Creatures"; private static readonly Dictionary<string, ConfigEntry<BeastRule>> s_rules = new Dictionary<string, ConfigEntry<BeastRule>>(StringComparer.Ordinal); private static readonly HashSet<string> s_bosses = new HashSet<string>(StringComparer.Ordinal); private Harmony _harmony; private GUIStyle _style; private GUIStyle _shadow; private ConfigEntry<T> Synced<T>(string section, string key, T value, string description, AcceptableValueBase range = null) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown ConfigurationManagerAttributes val = new ConfigurationManagerAttributes { IsAdminOnly = true }; return ((BaseUnityPlugin)this).Config.Bind<T>(section, key, value, new ConfigDescription(description, range, new object[1] { val })); } private void Awake() { //IL_0243: Unknown result type (might be due to invalid IL or missing references) //IL_024d: Expected O, but got Unknown //IL_02e0: Unknown result type (might be due to invalid IL or missing references) //IL_02ea: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Instance = this; Enabled = Synced("1 - General", "Enabled", value: true, "Rest idle tame animals and slow down creatures that are far from every player (only tamed ones while Tamed only is on). Turn off to compare (also live; 'idlebeasts test' in the console does it for you)."); TamedOnly = Synced("1 - General", "Tamed only", value: true, "Only your tamed animals are touched: idle tame animals rest, and tame animals far from every player are slowed. Wild creatures always run at full speed, wherever they are. Turn off to also slow wild creatures that are far from everyone: that saves more time when many creatures are loaded, but wild ones then think and move less often while nobody is near them. Changes right away."); RestWhenIdle = Synced("2 - Resting animals", "Rest when idle", value: true, "Tame animals that have nothing to do and nobody near stop wandering and rest: they stand still, and their AI and body update about once a second instead of 20 to 50 times. They wake at once when a player comes near, an enemy shows up, they're hurt, ridden or told to follow, or they go for food."); WakeDistance = Synced("2 - Resting animals", "Wake distance", 20f, "Animals only rest while every player is further away than this (metres), and a player coming this close wakes them. It rests again only when everyone is a little further away. Raise it to keep animals awake further out, lower it to let them rest while you're nearer.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(3f, 60f)); EnemyDistance = Synced("2 - Resting animals", "Enemy distance", 30f, "An enemy this close (metres) wakes resting animals.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 100f)); IdleSeconds = Synced("2 - Resting animals", "Idle seconds", 5f, "How long an animal must have nothing to do before it settles down.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 120f)); OnlyInPens = Synced("2 - Resting animals", "Only in pens", value: false, "Only animals inside a PenPosts pen rest (needs PenPosts)."); RestTurn = Synced("2 - Resting animals", "Resting turn every", 1f, "Seconds between a resting animal's turns, when it checks for food and danger and its body is updated.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 5f)); SlowFar = Synced("3 - Far creatures", "Slow far creatures", value: true, "Creatures further than Far distance from every player, and not fighting, hunting or following anyone, think and move with fewer updates. Nobody is close enough to see the difference. While Tamed only is on (the default), only tamed creatures are slowed; wild ones always run at full speed."); FarDistance = Synced("3 - Far creatures", "Far distance", 50f, "Metres from the nearest player.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(20f, 200f)); FarAIEvery = Synced("3 - Far creatures", "Far AI every", 0.25f, "Seconds between a far creature's AI turns (normal: 0.05).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 2f)); FarPhysicsEvery = Synced("3 - Far creatures", "Far physics every Nth step", 2, "A far creature's body is updated on every Nth physics step (1 = every step).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 4)); FarDetailsEvery = Synced("3 - Far creatures", "Far details every Nth frame", 2, "Footsteps, foot placement, tails and position smoothing of far creatures every Nth frame (1 = every frame).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 4)); RestingLook = ((BaseUnityPlugin)this).Config.Bind<RestLook>("4 - Look", "Resting animation", RestLook.Frozen, "How resting animals look on your screen. Frozen: they stand still, no animation (cheapest). Slow: a few animation updates a second while in view. Normal: animation as usual (saves less)."); SlowRate = ((BaseUnityPlugin)this).Config.Bind<float>("4 - Look", "Slow animation rate", 5f, new ConfigDescription("Animation updates a second for resting animals when Resting animation is Slow.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 20f), Array.Empty<object>())); ShowStats = ((BaseUnityPlugin)this).Config.Bind<bool>("6 - Diagnostics", "Show stats", false, "Show how many creatures rest or are slowed, and how much time the game spends on creature updates, in the bottom right corner of the screen."); BindKnownCreatures(); ApplySettings(); ((BaseUnityPlugin)this).Config.SettingChanged += delegate { ApplySettings(); }; SynchronizationManager.OnConfigurationSynchronized += delegate { ApplySettings(); }; CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new IdleBeastsCommand()); _harmony = new Harmony("anwo.IdleBeasts"); _harmony.PatchAll(typeof(Loops)); if (Chainloader.PluginInfos.Keys.Any((string k) => k.IndexOf("PerformanceOverhaul", StringComparison.OrdinalIgnoreCase) >= 0)) { Log.LogWarning((object)"Valheim Performance Overhaul is installed too: both change how creatures are updated. Use one of them."); } Log.LogInfo((object)"IdleBeasts 1.0.2 loaded."); } internal static void ApplySettings() { RuleSettings settings = Beasts.Settings; settings.Enabled = Enabled.Value && !Report.ForcedOff; settings.TamedOnly = TamedOnly.Value; settings.Rest = RestWhenIdle.Value; settings.WakeDistance = WakeDistance.Value; settings.EnemyDistance = EnemyDistance.Value; settings.IdleSeconds = IdleSeconds.Value; settings.OnlyInPens = OnlyInPens.Value; settings.Far = SlowFar.Value; settings.FarDistance = FarDistance.Value; Beasts.RestPhysics = Schedule.Period(stepSeconds: (Time.fixedDeltaTime > 0f) ? Time.fixedDeltaTime : 0.02f, seconds: RestTurn.Value); Beasts.RestAI = Schedule.Period(RestTurn.Value, 0.05f); Beasts.FarAI = Schedule.Period(FarAIEvery.Value, 0.05f); Beasts.FarPhysics = Mathf.Clamp(FarPhysicsEvery.Value, 1, 4); Beasts.FarDetails = Mathf.Clamp(FarDetailsEvery.Value, 1, 4); if (!settings.Enabled) { Beasts.WakeAll(); } else { Beasts.ApplyLook(); } } internal static BeastRule RuleFor(string prefab, Character c) { if (s_rules.TryGetValue(prefab, out var value) || s_rules.TryGetValue(Keys.Clean(prefab), out value)) { return value.Value; } if (s_bosses.Contains(prefab) || ((Object)(object)c != (Object)null && c.IsBoss())) { return BeastRule.Never; } return BeastRule.Default; } private static string RuleHelp(string name, bool boss) { return (string.IsNullOrEmpty(name) ? "" : (name + ". ")) + "Default: rests when tame and idle, slowed when far away (wild ones only when Tamed only is off). Never: always full speed. RestOnly: only resting. FarOnly: only slowed when far (same rule for wild ones)." + (boss ? " Bosses are Never by default." : ""); } private void BindKnownCreatures() { try { if (!(AccessTools.Property(typeof(ConfigFile), "OrphanedEntries")?.GetValue(((BaseUnityPlugin)this).Config) is Dictionary<ConfigDefinition, string> dictionary)) { return; } foreach (ConfigDefinition item in dictionary.Keys.Where((ConfigDefinition d) => d.Section == "5 - Creatures").ToList()) { if (!s_rules.ContainsKey(item.Key)) { s_rules[item.Key] = Synced("5 - Creatures", item.Key, BeastRule.Default, RuleHelp("", boss: false)); } } } catch (Exception ex) { Log.LogDebug((object)("Reading earlier creature settings failed: " + ex.Message)); } } internal static void BindCreatures(ZNetScene scene) { if ((Object)(object)scene == (Object)null || scene.m_prefabs == null || (Object)(object)Instance == (Object)null) { return; } int num = 0; foreach (GameObject prefab in scene.m_prefabs) { if ((Object)(object)prefab == (Object)null) { continue; } Character component = prefab.GetComponent<Character>(); if ((Object)(object)component == (Object)null || (Object)(object)prefab.GetComponent<BaseAI>() == (Object)null || component is Player) { continue; } string key = Keys.Clean(((Object)prefab).name); bool boss = component.m_boss; if (boss) { s_bosses.Add(((Object)prefab).name); } if (s_rules.ContainsKey(key)) { continue; } string text = component.m_name; try { if (Localization.instance != null) { text = Localization.instance.Localize(text); } } catch { } s_rules[key] = Instance.Synced("5 - Creatures", key, boss ? BeastRule.Never : BeastRule.Default, RuleHelp(text, boss)); num++; } if (num > 0) { Log.LogInfo((object)string.Format("Creature settings: {0} creatures ({1} new) under '{2}'.", s_rules.Count, num, "5 - Creatures")); } } private void Update() { try { Report.Update(); if (Beasts.Settings.Enabled || Report.Measuring) { Beasts.Tick(); } else if (Beasts.All.Count > 0) { Beasts.Clear(); } Beasts.AnimateResting(); Stats.Frame(); } catch (Exception ex) { Log.LogDebug((object)("Update failed: " + ex.Message)); } } private void OnGUI() { //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Expected O, but got Unknown //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_0146: 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_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: 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_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Expected O, but got Unknown //IL_00b0: Unknown result type (might be due to invalid IL or missing references) if (ShowStats.Value && !((Object)(object)Player.m_localPlayer == (Object)null) && !string.IsNullOrEmpty(Stats.Line)) { if (_style == null) { _style = new GUIStyle(GUI.skin.label) { fontSize = 15, wordWrap = false, alignment = (TextAnchor)8 }; _style.normal.textColor = new Color(1f, 0.95f, 0.8f); _shadow = new GUIStyle(_style); _shadow.normal.textColor = new Color(0f, 0f, 0f, 0.85f); } Vector2 val = _style.CalcSize(new GUIContent(Stats.Line)); float num = Mathf.Min(val.x + 4f, (float)Screen.width - 40f); float num2 = val.y + 4f; Rect val2 = default(Rect); ((Rect)(ref val2))..ctor((float)Screen.width - num - 20f, (float)Screen.height - num2 - 20f, num, num2); GUI.Label(new Rect(((Rect)(ref val2)).x + 1f, ((Rect)(ref val2)).y + 1f, ((Rect)(ref val2)).width, ((Rect)(ref val2)).height), Stats.Line, _shadow); GUI.Label(val2, Stats.Line, _style); } } private void OnDestroy() { try { Beasts.Clear(); } catch { } Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } internal sealed class IdleBeastsCommand : ConsoleCommand { public override string Name => "idlebeasts"; public override string Help => "IdleBeasts: status. 'test' compares creature update time with the mod off and on, 'report' writes the creature report, 'on'/'off' turns it on or off, 'wake' wakes every resting animal."; public override List<string> CommandOptionList() { return new List<string> { "test", "report", "on", "off", "wake" }; } public override void Run(string[] args) { switch ((args.Length != 0) ? args[0].ToLowerInvariant() : "") { case "test": Report.StartTest(); break; case "report": Report.Write(); break; case "on": IdleBeastsPlugin.Enabled.Value = true; Report.Say("IdleBeasts on."); break; case "off": IdleBeastsPlugin.Enabled.Value = false; Report.Say("IdleBeasts off."); break; case "wake": Beasts.WakeAll(); Report.Say("IdleBeasts: every animal woken."); break; default: Report.Say(string.IsNullOrEmpty(Stats.Line) ? "IdleBeasts: no numbers yet." : Stats.Line.Replace("\n", " / ")); Report.Say(Report.LastTest); break; } } } internal static class Report { private sealed class KindInfo { public string Name; public string Culling = "?"; public string UpdateMode = "?"; public int Meshes; public int Bones; public string AI; public int Rested; public int Slowed; } private enum Phase { None, OffWarm, Off, OnWarm, On } private static readonly Dictionary<string, KindInfo> s_kinds = new Dictionary<string, KindInfo>(); private static Phase s_phase; private static float s_phaseEnd; private static float s_sumMs; private static float s_sumFps; private static int s_samples; private static string s_offResult = ""; private static string s_onResult = ""; internal static string LastTest = "No test run yet (type 'idlebeasts test' in the console)."; internal static string PathName => Path.Combine(Paths.ConfigPath, "anwo.IdleBeasts.report.txt"); internal static bool Measuring => s_phase != Phase.None; internal static bool ForcedOff { get { if (s_phase != Phase.OffWarm) { return s_phase == Phase.Off; } return true; } } internal static void Seen(BeastState st) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: 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_007e: Unknown result type (might be due to invalid IL or missing references) if (s_kinds.ContainsKey(st.Prefab)) { return; } KindInfo kindInfo = new KindInfo { Name = Localize(st.Character.m_name), AI = ((object)st.AI).GetType().Name }; if ((Object)(object)st.Animator != (Object)null) { kindInfo.Culling = ((object)st.Animator.cullingMode/*cast due to .constrained prefix*/).ToString(); kindInfo.UpdateMode = ((object)st.Animator.updateMode/*cast due to .constrained prefix*/).ToString(); } if (st.Renderers != null) { kindInfo.Meshes = st.Renderers.Length; Renderer[] renderers = st.Renderers; foreach (Renderer obj in renderers) { SkinnedMeshRenderer val = (SkinnedMeshRenderer)(object)((obj is SkinnedMeshRenderer) ? obj : null); if (val != null && val.bones != null) { kindInfo.Bones = Math.Max(kindInfo.Bones, val.bones.Length); } } } s_kinds[st.Prefab] = kindInfo; } internal static void Count(BeastState st, Mode mode) { if (s_kinds.TryGetValue(st.Prefab, out var value)) { switch (mode) { case Mode.Resting: value.Rested++; break; case Mode.Far: value.Slowed++; break; } } } private static string Localize(string token) { if (string.IsNullOrEmpty(token)) { return ""; } if (Localization.instance == null) { return token; } return Localization.instance.Localize(token); } internal static void StartTest() { if (Measuring) { Say("IdleBeasts: a test is already running."); return; } s_offResult = (s_onResult = ""); Go(Phase.OffWarm, 3f); Say("IdleBeasts test: 13 s with the mod off, then 25 s with it on (15 s for the animals to settle, 10 s measured). Stay where you are."); } private static void Go(Phase p, float seconds) { s_phase = p; s_phaseEnd = Time.unscaledTime + seconds; s_sumMs = (s_sumFps = 0f); s_samples = 0; IdleBeastsPlugin.ApplySettings(); } internal static void Update() { if (s_phase != Phase.None && !(Time.unscaledTime < s_phaseEnd)) { switch (s_phase) { case Phase.OffWarm: Go(Phase.Off, 10f); break; case Phase.Off: s_offResult = Result(); Go(Phase.OnWarm, 15f); break; case Phase.OnWarm: Go(Phase.On, 10f); break; case Phase.On: { s_onResult = Result(); s_phase = Phase.None; IdleBeastsPlugin.ApplySettings(); Stats.Counts(out var total, out var owned, out var resting, out var _, out var far); LastTest = $"Test {DateTime.Now:yyyy-MM-dd HH:mm}: mod off {s_offResult}; mod on {s_onResult} ({total} creatures loaded, this PC runs {owned}: {resting} resting, {far} far)."; Say("IdleBeasts " + LastTest); Write(); break; } } } } internal static void Sample() { if (s_phase == Phase.Off || s_phase == Phase.On) { s_sumMs += Stats.TotalMs(); s_sumFps += Stats.Fps; s_samples++; } } private static string Result() { if (s_samples == 0) { return "(no samples)"; } return $"{s_sumMs / (float)s_samples:0.00} ms per frame in creature updates at {s_sumFps / (float)s_samples:0} FPS"; } internal static void Say(string text) { IdleBeastsPlugin.Log.LogInfo((object)text); try { Console instance = Console.instance; if (instance != null) { instance.Print(text); } } catch { } try { Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)1, (text.Length > 90) ? (text.Substring(0, 90) + "...") : text, 0, (Sprite)null, false); } } catch { } } internal static void Write() { try { Dictionary<string, int[]> loaded = new Dictionary<string, int[]>(); foreach (BeastState item in Beasts.All) { if (item.Alive) { if (!loaded.TryGetValue(item.Prefab, out var value)) { value = (loaded[item.Prefab] = new int[4]); } value[0]++; if (item.View.IsOwner()) { value[1]++; } if (item.Mode == Mode.Resting) { value[2]++; } if (item.Mode == Mode.Far) { value[3]++; } } } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(string.Format("# IdleBeasts {0} report, {1:yyyy-MM-dd HH:mm}", "1.0.2", DateTime.Now)); stringBuilder.AppendLine("# " + LastTest); stringBuilder.AppendLine("# Now: " + Stats.Line.Replace("\n", " / ")); stringBuilder.AppendLine("# Creature | Prefab | Loaded | This PC runs | Resting | Far | Times rested | Times slowed | AI | Animator culling | Animator update | Skinned meshes | Most bones | Rule"); int[] value4; foreach (KeyValuePair<string, KindInfo> item2 in from p in s_kinds orderby loaded.TryGetValue(p.Key, out value4) ? value4[0] : 0 descending, p.Key select p) { KindInfo value2 = item2.Value; loaded.TryGetValue(item2.Key, out var value3); value3 = value3 ?? new int[4]; stringBuilder.AppendLine($"{value2.Name} | {item2.Key} | {value3[0]} | {value3[1]} | {value3[2]} | {value3[3]} | {value2.Rested} | {value2.Slowed} | {value2.AI} | {value2.Culling} | {value2.UpdateMode} | {value2.Meshes} | {value2.Bones} | {IdleBeastsPlugin.RuleFor(item2.Key, null)}"); } File.WriteAllText(PathName, stringBuilder.ToString()); Say("IdleBeasts report written: " + Path.GetFileName(PathName)); } catch (Exception ex) { IdleBeastsPlugin.Log.LogWarning((object)("Writing the report failed: " + ex.Message)); } } } public enum BeastRule { Default, Never, RestOnly, FarOnly } public enum Mode { Normal, Settling, Resting, Far } public struct Look { public bool Owner; public bool Tame; public bool Dead; public bool Rider; public bool Following; public bool Target; public bool Alerted; public bool Eating; public bool Attacking; public bool Hunting; public bool Grounded; public bool Swimming; public bool Flying; public bool OnShip; public bool InPen; public bool Moving; public float Speed; public float NearestPlayer; public float NearestEnemy; public float SinceHurt; } public sealed class RuleSettings { public bool Enabled = true; public bool TamedOnly = true; public bool Rest = true; public float WakeDistance = 20f; public float EnemyDistance = 30f; public float IdleSeconds = 5f; public bool OnlyInPens; public bool Far = true; public float FarDistance = 50f; public const float Margin = 2f; public const float SettleStill = 1f; public const float HurtCalm = 10f; public const float StillSpeed = 0.15f; } public static class Rules { public static bool AllowsRest(BeastRule r) { if (r != BeastRule.Default) { return r == BeastRule.RestOnly; } return true; } public static bool AllowsFar(BeastRule r) { if (r != BeastRule.Default) { return r == BeastRule.FarOnly; } return true; } public static bool Quiet(Look l, BeastRule r, RuleSettings s, float extra) { if (!s.Enabled || !s.Rest || !AllowsRest(r) || !l.Owner || !l.Tame) { return false; } if (l.Dead || l.Rider || l.Following || l.Target || l.Alerted || l.Eating || l.Attacking || l.Hunting) { return false; } if (l.Swimming || l.Flying || l.OnShip) { return false; } if (s.OnlyInPens && !l.InPen) { return false; } if (l.SinceHurt < 10f) { return false; } if (l.NearestPlayer > s.WakeDistance + extra) { return l.NearestEnemy > s.EnemyDistance + extra; } return false; } public static bool Far(Look l, BeastRule r, RuleSettings s) { if (!s.Enabled || !s.Far || !AllowsFar(r)) { return false; } if (s.TamedOnly && !l.Tame) { return false; } if (l.Dead || l.Rider || l.Following || l.Hunting || l.Attacking || l.Alerted) { return false; } if (l.Owner && l.Target) { return false; } return l.NearestPlayer > s.FarDistance; } public static Mode Next(Mode current, Look l, BeastRule r, RuleSettings s, float quietFor, float stillFor) { bool flag = current == Mode.Resting || current == Mode.Settling; bool flag2 = Quiet(l, r, s, flag ? 0f : 2f); if (current == Mode.Resting) { if (flag2 && !l.Moving) { return Mode.Resting; } } else if (flag2) { if (current == Mode.Settling) { if (!l.Moving && l.Grounded && l.Speed < 0.15f && stillFor >= 1f) { return Mode.Resting; } return Mode.Settling; } if (quietFor >= s.IdleSeconds) { return Mode.Settling; } } if (!Far(l, r, s)) { return Mode.Normal; } return Mode.Far; } public static Mode NextNotOwned(bool ownerSaysResting, Look l, BeastRule r, RuleSettings s) { if (!s.Enabled) { return Mode.Normal; } if (ownerSaysResting && AllowsRest(r) && !l.Dead) { return Mode.Resting; } if (!Far(l, r, s)) { return Mode.Normal; } return Mode.Far; } } public static class Schedule { public static bool Due(long step, int phase, int period) { if (period <= 1) { return true; } return (step + phase) % period == 0; } public static int Period(float seconds, float stepSeconds) { if (stepSeconds <= 0f) { return 1; } return Math.Max(1, (int)Math.Round(seconds / stepSeconds)); } public static int Phase(int id, int span) { if (span <= 1) { return 0; } int num = id * 7919; return (int)((uint)(num ^ (num >> 13)) % (uint)span); } } public struct Owed { public float Pending; public void Add(float dt) { Pending += dt; } public float Take(float cap) { float result = Math.Min(Pending, cap); Pending = 0f; return result; } } public static class Keys { public static string Clean(string name) { if (string.IsNullOrEmpty(name)) { return "_"; } char[] array = name.ToCharArray(); for (int i = 0; i < array.Length; i++) { if ("=\n\t\\\"'[]".IndexOf(array[i]) >= 0) { array[i] = '_'; } } return new string(array).Trim(); } } public enum RestLook { Frozen, Slow, Normal } internal static class Stats { internal enum Bucket { AI, Body, Sync, Details, RestAnimation } private const int Buckets = 5; private static readonly long[] s_ticks = new long[5]; private static long s_calls; private static long s_skipped; private static int s_frames; private static float s_windowStart; internal static readonly float[] MsPerFrame = new float[5]; internal static float Fps; internal static float SkippedShare; internal static string Line = ""; internal static long Now() { return Stopwatch.GetTimestamp(); } internal static void AddTime(Bucket b, long t0) { s_ticks[(int)b] += Stopwatch.GetTimestamp() - t0; } internal static Bucket BucketOf(Loops.Kind k) { switch (k) { case Loops.Kind.Body: return Bucket.Body; case Loops.Kind.SyncFixed: case Loops.Kind.AnimSync: case Loops.Kind.SyncLate: return Bucket.Sync; case Loops.Kind.AI: return Bucket.AI; default: return Bucket.Details; } } internal static void Creature(Loops.Kind k) { s_calls++; } internal static void Skipped(Loops.Kind k) { s_skipped++; } internal static float TotalMs() { float num = 0f; for (int i = 0; i < 5; i++) { num += MsPerFrame[i]; } return num; } internal static void Frame() { s_frames++; float unscaledTime = Time.unscaledTime; if (s_windowStart <= 0f) { s_windowStart = unscaledTime; return; } float num = unscaledTime - s_windowStart; if (!(num < 1f)) { double num2 = 1000.0 / (double)Stopwatch.Frequency; for (int i = 0; i < 5; i++) { MsPerFrame[i] = (float)((double)s_ticks[i] * num2 / (double)Mathf.Max(1, s_frames)); s_ticks[i] = 0L; } Fps = (float)s_frames / num; SkippedShare = ((s_calls > 0) ? ((float)s_skipped / (float)s_calls) : 0f); s_calls = (s_skipped = 0L); s_frames = 0; s_windowStart = unscaledTime; Line = Describe(); Report.Sample(); } } internal static void Counts(out int total, out int owned, out int resting, out int settling, out int far) { total = (owned = (resting = (settling = (far = 0)))); foreach (BeastState item in Beasts.All) { if (item.Alive) { total++; if (item.View.IsOwner()) { owned++; } if (item.Mode == Mode.Resting) { resting++; } else if (item.Mode == Mode.Settling) { settling++; } else if (item.Mode == Mode.Far) { far++; } } } } private static string Describe() { Counts(out var total, out var owned, out var resting, out var settling, out var far); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("IdleBeasts").Append((IdleBeastsPlugin.Enabled.Value && !Report.ForcedOff) ? "" : " (off, measuring)").Append(": "); stringBuilder.Append(total).Append(" creatures loaded, this PC runs ").Append(owned) .Append(" · "); stringBuilder.Append(resting).Append(" resting, ").Append(settling) .Append(" settling, ") .Append(far) .Append(" far\n"); stringBuilder.Append("Creature updates ").Append(TotalMs().ToString("0.00")).Append(" ms per frame (AI ") .Append(MsPerFrame[0].ToString("0.00")) .Append(", body ") .Append(MsPerFrame[1].ToString("0.00")) .Append(", sync ") .Append(MsPerFrame[2].ToString("0.00")) .Append(", details ") .Append(MsPerFrame[3].ToString("0.00")); if (MsPerFrame[4] > 0f) { stringBuilder.Append(", resting animation ").Append(MsPerFrame[4].ToString("0.00")); } stringBuilder.Append(") · ").Append((SkippedShare * 100f).ToString("0")).Append("% skipped · ") .Append(Fps.ToString("0")) .Append(" FPS"); return stringBuilder.ToString(); } }