Decompiled source of LooseEnds v1.1.0

LooseEnds.dll

Decompiled 9 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 HarmonyLib;
using Il2CppFishNet;
using Il2CppFishNet.Connection;
using Il2CppFishNet.Managing;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppScheduleOne.Combat;
using Il2CppScheduleOne.DevUtilities;
using Il2CppScheduleOne.Dragging;
using Il2CppScheduleOne.Employees;
using Il2CppScheduleOne.Law;
using Il2CppScheduleOne.NPCs;
using Il2CppScheduleOne.NPCs.Behaviour;
using Il2CppScheduleOne.Networking;
using Il2CppScheduleOne.PlayerScripts;
using Il2CppScheduleOne.Police;
using Il2CppScheduleOne.Vehicles;
using Il2CppScheduleOne.Vision;
using Il2CppScheduleOne.VoiceOver;
using Il2CppSystem.Collections.Generic;
using LooseEnds;
using LooseEnds.Config;
using LooseEnds.Detection;
using LooseEnds.Killer;
using LooseEnds.Networking;
using LooseEnds.Reaction;
using LooseEnds.Weight;
using MelonLoader;
using MelonLoader.Preferences;
using Microsoft.CodeAnalysis;
using ModManagerPhoneApp;
using S1API.Lifecycle;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(Core), "Loose Ends", "1.1.0", "DooDesch", null)]
[assembly: MelonGame("TVGS", "Schedule I")]
[assembly: MelonOptionalDependencies(new string[] { "ModManager&PhoneApp" })]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("LooseEnds")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0+099c7ee3f707ee3b1700548f6e165dbd879f6ede")]
[assembly: AssemblyProduct("LooseEnds")]
[assembly: AssemblyTitle("LooseEnds")]
[assembly: NeutralResourcesLanguage("en-US")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.1.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 LooseEnds
{
	public sealed class Core : MelonMod
	{
		private bool _inWorld;

		private float _scanAccum;

		private float _reconcileAccum;

		private int _prevMinsSinceArrested = -1;

		private readonly List<CorpseRecord> _discovered = new List<CorpseRecord>();

		private int _activeLogState = -1;

		internal static string WitnessStatus = "init";

		public static Core Instance { get; private set; }

		public static Instance Log { get; private set; }

		[Conditional("DEBUG")]
		public static void LogDebug(string msg)
		{
			Instance log = Log;
			if (log != null)
			{
				log.Msg(msg);
			}
		}

		public override void OnInitializeMelon()
		{
			Instance = this;
			Log = ((MelonBase)this).LoggerInstance;
			Preferences.Initialize();
			try
			{
				((MelonBase)this).HarmonyInstance.PatchAll();
			}
			catch (Exception ex)
			{
				Log.Warning("[Core] Harmony patch failed: " + ex.Message);
			}
			GameLifecycle.OnSaveStart += ResetState;
			GameLifecycle.OnPreSceneChange += ResetState;
			HookModManager();
			Log.Msg("Loose Ends v1.1.0 - witness system + corpse weight.");
		}

		private static void ResetState()
		{
			CorpseTracker.Clear();
			KillerRegistry.Clear();
			CorpseWeight.RestoreAll();
			if (Instance != null)
			{
				Instance._prevMinsSinceArrested = -1;
			}
		}

		private void HookModManager()
		{
			try
			{
				ModSettingsEvents.OnPhonePreferencesSaved += OnSettingsSaved;
				ModSettingsEvents.OnMenuPreferencesSaved += OnSettingsSaved;
				Log.Msg("[Core] Mod Manager & Phone App hooked (settings apply live).");
			}
			catch (Exception)
			{
				Log.Msg("[Core] Mod Manager & Phone App not present - settings via the MelonPreferences config file (apply live on save).");
			}
		}

		private void OnSettingsSaved()
		{
		}

		public override void OnPreferencesSaved()
		{
			OnSettingsSaved();
		}

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

		public override void OnSceneWasUnloaded(int buildIndex, string sceneName)
		{
			_inWorld = false;
			WitnessStatus = "[not in world]";
			ResetState();
		}

		private bool WitnessActive()
		{
			if (!Preferences.Enabled)
			{
				WitnessStatus = "[OFF: disabled in settings]";
				if (_activeLogState != 0)
				{
					Log.Msg("[Core] Disabled via preferences - running vanilla.");
					_activeLogState = 0;
				}
				return false;
			}
			if (Net.IsCoop() && !Preferences.EnableInMultiplayer)
			{
				WitnessStatus = "[OFF: co-op - set EnableInMultiplayer]";
				if (_activeLogState != 1)
				{
					Log.Msg("[Core] Co-op session detected - witness system auto-DISABLED (set EnableInMultiplayer to opt in after a 2-player test).");
					_activeLogState = 1;
				}
				return false;
			}
			if (!Net.IsServer)
			{
				WitnessStatus = "[client - host runs detection]";
				if (_activeLogState != 2)
				{
					Log.Msg("[Core] Not the network authority - witness detection runs on the host.");
					_activeLogState = 2;
				}
				return false;
			}
			WitnessStatus = "[ACTIVE]";
			if (_activeLogState != 3)
			{
				Log.Msg("[Core] Witness system ACTIVE.");
				_activeLogState = 3;
			}
			return true;
		}

		public override void OnUpdate()
		{
			if (!_inWorld)
			{
				return;
			}
			CheckArrest();
			if (!WitnessActive())
			{
				return;
			}
			float deltaTime = Time.deltaTime;
			_reconcileAccum += deltaTime;
			if (_reconcileAccum >= 2f)
			{
				_reconcileAccum = 0f;
				CorpseTracker.Reconcile();
			}
			if (CorpseTracker.Count == 0)
			{
				_scanAccum = 0f;
				return;
			}
			_scanAccum += deltaTime;
			if (!(_scanAccum < Preferences.ScanIntervalSeconds))
			{
				_scanAccum = 0f;
				WitnessTick();
			}
		}

		private void CheckArrest()
		{
			try
			{
				if (!Net.IsServer)
				{
					return;
				}
				Player local = Player.Local;
				if ((Object)(object)local == (Object)null)
				{
					return;
				}
				PlayerCrimeData crimeData = local.CrimeData;
				if ((Object)(object)crimeData == (Object)null)
				{
					return;
				}
				int minsSinceLastArrested = crimeData.MinsSinceLastArrested;
				if (_prevMinsSinceArrested >= 0 && minsSinceLastArrested < _prevMinsSinceArrested)
				{
					KillerRegistry.Clear();
					CorpseTracker.ResolveAll();
					Instance log = Log;
					if (log != null)
					{
						log.Msg("[Core] Player arrested - cleared killer attribution for all bodies.");
					}
				}
				_prevMinsSinceArrested = minsSinceLastArrested;
			}
			catch (Exception ex)
			{
				Instance log2 = Log;
				if (log2 != null)
				{
					log2.Warning("[Core] arrest watcher failed: " + ex.Message);
				}
			}
		}

		private void WitnessTick()
		{
			SightingScanner.Scan(_discovered);
			float time = Time.time;
			float reactionDelaySeconds = Preferences.ReactionDelaySeconds;
			bool oncePerCorpse = Preferences.OncePerCorpse;
			float responseCooldownSeconds = Preferences.ResponseCooldownSeconds;
			foreach (CorpseRecord record in CorpseTracker.Records)
			{
				if (record.Calling)
				{
					ReactionDispatcher.UpdateCall(record, time);
				}
				else
				{
					if (!record.Discovered)
					{
						continue;
					}
					if (record.Dispatched)
					{
						if (!oncePerCorpse && time - record.DispatchedTime >= responseCooldownSeconds)
						{
							record.Discovered = false;
							record.Dispatched = false;
							record.Discoverer = null;
						}
					}
					else if (!(time - record.FirstSeenTime < reactionDelaySeconds))
					{
						ReactionDispatcher.TryStartResponse(record);
					}
				}
			}
		}
	}
}
namespace LooseEnds.Weight
{
	internal static class CorpseWeight
	{
		private struct Saved
		{
			public Draggable D;

			public int NpcId;

			public float DragMult;

			public Rigidbody Rb;

			public float Drag;

			public float AngularDrag;

			public float Mass;

			public bool HasRb;
		}

		private static readonly Dictionary<int, Saved> _active = new Dictionary<int, Saved>();

		internal static int ActiveCount => _active.Count;

		internal static void OnStartDragging(Draggable d)
		{
			if (!Preferences.Enabled || (Object)(object)d == (Object)null)
			{
				return;
			}
			float corpseWeightMultiplier = Preferences.CorpseWeightMultiplier;
			if (corpseWeightMultiplier <= 1.0001f)
			{
				return;
			}
			int instanceID;
			try
			{
				instanceID = ((Object)d).GetInstanceID();
			}
			catch
			{
				return;
			}
			if (_active.ContainsKey(instanceID) || !IsBodyDraggable(d))
			{
				return;
			}
			Saved value = default(Saved);
			try
			{
				value.D = d;
				try
				{
					NPC componentInParent = ((Component)d).GetComponentInParent<NPC>();
					value.NpcId = (((Object)(object)componentInParent != (Object)null) ? ((Object)componentInParent).GetInstanceID() : 0);
				}
				catch
				{
					value.NpcId = 0;
				}
				value.DragMult = d.DragForceMultiplier;
				d.DragForceMultiplier = Mathf.Max(0.05f, value.DragMult / corpseWeightMultiplier);
				Rigidbody rigidbody = d.Rigidbody;
				if ((Object)(object)rigidbody != (Object)null)
				{
					value.Rb = rigidbody;
					value.HasRb = true;
					value.Drag = rigidbody.drag;
					value.AngularDrag = rigidbody.angularDrag;
					value.Mass = rigidbody.mass;
					rigidbody.drag = value.Drag + (corpseWeightMultiplier - 1f) * 2f;
					rigidbody.angularDrag = value.AngularDrag + (corpseWeightMultiplier - 1f) * 2f;
					rigidbody.mass = value.Mass * Mathf.Sqrt(corpseWeightMultiplier);
				}
				_active[instanceID] = value;
			}
			catch
			{
			}
		}

		internal static void OnStopDragging(Draggable d)
		{
			if (!((Object)(object)d == (Object)null))
			{
				int instanceID;
				try
				{
					instanceID = ((Object)d).GetInstanceID();
				}
				catch
				{
					return;
				}
				if (_active.TryGetValue(instanceID, out var value))
				{
					RestoreEntry(value);
					_active.Remove(instanceID);
				}
			}
		}

		private static void RestoreEntry(Saved s)
		{
			try
			{
				if ((Object)(object)s.D != (Object)null)
				{
					s.D.DragForceMultiplier = s.DragMult;
				}
			}
			catch
			{
			}
			try
			{
				if (s.HasRb && (Object)(object)s.Rb != (Object)null)
				{
					s.Rb.drag = s.Drag;
					s.Rb.angularDrag = s.AngularDrag;
					s.Rb.mass = s.Mass;
				}
			}
			catch
			{
			}
		}

		internal static void RestoreForNpcId(int npcId)
		{
			if (npcId == 0 || _active.Count == 0)
			{
				return;
			}
			List<int> list = null;
			foreach (KeyValuePair<int, Saved> item in _active)
			{
				if (item.Value.NpcId == npcId)
				{
					RestoreEntry(item.Value);
					(list ?? (list = new List<int>())).Add(item.Key);
				}
			}
			if (list != null)
			{
				for (int i = 0; i < list.Count; i++)
				{
					_active.Remove(list[i]);
				}
			}
		}

		internal static void RestoreAll()
		{
			foreach (KeyValuePair<int, Saved> item in _active)
			{
				RestoreEntry(item.Value);
			}
			_active.Clear();
		}

		private static bool IsBodyDraggable(Draggable d)
		{
			try
			{
				NPC componentInParent = ((Component)d).GetComponentInParent<NPC>();
				if ((Object)(object)componentInParent != (Object)null)
				{
					NPCHealth health = componentInParent.Health;
					if ((Object)(object)health != (Object)null && (health.IsDead || health.IsKnockedOut))
					{
						return true;
					}
				}
			}
			catch
			{
			}
			try
			{
				foreach (CorpseRecord record in CorpseTracker.Records)
				{
					NPC npc = record.Npc;
					if (!((Object)(object)npc == (Object)null))
					{
						NPCMovement movement = npc.Movement;
						if ((Object)(object)movement != (Object)null && (Object)(object)movement.RagdollDraggable == (Object)(object)d)
						{
							return true;
						}
					}
				}
			}
			catch
			{
			}
			return false;
		}

		internal static void Clear()
		{
			_active.Clear();
		}
	}
}
namespace LooseEnds.Reaction
{
	internal static class CitizenReaction
	{
		internal static void React(NPC discoverer, Vector3 bodyPos)
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)discoverer == (Object)null)
			{
				return;
			}
			Vo(discoverer, (EVOLineType)4);
			bool flag = false;
			bool flag2 = false;
			try
			{
				flag = (Object)(object)((Il2CppObjectBase)discoverer).TryCast<PoliceOfficer>() != (Object)null;
			}
			catch
			{
			}
			try
			{
				flag2 = (Object)(object)((Il2CppObjectBase)discoverer).TryCast<Employee>() != (Object)null;
			}
			catch
			{
			}
			if (flag || flag2)
			{
				return;
			}
			try
			{
				NPCMovement movement = discoverer.Movement;
				if ((Object)(object)movement != (Object)null)
				{
					Vector3 val = bodyPos - ((Component)discoverer).transform.position;
					val.y = 0f;
					if (((Vector3)(ref val)).sqrMagnitude > 0.0001f)
					{
						movement.FaceDirection(((Vector3)(ref val)).normalized, 0.5f);
					}
				}
			}
			catch (Exception ex)
			{
				Instance log = Core.Log;
				if (log != null)
				{
					log.Warning("[Reaction] face-body failed: " + ex.Message);
				}
			}
		}

		internal static void Alert(NPC npc)
		{
			if (!((Object)(object)npc == (Object)null))
			{
				Vo(npc, (EVOLineType)4);
			}
		}

		private static void Vo(NPC npc, EVOLineType type)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				npc.PlayVO(type, false);
			}
			catch
			{
			}
		}
	}
	internal static class ReactionDispatcher
	{
		private const float CallWindowSeconds = 4f;

		private const float CallTimeoutSeconds = 25f;

		private const float CallActivationGrace = 1.5f;

		private const float CallRetryBackoff = 2f;

		internal static void TryStartResponse(CorpseRecord rec)
		{
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			if (rec == null || rec.Dispatched || rec.Calling)
			{
				return;
			}
			float time = Time.time;
			if (Preferences.RequirePlayerAlsoSeen && !DiscovererSeesAnyPlayer(rec.Discoverer))
			{
				return;
			}
			Player val = ResolveKiller(rec);
			Vector3 bodyPos = CorpsePosition(rec);
			if ((Object)(object)val == (Object)null)
			{
				Instance log = Core.Log;
				if (log != null)
				{
					log.Msg($"[Reaction] corpse={rec.Id} discovered but no known culprit - nothing to investigate.");
				}
				rec.Dispatched = true;
				rec.DispatchedTime = time;
			}
			else
			{
				if (Preferences.WitnessCallsPolice && !IsPolice(rec.Discoverer) && (time < rec.CallRetryAfter || IsBusyCalling(rec.Discoverer) || StartCall(rec, val, bodyPos, time)))
				{
					return;
				}
				try
				{
					CitizenReaction.React(rec.Discoverer, bodyPos);
				}
				catch (Exception ex)
				{
					Instance log2 = Core.Log;
					if (log2 != null)
					{
						log2.Warning("[Reaction] witness reaction failed: " + ex.Message);
					}
				}
				DispatchToScene(rec, val, bodyPos);
				rec.Dispatched = true;
				rec.DispatchedTime = time;
			}
		}

		private static bool StartCall(CorpseRecord rec, Player killer, Vector3 bodyPos, float now)
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			NPC discoverer = rec.Discoverer;
			if ((Object)(object)discoverer == (Object)null)
			{
				return false;
			}
			try
			{
				NPCBehaviour behaviour = discoverer.Behaviour;
				CallPoliceBehaviour val = (((Object)(object)behaviour != (Object)null) ? behaviour.CallPoliceBehaviour : null);
				if ((Object)(object)val == (Object)null)
				{
					return false;
				}
				CitizenReaction.Alert(discoverer);
				val.ReportedCrime = (Crime)new DeadlyAssault();
				val.Target = killer;
				((Behaviour)val).Enable_Networked();
				rec.Calling = true;
				rec.CallStartTime = now;
				rec.CallWasActive = false;
				rec.CallKiller = killer;
				rec.ScenePos = bodyPos;
				Instance log = Core.Log;
				if (log != null)
				{
					log.Msg($"[Reaction] witness {SafeId(discoverer)} CALLING police on {SafeCode(killer)} for corpse={rec.Id} - {4f:F0}s window (silence them to stop it).");
				}
				return true;
			}
			catch (Exception ex)
			{
				Instance log2 = Core.Log;
				if (log2 != null)
				{
					log2.Warning("[Reaction] StartCall failed: " + ex.Message);
				}
				return false;
			}
		}

		internal static void UpdateCall(CorpseRecord rec, float now)
		{
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			if (rec == null || !rec.Calling)
			{
				return;
			}
			float num = now - rec.CallStartTime;
			NPC discoverer = rec.Discoverer;
			CallPoliceBehaviour val = SafeCpb(discoverer);
			bool flag = SafeConscious(discoverer);
			bool flag2 = (Object)(object)val != (Object)null && SafeActive(val);
			bool flag3 = (Object)(object)val != (Object)null && SafeEnabled(val);
			if (flag2)
			{
				rec.CallWasActive = true;
			}
			if (rec.CallWasActive && !flag3 && num >= 4f)
			{
				try
				{
					Player callKiller = rec.CallKiller;
					if ((Object)(object)((callKiller != null) ? callKiller.CrimeData : null) != (Object)null)
					{
						rec.CallKiller.CrimeData.LastKnownPosition = rec.ScenePos;
					}
				}
				catch (Exception ex)
				{
					Instance log = Core.Log;
					if (log != null)
					{
						log.Warning("[Reaction] scene redirect failed: " + ex.Message);
					}
				}
				rec.Calling = false;
				rec.Dispatched = true;
				rec.DispatchedTime = now;
				Instance log2 = Core.Log;
				if (log2 != null)
				{
					log2.Msg($"[Reaction] call CONNECTED for corpse={rec.Id} - officers sent to the scene ({rec.ScenePos.x:F1},{rec.ScenePos.y:F1},{rec.ScenePos.z:F1}).");
				}
			}
			else if (!flag)
			{
				CancelCall(discoverer);
				rec.Calling = false;
				rec.CallWasActive = false;
				rec.Discovered = false;
				rec.Discoverer = null;
				Instance log3 = Core.Log;
				if (log3 != null)
				{
					log3.Msg($"[Reaction] witness SILENCED before the call connected for corpse={rec.Id} - no police called.");
				}
			}
			else if (((!rec.CallWasActive && num > 1.5f) || (rec.CallWasActive && flag3 && !flag2)) && IsFighting(discoverer))
			{
				CancelCall(discoverer);
				rec.Calling = false;
				rec.CallWasActive = false;
				rec.CallRetryAfter = now + 2f;
				Instance log4 = Core.Log;
				if (log4 != null)
				{
					log4.Msg($"[Reaction] witness is fighting the player for corpse={rec.Id} - holding at SEEN, will call once combat ends.");
				}
			}
			else if (num > 25f)
			{
				CancelCall(discoverer);
				rec.Calling = false;
				rec.CallWasActive = false;
				rec.Discovered = false;
				rec.Discoverer = null;
				Instance log5 = Core.Log;
				if (log5 != null)
				{
					log5.Warning($"[Reaction] call for corpse={rec.Id} did not resolve within {25f:F0}s - re-armed.");
				}
			}
		}

		private static void CancelCall(NPC witness)
		{
			try
			{
				object obj;
				if (witness == null)
				{
					obj = null;
				}
				else
				{
					NPCBehaviour behaviour = witness.Behaviour;
					obj = ((behaviour != null) ? behaviour.CallPoliceBehaviour : null);
				}
				CallPoliceBehaviour val = (CallPoliceBehaviour)obj;
				if ((Object)(object)val != (Object)null && ((Behaviour)val).Enabled)
				{
					((Behaviour)val).Disable_Networked((NetworkConnection)null);
				}
			}
			catch
			{
			}
		}

		private static bool IsBusyCalling(NPC witness)
		{
			try
			{
				object obj;
				if (witness == null)
				{
					obj = null;
				}
				else
				{
					NPCBehaviour behaviour = witness.Behaviour;
					obj = ((behaviour != null) ? behaviour.CallPoliceBehaviour : null);
				}
				CallPoliceBehaviour val = (CallPoliceBehaviour)obj;
				return (Object)(object)val != (Object)null && ((Behaviour)val).Active;
			}
			catch
			{
				return false;
			}
		}

		private static bool IsFighting(NPC witness)
		{
			try
			{
				NPCBehaviour val = (((Object)(object)witness != (Object)null) ? witness.Behaviour : null);
				if ((Object)(object)val == (Object)null)
				{
					return false;
				}
				Behaviour activeBehaviour = val.activeBehaviour;
				if ((Object)(object)activeBehaviour == (Object)null)
				{
					return false;
				}
				CombatBehaviour combatBehaviour = val.CombatBehaviour;
				return (Object)(object)combatBehaviour != (Object)null && ((Object)combatBehaviour).GetInstanceID() == ((Object)activeBehaviour).GetInstanceID();
			}
			catch
			{
				return false;
			}
		}

		private static CallPoliceBehaviour SafeCpb(NPC witness)
		{
			try
			{
				return ((Object)(object)witness != (Object)null && (Object)(object)witness.Behaviour != (Object)null) ? witness.Behaviour.CallPoliceBehaviour : null;
			}
			catch
			{
				return null;
			}
		}

		private static bool SafeActive(CallPoliceBehaviour cpb)
		{
			try
			{
				return ((Behaviour)cpb).Active;
			}
			catch
			{
				return false;
			}
		}

		private static bool SafeEnabled(CallPoliceBehaviour cpb)
		{
			try
			{
				return ((Behaviour)cpb).Enabled;
			}
			catch
			{
				return false;
			}
		}

		private static Player ResolveKiller(CorpseRecord rec)
		{
			if ((Object)(object)rec.Killer != (Object)null)
			{
				return rec.Killer;
			}
			if (Preferences.AttributeUnknownToLocalPlayer && !Net.IsCoop())
			{
				try
				{
					return Player.Local;
				}
				catch
				{
					return null;
				}
			}
			return null;
		}

		private static void DispatchToScene(CorpseRecord rec, Player killer, Vector3 bodyPos)
		{
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Expected O, but got Unknown
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0191: Unknown result type (might be due to invalid IL or missing references)
			//IL_0197: Expected I4, but got Unknown
			//IL_01a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Expected O, but got Unknown
			if ((Object)(object)killer == (Object)null)
			{
				return;
			}
			PlayerCrimeData crimeData = killer.CrimeData;
			if ((Object)(object)crimeData == (Object)null)
			{
				Instance log = Core.Log;
				if (log != null)
				{
					log.Warning($"[Reaction] SCENE corpse={rec.Id} - suspect has no CrimeData; skipping dispatch.");
				}
				return;
			}
			EPursuitLevel val = (EPursuitLevel)Preferences.PursuitLevelInt;
			try
			{
				if (crimeData.CurrentPursuitLevel < val)
				{
					crimeData.SetPursuitLevel(val);
				}
			}
			catch (Exception ex)
			{
				Instance log2 = Core.Log;
				if (log2 != null)
				{
					log2.Warning("[Reaction] SetPursuitLevel failed: " + ex.Message);
				}
			}
			try
			{
				crimeData.AddCrime((Crime)new DeadlyAssault(), 1);
			}
			catch (Exception ex2)
			{
				Instance log3 = Core.Log;
				if (log3 != null)
				{
					log3.Warning("[Reaction] AddCrime failed: " + ex2.Message);
				}
			}
			try
			{
				if (Singleton<LawManager>.InstanceExists)
				{
					Singleton<LawManager>.Instance.PoliceCalled(killer, (Crime)new DeadlyAssault());
				}
			}
			catch (Exception ex3)
			{
				Instance log4 = Core.Log;
				if (log4 != null)
				{
					log4.Warning("[Reaction] PoliceCalled failed: " + ex3.Message);
				}
			}
			try
			{
				crimeData.LastKnownPosition = bodyPos;
			}
			catch (Exception ex4)
			{
				Instance log5 = Core.Log;
				if (log5 != null)
				{
					log5.Warning("[Reaction] LastKnownPosition redirect failed: " + ex4.Message);
				}
			}
			Instance log6 = Core.Log;
			if (log6 != null)
			{
				log6.Msg($"[Reaction] SCENE corpse={rec.Id} suspect={SafeCode(killer)} pursuit={(int)val} body=({bodyPos.x:F1},{bodyPos.y:F1},{bodyPos.z:F1})");
			}
		}

		private static Vector3 CorpsePosition(CorpseRecord rec)
		{
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//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)
			try
			{
				NPC npc = rec.Npc;
				if ((Object)(object)npc != (Object)null)
				{
					NPCMovement movement = npc.Movement;
					if ((Object)(object)movement != (Object)null)
					{
						Draggable ragdollDraggable = movement.RagdollDraggable;
						if ((Object)(object)ragdollDraggable != (Object)null && (Object)(object)((Component)ragdollDraggable).transform != (Object)null)
						{
							return ((Component)ragdollDraggable).transform.position;
						}
					}
					return ((Component)npc).transform.position;
				}
			}
			catch
			{
			}
			return Vector3.zero;
		}

		private static bool DiscovererSeesAnyPlayer(NPC discoverer)
		{
			if ((Object)(object)discoverer == (Object)null)
			{
				return false;
			}
			try
			{
				NPCAwareness awareness = discoverer.Awareness;
				VisionCone val = (((Object)(object)awareness != (Object)null) ? awareness.VisionCone : null);
				if ((Object)(object)val == (Object)null)
				{
					return false;
				}
				Player local = Player.Local;
				if ((Object)(object)local != (Object)null && val.IsPlayerVisible(local))
				{
					return true;
				}
			}
			catch
			{
			}
			return false;
		}

		private static bool IsPolice(NPC npc)
		{
			try
			{
				return (Object)(object)npc != (Object)null && (Object)(object)((Il2CppObjectBase)npc).TryCast<PoliceOfficer>() != (Object)null;
			}
			catch
			{
				return false;
			}
		}

		private static bool SafeConscious(NPC npc)
		{
			try
			{
				return (Object)(object)npc != (Object)null && npc.IsConscious;
			}
			catch
			{
				return false;
			}
		}

		internal static string SafeCode(Player p)
		{
			if ((Object)(object)p == (Object)null)
			{
				return "null";
			}
			try
			{
				Player local = Player.Local;
				if ((Object)(object)local != (Object)null && ((Object)local).GetInstanceID() == ((Object)p).GetInstanceID())
				{
					return "you";
				}
			}
			catch
			{
			}
			try
			{
				return p.PlayerCode;
			}
			catch
			{
				return "?";
			}
		}

		private static string SafeId(NPC npc)
		{
			try
			{
				return ((Object)(object)npc != (Object)null) ? ((Object)npc).GetInstanceID().ToString() : "null";
			}
			catch
			{
				return "?";
			}
		}
	}
}
namespace LooseEnds.Patches
{
	[HarmonyPatch(typeof(Draggable), "StartDragging")]
	internal static class Draggable_StartDragging_Patch
	{
		private static void Postfix(Draggable __instance)
		{
			try
			{
				CorpseWeight.OnStartDragging(__instance);
			}
			catch
			{
			}
		}
	}
	[HarmonyPatch(typeof(Draggable), "StopDragging")]
	internal static class Draggable_StopDragging_Patch
	{
		private static void Postfix(Draggable __instance)
		{
			try
			{
				CorpseWeight.OnStopDragging(__instance);
			}
			catch
			{
			}
		}
	}
	[HarmonyPatch(typeof(NPCHealth), "NotifyAttackedByPlayer")]
	internal static class NPCHealth_NotifyAttackedByPlayer_Patch
	{
		private static void Postfix(NPCHealth __instance, Player player)
		{
			try
			{
				KillerRegistry.RecordAttacker(__instance.npc, player);
			}
			catch
			{
			}
		}
	}
	[HarmonyPatch(typeof(NPCHealth), "Die")]
	internal static class NPCHealth_Die_Patch
	{
		private static void Postfix(NPCHealth __instance)
		{
			try
			{
				NPC npc = __instance.npc;
				if (!((Object)(object)npc == (Object)null))
				{
					Player killer = KillerRegistry.RecordKill(npc);
					CorpseTracker.OnNpcDied(npc, killer);
				}
			}
			catch
			{
			}
		}
	}
	[HarmonyPatch(typeof(NPCHealth), "KnockOut")]
	internal static class NPCHealth_KnockOut_Patch
	{
		private static void Postfix(NPCHealth __instance)
		{
			try
			{
				if (Preferences.ReactToKnockedOut)
				{
					NPC npc = __instance.npc;
					if (!((Object)(object)npc == (Object)null))
					{
						CorpseTracker.OnNpcDied(npc, KillerRegistry.GetKiller(npc));
					}
				}
			}
			catch
			{
			}
		}
	}
}
namespace LooseEnds.Networking
{
	internal static class Net
	{
		private static NetworkManager Nm
		{
			get
			{
				try
				{
					return InstanceFinder.NetworkManager;
				}
				catch
				{
					return null;
				}
			}
		}

