Decompiled source of Siesta v1.0.0

Siesta.dll

Decompiled 10 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppScheduleOne.DevUtilities;
using Il2CppScheduleOne.Economy;
using Il2CppScheduleOne.Employees;
using Il2CppScheduleOne.NPCs;
using Il2CppScheduleOne.NPCs.Behaviour;
using Il2CppScheduleOne.Networking;
using Il2CppScheduleOne.PlayerScripts;
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;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(Core), "Siesta", "1.0.0", "DooDesch", "https://github.com/DooDesch/ScheduleOne-Siesta")]
[assembly: MelonGame("TVGS", "Schedule I")]
[assembly: MelonOptionalDependencies(new string[] { "ModManager&PhoneApp" })]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("Siesta")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+5262514cead1cb81621057a8fb8c2e6329f883d8")]
[assembly: AssemblyProduct("Siesta")]
[assembly: AssemblyTitle("Siesta")]
[assembly: NeutralResourcesLanguage("en-US")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Siesta
{
	public sealed class Core : MelonMod
	{
		private bool _inWorld;

		private float _teleElapsed;

		private int _teleFrames;

		private float _teleMaxDt;

		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();
			if (Preferences.MoreNpcsCompat)
			{
				MoreNpcsCompat.Apply(((MelonBase)this).HarmonyInstance);
			}
			GameLifecycle.OnSaveStart += delegate
			{
				LodController.RestoreAll("save starting");
			};
			GameLifecycle.OnPreSceneChange += delegate
			{
				LodController.RestoreAll("scene changing");
			};
			Log.Msg("Siesta v1.0.0 - NPC LOD active.");
		}

		public override void OnSceneWasLoaded(int buildIndex, string sceneName)
		{
			_inWorld = sceneName == "Main";
		}

		public override void OnSceneWasUnloaded(int buildIndex, string sceneName)
		{
			_inWorld = false;
			LodController.Reset();
		}

		public override void OnUpdate()
		{
			if (_inWorld)
			{
				LodController.Tick();
				TelemetryTick();
			}
		}

		public override void OnGUI()
		{
			if (_inWorld && Preferences.ShowFpsCounter)
			{
				FpsCounter.Draw();
			}
		}

		public override void OnApplicationQuit()
		{
			LodController.RestoreAll("application quit");
		}

		public override void OnDeinitializeMelon()
		{
			LodController.RestoreAll("melon unload");
		}

		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);
				Log.Msg($"[telemetry] fps={value:F0} (min {value2:F0})  npcs={full + cosmetic + deep}  full={full} cosmetic={cosmetic} deep={deep}");
			}
			catch
			{
			}
			finally
			{
				_teleElapsed = 0f;
				_teleFrames = 0;
				_teleMaxDt = 0f;
			}
		}
	}
}
namespace Siesta.UI
{
	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
{
	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 (npc.IsImportant)
				{
					return "important";
				}
				if (ExemptOnAnyBehaviour)
				{
					NPCBehaviour behaviour = npc.Behaviour;
					if ((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;
			}
		}
	}
	internal static class LodController
	{
		internal enum Control
		{
			Auto,
			ForceFull,
			ForceCosmetic,
			ForceDeep
		}

		private static int _cursor;

		private static readonly HashSet<int> _failed = new HashSet<int>();

		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;

		internal static void Tick()
		{
			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();
			int num = Math.Min(Preferences.BudgetPerFrame, count);
			for (int i = 0; i < num; i++)
			{
				if (_cursor >= count)
				{
					_cursor = 0;
				}
				NPC val;
				try
				{
					val = nPCRegistry[_cursor];
				}
				catch
				{
					_cursor++;
					continue;
				}
				_cursor++;
				if (!((Object)(object)val == (Object)null))
				{
					Evaluate(val, authoritative);
				}
			}
		}

		private static void Evaluate(NPC npc, bool authoritative)
		{
			//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_004a: 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)
			int instanceID;
			try
			{
				instanceID = ((Object)npc).GetInstanceID();
			}
			catch
			{
				return;
			}
			NpcModState orAdd = LodRegistry.GetOrAdd(instanceID, npc);
			if (_failed.Contains(instanceID))
			{
				if (orAdd.Tier != LodState.Full)
				{
					LodLevers.ForceFull(npc, orAdd);
				}
				return;
			}
			orAdd.Resolve(npc);
			Vector3 centerPoint;
			try
			{
				centerPoint = npc.CenterPoint;
			}
			catch
			{
				return;
			}
			float d = MinSqrDistToPlayer(centerPoint);
			bool onScreen = IsOnScreen(centerPoint);
			LodState lodState = Decide(npc, orAdd, d, onScreen, authoritative);
			if (lodState == orAdd.Tier)
			{
				return;
			}
			try
			{
				LodLevers.ApplyTier(npc, orAdd, lodState, authoritative);
				if (orAdd.WakeFailed)
				{
					orAdd.WakeFailed = false;
					_failed.Add(instanceID);
					Instance log = Core.Log;
					if (log != null)
					{
						log.Warning($"[Siesta] NPC {instanceID} wake failed - forcing permanent exempt (kept Full).");
					}
					LodLevers.ForceFull(npc, orAdd);
				}
			}
			catch (Exception ex)
			{
				Instance log2 = Core.Log;
				if (log2 != null)
				{
					log2.Warning("[Siesta] ApplyTier failed: " + ex.Message);
				}
			}
		}

		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 ex)
			{
				Instance log = Core.Log;
				if (log != null)
				{
					log.Warning("[Siesta] RestoreAll failed: " + ex.Message);
				}
			}
		}

