Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of Siesta v1.3.2
Siesta.dll
Decompiled 5 days agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Reflection.Emit; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using DooDesch; using HarmonyLib; using Il2CppFishNet.Object; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppScheduleOne.DevUtilities; using Il2CppScheduleOne.Economy; using Il2CppScheduleOne.Employees; using Il2CppScheduleOne.GameTime; using Il2CppScheduleOne.NPCs; using Il2CppScheduleOne.NPCs.Behaviour; using Il2CppScheduleOne.NPCs.Schedules; using Il2CppScheduleOne.Networking; using Il2CppScheduleOne.Persistence; using Il2CppScheduleOne.PlayerScripts; using Il2CppScheduleOne.Police; using Il2CppScheduleOne.Reporting; using Il2CppScheduleOne.Vision; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using MelonLoader; using MelonLoader.Preferences; using Microsoft.CodeAnalysis; using S1API.Lifecycle; using Siesta; using Siesta.Compat; using Siesta.Config; using Siesta.Lod; using Siesta.UI; using UnityEngine; using UnityEngine.AI; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Core), "Siesta", "1.3.2", "DooDesch", "https://github.com/DooDesch-Mods/ScheduleOne-Siesta")] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: AssemblyMetadata("SourceNotice", "Siesta by DooDesch, MIT License. The source code is public at https://github.com/DooDesch-Mods/ScheduleOne-Siesta. Please read, change and reuse the source code, not decompiled code from this file. Text and data mining reserved under Art. 4(3) Directive (EU) 2019/790 and § 44b(3) UrhG.")] [assembly: AssemblyMetadata("License", "https://github.com/DooDesch-Mods/ScheduleOne-Siesta/blob/main/LICENSE.md")] [assembly: AssemblyMetadata("SourceCode", "https://github.com/DooDesch-Mods/ScheduleOne-Siesta")] [assembly: AssemblyMetadata("tdm-reservation", "1")] [assembly: AssemblyMetadata("tdm-policy", "https://github.com/DooDesch-Mods/ScheduleOne-Siesta/blob/main/LICENSE.md")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("DooDesch")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright © DooDesch. MIT License. Source code: https://github.com/DooDesch-Mods/ScheduleOne-Siesta")] [assembly: AssemblyFileVersion("1.3.2.0")] [assembly: AssemblyInformationalVersion("1.3.2+e1e7f9b76f65c719e1313bbc80f7cd5ab39462ba")] [assembly: AssemblyProduct("Siesta")] [assembly: AssemblyTitle("Siesta")] [assembly: NeutralResourcesLanguage("en-US")] [assembly: AssemblyMetadata("AI_Assisted_Creation", "This assembly was partially created with the assistance of Generative AI.")] [assembly: AssemblyMetadata("AI_Model_Vendor", "Anthropic/OpenAI")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.3.2.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NativeIntegerAttribute : Attribute { public readonly bool[] TransformFlags; public NativeIntegerAttribute() { TransformFlags = new bool[1] { true }; } public NativeIntegerAttribute(bool[] P_0) { TransformFlags = P_0; } } [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 DooDesch { [AttributeUsage(AttributeTargets.All, Inherited = false)] internal sealed class SourceNoticeAttribute : Attribute { public string Notice { get; } public SourceNoticeAttribute(string notice) { Notice = notice; } } [SourceNotice("Siesta by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-Siesta, not decompiled code.")] internal static class SourceNotice { internal const string Text = "Siesta by DooDesch, MIT License. The source code is public at https://github.com/DooDesch-Mods/ScheduleOne-Siesta. Please read, change and reuse the source code, not decompiled code from this file. Text and data mining reserved under Art. 4(3) Directive (EU) 2019/790 and § 44b(3) UrhG."; internal const string Short = "Siesta by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-Siesta, not decompiled code."; } [SourceNotice("Siesta by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-Siesta, not decompiled code.")] internal static class ModVersion { internal const string Current = "1.3.2"; } } namespace Siesta { [SourceNotice("Siesta by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-Siesta, not decompiled code.")] public sealed class Core : MelonMod { private bool _inWorld; private float _teleElapsed; private int _teleFrames; private float _teleMaxDt; private Action _onDayPass; private bool _dayPassHooked; public static Core Instance { get; private set; } public static Instance Log { get; private set; } public override void OnInitializeMelon() { Instance = this; Log = ((MelonBase)this).LoggerInstance; Preferences.Initialize(); MoreNpcsCompat.Apply(((MelonBase)this).HarmonyInstance); MoreNpcsScanCompat.Apply(((MelonBase)this).HarmonyInstance); MoveThrottle.Apply(((MelonBase)this).HarmonyInstance); StayInBuildingRepath.Apply(((MelonBase)this).HarmonyInstance); LogHandlerLeak.Apply(((MelonBase)this).HarmonyInstance); HeapScrub.Apply(((MelonBase)this).HarmonyInstance); GameLifecycle.OnSaveStart += delegate { LodController.RestoreAll("save starting"); }; GameLifecycle.OnPreSceneChange += delegate { LodController.RestoreAll("scene changing"); }; Log.Msg("Siesta v1.3.2 - NPC LOD active."); } public override void OnSceneWasLoaded(int buildIndex, string sceneName) { _inWorld = sceneName == "Main"; if (_inWorld) { HookDayPass(); } } public override void OnSceneWasUnloaded(int buildIndex, string sceneName) { _inWorld = false; UnhookDayPass(); LodController.Reset(); } public override void OnUpdate() { if (_inWorld) { if (!_dayPassHooked) { HookDayPass(); } LodController.Tick(); TelemetryTick(); } } public override void OnGUI() { if (_inWorld && Preferences.ShowFpsCounter) { FpsCounter.Draw(); } } public override void OnApplicationQuit() { UnhookDayPass(); LodController.RestoreAll("application quit"); } public override void OnDeinitializeMelon() { UnhookDayPass(); LodController.RestoreAll("melon unload"); } private void HookDayPass() { if (_dayPassHooked) { return; } try { if (!NetworkSingleton<TimeManager>.InstanceExists) { return; } TimeManager instance = NetworkSingleton<TimeManager>.Instance; if (!((Object)(object)instance == (Object)null)) { _onDayPass = Action.op_Implicit((Action)delegate { LodController.RestoreAll("new day"); }); instance.onDayPass += _onDayPass; _dayPassHooked = true; } } catch (Exception ex) { Log.Warning("onDayPass hook failed: " + ex.Message); } } private void UnhookDayPass() { if (!_dayPassHooked) { return; } try { if (NetworkSingleton<TimeManager>.InstanceExists) { TimeManager instance = NetworkSingleton<TimeManager>.Instance; if ((Object)(object)instance != (Object)null && (Delegate)(object)_onDayPass != (Delegate)null) { instance.onDayPass -= _onDayPass; } } } catch { } finally { _onDayPass = null; _dayPassHooked = false; } } private void TelemetryTick() { float unscaledDeltaTime = Time.unscaledDeltaTime; _teleElapsed += unscaledDeltaTime; _teleFrames++; if (unscaledDeltaTime > _teleMaxDt) { _teleMaxDt = unscaledDeltaTime; } if (_teleElapsed < 15f) { return; } try { float value = (float)_teleFrames / _teleElapsed; float value2 = ((_teleMaxDt > 0f) ? (1f / _teleMaxDt) : 0f); LodRegistry.CountByTier(out var full, out var cosmetic, out var deep); string text = LodLog.Tally(); MoveThrottle.TakeTally(out var ran, out var skipped); Log.Msg($"[telemetry] fps={value:F0} (min {value2:F0}) npcs={full + cosmetic + deep} full={full} cosmetic={cosmetic} deep={deep} throttled={MoveThrottle.Count} (steps run {ran}, skipped {skipped})" + ((text == null) ? "" : (" threw: " + text))); } catch { } finally { _teleElapsed = 0f; _teleFrames = 0; _teleMaxDt = 0f; } } } } namespace Siesta.UI { [SourceNotice("Siesta by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-Siesta, not decompiled code.")] internal static class FpsCounter { private static GUIStyle _style; internal static void Draw() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Invalid comparison between Unknown and I4 //IL_0044: 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_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) if (Event.current == null || (int)Event.current.type == 7) { float smoothDeltaTime = Time.smoothDeltaTime; int num = ((smoothDeltaTime > 0f) ? Mathf.RoundToInt(1f / smoothDeltaTime) : 0); if (_style == null) { _style = new GUIStyle(GUI.skin.label) { fontSize = 22, fontStyle = (FontStyle)1, alignment = (TextAnchor)2 }; } _style.normal.textColor = (Color)((num >= 50) ? Color.green : ((num >= 30) ? Color.yellow : new Color(1f, 0.45f, 0.45f))); string text = num + " FPS"; Rect val = default(Rect); ((Rect)(ref val))..ctor((float)Screen.width - 140f, 6f, 132f, 28f); GUI.color = new Color(0f, 0f, 0f, 0.6f); GUI.Label(new Rect(((Rect)(ref val)).x + 1f, ((Rect)(ref val)).y + 1f, ((Rect)(ref val)).width, ((Rect)(ref val)).height), text, _style); GUI.color = Color.white; GUI.Label(val, text, _style); } } } } namespace Siesta.Lod { [SourceNotice("Siesta by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-Siesta, not decompiled code.")] internal static class Exemptions { internal static bool ExemptOnAnyBehaviour; internal static bool IsDeepCullExempt(NPC npc, NpcModState st) { return Reason(npc, st) != null; } internal static string Reason(NPC npc, NpcModState st) { try { if (!npc.IsConscious) { return "unconscious"; } if (npc.IsInVehicle) { return "in-vehicle"; } if (npc.IsPanicked || npc.isUnsettled) { return "panicked"; } if ((Object)(object)st.Officer != (Object)null) { return "police"; } if ((Object)(object)npc.DialogueHandler != (Object)null && npc.DialogueHandler.IsDialogueInProgress) { return "in-dialogue"; } if (npc.isInBuilding) { return "in-building"; } NPCScheduleManager schedule = st.Schedule; if ((Object)(object)schedule != (Object)null) { NPCAction activeAction = schedule.ActiveAction; if ((Object)(object)activeAction != (Object)null && (Object)(object)((Il2CppObjectBase)activeAction).TryCast<NPCEvent_StayInBuilding>() != (Object)null) { return "building-transit"; } } NPCBehaviour behaviour = npc.Behaviour; if ((Object)(object)behaviour != (Object)null && (Object)(object)behaviour.activeBehaviour != (Object)null && (((Object)(object)behaviour.CombatBehaviour != (Object)null && ((Behaviour)behaviour.CombatBehaviour).Active) || ((Object)(object)behaviour.FleeBehaviour != (Object)null && ((Behaviour)behaviour.FleeBehaviour).Active) || ((Object)(object)behaviour.CoweringBehaviour != (Object)null && ((Behaviour)behaviour.CoweringBehaviour).Active) || ((Object)(object)behaviour.CallPoliceBehaviour != (Object)null && ((Behaviour)behaviour.CallPoliceBehaviour).Active) || ((Object)(object)behaviour.RequestProductBehaviour != (Object)null && ((Behaviour)behaviour.RequestProductBehaviour).Active) || ((Object)(object)behaviour.GenericDialogueBehaviour != (Object)null && ((Behaviour)behaviour.GenericDialogueBehaviour).Active))) { return "active-critical-behaviour"; } if (ExemptOnAnyBehaviour && (Object)(object)behaviour != (Object)null) { if ((Object)(object)behaviour.activeBehaviour != (Object)null) { return "active-behaviour"; } List<Behaviour> behaviourStack = behaviour.behaviourStack; if (behaviourStack != null && behaviourStack.Count > 0) { return "behaviour-stack"; } } if ((Object)(object)st.Dealer != (Object)null) { if ((Object)(object)st.Dealer.currentContract != (Object)null) { return "dealer-contract"; } DealerAttendDealBehaviour attendDealBehaviour = st.Dealer._attendDealBehaviour; if ((Object)(object)attendDealBehaviour != (Object)null && ((Behaviour)attendDealBehaviour).Active) { return "dealer-attend"; } } if ((Object)(object)st.Employee != (Object)null) { if (st.Employee.IsAnyWorkInProgress()) { return "employee-working"; } if (!st.Employee.ShouldIdle() && !st.Employee.IsWaitingOutside) { return "employee-busy"; } } if ((Object)(object)st.Customer != (Object)null) { if ((Object)(object)st.Customer.CurrentContract != (Object)null) { return "customer-contract"; } if (st.Customer.IsAwaitingDelivery) { return "customer-awaiting"; } } return null; } catch (Exception ex) { return "ex:" + ex.Message; } } } [SourceNotice("Siesta by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-Siesta, not decompiled code.")] internal static class LodController { private struct FailInfo { public float RetryAt; public int Count; } internal enum Control { Auto, ForceFull, ForceCosmetic, ForceDeep } private static int _cursor; private const float RetryAfterSeconds = 60f; private const int MaxConsecutiveFailures = 3; private static readonly Dictionary<int, FailInfo> _failed = new Dictionary<int, FailInfo>(); internal static Control Mode = Control.Auto; private static Vector3[] _players = (Vector3[])(object)new Vector3[8]; private static int _playerCount; private static Camera _cam; private static float _camRefreshAt; private static bool IsSpawned(NPC npc) { try { NetworkObject networkObject = ((NetworkBehaviour)npc).NetworkObject; return (Object)(object)networkObject != (Object)null && networkObject.IsSpawned; } catch { return false; } } private static bool IsFailHeld(int id) { if (!_failed.TryGetValue(id, out var value)) { return false; } if (value.Count >= 3) { return true; } return Time.unscaledTime < value.RetryAt; } private static void NoteFailure(int id) { _failed.TryGetValue(id, out var value); value.Count++; value.RetryAt = Time.unscaledTime + 60f; _failed[id] = value; } internal static void Tick() { //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) if (!Preferences.EnableLod) { if (LodRegistry.HasAny) { RestoreAll("LOD disabled"); } return; } if (Net.IsMultiplayer() && !Preferences.EnableInMultiplayer) { if (LodRegistry.HasAny) { RestoreAll("multiplayer + EnableInMultiplayer off"); } return; } if (Mode != Control.Auto) { ForceAll((Mode != Control.ForceFull) ? ((Mode == Control.ForceCosmetic) ? LodState.Cosmetic : LodState.Deep) : LodState.Full); return; } List<NPC> nPCRegistry = NPCManager.NPCRegistry; if (nPCRegistry == null) { return; } int count; try { count = nPCRegistry.Count; } catch { return; } if (count == 0) { return; } SnapshotPlayers(); if (_playerCount == 0) { return; } RefreshCamera(); bool authoritative = Net.IsAuthoritative(); float num = Preferences.CosmeticDistance * Preferences.CosmeticDistance; bool respectOnScreen = Preferences.RespectOnScreen; int num2 = Preferences.BudgetPerFrame; for (int i = 0; i < count; i++) { NPC val; try { val = nPCRegistry[i]; } catch { continue; } if ((Object)(object)val == (Object)null || !IsSpawned(val)) { continue; } int instanceID; try { instanceID = ((Object)val).GetInstanceID(); } catch { continue; } if (IsFailHeld(instanceID)) { continue; } NpcModState orAdd = LodRegistry.GetOrAdd(instanceID, val); if (orAdd.Tier == LodState.Full && !orAdd.MoveThrottled) { continue; } Vector3 centerPoint; try { centerPoint = val.CenterPoint; } catch { continue; } bool flag = MinSqrDistToPlayer(centerPoint) < num; if (flag && orAdd.MoveThrottled && !orAdd.ThrottledWhileStuck) { ReleaseThrottle(val, orAdd); } if (orAdd.Tier == LodState.Full) { continue; } if (!flag) { if (!respectOnScreen || !IsOnScreen(centerPoint) || num2 <= 0) { continue; } num2--; } try { LodLevers.ApplyTier(val, orAdd, LodState.Full, authoritative); if (orAdd.WakeFailed) { HandleWakeFailure(val, orAdd, instanceID); } } catch (Exception e) { LodLog.Vanilla("promote pre-pass", instanceID, e); NoteFailure(instanceID); LodLevers.ForceFull(val, orAdd); } } int num3 = Math.Min(Preferences.BudgetPerFrame, count); for (int j = 0; j < num3; j++) { if (_cursor >= count) { _cursor = 0; } NPC val2; try { val2 = nPCRegistry[_cursor]; } catch { _cursor++; continue; } _cursor++; if (!((Object)(object)val2 == (Object)null) && IsSpawned(val2)) { Evaluate(val2, authoritative); } } } private static void Evaluate(NPC npc, bool authoritative) { //IL_0054: 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_005f: 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) int instanceID; try { instanceID = ((Object)npc).GetInstanceID(); } catch { return; } NpcModState orAdd = LodRegistry.GetOrAdd(instanceID, npc); if (IsFailHeld(instanceID)) { if (orAdd.Tier != LodState.Full) { LodLevers.ForceFull(npc, orAdd); } return; } orAdd.Resolve(npc); if (orAdd.Hidden) { try { if (npc.isVisible) { orAdd.Hidden = false; } } catch { } } Vector3 centerPoint; try { centerPoint = npc.CenterPoint; } catch { return; } float d = MinSqrDistToPlayer(centerPoint); bool onScreen = IsOnScreen(centerPoint); UpdateThrottle(npc, orAdd, d, authoritative); LodState lodState = Decide(npc, orAdd, d, onScreen, authoritative); if (lodState == orAdd.Tier) { return; } try { LodLevers.ApplyTier(npc, orAdd, lodState, authoritative); if (orAdd.WakeFailed) { HandleWakeFailure(npc, orAdd, instanceID); } } catch (Exception e) { LodLog.Vanilla("ApplyTier", instanceID, e); NoteFailure(instanceID); LodLevers.ForceFull(npc, orAdd); } } private static void UpdateThrottle(NPC npc, NpcModState st, float d2, bool authoritative) { bool flag = false; if (authoritative && Preferences.ThrottleFarMovement && !MoveThrottle.Off && (Object)(object)st.Officer == (Object)null) { float cosmeticDistance = Preferences.CosmeticDistance; float num = (st.MoveThrottled ? cosmeticDistance : (cosmeticDistance + Preferences.Hysteresis)); bool num2 = d2 >= num * num; if (num2 | (st.ThrottledWhileStuck = !num2 && IsStuckOnDestination(npc, st))) { bool flag2 = true; try { flag2 = (Object)(object)npc.Avatar != (Object)null && npc.Avatar.Ragdolled; } catch (Exception e) { LodLog.Vanilla("Avatar.Ragdolled", st.Id, e); } flag = !flag2; } } if (flag == st.MoveThrottled) { return; } if (!flag) { ReleaseThrottle(npc, st); return; } try { NPCMovement movement = npc.Movement; if (!((Object)(object)movement == (Object)null)) { MoveThrottle.Throttle(movement); st.MoveThrottled = MoveThrottle.IsThrottled(movement); } } catch (Exception e2) { LodLog.Vanilla("throttle movement", st.Id, e2); } } private static bool IsStuckOnDestination(NPC npc, NpcModState st) { try { NPCMovement movement = npc.Movement; if ((Object)(object)movement == (Object)null || !movement._hasDestination || movement.IsMoving || !movement.CanMove || movement.IsOnLadder) { return false; } NavMeshAgent agent = movement._agent; return (Object)(object)agent != (Object)null && !agent.pathPending; } catch (Exception e) { LodLog.Vanilla("movement state", st.Id, e); return false; } } private static void ReleaseThrottle(NPC npc, NpcModState st) { st.MoveThrottled = false; st.ThrottledWhileStuck = false; try { MoveThrottle.Release(npc.Movement); } catch (Exception e) { LodLog.Vanilla("release movement throttle", st.Id, e); } } private static void HandleWakeFailure(NPC npc, NpcModState st, int id) { st.WakeFailed = false; NoteFailure(id); Instance log = Core.Log; if (log != null) { log.Warning($"NPC {id} wake failed - keeping Full, will retry in {60f:F0}s."); } LodLevers.ForceFull(npc, st); } private static LodState Decide(NPC npc, NpcModState st, float d2, bool onScreen, bool authoritative) { if (onScreen && Preferences.RespectOnScreen) { return LodState.Full; } float cosmeticDistance = Preferences.CosmeticDistance; float deepDistance = Preferences.DeepDistance; float hysteresis = Preferences.Hysteresis; float num = cosmeticDistance * cosmeticDistance; float num2 = (cosmeticDistance + hysteresis) * (cosmeticDistance + hysteresis); float num3 = deepDistance * deepDistance; float num4 = (deepDistance + hysteresis) * (deepDistance + hysteresis); LodState lodState = st.Tier switch { LodState.Full => (d2 >= num4) ? LodState.Deep : ((d2 >= num2) ? LodState.Cosmetic : LodState.Full), LodState.Cosmetic => (!(d2 < num)) ? ((!(d2 >= num4)) ? LodState.Cosmetic : LodState.Deep) : LodState.Full, _ => (!(d2 < num)) ? ((d2 < num3) ? LodState.Cosmetic : LodState.Deep) : LodState.Full, }; if (lodState == LodState.Deep) { string text = (st.ExemptReason = ((!authoritative) ? "not-host" : Exemptions.Reason(npc, st))); if (!Preferences.UseDeepCull || text != null) { lodState = (Preferences.UseCosmeticCull ? LodState.Cosmetic : LodState.Full); } } if (lodState == LodState.Cosmetic && !Preferences.UseCosmeticCull) { lodState = LodState.Full; } return lodState; } private static float MinSqrDistToPlayer(Vector3 p) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) float num = float.MaxValue; for (int i = 0; i < _playerCount; i++) { Vector3 val = _players[i] - p; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (sqrMagnitude < num) { num = sqrMagnitude; } } return num; } private static void SnapshotPlayers() { //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) _playerCount = 0; try { List<Player> playerList = Player.PlayerList; if (playerList != null && playerList.Count > 0) { int count = playerList.Count; for (int i = 0; i < count; i++) { Player val = playerList[i]; if (!((Object)(object)val == (Object)null)) { Transform transform = ((Component)val).transform; if (!((Object)(object)transform == (Object)null)) { Add(transform.position); } } } } if (_playerCount == 0) { Player local = Player.Local; if ((Object)(object)local != (Object)null && (Object)(object)((Component)local).transform != (Object)null) { Add(((Component)local).transform.position); } } } catch { } } private static void Add(Vector3 v) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (_playerCount < _players.Length) { _players[_playerCount++] = v; } } private static void RefreshCamera() { if (!((Object)(object)_cam != (Object)null) || !(Time.unscaledTime < _camRefreshAt)) { try { _cam = Camera.main; } catch { _cam = null; } _camRefreshAt = Time.unscaledTime + 2f; } } private static bool IsOnScreen(Vector3 p) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_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_0043: Unknown result type (might be due to invalid IL or missing references) Camera cam = _cam; if ((Object)(object)cam == (Object)null) { return false; } try { Transform transform = ((Component)cam).transform; Vector3 val = p - transform.position; float magnitude = ((Vector3)(ref val)).magnitude; if (magnitude < 0.001f) { return true; } return Vector3.Dot(transform.forward, val / magnitude) > 0.5f; } catch { return false; } } internal static void RestoreAll(string reason) { try { LodRegistry.RestoreAll(); _failed.Clear(); _cursor = 0; } catch (Exception e) { LodLog.Vanilla("RestoreAll", 0, e); } } internal static void Reset() { LodRegistry.Reset(); LodLog.Reset(); _failed.Clear(); _cursor = 0; _cam = null; } internal static void ForceAll(LodState target) { List<NPC> nPCRegistry = NPCManager.NPCRegistry; if (nPCRegistry == null) { return; } int count; try { count = nPCRegistry.Count; } catch { return; } bool flag = Net.IsAuthoritative(); for (int i = 0; i < count; i++) { NPC val; try { val = nPCRegistry[i]; } catch { continue; } if ((Object)(object)val == (Object)null || !IsSpawned(val)) { continue; } int instanceID; try { instanceID = ((Object)val).GetInstanceID(); } catch { continue; } if (!IsFailHeld(instanceID)) { NpcModState orAdd = LodRegistry.GetOrAdd(instanceID, val); orAdd.Resolve(val); LodState lodState = target; if (lodState == LodState.Deep && (orAdd.ExemptReason = ((!flag) ? "not-host" : Exemptions.Reason(val, orAdd))) != null) { lodState = LodState.Cosmetic; } try { LodLevers.ApplyTier(val, orAdd, lodState, flag); } catch { } } } } } [SourceNotice("Siesta by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-Siesta, not decompiled code.")] internal static class LodLevers { private const string PauseControl = "siesta_pause"; internal static void ApplyTier(NPC npc, NpcModState st, LodState target, bool authoritative) { if (target == st.Tier) { return; } switch (target) { case LodState.Full: if (st.DeepApplied) { RestoreDeep(npc, st); } if (st.Hidden) { Show(npc, st); } break; case LodState.Cosmetic: if (st.DeepApplied) { RestoreDeep(npc, st); } if (!st.Hidden) { Hide(npc, st, authoritative); } break; case LodState.Deep: if (!st.DeepApplied) { ApplyDeep(npc, st, authoritative); } if (!st.Hidden) { Hide(npc, st, authoritative); } break; } st.Tier = target; } internal static void ForceFull(NPC npc, NpcModState st) { try { if (st.DeepApplied) { RestoreDeep(npc, st); } if (st.Hidden) { Show(npc, st); } if (st.MoveThrottled) { MoveThrottle.Release(npc.Movement); st.MoveThrottled = false; st.ThrottledWhileStuck = false; } st.Tier = LodState.Full; } catch (Exception e) { LodLog.Vanilla("ForceFull", st.Id, e); } } private static void Hide(NPC npc, NpcModState st, bool authoritative) { st.Hidden = true; try { npc.SetVisible(false, false); } catch (Exception e) { LodLog.Vanilla("SetVisible(false)", st.Id, e); } if (!authoritative) { return; } try { NPCMovement movement = npc.Movement; if (movement != null) { movement.OnVisibilityChange(true); } } catch (Exception e2) { LodLog.Vanilla("OnVisibilityChange(true)", st.Id, e2); } } private static void Show(NPC npc, NpcModState st) { st.Hidden = false; bool flag = false; try { flag = npc.isInBuilding || npc.IsInVehicle; } catch { } if (!flag) { try { npc.SetVisible(true, false); } catch (Exception e) { LodLog.Vanilla("SetVisible(true)", st.Id, e); } } } private static void ApplyDeep(NPC npc, NpcModState st, bool authoritative) { if (!authoritative) { Instance log = Core.Log; if (log != null) { log.Warning($"ERROR client-attempted-deepcull npc={st.Id} (ignored)"); } return; } NPCMovement movement = npc.Movement; if ((Object)(object)movement != (Object)null && !IsPaused(movement)) { try { NavMeshAgent agent = movement._agent; if ((Object)(object)agent != (Object)null && ((Behaviour)agent).enabled && agent.isOnNavMesh) { Pause(movement, agent); st.WePausedMovement = true; } } catch (Exception e) { LodLog.Vanilla("pause movement", st.Id, e); } } NPCScheduleManager schedule = st.Schedule; if ((Object)(object)schedule != (Object)null && schedule.ScheduleEnabled) { st.WeDisabledSchedule = true; try { schedule.DisableSchedule(); } catch (Exception e2) { LodLog.Vanilla("DisableSchedule", st.Id, e2); } } try { NPCAwareness awareness = npc.Awareness; if ((Object)(object)awareness != (Object)null) { awareness.SetAwarenessActive(false); st.WeDisabledAwareness = true; } } catch { } st.DeepApplied = true; } private static void RestoreDeep(NPC npc, NpcModState st) { if (st.WeDisabledAwareness) { try { NPCAwareness awareness = npc.Awareness; if ((Object)(object)awareness != (Object)null) { awareness.SetAwarenessActive(true); } } catch { } st.WeDisabledAwareness = false; } NPCMovement movement = npc.Movement; if (st.WePausedMovement && (Object)(object)movement != (Object)null) { st.WePausedMovement = false; try { Resume(movement); } catch (Exception e) { LodLog.Vanilla("resume movement", st.Id, e); } if (!RepairNavMesh(npc, movement, st)) { st.WakeFailed = true; st.DeepApplied = false; return; } } NPCScheduleManager schedule = st.Schedule; if (st.WeDisabledSchedule && (Object)(object)schedule != (Object)null) { st.WeDisabledSchedule = false; try { schedule.EnableSchedule(); schedule.EnforceState(false); } catch (Exception e2) { LodLog.Vanilla("EnableSchedule", st.Id, e2); } } st.DeepApplied = false; } internal static bool IsPaused(NPCMovement mv) { NPCSpeedController speedController = mv.SpeedController; if (speedController != null) { return speedController.DoesSpeedControlExist("siesta_pause"); } return false; } private static void Pause(NPCMovement mv, NavMeshAgent agent) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) NPCSpeedController speedController = mv.SpeedController; if (speedController != null) { speedController.AddSpeedControl("siesta_pause", 0f, int.MaxValue, (EType)1); } agent.isStopped = true; agent.velocity = Vector3.zero; } private static void Resume(NPCMovement mv) { NPCSpeedController speedController = mv.SpeedController; if (speedController != null) { speedController.RemoveSpeedControl("siesta_pause"); } NavMeshAgent agent = mv._agent; if ((Object)(object)agent != (Object)null && ((Behaviour)agent).enabled && agent.isOnNavMesh) { agent.isStopped = false; } } private static bool RepairNavMesh(NPC npc, NPCMovement mv, NpcModState st) { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) try { NavMeshAgent agent = mv._agent; if ((Object)(object)agent == (Object)null) { Instance log = Core.Log; if (log != null) { log.Warning($"WAKE-FAILED npc={st.Id} reason=agent-null"); } return false; } if (!((Behaviour)agent).enabled) { mv.SetAgentEnabled(true); } if (agent.isOnNavMesh) { return true; } NavMeshHit val = default(NavMeshHit); if (mv.SmartSampleNavMesh(mv.FootPosition, ref val, 1f, 10f, 3)) { mv.Warp(((NavMeshHit)(ref val)).position); mv.SetAgentEnabled(false); mv.SetAgentEnabled(true); if (agent.isOnNavMesh) { return true; } } mv.Warp(mv.FootPosition); if (agent.isOnNavMesh) { return true; } Instance log2 = Core.Log; if (log2 != null) { log2.Warning($"WAKE-FAILED npc={st.Id} reason=still-off-navmesh"); } return false; } catch (Exception ex) { Instance log3 = Core.Log; if (log3 != null) { log3.Warning($"WAKE-FAILED npc={st.Id} reason=exception {ex.Message}"); } return false; } } } [SourceNotice("Siesta by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-Siesta, not decompiled code.")] internal static class LodLog { private static readonly Dictionary<string, int> _counts = new Dictionary<string, int>(); internal static void Vanilla(string site, int npcId, Exception e) { string text = site + ": " + (e?.Message ?? "(no message)"); _counts.TryGetValue(text, out var value); _counts[text] = value + 1; if (value == 0) { Instance log = Core.Log; if (log != null) { log.Warning($"game code threw in {site} (npc={npcId}): {text} - tier applied anyway; repeats are counted, not logged."); } } } internal static string Tally() { if (_counts.Count == 0) { return null; } StringBuilder stringBuilder = new StringBuilder(); foreach (KeyValuePair<string, int> count in _counts) { if (stringBuilder.Length > 0) { stringBuilder.Append(" "); } stringBuilder.Append(count.Key).Append(" x").Append(count.Value); } return stringBuilder.ToString(); } internal static void Reset() { _counts.Clear(); } } [SourceNotice("Siesta by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-Siesta, not decompiled code.")] internal static class LodRegistry { private static readonly Dictionary<int, NpcModState> _states = new Dictionary<int, NpcModState>(); internal static bool HasAny => _states.Count > 0; internal static int Count => _states.Count; internal static NpcModState GetOrAdd(int id, NPC npc) { if (!_states.TryGetValue(id, out var value)) { value = new NpcModState(id, npc); _states[id] = value; try { MoveThrottle.Release(npc.Movement); } catch (Exception e) { LodLog.Vanilla("new NPC throttle reset", id, e); } } else { value.Npc = npc; } return value; } internal static void RestoreAll() { foreach (KeyValuePair<int, NpcModState> state in _states) { NpcModState value = state.Value; try { if ((Object)(object)value.Npc != (Object)null) { LodLevers.ForceFull(value.Npc, value); } } catch { } } _states.Clear(); MoveThrottle.Clear(); } internal static void Reset() { _states.Clear(); MoveThrottle.Clear(); } internal static string ReasonTally() { Dictionary<string, int> dictionary = new Dictionary<string, int>(); foreach (KeyValuePair<int, NpcModState> state in _states) { string key = state.Value.ExemptReason ?? "ELIGIBLE"; dictionary.TryGetValue(key, out var value); dictionary[key] = value + 1; } StringBuilder stringBuilder = new StringBuilder(); foreach (KeyValuePair<string, int> item in dictionary) { stringBuilder.Append(item.Key).Append('=').Append(item.Value) .Append(" "); } return stringBuilder.ToString().Trim(); } internal static void CountByTier(out int full, out int cosmetic, out int deep) { full = 0; cosmetic = 0; deep = 0; using Dictionary<int, NpcModState>.Enumerator enumerator = _states.GetEnumerator(); while (enumerator.MoveNext()) { switch (enumerator.Current.Value.Tier) { case LodState.Full: full++; break; case LodState.Cosmetic: cosmetic++; break; default: deep++; break; } } } } [SourceNotice("Siesta by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-Siesta, not decompiled code.")] internal enum LodState { Full, Cosmetic, Deep } [SourceNotice("Siesta by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-Siesta, not decompiled code.")] internal static class MoveThrottle { internal const float Interval = 0.2f; private static readonly Dictionary<IntPtr, float> _due = new Dictionary<IntPtr, float>(); private static bool _patched; private static int _skipped; private static int _ran; internal static bool Off = false; internal static bool Bypass = false; internal static int Count => _due.Count; internal static void Apply(Harmony harmony) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown try { MethodInfo methodInfo = AccessTools.Method(typeof(NPCMovement), "FixedUpdate", (Type[])null, (Type[])null); if (methodInfo == null) { Instance log = Core.Log; if (log != null) { log.Warning("Movement throttle: NPCMovement.FixedUpdate not found - far NPCs keep the engine's step rate."); } return; } harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(MoveThrottle), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _patched = true; Instance log2 = Core.Log; if (log2 != null) { log2.Msg("Movement throttle: on (far NPCs step every " + 0.2f + " s)."); } } catch (Exception ex) { Instance log3 = Core.Log; if (log3 != null) { log3.Error("Movement throttle: patching NPCMovement.FixedUpdate failed, far NPCs keep the engine's step rate: " + ex.Message); } } } internal static bool IsThrottled(NPCMovement mv) { if ((Object)(object)mv != (Object)null) { return _due.ContainsKey(((Il2CppObjectBase)mv).Pointer); } return false; } internal static void Throttle(NPCMovement mv) { if (_patched && !((Object)(object)mv == (Object)null)) { IntPtr pointer = ((Il2CppObjectBase)mv).Pointer; if (!_due.ContainsKey(pointer)) { _due[pointer] = Time.fixedTime + Random.Range(0f, 0.2f); } } } internal static void Release(NPCMovement mv) { if (!((Object)(object)mv == (Object)null)) { _due.Remove(((Il2CppObjectBase)mv).Pointer); } } internal static void Clear() { _due.Clear(); } internal static void TakeTally(out int ran, out int skipped) { ran = _ran; skipped = _skipped; _ran = 0; _skipped = 0; } private static bool Prefix(NPCMovement __instance) { if (_due.Count == 0 || Bypass) { return true; } try { IntPtr pointer = ((Il2CppObjectBase)__instance).Pointer; if (!_due.TryGetValue(pointer, out var value)) { return true; } float fixedTime = Time.fixedTime; if (fixedTime < value) { _skipped++; return false; } _due[pointer] = fixedTime + 0.2f; _ran++; return true; } catch (Exception e) { LodLog.Vanilla("movement throttle prefix", 0, e); return true; } } } [SourceNotice("Siesta by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-Siesta, not decompiled code.")] internal sealed class NpcModState { internal readonly int Id; internal NPC Npc; internal LodState Tier; internal bool Hidden; internal bool DeepApplied; internal bool WePausedMovement; internal bool WeDisabledSchedule; internal bool WeDisabledAwareness; internal bool MoveThrottled; internal bool ThrottledWhileStuck; internal bool WakeFailed; internal string ExemptReason; private bool _resolved; internal NPCScheduleManager Schedule; internal Dealer Dealer; internal Employee Employee; internal Customer Customer; internal PoliceOfficer Officer; internal NpcModState(int id, NPC npc) { Id = id; Npc = npc; } internal void Resolve(NPC npc) { if (_resolved) { return; } _resolved = true; try { Schedule = ((Component)npc).GetComponentInChildren<NPCScheduleManager>(true); } catch { } try { Dealer = ((Il2CppObjectBase)npc).TryCast<Dealer>(); } catch { } try { Employee = ((Il2CppObjectBase)npc).TryCast<Employee>(); } catch { } try { Customer = ((Component)npc).GetComponent<Customer>(); } catch { } try { Officer = ((Il2CppObjectBase)npc).TryCast<PoliceOfficer>(); } catch { } } } } namespace Siesta.Config { [SourceNotice("Siesta by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-Siesta, not decompiled code.")] internal static class Preferences { private const string CategoryId = "Siesta_01_Main"; private static MelonPreferences_Category _category; private static MelonPreferences_Entry<bool> _enableLod; private static MelonPreferences_Entry<bool> _enableInMp; private static MelonPreferences_Entry<float> _cosmeticDistance; private static MelonPreferences_Entry<float> _deepDistance; private static MelonPreferences_Entry<float> _hysteresis; private static MelonPreferences_Entry<int> _budgetPerFrame; private static MelonPreferences_Entry<bool> _useCosmeticCull; private static MelonPreferences_Entry<bool> _useDeepCull; private static MelonPreferences_Entry<bool> _respectOnScreen; private static MelonPreferences_Entry<bool> _throttleMovement; private static MelonPreferences_Entry<bool> _freeOldSceneMemory; private static MelonPreferences_Entry<bool> _showFps; internal static bool EnableLod => _enableLod?.Value ?? true; internal static bool EnableInMultiplayer => _enableInMp?.Value ?? true; internal static float CosmeticDistance => Mathf.Clamp(_cosmeticDistance?.Value ?? 40f, 15f, 300f); internal static float DeepDistance => Mathf.Clamp(_deepDistance?.Value ?? 80f, 30f, 500f); internal static float Hysteresis => Mathf.Clamp(_hysteresis?.Value ?? 8f, 0f, 50f); internal static int BudgetPerFrame => Mathf.Clamp(_budgetPerFrame?.Value ?? 32, 4, 256); internal static bool UseCosmeticCull => _useCosmeticCull?.Value ?? true; internal static bool UseDeepCull => _useDeepCull?.Value ?? true; internal static bool RespectOnScreen => _respectOnScreen?.Value ?? true; internal static bool ThrottleFarMovement => _throttleMovement?.Value ?? true; internal static bool FreeOldSceneMemory => _freeOldSceneMemory?.Value ?? true; internal static bool ShowFpsCounter => _showFps?.Value ?? false; internal static void Initialize() { if (_category == null) { _category = MelonPreferences.CreateCategory("Siesta_01_Main", "Siesta (NPC Performance)"); _enableLod = Create("EnableLod", def: true, "Enable NPC LOD", "Master switch. When ON (default), NPCs that are off-screen and far from you are culled to recover performance, and restored as you approach. Turn OFF to run fully vanilla (everything restored)."); _enableInMp = Create("EnableInMultiplayer", def: true, "Enable in multiplayer", "ON (default): in a co-op session, cosmetic culling still runs locally (safe - it only hides distant NPCs on YOUR screen), and the deeper movement/schedule culling runs ONLY on the host (who owns NPC simulation). OFF: the mod does nothing in multiplayer."); _cosmeticDistance = Create("CosmeticDistance", 40f, "Cosmetic cull distance (m)", "Off-screen NPCs farther than this are hidden (renderer off) to save animation/draw cost. They keep moving and behaving normally. Lower = more FPS / nearer horizon. Clamped 15-300.", (ValueValidator)(object)new ValueRange<float>(15f, 300f)); _deepDistance = Create("DeepDistance", 80f, "Deep cull distance (m)", "Off-screen, idle, non-essential NPCs farther than this also have their movement + schedule paused (the biggest saving). They catch up to their correct schedule position when you return. Dealers/employees/customers mid-task, story NPCs, and anyone near any player are never deep-culled. Host-only in multiplayer. Clamped 30-500.", (ValueValidator)(object)new ValueRange<float>(30f, 500f)); _hysteresis = Create("Hysteresis", 8f, "Hysteresis margin (m)", "Dead-zone around each distance boundary so NPCs don't flicker between states when you stand near an edge. Clamped 0-50.", (ValueValidator)(object)new ValueRange<float>(0f, 50f)); _budgetPerFrame = Create("BudgetPerFrame", 32, "NPCs re-evaluated per frame", "The mod re-checks this many NPCs each frame on a rolling cursor (so the whole population is covered every few frames) - keeps the mod's own cost flat. Lower = cheaper but slower to react. Clamped 4-256.", (ValueValidator)(object)new ValueRange<int>(4, 256)); _useCosmeticCull = Create("UseCosmeticCull", def: true, "Use cosmetic culling (hide distant NPCs)", "ON (default): hide off-screen distant NPCs (renderer off). Purely cosmetic and always safe. OFF: never hide NPCs (disables the cheapest, safest tier)."); _useDeepCull = Create("UseDeepCull", def: true, "Use deep culling (pause movement + schedule)", "ON (default): also pause movement + schedule for far, idle, non-essential NPCs (recovers the biggest cost, the NavMeshAgent). Fully reversible with schedule catch-up. OFF: cosmetic culling only (maximally conservative - zero AI impact, smaller FPS gain)."); _respectOnScreen = Create("RespectOnScreen", def: true, "Never cull on-screen NPCs", "ON (default): an NPC roughly in front of the camera is never culled, even when far, so you never see a visible NPC freeze or pop. Recommended ON."); _throttleMovement = Create("ThrottleFarMovement", def: true, "Throttle far NPC movement checks", "ON (default): NPCs farther than the cosmetic distance, and NPCs that stand still on a destination they cannot reach, run their movement check 5 times a second instead of every physics step. A stuck NPC re-plans its path in each of those checks. Host-only in multiplayer. OFF: every NPC checks every step, as in vanilla."); _freeOldSceneMemory = Create("FreeOldSceneMemory", def: true, "Free the memory of old scenes", "ON (default): every time a save loads, what is left of the previous scene is released. OFF: as in vanilla, memory grows with every load until the game restarts."); _showFps = Create("ShowFpsCounter", def: false, "Show FPS counter", "Small on-screen FPS readout (top-right). OFF by default. Applies live."); } } private static MelonPreferences_Entry<T> Create<T>(string id, T def, string name, string desc = null, ValueValidator validator = null) { if (validator != null) { return _category.CreateEntry<T>(id, def, name, desc, false, false, validator); } return _category.CreateEntry<T>(id, def, name, desc, false, false, (ValueValidator)null, (string)null); } } } namespace Siesta.Compat { [SourceNotice("Siesta by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-Siesta, not decompiled code.")] internal static class HeapScrub { private struct Section { public ulong Start; public uint Size; public unsafe byte* Bytes; } private struct Field { public uint Offset; public uint TypeIndex; public unsafe byte* Name; public byte IsStatic; } private struct MetaType { public uint Flags; public unsafe Field* Fields; public uint FieldCount; public uint StaticsSize; public unsafe byte* Statics; public uint BaseOrElement; public unsafe byte* Name; public unsafe byte* Assembly; public ulong TypeInfo; public uint Size; } private struct Snapshot { public uint SectionCount; public unsafe Section* Sections; public uint StackCount; public unsafe Section* Stacks; public uint TypeCount; public unsafe MetaType* Types; public uint HandleCount; public unsafe ulong* Handles; public uint PointerSize; public uint ObjectHeader; public uint ArrayHeader; public uint BoundsOffset; public uint SizeOffset; public uint Granularity; public unsafe void* User; } private sealed class Walk { private unsafe readonly Snapshot* _snap; private readonly int _typeCount; private readonly int _header; private readonly Dictionary<ulong, int> _byInfo; private readonly ulong[] _sectionStarts; private readonly int[] _sectionIndex; private readonly bool[] _isUnity; private readonly bool[] _isDelegate; private readonly bool[] _isClosure; private readonly bool[] _isNpc; private readonly uint[] _multicastOffset; private readonly int[][] _instanceSlots; private readonly int[][] _elementSlots; private readonly Dictionary<ulong, bool> _returnsInMemory = new Dictionary<ulong, bool>(); private readonly HashSet<ulong> _seen = new HashSet<ulong>(); private readonly Stack<ulong> _todo = new Stack<ulong>(); private readonly HashSet<ulong> _dead = new HashSet<ulong>(); private readonly HashSet<ulong> _kept = new HashSet<ulong>(); private readonly List<ulong> _delegates = new List<ulong>(); private unsafe Section* _last; private ulong _cached; private ulong _target; private ulong _invoke; private ulong _methodPtr; private ulong _code; private ulong _method; private long _reached; private long _unknown; private long _closures; internal unsafe Walk(Snapshot* snap) { _snap = snap; _typeCount = (int)snap->TypeCount; _header = (int)snap->ObjectHeader; _byInfo = new Dictionary<ulong, int>(_typeCount); _isUnity = new bool[_typeCount]; _isDelegate = new bool[_typeCount]; _isClosure = new bool[_typeCount]; _isNpc = new bool[_typeCount]; _multicastOffset = new uint[_typeCount]; _instanceSlots = new int[_typeCount][]; _elementSlots = new int[_typeCount][]; _sectionStarts = new ulong[snap->SectionCount]; _sectionIndex = new int[snap->SectionCount]; for (uint num = 0u; num < snap->SectionCount; num++) { _sectionStarts[num] = snap->Sections[num].Start; _sectionIndex[num] = (int)num; } Array.Sort(_sectionStarts, _sectionIndex); } internal unsafe string Cut() { if (!FindTypes()) { return "no change, the runtime's type table has an unexpected layout"; } Roots(); Follow(); _dead.ExceptWith(_kept); long num = 0L; long num2 = 0L; IntPtr intPtr = (nint)(delegate*<nint>)(&Nothing); foreach (ulong @delegate in _delegates) { uint num3 = _multicastOffset[_byInfo[Peek(@delegate)]]; if ((num3 == uint.MaxValue || Peek(@delegate + num3) == 0L) && Doomed(Peek(@delegate + _target), 2)) { if (ReturnsInMemory(Peek(@delegate + _method))) { num2++; continue; } *(long*)(@delegate + _target) = 0L; *(long*)(@delegate + _code) = 0L; *(long*)(@delegate + _invoke) = (long)intPtr; *(long*)(@delegate + _methodPtr) = (long)intPtr; num++; } } long num4 = 0L; foreach (ulong item in _dead) { int[] array = _instanceSlots[_byInfo[Peek(item)]]; foreach (int num5 in array) { ulong* ptr = (ulong*)(item + (ulong)num5); if (*ptr != 0L) { *ptr = 0uL; num4++; } } } return $"{_dead.Count} destroyed objects let go of {num4} references, {num} handlers off ({_closures} through a closure, {num2} left on because of their return type), {_kept.Count} removed NPC components kept, {_reached} objects walked, {_unknown} references without a known type"; } private unsafe bool FindTypes() { int num = -1; int num2 = -1; int num3 = -1; for (int i = 0; i < _typeCount; i++) { _byInfo[_snap->Types[i].TypeInfo] = i; switch (Str(_snap->Types[i].Name)) { case "UnityEngine.Object": num = i; break; case "System.Delegate": num2 = i; break; case "ScheduleOne.NPCs.NPC": num3 = i; break; } } if (num3 < 0) { Instance log = Core.Log; if (log != null) { log.Warning("Old scene memory: ScheduleOne.NPCs.NPC is missing from the type table, removed NPC components lose their fields."); } } if (num < 0 || num2 < 0) { Instance log2 = Core.Log; if (log2 != null) { log2.Warning($"Old scene memory: base types missing, UnityEngine.Object {num}, System.Delegate {num2}."); } return false; } _cached = Offset(num, "m_CachedPtr"); _target = Offset(num2, "m_target"); _invoke = Offset(num2, "invoke_impl"); _methodPtr = Offset(num2, "method_ptr"); _code = Offset(num2, "method_code"); _method = Offset(num2, "method"); if (_cached == uint.MaxValue || _target == uint.MaxValue || _invoke == uint.MaxValue || _methodPtr == uint.MaxValue || _code == uint.MaxValue || _method == uint.MaxValue) { Instance log3 = Core.Log; if (log3 != null) { log3.Warning($"Old scene memory: fields missing, m_CachedPtr {_cached}, m_target {_target}, invoke_impl {_invoke}, method_ptr {_methodPtr}, method_code {_code}, method {_method}."); } return false; } for (int j = 0; j < _typeCount; j++) { MetaType* ptr = _snap->Types + j; _multicastOffset[j] = uint.MaxValue; if ((ptr->Flags & 3) != 0) { continue; } _isClosure[j] = Str(ptr->Name).Contains("<>c__DisplayClass"); List<int> list = new List<int>(); int num4 = j; int num5 = 0; while (num4 >= 0 && num4 < _typeCount && num5++ < 64) { MetaType* ptr2 = _snap->Types + num4; if (num4 == num) { _isUnity[j] = true; } if (num4 == num2) { _isDelegate[j] = true; } if (num4 == num3) { _isNpc[j] = true; } for (uint num6 = 0u; num6 < ptr2->FieldCount; num6++) { Field* ptr3 = ptr2->Fields + num6; if (ptr3->IsStatic == 0 && ptr3->TypeIndex < (uint)_typeCount) { if (Str(ptr3->Name) == "delegates") { _multicastOffset[j] = ptr3->Offset; } if ((_snap->Types[ptr3->TypeIndex].Flags & 1) != 0) { AddStruct((int)ptr3->TypeIndex, (int)ptr3->Offset, list, 0); } else { list.Add((int)ptr3->Offset); } } } if (ptr2->BaseOrElement == (uint)num4 || ptr2->BaseOrElement >= (uint)_typeCount) { break; } num4 = (int)ptr2->BaseOrElement; } _instanceSlots[j] = list.ToArray(); } return true; } private unsafe uint Offset(int type, string field) { for (uint num = 0u; num < _snap->Types[type].FieldCount; num++) { if (Str(_snap->Types[type].Fields[num].Name) == field) { return _snap->Types[type].Fields[num].Offset; } } return uint.MaxValue; } private unsafe void AddStruct(int type, int at, List<int> into, int depth) { if (depth > 8) { return; } MetaType* ptr = _snap->Types + type; for (uint num = 0u; num < ptr->FieldCount; num++) { Field* ptr2 = ptr->Fields + num; if (ptr2->IsStatic == 0 && ptr2->TypeIndex < (uint)_typeCount && ptr2->TypeIndex != (uint)type) { if ((_snap->Types[ptr2->TypeIndex].Flags & 1) != 0) { AddStruct((int)ptr2->TypeIndex, at + (int)ptr2->Offset - _header, into, depth + 1); } else { into.Add(at + (int)ptr2->Offset - _header); } } } } private unsafe ulong Peek(ulong address) { if (_last == null || address < _last->Start || address + 8 > _last->Start + _last->Size) { int num = Array.BinarySearch(_sectionStarts, address); if (num < 0) { num = ~num - 1; } if (num < 0) { return 0uL; } _last = _snap->Sections + _sectionIndex[num]; if (address + 8 > _last->Start + _last->Size) { return 0uL; } } return *(ulong*)(_last->Bytes + (address - _last->Start)); } private void Visit(ulong address) { if (address != 0L && (address & 7) == 0L && _seen.Add(address)) { _todo.Push(address); } } private unsafe void Roots() { for (uint num = 0u; num < _snap->HandleCount; num++) { Visit(_snap->Handles[num]); } List<int> list = new List<int>(); for (int i = 0; i < _typeCount; i++) { MetaType* ptr = _snap->Types + i; if (ptr->StaticsSize == 0 || ptr->Statics == null) { continue; } for (uint num2 = 0u; num2 < ptr->FieldCount; num2++) { Field* ptr2 = ptr->Fields + num2; if (ptr2->IsStatic == 0 || ptr2->TypeIndex >= (uint)_typeCount) { continue; } list.Clear(); if ((_snap->Types[ptr2->TypeIndex].Flags & 1) != 0) { AddStruct((int)ptr2->TypeIndex, (int)ptr2->Offset, list, 0); } else { list.Add((int)ptr2->Offset); } foreach (int item in list) { if (item >= 0 && item + 8 <= ptr->StaticsSize) { Visit(*(ulong*)(ptr->Statics + item)); } } } } } private unsafe void Follow() { while (_todo.Count > 0) { ulong num = _todo.Pop(); if (!_byInfo.TryGetValue(Peek(num), out var value)) { _unknown++; continue; } MetaType* ptr = _snap->Types + value; _reached++; if ((ptr->Flags & 2) != 0) { FollowArray(num, ptr); continue; } int[] array = _instanceSlots[value]; if (array == null) { continue; } if (_isUnity[value] && Peek(num + _cached) == 0L) { _dead.Add(num); continue; } if (_isDelegate[value]) { _delegates.Add(num); } if (_isUnity[value]) { KeepRemovedNpcs(num, array); } int[] array2 = array; foreach (int num2 in array2) { Visit(Peek(num + (ulong)num2)); } } } private void KeepRemovedNpcs(ulong holder, int[] slots) { foreach (int num in slots) { ulong num2 = Peek(holder + (ulong)num); if (num2 != 0L && (num2 & 7) == 0L && _byInfo.TryGetValue(Peek(num2), out var value) && _isNpc[value] && Peek(num2 + _cached) == 0L) { _kept.Add(num2); } } } private unsafe void FollowArray(ulong at, MetaType* t) { ulong num = Peek(at + _snap->SizeOffset); int baseOrElement = (int)t->BaseOrElement; if (num >= 2147483648u || baseOrElement < 0 || baseOrElement >= _typeCount) { return; } ulong num2 = at + _snap->ArrayHeader; if ((_snap->Types[baseOrElement].Flags & 1) == 0) { for (ulong num3 = 0uL; num3 < num; num3++) { Visit(Peek(num2 + num3 * 8)); } return; } int[] array = _elementSlots[baseOrElement]; if (array == null) { List<int> list = new List<int>(); AddStruct(baseOrElement, 0, list, 0); array = (_elementSlots[baseOrElement] = list.ToArray()); } if (array.Length == 0) { return; } ulong num4 = _snap->Types[baseOrElement].Size; for (ulong num5 = 0uL; num5 < num; num5++) { int[] array2 = array; foreach (int num6 in array2) { Visit(Peek(num2 + num5 * num4 + (ulong)num6)); } } } private bool Doomed(ulong target, int depth) { if (target == 0L) { return false; } if (_dead.Contains(target)) { return true; } if (depth == 0) { return false; } if (!_byInfo.TryGetValue(Peek(target), out var value) || !_isClosure[value]) { return false; } int[] array = _instanceSlots[value]; foreach (int num in array) { if (Doomed(Peek(target + (ulong)num), depth - 1)) { _closures++; return true; } } return false; } private bool ReturnsInMemory(ulong method) { if (_returnsInMemory.TryGetValue(method, out var value)) { return value; } bool flag = true; if (method != 0L) { IntPtr intPtr = IL2CPP.il2cpp_method_get_return_type((IntPtr)(long)method); IntPtr intPtr2 = ((intPtr == IntPtr.Zero) ? IntPtr.Zero : IL2CPP.il2cpp_class_from_type(intPtr)); if (intPtr2 != IntPtr.Zero) { flag = IL2CPP.il2cpp_class_is_valuetype(intPtr2) && IL2CPP.il2cpp_class_instance_size(intPtr2) - _header > 8; } } _returnsInMemory[method] = flag; return flag; } } private const uint FlagValueType = 1u; private const uint FlagArray = 2u; private const long Mb = 1048576L; internal static bool Off; internal static void Apply(Harmony harmony) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown try { MethodInfo methodInfo = AccessTools.Method(typeof(LoadManager), "CleanUp", (Type[])null, (Type[])null); harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(HeapScrub).GetMethod("AfterCleanUp", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } catch (Exception ex) { Instance log = Core.Log; if (log != null) { log.Warning("Old scene memory fix not applied, the heap grows with every load: " + ex.Message); } } } private static void AfterCleanUp() { if (Off || !Preferences.FreeOldSceneMemory) { Instance log = Core.Log; if (log != null) { log.Msg($"Old scene memory: switched off, heap {GC.GetTotalMemory(false) / 1048576} MB, nothing released."); } } else { Run(); } } internal unsafe static void Run() { try { Stopwatch stopwatch = Stopwatch.StartNew(); long value = GC.GetTotalMemory(false) / 1048576; int value2 = ForgetDestroyedSightables(); Snapshot* ptr = (Snapshot*)(void*)IL2CPP.il2cpp_capture_memory_snapshot(); if (ptr == null) { Instance log = Core.Log; if (log != null) { log.Warning("Old scene memory: the runtime gave no memory snapshot, nothing released."); } return; } string value3; try { value3 = new Walk(ptr).Cut(); } finally { IL2CPP.il2cpp_free_captured_memory_snapshot((IntPtr)ptr); } GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); long value4 = GC.GetTotalMemory(false) / 1048576; Instance log2 = Core.Log; if (log2 != null) { log2.Msg($"Old scene memory: heap {value} -> {value4} MB, {value3}, {value2} destroyed sightables left the vision cones, {stopwatch.ElapsedMilliseconds} ms"); } } catch (Exception ex) { Instance log3 = Core.Log; if (log3 != null) { log3.Error("Old scene memory: failed, the heap keeps what it holds: " + ex); } } } private static int ForgetDestroyedSightables() { int num = 0; foreach (VisionCone item in Resources.FindObjectsOfTypeAll<VisionCone>()) { List<ISightable> sightablesOfInterest = item.sightablesOfInterest; for (int num2 = (sightablesOfInterest?.Count ?? 0) - 1; num2 >= 0; num2--) { ISightable val = sightablesOfInterest[num2]; if (IsDestroyed(val)) { sightablesOfInterest.RemoveAt(num2); item.stateSettings?.Remove(val); item.sightableDatas?.Remove(val); num++; } } List<VisionEvent> activeVisionEvents = item.activeVisionEvents; for (int num3 = (activeVisionEvents?.Count ?? 0) - 1; num3 >= 0; num3--) { if (IsDestroyed(activeVisionEvents[num3].Target)) { activeVisionEvents.RemoveAt(num3); } } } return num; } private static bool IsDestroyed(ISightable sightable) { if (sightable == null) { return false; } Object val = ((Il2CppObjectBase)sightable).TryCast<Object>(); if (val != null) { return val == (Object)null; } return false; } [UnmanagedCallersOnly] private static nint Nothing() { return 0; } private unsafe static string Str(byte* p) { object obj; if (p != null) { obj = Marshal.PtrToStringUTF8((IntPtr)p); if (obj == null) { return ""; } } else { obj = ""; } return (string)obj; } } [SourceNotice("Siesta by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-Siesta, not decompiled code.")] internal static class LogHandlerLeak { internal static bool Off; internal static int Skipped; internal static void Apply(Harmony harmony) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown try { MethodInfo methodInfo = AccessTools.Method(typeof(DebugLogCollector), "Awake", (Type[])null, (Type[])null); harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(LogHandlerLeak).GetMethod("BeforeAwake", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } catch (Exception ex) { Instance log = Core.Log; if (log != null) { log.Warning("Log handler fix not applied, the game keeps adding handlers: " + ex.Message); } } } private static bool BeforeAwake() { if (Off) { return true; } try { if (!IsSubscribed()) { return true; } if (Skipped++ == 0) { Instance log = Core.Log; if (log != null) { log.Msg("Log handler fix active: the game's log collector stays subscribed once."); } } return false; } catch (Exception ex) { Instance log2 = Core.Log; if (log2 != null) { log2.Warning("Log handler fix skipped for this scene: " + ex.Message); } return true; } } private static bool IsSubscribed() { LogCallback s_LogCallbackHandler = Application.s_LogCallbackHandler; if ((Delegate)(object)s_LogCallbackHandler == (Delegate)null) { return false; } foreach (Delegate item in (Il2CppArrayBase<Delegate>)(object)((Delegate)s_LogCallbackHandler).GetInvocationList()) { Object target = item.Target; if ((Object)(object)((target != null) ? ((Il2CppObjectBase)target).TryCast<DebugLogCollector>() : null) != (Object)null) { return true; } } return false; } } [SourceNotice("Siesta by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-Siesta, not decompiled code.")] internal static class MoreNpcsCompat { private const string WatcherType = "PPHylandHandoverWarning"; private const string WatcherMethod = "RefreshThrottled"; private static MethodInfo _watcher; private static Action _invoke; private static bool _givenUp; internal static void Apply(Harmony harmony) { //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Expected O, but got Unknown try { Assembly assembly = FindAssembly("MoreNPCs"); if (assembly == null) { return; } MethodInfo methodInfo = assembly.GetType("MoreNPCs.Core", throwOnError: false)?.GetMethod("OnUpdate", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (methodInfo == null) { Instance log = Core.Log; if (log != null) { log.Warning("MoreNPCs compat: MoreNPCs.Core.OnUpdate not found - skipped."); } } else { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(MoreNpcsCompat).GetMethod("Transpile", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null); } } catch (Exception ex) { Instance log2 = Core.Log; if (log2 != null) { log2.Warning("MoreNPCs compat failed: " + ex.Message); } } } private static IEnumerable<CodeInstruction> Transpile(IEnumerable<CodeInstruction> instructions) { List<CodeInstruction> list = new List<CodeInstruction>(instructions); MethodInfo method = typeof(MoreNpcsCompat).GetMethod("Guarded", BindingFlags.Static | BindingFlags.NonPublic); for (int i = 0; i < list.Count; i++) { CodeInstruction val = list[i]; if ((!(val.opcode != OpCodes.Call) || !(val.opcode != OpCodes.Callvirt)) && val.operand is MethodInfo methodInfo && !(methodInfo.Name != "RefreshThrottled") && !(methodInfo.DeclaringType == null) && !(methodInfo.DeclaringType.Name != "PPHylandHandoverWarning") && methodInfo.GetParameters().Length == 0 && methodInfo.IsStatic) { _watcher = methodInfo; val.opcode = OpCodes.Call; val.operand = method; return list; } } Instance log = Core.Log; if (log != null) { log.Msg("MoreNPCs compat: no PPHylandHandoverWarning.RefreshThrottled call in OnUpdate - nothing to guard, MoreNPCs left untouched."); } return list; } private static void Guarded() { if (_givenUp) { return; } try { if (_invoke == null) { _invoke = (Action)Delegate.CreateDelegate(typeof(Action), _watcher); } _invoke(); } catch (Exception ex) { _givenUp = true; Instance log = Core.Log; if (log != null) { log.Warning("MoreNPCs' PPHylandHandoverWarning cannot run on this install (" + ex.GetType().Name + ") - Siesta will not call it again this session. This usually means the CrossCompat/Mono MoreNPCs build is installed on a standard IL2CPP game; the rest of MoreNPCs is unaffected."); } } } private static Assembly FindAssembly(string simpleName) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; i++) { try { if (assemblies[i].GetName().Name == simpleName) { return assemblies[i]; } } catch { } } return null; } } [SourceNotice("Siesta by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-Siesta, not decompiled code.")] internal static class MoreNpcsScanCompat { private const float ListingRetrySeconds = 60f; private static readonly Dictionary<string, Transform> _listingHit = new Dictionary<string, Transform>(StringComparer.Ordinal); private static readonly Dictionary<string, float> _listingMiss = new Dictionary<string, float>(StringComparer.Ordinal); private static Scene _ddolScene; private static bool _ddolKnown; private static bool _rootsFailed; private static bool _listingFailed; private static MethodInfo _rootsTarget; private static MethodInfo _listingTarget; private static MethodInfo _dealersTarget; internal static bool Off = false; internal static void Apply(Harmony harmony) { Assembly assembly = FindAssembly("MoreNPCs_Il2cpp") ?? FindAssembly("MoreNPCs"); if (assembly == null) { Instance log = Core.Log; if (log != null) { log.Msg("More NPCs scan compat: More NPCs is not installed, nothing to patch."); } return; } Type? type = assembly.GetType("MoreNPCs.Utils.GameDealerFinder", throwOnError: false); _rootsTarget = type?.GetMethod("GetRootTransforms", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _dealersTarget = type?.GetMethod("GetRecruitedDealersFromGame", BindingFlags.Static | BindingFlags.Public); if (_rootsTarget != null && (_rootsTarget.ReturnType != typeof(Transform[]) || _rootsTarget.GetParameters().Length != 0)) { Instance log2 = Core.Log; if (log2 != null) { log2.Warning("More NPCs scan compat: GameDealerFinder.GetRootTransforms has another signature, left untouched."); } _rootsTarget = null; } else { Patch(harmony, _rootsTarget, "RootsPrefix", "GameDealerFinder.GetRootTransforms"); } _listingTarget = assembly.GetType("MoreNPCs.Manager.ReOfficeWhiteboardDisplay", throwOnError: false)?.GetMethod("FindPropertyListingTransform", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (_listingTarget != null && (_listingTarget.ReturnType != typeof(Transform) || _listingTarget.GetParameters().Length != 1 || _listingTarget.GetParameters()[0].ParameterType != typeof(string))) { Instance log3 = Core.Log; if (log3 != null) { log3.Warning("More NPCs scan compat: ReOfficeWhiteboardDisplay.FindPropertyListingTransform has another signature, left untouched."); } _listingTarget = null; } else { Patch(harmony, _listingTarget, "ListingPrefix", "ReOfficeWhiteboardDisplay.FindPropertyListingTransform"); } } private static void Patch(Harmony harmony, MethodInfo target, string prefix, string label) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Expected O, but got Unknown if (target == null) { Instance log = Core.Log; if (log != null) { log.Warning("More NPCs scan compat: " + label + " not found, More NPCs keeps its own search."); } return; } try { harmony.Patch((MethodBase)target, new HarmonyMethod(typeof(MoreNpcsScanCompat).GetMethod(prefix, BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Instance log2 = Core.Log; if (log2 != null) { log2.Msg("More NPCs scan compat: " + label + " replaced."); } } catch (Exception ex) { Instance log3 = Core.Log; if (log3 != null) { log3.Error("More NPCs scan compat: patching " + label + " failed, More NPCs keeps its own search: " + ex.Message); } } } private static bool RootsPrefix(ref Transform[] __result) { if (Off || _rootsFailed) { return true; } try { __result = DealerRoots(); return false; } catch (Exception ex) { _rootsFailed = true; Instance log = Core.Log; if (log != null) { log.Warning("More NPCs scan compat: dealer search threw " + ex.GetType().Name + " (" + ex.Message + "), More NPCs uses its own search for the rest of the session."); } return true; } } internal static Transform[] DealerRoots() { List<Transform> list = new List<Transform>(); List<NPC> nPCRegistry = NPCManager.NPCRegistry; if (nPCRegistry == null) { return list.ToArray(); } int count = nPCRegistry.Count; for (int i = 0; i < count; i++) { NPC val = nPCRegistry[i]; if (!((Object)(object)val == (Object)null) && ((Component)val).gameObject.activeInHierarchy) { Transform transform = ((Component)val).transform; if (!((Object)(object)transform.parent != (Object)null) && !((Object)(object)((Il2CppObjectBase)val).TryCast<Dealer>() == (Object)null)) { list.Add(transform); } } } return list.ToArray(); } private static bool ListingPrefix(string listingName, ref Transform __result) { if (Off || _listingFailed) { return true; } try { __result = FindListing(listingName); return false; } catch (Exception ex) { _listingFailed = true; Instance log = Core.Log; if (log != null) { log.Warning("More NPCs scan compat: listing search threw " + ex.GetType().Name + " (" + ex.Message + "), More NPCs uses its own search for the rest of the session."); } return true; } } private static Transform FindListing(string name) { if (string.IsNullOrEmpty(name)) { return null; } if (_listingHit.TryGetValue(name, out var value) && (Object)(object)value != (Object)null) { return value; } if (_listingMiss.TryGetValue(name, out var value2) && Time.time - value2 < 60f) { return null; } GameObject val = ((name.IndexOf('/') < 0) ? GameObject.Find(name) : null); Transform val2 = (((Object)(object)val != (Object)null) ? val.transform : SweepLoadedScenes(name)); if ((Object)(object)val2 != (Object)null) { _listingHit[name] = val2; _listingMiss.Remove(name); } else { _listingMiss[name] = Time.time; } return val2; } private static Transform SweepLoadedScenes(string name) { //IL_002a: 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) int num = name.IndexOf(' '); string family = ((num > 0) ? name.Substring(0, num + 1) : null); Transform found = null; int sceneCount = SceneManager.sceneCount; for (int i = 0; i < sceneCount; i++) { Sweep(SceneManager.GetSceneAt(i), name, family, ref found); } Sweep(DontDestroyScene(), name, family, ref found); return found; } private static void Sweep(Scene scene, string name, string family, ref Transform found) { if (!((Scene)(ref scene)).IsValid() || !((Scene)(ref scene)).isLoaded) { return; } Il2CppReferenceArray<GameObject> rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects(); if (rootGameObjects == null) { return; } foreach (GameObject item in (Il2CppArrayBase<GameObject>)(object)rootGameObjects) { if ((Object)(object)item == (Object)null) { continue; } foreach (Transform componentsInChild in item.GetComponentsInChildren<Transform>(true)) { string name2 = ((Object)componentsInChild).name; if ((Object)(object)found == (Object)null && name2 == name) { found = componentsInChild; } if (family != null && name2.StartsWith(family, StringComparison.Ordinal) && !_listingHit.ContainsKey(name2)) { _listingHit[name2] = componentsInChild; } } } } private static Scene DontDestroyScene() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown //IL_001d: 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_002d: Expected O, but got Unknown //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) if (_ddolKnown) { return _ddolScene; } GameObject val = new GameObject("Siesta_DontDestroyProbe"); Object.DontDestroyOnLoad((Object)val); _ddolScene = val.scene; Object.Destroy((Object)val); _ddolKnown = true; return _ddolScene; } internal static void ClearCaches() { _listingHit.Clear(); _listingMiss.Clear(); } private static Assembly FindAssembly(string simpleName) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; i++) { try { if (assemblies[i].GetName().Name == simpleName) { return assemblies[i]; } } catch (Exception ex) { Instance log = Core.Log; if (log != null) { log.Warning("More NPCs scan compat: skipped an assembly whose name cannot be read: " + ex.Message); } } } return null; } } [SourceNotice("Siesta by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-Siesta, not decompiled code.")] internal static class Net { internal static bool IsMultiplayer() { try { Lobby instance = Singleton<Lobby>.Instance; if ((Object)(object)instance == (Object)null) { return false; } return instance.IsInLobby && instance.PlayerCount > 1; } catch { return false; } } internal static bool IsAuthoritative() { try { Lobby instance = Singleton<Lobby>.Instance; if ((Object)(object)instance == (Object)null || !instance.IsInLobby) { return true; } return instance.IsHost; } catch { return true; } } } [SourceNotice("Siesta by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-Siesta, not decompiled code.")] internal static class StayInBuildingRepath { private static bool _patched; private static bool _failed; internal static bool Off; internal static void Apply(Harmony harmony) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown try { MethodInfo methodInfo = AccessTools.Method(typeof(NPCEvent_StayInBuilding), "OnActiveTick", (Type[])null, (Type[])null); if (methodInfo == null) { Instance log = Core.Log; if (log != null) { log.Warning("Stay-in-building repath: NPCEvent_StayInBuilding.OnActiveTick not found, NPCs walking to a building keep re-asking for their path."); } return; } harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(StayInBuildingRepath), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _patched = true; Instance log2 = Core.Log; if (log2 != null) { log2.Msg("Stay-in-building repath: on."); } } catch (Exception ex) { Instance log3 = Core.Log; if (log3 != null) { log3.Error("Stay-in-building repath: patching failed, NPCs walking to a building keep re-asking for their path: " + ex.Message); } } } private static bool Prefix(NPCEvent_StayInBuilding __instance, out long __state) { //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) __state = Stopwatch.GetTimestamp(); if (!_patched || _failed) { return true; } try { NPC npc = ((NPCAction)__instance).npc; NPCMovement val = (((Object)(object)npc != (Object)null) ? npc.Movement : null); if ((Object)(object)val == (Object)null) { return true; } NavMeshAgent agent = val._agent; bool flag = (Object)(object)agent != (Object)null && agent.pathPending; if (Off || !flag || !((NPCAction)__instance).IsActive) { return true; } if (__instance.InBuilding || __instance.IsEntering) { return true; } if ((Object)(object)__instance.Building == (Object)null) { return true; } Transform entryPoint = __instance.GetEntryPoint(); if ((Object)(object)entryPoint == (Object)null) { return true; } if (Vector3.Distance(val.CurrentDestination, entryPoint.position) > 2f) { return true; } if (NetworkSingleton<TimeManager>.InstanceExists && NetworkSingleton<TimeManager>.Instance.CurrentTime == ((NPCAction)__instance).GetEndTime()) { return true; } return false; } catch (Exception ex) { _failed = true; Instance log = Core.Log; if (log != null) { log.Error("Stay-in-building repath: check failed, turned off for this session: " + ex.Message); } LodLog.Vanilla("stay-in-building repath", 0, ex); return true; } } } }