		internal static bool Online
		{
			get
			{
				NetworkManager nm = Nm;
				try
				{
					return (Object)(object)nm != (Object)null && (nm.IsServer || nm.IsClient);
				}
				catch
				{
					return false;
				}
			}
		}

		internal static bool IsServer
		{
			get
			{
				NetworkManager nm = Nm;
				try
				{
					return (Object)(object)nm != (Object)null && nm.IsServer;
				}
				catch
				{
					return false;
				}
			}
		}

		internal static bool IsCoop()
		{
			try
			{
				Lobby instance = Singleton<Lobby>.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					return false;
				}
				return instance.IsInLobby && instance.PlayerCount > 1;
			}
			catch
			{
				return false;
			}
		}
	}
}
namespace LooseEnds.Killer
{
	internal static class KillerRegistry
	{
		private struct Attack
		{
			public Player Player;

			public float Time;
		}

		private const float AttributionWindowSeconds = 120f;

		private static readonly Dictionary<int, Attack> _lastAttacker = new Dictionary<int, Attack>();

		private static readonly Dictionary<int, Player> _killer = new Dictionary<int, Player>();

		internal static void RecordAttacker(NPC npc, Player player)
		{
			if (!((Object)(object)npc == (Object)null) && !((Object)(object)player == (Object)null))
			{
				_lastAttacker[((Object)npc).GetInstanceID()] = new Attack
				{
					Player = player,
					Time = Time.time
				};
			}
		}