		internal static void Reset()
		{
			LodRegistry.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)
				{
					continue;
				}
				int instanceID;
				try
				{
					instanceID = ((Object)val).GetInstanceID();
				}
				catch
				{
					continue;
				}
				if (!_failed.Contains(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
					{
					}
				}
			}
		}
	}
	internal static class LodLevers
	{
		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);
				}
				break;
			case LodState.Deep:
				if (!st.Hidden)
				{
					Hide(npc, st);
				}
				if (!st.DeepApplied)
				{
					ApplyDeep(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);
				}
				st.Tier = LodState.Full;
			}
			catch (Exception ex)
			{
				Instance log = Core.Log;
				if (log != null)
				{
					log.Warning("[Siesta] ForceFull failed: " + ex.Message);
				}
			}
		}

		private static void Hide(NPC npc, NpcModState st)
		{
			npc.SetVisible(false, false);
			st.Hidden = true;
		}

		private static void Show(NPC npc, NpcModState st)
		{
			npc.SetVisible(true, false);
			st.Hidden = false;
		}

		private static void ApplyDeep(NPC npc, NpcModState st, bool authoritative)
		{
			if (!authoritative)
			{
				Instance log = Core.Log;
				if (log != null)
				{
					log.Warning($"[Siesta] ERROR client-attempted-deepcull npc={st.Id} (ignored)");
				}
				return;
			}
			NPCMovement movement = npc.Movement;
			if ((Object)(object)movement != (Object)null && !movement.IsPaused)
			{
				movement.PauseMovement();
				st.WePausedMovement = true;
			}
			NPCScheduleManager schedule = st.Schedule;
			if ((Object)(object)schedule != (Object)null && schedule.ScheduleEnabled)
			{
				schedule.DisableSchedule();
				st.WeDisabledSchedule = true;
			}
			st.DeepApplied = true;
		}

		private static void RestoreDeep(NPC npc, NpcModState st)
		{
			NPCScheduleManager schedule = st.Schedule;
			if (st.WeDisabledSchedule && (Object)(object)schedule != (Object)null)
			{
				schedule.EnableSchedule();
				schedule.EnforceState(false);
				st.WeDisabledSchedule = false;
			}
			NPCMovement movement = npc.Movement;
			if (st.WePausedMovement && (Object)(object)movement != (Object)null)
			{
				movement.ResumeMovement();
				st.WePausedMovement = false;
				if (!RepairNavMesh(npc, movement, st))
				{
					st.WakeFailed = true;
				}
			}
			st.DeepApplied = false;
		}

		private static bool RepairNavMesh(NPC npc, NPCMovement mv, NpcModState st)
		{
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: 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($"[Siesta] WAKE-FAILED npc={st.Id} reason=agent-null");
					}
					return false;
				}
				if (!((Behaviour)agent).enabled)
				{
					mv.SetAgentEnabled(true);
				}
				if (agent.isOnNavMesh)
				{
					return true;
				}
				mv.WarpToNavMesh();
				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);
					if (agent.isOnNavMesh)
					{
						return true;
					}
				}
				mv.Warp(mv.FootPosition);
				if (agent.isOnNavMesh)
				{
					return true;
				}
				Instance log2 = Core.Log;
				if (log2 != null)
				{
					log2.Warning($"[Siesta] WAKE-FAILED npc={st.Id} reason=still-off-navmesh");
				}
				return false;
			}
			catch (Exception ex)
			{
				Instance log3 = Core.Log;
				if (log3 != null)
				{
					log3.Warning($"[Siesta] WAKE-FAILED npc={st.Id} reason=exception {ex.Message}");
				}
				return false;
			}
		}
	}
	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;
			}
			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();
		}

		internal static void Reset()
		{
			_states.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;
				}
			}
		}
	}
	internal enum LodState
	{
		Full,
		Cosmetic,
		Deep
	}
	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 WakeFailed;

		internal string ExemptReason;

		private bool _resolved;

		internal NPCScheduleManager Schedule;

		internal Dealer Dealer;

		internal Employee Employee;

		internal Customer Customer;

		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
			{
			}
		}
	}
}
namespace Siesta.Config
{
	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> _showFps;

