Decompiled source of Siesta v1.3.2

Siesta.dll

Decompiled 5 days ago
using 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;
			}
		}
	}
}