		internal static Player RecordKill(NPC npc)
		{
			if ((Object)(object)npc == (Object)null)
			{
				return null;
			}
			int instanceID = ((Object)npc).GetInstanceID();
			if (_lastAttacker.TryGetValue(instanceID, out var value) && (Object)(object)value.Player != (Object)null && Time.time - value.Time <= 120f)
			{
				_killer[instanceID] = value.Player;
				return value.Player;
			}
			return null;
		}

		internal static Player GetKiller(NPC npc)
		{
			if ((Object)(object)npc == (Object)null)
			{
				return null;
			}
			int instanceID = ((Object)npc).GetInstanceID();
			if (_killer.TryGetValue(instanceID, out var value) && (Object)(object)value != (Object)null)
			{
				return value;
			}
			if (_lastAttacker.TryGetValue(instanceID, out var value2) && (Object)(object)value2.Player != (Object)null && Time.time - value2.Time <= 120f)
			{
				return value2.Player;
			}
			return null;
		}

		internal static void Forget(NPC npc)
		{
			if (!((Object)(object)npc == (Object)null))
			{
				ForgetId(((Object)npc).GetInstanceID());
			}
		}

		internal static void ForgetId(int id)
		{
			_lastAttacker.Remove(id);
			_killer.Remove(id);
		}