		private static MelonPreferences_Entry<bool> _moreNpcsCompat;

		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 ShowFpsCounter => _showFps?.Value ?? false;

		internal static bool MoreNpcsCompat => _moreNpcsCompat?.Value ?? true;

		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.");
				_showFps = Create("ShowFpsCounter", def: false, "Show FPS counter", "Small on-screen FPS readout (top-right). OFF by default. Applies live.");
				_moreNpcsCompat = Create("MoreNpcsAutoCompat", def: true, "MoreNPCs auto-compat", "ON (default). Siesta auto-detects \"Fannso's MoreNPCs\" and, ONLY if its build is incompatible with this IL2CPP install (it would throw a TypeLoadException every frame), neutralizes its crashing per-frame watcher (MoreNPCs.Core.OnUpdate) so the game stays stable - MoreNPCs' NPCs still spawn via S1API. No effect if MoreNPCs is absent or already compatible. Turn OFF to never apply it. Requires a game restart to take effect.");
			}
		}

		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
{
	internal static class MoreNpcsCompat
	{
		private const string ProbeType = "ScheduleOne.UI.Handover.HandoverScreen";

		internal static void Apply(Harmony harmony)
		{
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Expected O, but got Unknown
			try
			{
				Assembly assembly = FindAssembly("MoreNPCs");
				if (assembly == null)
				{
					return;
				}
				Assembly assembly2 = FindAssembly("Assembly-CSharp");
				if (assembly2 != null && assembly2.GetType("ScheduleOne.UI.Handover.HandoverScreen", throwOnError: false) != null)
				{
					Instance log = Core.Log;
					if (log != null)
					{
						log.Msg("[Siesta] MoreNPCs detected and compatible with this install - compat shim not needed.");
					}
					return;
				}
				MethodInfo methodInfo = assembly.GetType("MoreNPCs.Core", throwOnError: false)?.GetMethod("OnUpdate", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (methodInfo == null)
				{
					Instance log2 = Core.Log;
					if (log2 != null)
					{
						log2.Warning("[Siesta] MoreNPCs compat: MoreNPCs.Core.OnUpdate not found - skipped.");
					}
					return;
				}
				HarmonyMethod val = new HarmonyMethod(typeof(MoreNpcsCompat).GetMethod("SkipOriginal", BindingFlags.Static | BindingFlags.NonPublic));
				harmony.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Instance log3 = Core.Log;
				if (log3 != null)
				{
					log3.Msg("[Siesta] MoreNPCs (incompatible build for this IL2CPP install) auto-detected - compat shim applied: skipping MoreNPCs.Core.OnUpdate to stop the per-frame TypeLoadException storm; its NPCs still spawn via S1API.");
				}
			}
			catch (Exception ex)
			{
				Instance log4 = Core.Log;
				if (log4 != null)
				{
					log4.Warning("[Siesta] MoreNPCs compat shim failed: " + ex.Message);
				}
			}
		}

		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;
		}

		private static bool SkipOriginal()
		{
			return false;
		}
	}
	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;
			}
		}
	}
}