		internal static void Clear()
		{
			_lastAttacker.Clear();
			_killer.Clear();
		}
	}
}
namespace LooseEnds.Detection
{
	internal sealed class CorpseRecord
	{
		public NPC Npc;

		public int Id;

		public Player Killer;

		public bool Discovered;

		public float FirstSeenTime;

		public NPC Discoverer;

		public bool Dispatched;

		public float DispatchedTime;

		public bool Calling;

		public float CallStartTime;

		public bool CallWasActive;

		public float CallRetryAfter;

		public Player CallKiller;

		public Vector3 ScenePos;
	}
	internal static class CorpseTracker
	{
		private static readonly Dictionary<int, CorpseRecord> _corpses = new Dictionary<int, CorpseRecord>();

		private static readonly List<int> _toRemove = new List<int>();

		internal static int Count => _corpses.Count;

		internal static Dictionary<int, CorpseRecord>.ValueCollection Records => _corpses.Values;

		internal static bool TryGet(NPC npc, out CorpseRecord rec)
		{
			rec = null;
			if ((Object)(object)npc == (Object)null)
			{
				return false;
			}
			return _corpses.TryGetValue(((Object)npc).GetInstanceID(), out rec);
		}

		internal static void OnNpcDied(NPC npc, Player killer)
		{
			if (!((Object)(object)npc == (Object)null))
			{
				int instanceID = ((Object)npc).GetInstanceID();
				if (!_corpses.TryGetValue(instanceID, out var value))
				{
					value = new CorpseRecord
					{
						Npc = npc,
						Id = instanceID
					};
					_corpses[instanceID] = value;
				}
				if ((Object)(object)killer != (Object)null)
				{
					value.Killer = killer;
				}
			}
		}

		internal static void Reconcile()
		{
			bool reactToKnockedOut = Preferences.ReactToKnockedOut;
			try
			{
				List<NPC> nPCRegistry = NPCManager.NPCRegistry;
				if (nPCRegistry != null)
				{
					int count = nPCRegistry.Count;
					for (int i = 0; i < count; i++)
					{
						NPC val = nPCRegistry[i];
						if ((Object)(object)val == (Object)null)
						{
							continue;
						}
						NPCHealth health;
						try
						{
							health = val.Health;
						}
						catch
						{
							continue;
						}
						if ((Object)(object)health == (Object)null)
						{
							continue;
						}
						bool flag;
						try
						{
							flag = health.IsDead || (reactToKnockedOut && health.IsKnockedOut);
						}
						catch
						{
							continue;
						}
						if (flag)
						{
							int instanceID = ((Object)val).GetInstanceID();
							if (!_corpses.ContainsKey(instanceID))
							{
								_corpses[instanceID] = new CorpseRecord
								{
									Npc = val,
									Id = instanceID,
									Killer = KillerRegistry.GetKiller(val)
								};
							}
						}
					}
				}
			}
			catch
			{
			}
			_toRemove.Clear();
			foreach (KeyValuePair<int, CorpseRecord> corpse in _corpses)
			{
				NPC npc = corpse.Value.Npc;
				bool flag2 = false;
				try
				{
					if ((Object)(object)npc == (Object)null)
					{
						flag2 = true;
					}
					else
					{
						NPCHealth health2 = npc.Health;
						if (!((Object)(object)health2 != (Object)null) || (!health2.IsDead && (!reactToKnockedOut || !health2.IsKnockedOut)))
						{
							flag2 = true;
						}
					}
				}
				catch
				{
					flag2 = true;
				}
				if (flag2)
				{
					_toRemove.Add(corpse.Key);
				}
			}
			for (int j = 0; j < _toRemove.Count; j++)
			{
				int num = _toRemove[j];
				CorpseWeight.RestoreForNpcId(num);
				_corpses.Remove(num);
				KillerRegistry.ForgetId(num);
			}
		}

		internal static void Remove(int id)
		{
			_corpses.Remove(id);
			KillerRegistry.ForgetId(id);
		}

		internal static void Clear()
		{
			_corpses.Clear();
		}

		internal static void ResolveAll()
		{
			foreach (CorpseRecord value in _corpses.Values)
			{
				value.Discovered = true;
				value.Dispatched = true;
			}
		}
	}
	internal static class SightingScanner
	{
		private static int _observerCursor;

		private const float EyeHeight = 1.6f;

		private const float NoticeExposureThreshold = 0.4f;

		internal static void Scan(List<CorpseRecord> newlyDiscovered)
		{
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0150: Unknown result type (might be due to invalid IL or missing references)
			//IL_0155: Unknown result type (might be due to invalid IL or missing references)
			//IL_016c: Unknown result type (might be due to invalid IL or missing references)
			newlyDiscovered.Clear();
			List<NPC> nPCRegistry = NPCManager.NPCRegistry;
			if (nPCRegistry == null)
			{
				return;
			}
			int count;
			try
			{
				count = nPCRegistry.Count;
			}
			catch
			{
				return;
			}
			if (count == 0)
			{
				return;
			}
			bool requireLineOfSight = Preferences.RequireLineOfSight;
			bool useVisionConeRange = Preferences.UseVisionConeRange;
			float detectionRange = Preferences.DetectionRange;
			float num = Mathf.Max(useVisionConeRange ? Preferences.ObserverCullRadius : detectionRange, Preferences.NoticeRadius);
			float num2 = num * num;
			int maxRaycastsPerScan = Preferences.MaxRaycastsPerScan;
			int num3 = 0;
			foreach (CorpseRecord record in CorpseTracker.Records)
			{
				if (record.Discovered)
				{
					continue;
				}
				NPC npc = record.Npc;
				if ((Object)(object)npc == (Object)null)
				{
					continue;
				}
				Vector3 val;
				try
				{
					val = CorpsePosition(record);
				}
				catch
				{
					continue;
				}
				for (int i = 0; i < count; i++)
				{
					if (num3 >= maxRaycastsPerScan)
					{
						AdvanceCursor(num3, count);
						return;
					}
					int num4 = (_observerCursor + i) % count;
					NPC val2 = nPCRegistry[num4];
					if ((Object)(object)val2 == (Object)null || ((Object)val2).GetInstanceID() == record.Id)
					{
						continue;
					}
					NPCHealth health;
					try
					{
						health = val2.Health;
					}
					catch
					{
						continue;
					}
					if ((Object)(object)health == (Object)null)
					{
						continue;
					}
					try
					{
						if (health.IsDead || health.IsKnockedOut)
						{
							continue;
						}
						goto IL_012f;
					}
					catch
					{
					}
					continue;
					IL_012f:
					if (!RoleEnabled(val2))
					{
						continue;
					}
					Vector3 position;
					try
					{
						position = ((Component)val2).transform.position;
					}
					catch
					{
						continue;
					}
					Vector3 val3 = position - val;
					if (!(((Vector3)(ref val3)).sqrMagnitude > num2))
					{
						num3++;
						if (CanSee(val2, npc, val, requireLineOfSight))
						{
							record.Discovered = true;
							record.FirstSeenTime = Time.time;
							record.Discoverer = val2;
							newlyDiscovered.Add(record);
							break;
						}
					}
				}
			}
			AdvanceCursor(num3, count);
		}

		private static void AdvanceCursor(int checksUsed, int regCount)
		{
			if (regCount > 0)
			{
				_observerCursor = (_observerCursor + Mathf.Max(1, checksUsed)) % regCount;
			}
		}

		private static Vector3 CorpsePosition(CorpseRecord rec)
		{
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			NPC npc = rec.Npc;
			try
			{
				NPCMovement movement = npc.Movement;
				if ((Object)(object)movement != (Object)null)
				{
					Draggable ragdollDraggable = movement.RagdollDraggable;
					if ((Object)(object)ragdollDraggable != (Object)null)
					{
						Transform transform = ((Component)ragdollDraggable).transform;
						if ((Object)(object)transform != (Object)null)
						{
							return transform.position + Vector3.up * 0.5f;
						}
					}
				}
			}
			catch
			{
			}
			return ((Component)npc).transform.position + Vector3.up * 0.5f;
		}

		private static bool CanSee(NPC observer, NPC corpse, Vector3 bodyPos, bool requireLos)
		{
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			if (!requireLos)
			{
				return true;
			}
			NPCAwareness awareness;
			try
			{
				awareness = observer.Awareness;
			}
			catch
			{
				return false;
			}
			VisionCone val = (((Object)(object)awareness != (Object)null) ? awareness.VisionCone : null);
			Vector3 val2 = EyeOf(observer, val);
			Vector3 val3 = val2 - bodyPos;
			float magnitude = ((Vector3)(ref val3)).magnitude;
			if (magnitude <= Preferences.BodySightRange)
			{
				try
				{
					if ((Object)(object)val != (Object)null && val.IsPointWithinSight(bodyPos, false, (LandVehicle)null))
					{
						return true;
					}
				}
				catch
				{
				}
			}
			try
			{
				float noticeRadius = Preferences.NoticeRadius;
				if (magnitude <= noticeRadius && BodyExposedTo(corpse, observer, val2, noticeRadius, bodyPos, val))
				{
					return true;
				}
			}
			catch
			{
			}
			return false;
		}

		private static bool BodyExposedTo(NPC corpse, NPC observer, Vector3 eye, float range, Vector3 bodyPos, VisionCone cone)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				EntityVisibility val = (((Object)(object)corpse != (Object)null) ? corpse.Visibility : null);
				if ((Object)(object)val != (Object)null)
				{
					return val.CalculateExposureToPoint(eye, range + 3f, observer) >= 0.4f;
				}
			}
			catch
			{
			}
			return HasClearPath(eye, bodyPos, cone);
		}

		private static Vector3 EyeOf(NPC observer, VisionCone cone)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: 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)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if ((Object)(object)cone != (Object)null && (Object)(object)cone.VisionOrigin != (Object)null)
				{
					return cone.VisionOrigin.position;
				}
			}
			catch
			{
			}
			return ((Component)observer).transform.position + Vector3.up * 1.6f;
		}

		private static bool HasClearPath(Vector3 eye, Vector3 bodyPos, VisionCone cone)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: 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)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = bodyPos - eye;
			float magnitude = ((Vector3)(ref val)).magnitude;
			if (magnitude <= 0.6f)
			{
				return true;
			}
			val /= magnitude;
			float num = magnitude - 0.5f;
			try
			{
				if ((Object)(object)cone != (Object)null)
				{
					LayerMask visibilityBlockingLayers = cone.VisibilityBlockingLayers;
					if (((LayerMask)(ref visibilityBlockingLayers)).value != 0)
					{
						return !Physics.Raycast(eye, val, num, ((LayerMask)(ref visibilityBlockingLayers)).value);
					}
				}
			}
			catch
			{
			}
			return true;
		}

		private static bool RoleEnabled(NPC obs)
		{
			try
			{
				if ((Object)(object)((Il2CppObjectBase)obs).TryCast<PoliceOfficer>() != (Object)null)
				{
					return Preferences.ReactPolice;
				}
				if ((Object)(object)((Il2CppObjectBase)obs).TryCast<Employee>() != (Object)null)
				{
					return Preferences.ReactEmployees;
				}
			}
			catch
			{
			}
			return Preferences.ReactCitizens;
		}
	}
}
namespace LooseEnds.Config
{
	internal static class Preferences
	{
		private const string CategoryId = "LooseEnds_01_Main";

		private static MelonPreferences_Category _category;

		private static MelonPreferences_Entry<bool> _enabled;

		private static MelonPreferences_Entry<bool> _enableInMp;

		private static MelonPreferences_Entry<bool> _reactCitizens;

		private static MelonPreferences_Entry<bool> _reactPolice;

		private static MelonPreferences_Entry<bool> _reactEmployees;

		private static MelonPreferences_Entry<bool> _requireLos;

		private static MelonPreferences_Entry<bool> _useVisionRange;

		private static MelonPreferences_Entry<float> _detectionRange;

		private static MelonPreferences_Entry<float> _reactionDelay;

		private static MelonPreferences_Entry<float> _observerCullRadius;

		private static MelonPreferences_Entry<float> _bodySightRange;

		private static MelonPreferences_Entry<float> _noticeRadius;

		private static MelonPreferences_Entry<float> _scanInterval;

		private static MelonPreferences_Entry<int> _maxRaycastsPerScan;

		private static MelonPreferences_Entry<bool> _reactToKnockedOut;

		private static MelonPreferences_Entry<bool> _witnessCallsPolice;

		private static MelonPreferences_Entry<bool> _requirePlayerSeen;

		private static MelonPreferences_Entry<int> _pursuitLevel;

		private static MelonPreferences_Entry<bool> _oncePerCorpse;

		private static MelonPreferences_Entry<float> _responseCooldown;

		private static MelonPreferences_Entry<bool> _attributeUnknownToLocal;

		private static MelonPreferences_Entry<float> _weightMultiplier;

		internal static bool Enabled => _enabled?.Value ?? true;

		internal static bool EnableInMultiplayer => _enableInMp?.Value ?? false;

		internal static bool ReactCitizens => _reactCitizens?.Value ?? true;

		internal static bool ReactPolice => _reactPolice?.Value ?? true;

		internal static bool ReactEmployees => _reactEmployees?.Value ?? false;

		internal static bool RequireLineOfSight => _requireLos?.Value ?? true;

		internal static bool UseVisionConeRange => _useVisionRange?.Value ?? true;

		internal static float DetectionRange => Mathf.Clamp(_detectionRange?.Value ?? 12f, 3f, 40f);

		internal static float ReactionDelaySeconds => Mathf.Clamp(_reactionDelay?.Value ?? 3f, 0f, 30f);

		internal static float ObserverCullRadius => Mathf.Clamp(_observerCullRadius?.Value ?? 25f, 5f, 60f);

		internal static float BodySightRange => Mathf.Clamp(_bodySightRange?.Value ?? 12f, 4f, 30f);

		internal static float NoticeRadius => Mathf.Clamp(_noticeRadius?.Value ?? 6f, 2f, 15f);

		internal static float ScanIntervalSeconds => Mathf.Clamp(_scanInterval?.Value ?? 0.4f, 0.1f, 2f);

		internal static int MaxRaycastsPerScan => Mathf.Clamp(_maxRaycastsPerScan?.Value ?? 64, 8, 256);

		internal static bool ReactToKnockedOut => _reactToKnockedOut?.Value ?? false;

		internal static bool WitnessCallsPolice => _witnessCallsPolice?.Value ?? true;

		internal static bool RequirePlayerAlsoSeen => _requirePlayerSeen?.Value ?? false;

		internal static int PursuitLevelInt => Mathf.Clamp(_pursuitLevel?.Value ?? 1, 1, 4);

		internal static bool OncePerCorpse => _oncePerCorpse?.Value ?? true;

		internal static float ResponseCooldownSeconds => Mathf.Clamp(_responseCooldown?.Value ?? 60f, 5f, 600f);

		internal static bool AttributeUnknownToLocalPlayer => _attributeUnknownToLocal?.Value ?? true;

		internal static float CorpseWeightMultiplier => Mathf.Clamp(_weightMultiplier?.Value ?? 5f, 1f, 20f);

		internal static void Initialize()
		{
			if (_category == null)
			{
				_category = MelonPreferences.CreateCategory("LooseEnds_01_Main", "Loose Ends");
				_enabled = Create("Enabled", def: true, "Enabled", "Master switch. When ON, NPCs who SEE a dead body react and carried corpses are heavier. OFF = fully vanilla.");
				_enableInMp = Create("EnableInMultiplayer", def: false, "Enable in multiplayer (experimental)", "OFF (default): the witness system auto-disables in a real co-op lobby until it has been tested with 2 players. ON: force it on - it is host-authoritative and uses the game's own networked police calls, but verify pursuit syncs to all clients first.");
				_reactCitizens = Create("ReactCitizens", def: true, "Citizens react", "Civilian NPCs who see a corpse call the police.");
				_reactPolice = Create("ReactPolice", def: true, "Police react", "Police who see a corpse begin investigating.");
				_reactEmployees = Create("ReactEmployees", def: false, "Employees react", "Your hired employees (dealers/handlers) react to a corpse. OFF by default - they are your crew.");
				_requireLos = Create("RequireLineOfSight", def: true, "Require line of sight", "ON (default): use the NPC's vision cone, so a body behind a wall / in a dumpster / indoors / underwater is NOT seen (this is the whole 'hide the body' mechanic). OFF: a pure radius check (notices through walls).");
				_useVisionRange = Create("UseVisionConeRange", def: true, "Use the NPC's own vision range", "ON (default): rely on the game's vision-cone distance. OFF: use 'Detection range' below as an explicit radius.");
				_detectionRange = Create("DetectionRange", 12f, "Detection range (m)", "Explicit notice radius used when 'Use the NPC's own vision range' is OFF. Clamped 3-40.", (ValueValidator)(object)new ValueRange<float>(3f, 40f));
				_reactionDelay = Create("ReactionDelaySeconds", 3f, "Reaction delay (s)", "After the first sighting, wait this long before the response fires (the NPC 'processes' the scene; also debounces a quick glance). Clamped 0-30.", (ValueValidator)(object)new ValueRange<float>(0f, 30f));
				_observerCullRadius = Create("ObserverCullRadius", 25f, "Observer cull radius (m)", "Performance: only NPCs within this distance of a corpse are even considered as witnesses. Clamped 5-60.", (ValueValidator)(object)new ValueRange<float>(5f, 60f));
				_bodySightRange = Create("BodySightRange", 12f, "Body sight range (m)", "How far an NPC can NOTICE a body through their vision cone. A body lying flat on the ground is far less conspicuous than a standing person, so this is capped well below the NPC's normal sight range - otherwise a corpse in the open is spotted by anyone within ~25m and the response feels instant. Clamped 4-30.", (ValueValidator)(object)new ValueRange<float>(4f, 30f));
				_noticeRadius = Create("NoticeRadius", 6f, "Close-range notice radius (m)", "NPCs never look down at their feet, so a body lying flat is below their forward vision cone. Any living NPC within this radius with a clear line of sight (occlusion still respected) notices the body even if it is not in their cone - this is what makes someone standing over a corpse actually react. Clamped 2-15.", (ValueValidator)(object)new ValueRange<float>(2f, 15f));
				_scanInterval = Create("ScanIntervalSeconds", 0.4f, "Scan interval (s)", "How often the witness scan runs. Lower = faster notice, slightly more cost. Clamped 0.1-2.", (ValueValidator)(object)new ValueRange<float>(0.1f, 2f));
				_maxRaycastsPerScan = Create("MaxRaycastsPerScan", 64, "Max sight checks per scan", "Performance cap: at most this many vision checks per scan tick (round-robin if exceeded). Clamped 8-256.", (ValueValidator)(object)new ValueRange<int>(8, 256));
				_reactToKnockedOut = Create("ReactToKnockedOut", def: false, "React to unconscious NPCs", "OFF (default): only react to actually-dead NPCs. ON: also react to merely knocked-out NPCs.");
				_witnessCallsPolice = Create("WitnessCallsPolice", def: true, "Witness phones the police (call window)", "ON (default): a civilian witness pulls out their phone and calls the police over ~4 seconds (the game's own call animation, with a progress icon over their head). Knock them out or kill them before the call connects to stop it - that is your chance to silence the only witness. OFF: the police are alerted instantly. (A police officer who finds a body always reports it instantly - you cannot phone-block a cop.)");
				_requirePlayerSeen = Create("RequirePlayerAlsoSeen", def: false, "Killer must also be seen", "OFF (default): seeing the BODY is enough to start the response - hiding yourself is not enough, you must hide the body. ON: the discovering NPC must ALSO have the killer in sight before heat is applied (closer to vanilla crime-witnessing).");
				_pursuitLevel = Create("PursuitLevel", 1, "Pursuit level to apply (1-4)", "Maps to the game's pursuit levels: 1 = Investigating, 2 = Arresting, 3 = NonLethal, 4 = Lethal. Default 1 (Investigating - the status named in the request).", (ValueValidator)(object)new ValueRange<int>(1, 4));
				_oncePerCorpse = Create("OncePerCorpse", def: true, "Respond only once per corpse", "ON (default): a corpse that has already triggered a response will not trigger again. OFF: re-trigger is allowed, subject to the response cooldown.");
				_responseCooldown = Create("ResponseCooldownSeconds", 60f, "Re-trigger cooldown (s)", "Per-corpse re-trigger gap used ONLY when 'Respond only once per corpse' is OFF (how long before the same body can raise a fresh response). It does NOT throttle different bodies - every body a witness sees gets its own response. Clamped 5-600.", (ValueValidator)(object)new ValueRange<float>(5f, 600f));
				_attributeUnknownToLocal = Create("AttributeUnknownToLocalPlayer", def: true, "Blame local player when killer unknown", "ON (default, single-player): if a discovered corpse has no recorded killer, attribute it to the local player so 'Hunt' still has a target. In a co-op lobby this is treated as OFF (an unattributed body falls back to a scene response instead of blaming a random player).");
				_weightMultiplier = Create("CorpseWeightMultiplier", 5f, "Dragged body weight x", "How heavy a dragged body (dead OR knocked-out) feels to pull. Higher = the body resists more - it lags behind the carry point and drags sluggishly (and is heavier to throw). Does NOT slow the player. 1 = vanilla. Clamped 1-20.", (ValueValidator)(object)new ValueRange<float>(1f, 20f));
			}
		}

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