using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using FishNet; using FishNet.Connection; using FishNet.Managing; using FishNet.Object; using HarmonyLib; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using PoliceResponseOverhaul; using PoliceResponseOverhaul.Ambient; using PoliceResponseOverhaul.Armor; using PoliceResponseOverhaul.Authority; using PoliceResponseOverhaul.Cartel; using PoliceResponseOverhaul.Compat; using PoliceResponseOverhaul.Config; using PoliceResponseOverhaul.Heat; using PoliceResponseOverhaul.Helpers; using PoliceResponseOverhaul.Loadout; using PoliceResponseOverhaul.Patches; using PoliceResponseOverhaul.Pooling; using PoliceResponseOverhaul.Recovery; using PoliceResponseOverhaul.Response; using PoliceResponseOverhaul.World; using ScheduleOne; using ScheduleOne.AvatarFramework; using ScheduleOne.AvatarFramework.Equipping; using ScheduleOne.Cartel; using ScheduleOne.Clothing; using ScheduleOne.Combat; using ScheduleOne.Core.Items.Framework; using ScheduleOne.DevUtilities; using ScheduleOne.Doors; using ScheduleOne.GameTime; using ScheduleOne.ItemFramework; using ScheduleOne.Law; using ScheduleOne.Map; using ScheduleOne.NPCs; using ScheduleOne.NPCs.Behaviour; using ScheduleOne.PlayerScripts; using ScheduleOne.PlayerScripts.Health; using ScheduleOne.Police; using ScheduleOne.UI.Shop; using ScheduleOne.Vehicles; using ScheduleOne.Vehicles.AI; using ScheduleOne.VoiceOver; using TMPro; using UnityEngine; using UnityEngine.AI; using UnityEngine.Events; using UnityEngine.Rendering; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: MelonInfo(typeof(PoliceResponseOverhaulMod), "Police Response Overhaul", "1.1.4", "UncleTyrone", null)] [assembly: MelonColor(1, 0, 0, 255)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: MelonOptionalDependencies(new string[] { "MoreGuns", "K9 Patrol", "K9_Patrol", "K9Patrol", "SteamNetworkLib", "SteamNetworkLib-Mono", "SteamNetworkLib-Il2Cpp" })] [assembly: HarmonyDontPatchAll] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("PoliceResponseOverhaul-Mono")] [assembly: AssemblyConfiguration("Release Mono")] [assembly: AssemblyFileVersion("1.1.4.0")] [assembly: AssemblyInformationalVersion("1.1.4+6dcb88777ca9461846a840d9cc6732ac029eeeec")] [assembly: AssemblyProduct("PoliceResponseOverhaul-Mono")] [assembly: AssemblyTitle("PoliceResponseOverhaul-Mono")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.1.4.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; } } } internal static class ModVersion { public const string Current = "1.1.4"; } namespace PoliceResponseOverhaul { public static class BuildInfo { public const string Name = "Police Response Overhaul"; public const string Description = "Host-authoritative police response overhaul for Schedule I."; public const string Author = "UncleTyrone"; public const string Version = "1.1.4"; } public class PoliceResponseOverhaulMod : MelonMod { private static Instance Logger; private Harmony _harmony; private bool _harmonyApplied; private bool _systemsStarted; private bool _bootStarted; public override void OnInitializeMelon() { Logger = ((MelonBase)this).LoggerInstance; ModConfig.Initialize(); RoutesConfig.EnsureLoaded(); Logger.Msg("Police Response Overhaul loaded. Uninstall EvenMoreFootPatrols DLLs."); } public override void OnDeinitializeMelon() { SteamLobbySync.Shutdown(); PatchGate.Close(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmonyApplied = false; _bootStarted = false; } public override void OnSceneWasLoaded(int buildIndex, string sceneName) { if (sceneName != "Main") { _systemsStarted = false; _bootStarted = false; return; } PatchGate.Close(); HeatTracker.ResetSession(); OfficerPool.Clear(); ResponseDispatcher.Reset(); FinalResponse.Reset(); AmbientLaw.Reset(); BodyArmor.Reset(); LoadoutService.Reset(); K9Compat.Reset(); SteamLobbySync.Reset(); if (!_bootStarted) { _bootStarted = true; MelonCoroutines.Start(BootAfterSaveSettles()); } } public override void OnSceneWasUnloaded(int buildIndex, string sceneName) { if (sceneName == "Main") { PatchGate.Close(); _systemsStarted = false; _bootStarted = false; } } private IEnumerator BootAfterSaveSettles() { while (!InstanceFinder.IsServer && !InstanceFinder.IsClient) { yield return null; } float waited = 0f; while ((Object)(object)GameObject.Find("Player_Local") == (Object)null && waited < 90f) { yield return (object)new WaitForSeconds(0.25f); waited += 0.25f; } yield return (object)new WaitForSecondsRealtime(8f); if (!_harmonyApplied) { if (_harmony == null) { _harmony = new Harmony("com.uncletyrone.policeresponseoverhaul"); } Logger.Msg("Applying Harmony patches after save settle (batched + gated)..."); yield return CorePatches.ApplyBatched(_harmony, Logger); _harmonyApplied = true; } else if (!PatchGate.IsLive) { yield return (object)new WaitForSecondsRealtime(1f); PatchGate.Open(); } yield return StartSystems(); } public override void OnUpdate() { if (_systemsStarted && ModConfig.Enabled.Value) { if (ModConfig.F6WaypointHelper.Value && Input.GetKeyDown((KeyCode)287)) { LogPlayerCoordinates(); } SteamLobbySync.Tick(); if (NetworkAuthority.IsAuthoritative) { HeatTracker.Tick(); AmbientLaw.Tick(); ResponseDispatcher.Tick(); FinalResponse.Tick(); StuckRecovery.Tick(); K9Compat.Tick(); } } } public override void OnGUI() { HeatHud.OnGUI(); } private IEnumerator StartSystems() { if (NetworkAuthority.IsClientOnly) { Logger.Msg("Client-only: host is authoritative. Responses will be replicated by the game."); yield return (object)new WaitForSeconds(1f); BodyArmor.OnRegistryReady(); _systemsStarted = true; } else { Logger.Msg("Starting host police-response systems."); yield return (object)new WaitForSeconds(1f); BodyArmor.OnRegistryReady(); _systemsStarted = true; } } private static void LogPlayerCoordinates() { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: 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_004c: Unknown result type (might be due to invalid IL or missing references) GameObject val = GameObject.Find("Player_Local"); if ((Object)(object)val == (Object)null) { MelonLogger.Msg("Player_Local is missing, cannot fetch coordinates."); return; } Vector3 position = val.transform.position; MelonLogger.Msg($"{{ \"x\": {position.x}, \"y\": {position.y}, \"z\": {position.z} }}"); } } } namespace PoliceResponseOverhaul.World { public static class WorldHooks { public static void OnResponseDispatched(ResponsePersonality personality) { if (ModConfig.PoliceChatter.Value) { PlayChatter(); } if (ModConfig.HardCurfewOnPanic.Value && HeatTracker.Stars >= 4) { TryHardCurfew(); } } public static void OnPoliceCalled() { if (ModConfig.CivilianRadio.Value && HeatTracker.CurrentTier >= HeatTier.Heat && !ResponseDispatcher.HasActiveResponse) { ResponseDispatcher.ForceDispatch(); } } public static void ApplyBodySearchScaling(PoliceOfficer officer) { if (ModConfig.BodySearchScaling.Value && !((Object)(object)officer == (Object)null)) { officer.BodySearchChance = ((HeatTracker.CurrentTier >= HeatTier.Heat) ? 1f : 0.65f); officer.BodySearchDuration = ((HeatTracker.CurrentTier >= HeatTier.Surge) ? 4f : 6f); } } private static void PlayChatter() { try { PoliceChatterVO val = Object.FindObjectOfType(true); if (val != null) { val.PlayChatter(); } } catch { } } private static void TryHardCurfew() { try { CurfewManager val = Object.FindObjectOfType(true); if ((Object)(object)val != (Object)null) { GameAccess.Call(val, "SetCurfewModeEnabled", true); } } catch { } } } public static class HeatHud { private const float FlashHertz = 2.4f; private static GUIStyle _style; private static bool _loggedDrawError; public static void OnGUI() { //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Expected O, but got Unknown //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) if (!ModConfig.Enabled.Value || !ModConfig.HeatHud.Value || !InMainGame()) { return; } try { Player player = HeatTracker.LocalPlayer(); int num = HeatTracker.StarsFor(player); if (num > 0) { int num2; switch (HeatTracker.PhaseFor(player)) { case WantedPhase.Clear: return; case WantedPhase.Searching: num2 = ((Time.unscaledTime * 2.4f % 1f < 0.55f) ? 1 : 0); break; default: num2 = 1; break; } bool flag = (byte)num2 != 0; string text = StarText(num); GUIStyle val = LabelStyle(); Color val2 = default(Color); ((Color)(ref val2))..ctor(1f, 0.78f, 0.12f, 1f); val.normal.textColor = (Color)((!flag) ? new Color(1f, 1f, 1f, 0f) : ((num >= 6) ? val2 : Color.white)); Vector2 val3 = val.CalcSize(new GUIContent(text)); float num3 = 12f; float num4 = (float)Screen.width - val3.x - 28f - num3; float num5 = 10f; GUI.Label(new Rect(num4, num5, val3.x + num3 * 2f, val3.y + 10f), text, val); } } catch (Exception ex) { if (!_loggedDrawError) { _loggedDrawError = true; MelonLogger.Warning("Wanted HUD draw failed: " + ex.Message); } } } private static string StarText(int stars) { int num = ((stars >= 6) ? 6 : 5); int num2 = Mathf.Clamp(stars, 0, num); return new string('★', num2) + new string('☆', num - num2); } private static GUIStyle LabelStyle() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0033: 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_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown //IL_0068: Unknown result type (might be due to invalid IL or missing references) if (_style != null) { return _style; } _style = new GUIStyle((GUIStyle)(((Object)(object)GUI.skin != (Object)null) ? ((object)GUI.skin.label) : ((object)new GUIStyle()))) { fontSize = 39, fontStyle = (FontStyle)1, alignment = (TextAnchor)2, richText = false }; _style.normal.textColor = Color.white; return _style; } private static bool InMainGame() { //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) try { Scene activeScene = SceneManager.GetActiveScene(); if (!((Scene)(ref activeScene)).IsValid()) { return false; } string text = ((Scene)(ref activeScene)).name ?? string.Empty; if (text.IndexOf("menu", StringComparison.OrdinalIgnoreCase) >= 0) { return false; } if (text.IndexOf("lobby", StringComparison.OrdinalIgnoreCase) >= 0) { return false; } if (text.IndexOf("boot", StringComparison.OrdinalIgnoreCase) >= 0) { return false; } return text.IndexOf("main", StringComparison.OrdinalIgnoreCase) >= 0 || text.Length > 0; } catch { return true; } } } public static class OverhaulCommands { public static bool TryHandle(string raw) { if (string.IsNullOrWhiteSpace(raw)) { return false; } string[] array = raw.Trim().Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length == 0 || !array[0].Equals("overhaul", StringComparison.OrdinalIgnoreCase)) { return false; } switch ((array.Length > 1) ? array[1].ToLowerInvariant() : "help") { case "heat": case "wanted": { Player player = HeatTracker.LocalPlayer(); MelonLogger.Msg($"Wanted {HeatTracker.StarsFor(player)}★ ({HeatTracker.PhaseFor(player)}) session={HeatTracker.StarLabel} daily={HeatTracker.DailyKills} streak={HeatTracker.Streak}"); break; } case "reset": HeatTracker.ResetSession(); MelonLogger.Msg("Wanted level cleared."); break; case "stars": { int result2; int num = ((array.Length > 2 && int.TryParse(array[2], out result2)) ? result2 : 3); HeatTracker.SetStars(num, "command"); if (num > 0) { ResponseDispatcher.ForceDispatch(); } MelonLogger.Msg("Forced wanted " + HeatTracker.StarLabel + "."); break; } case "alert": HeatTracker.SetStars(1, "command"); ResponseDispatcher.ForceDispatch(); MelonLogger.Msg("Forced 1★ response."); break; case "surge": HeatTracker.SetStars(3, "command"); ResponseDispatcher.ForceDispatch(); MelonLogger.Msg("Forced 3★ response."); break; case "panic": HeatTracker.SetStars(4, "command"); ResponseDispatcher.ForceDispatch(); MelonLogger.Msg("Forced 4★ response."); break; case "manhunt": HeatTracker.SetStars(5, "command"); ResponseDispatcher.ForceDispatch(); MelonLogger.Msg("Forced 5★ Special Response."); break; case "juggernaut": case "lockdown": HeatTracker.SetStars(6, "command"); ResponseDispatcher.ForceDispatch(); MelonLogger.Msg("Forced 6★ juggernaut squad."); break; case "dispatch": ResponseDispatcher.ForceDispatch(); MelonLogger.Msg("Forced vehicle response."); break; case "final": { int result; int tier = ((array.Length <= 2 || !int.TryParse(array[2], out result)) ? 1 : result); FinalResponse.ForceTier(tier); break; } case "armor": BodyArmor.GiveToLocal(); break; default: MelonLogger.Msg("overhaul wanted|reset|stars [0-6]|alert|surge|panic|manhunt|juggernaut|dispatch|final [tier]|armor"); break; } return true; } } } namespace PoliceResponseOverhaul.Response { public static class JuggernautSquad { public const int MaxJuggernauts = 4; public const int SquadSize = 4; private static readonly HashSet Live = new HashSet(); private static bool _rpgClaimedThisWave; public static int WaveSize { get; private set; } = 1; public static bool Enabled => ModConfig.JuggernautsEnabled != null && ModConfig.JuggernautsEnabled.Value; public static void Reset() { Live.Clear(); WaveSize = 1; _rpgClaimedThisWave = false; } public static void BeginWaveOutfit() { _rpgClaimedThisWave = false; } public static bool ClaimRpgSlot() { if (WaveSize < 4 || _rpgClaimedThisWave) { return false; } _rpgClaimedThisWave = true; return true; } public static bool IsJuggernaut(PoliceOfficer officer) { return (Object)(object)officer != (Object)null && Live.Contains(((Object)officer).GetInstanceID()); } public static void Mark(PoliceOfficer officer) { if (!((Object)(object)officer == (Object)null)) { Live.Add(((Object)officer).GetInstanceID()); } } public static void OnOfficerKilled(PoliceOfficer officer) { if (Enabled && !((Object)(object)officer == (Object)null)) { int instanceID = ((Object)officer).GetInstanceID(); if (Live.Remove(instanceID) && HeatTracker.Stars >= 6) { WaveSize = Mathf.Min(4, WaveSize + 1); } } } public static int CrewWanted() { return 4; } public static int JuggernautsThisWave() { return Mathf.Clamp(WaveSize, 1, 4); } } public class PendingResponse { public int Cars; public int Foot; public ResponsePersonality Personality; public LoadoutTheme Theme; public float Delay; public Vector3 Origin; public Player Target; public bool IsDelayedWave; public bool IsLateWave; } public static class ResponseDispatcher { private static readonly Queue Queue = new Queue(); private static readonly List ActiveCrew = new List(); private static readonly List ActiveVehicles = new List(); private static readonly Dictionary VehicleChaseSlot = new Dictionary(); private static readonly Dictionary CorpseSince = new Dictionary(); private const float CorpseLingerSeconds = 75f; private static int _deathsTowardResponse; private static bool _running; private static float _lastDispatchTime; private static float _nextChaseRefresh; private static float _nextVehicleReclaim; private static float _nextNearbyAlert; private static readonly Dictionary VehicleDeployedAt = new Dictionary(); private static readonly Dictionary> VehicleCrewIds = new Dictionary>(); private static readonly HashSet LocalSupport = new HashSet(); public static bool HasActiveResponse => CountLiveResponseCars() > 0 || CountLiveCrew() > 0 || Queue.Count > 0 || _running; public static void Reset() { Queue.Clear(); ActiveCrew.Clear(); ActiveVehicles.Clear(); VehicleChaseSlot.Clear(); VehicleDeployedAt.Clear(); VehicleCrewIds.Clear(); ClearLocalSupport(); CorpseSince.Clear(); _deathsTowardResponse = 0; _running = false; JuggernautSquad.Reset(); } public static void StandDown() { //IL_006a: Unknown result type (might be due to invalid IL or missing references) Queue.Clear(); _running = false; _deathsTowardResponse = 0; PoliceOfficer[] array = ActiveCrew.ToArray(); foreach (PoliceOfficer val in array) { if (!((Object)(object)val == (Object)null)) { OfficerPool.Release(val); } } ActiveCrew.Clear(); ClearLocalSupport(); CorpseSince.Clear(); PoliceStation closestStation = OfficerPool.GetClosestStation(Vector3.zero); LandVehicle[] array2 = ActiveVehicles.ToArray(); foreach (LandVehicle val2 in array2) { if (!((Object)(object)val2 == (Object)null)) { int instanceID = ((Object)val2).GetInstanceID(); VehicleChaseSlot.Remove(instanceID); VehicleDeployedAt.Remove(instanceID); VehicleCrewIds.Remove(instanceID); OfficerPool.ReturnVehicle(closestStation, val2); } } ActiveVehicles.Clear(); OfficerPool.RecoverStrandedVehicles(null, force: true); JuggernautSquad.Reset(); } public static void OnOfficerKilled(PoliceOfficer officer) { if (NetworkAuthority.IsAuthoritative && ModConfig.Enabled.Value) { OfficerPool.FreeLease(officer); _deathsTowardResponse++; if (_deathsTowardResponse >= ModConfig.DeathsPerVehicleResponse.Value) { _deathsTowardResponse = 0; QueueFor(officer); } } } public static void OnOfficerDown(PoliceOfficer officer) { OfficerPool.FreeLease(officer); } public static void ForceDispatch(HeatTier? tier = null) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) Player val = ResolveTarget(); if (!((Object)(object)val == (Object)null)) { Enqueue(BuildPending(((Component)val).transform.position, val, tier ?? HeatTracker.CurrentTier, delayed: false, late: false, forceCars: true)); } } public static void Tick() { if (!NetworkAuthority.IsAuthoritative) { return; } ReapFinishedCrew(); ReapLocalSupport(); EnsureActiveVehiclesVisible(); if (!HeatTracker.IgnoreWanted) { AlertNearbyUnits(); RefreshVehicleChases(); if (!_running && Queue.Count > 0) { MelonCoroutines.Start(ProcessQueue()); } } } private static void QueueFor(PoliceOfficer officer) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) Player val = ResolveTarget(officer); if ((Object)(object)val == (Object)null) { return; } Vector3 origin = (((Object)(object)officer != (Object)null) ? ((Component)officer).transform.position : ((Component)val).transform.position); HeatTier currentTier = HeatTracker.CurrentTier; int num = CountLiveCrew(); if (num >= 2) { EnsureSinglePendingRefill(origin, val, currentTier); return; } bool flag = num <= 0 && Time.unscaledTime - _lastDispatchTime < 90f; Enqueue(BuildPending(origin, val, currentTier, delayed: false, late: false, flag || WantsVehicleChase(val))); if (num <= 1 && !QueueHasFollowUp() && HeatTracker.Stars >= 2 && Random.value < Mathf.Min(0.55f, ModConfig.DelayedWaveChance.Value)) { Enqueue(BuildPending(origin, val, HeatTracker.CurrentTier, delayed: true, late: false, forceCars: true)); } } private static void EnsureSinglePendingRefill(Vector3 origin, Player target, HeatTier tier) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) foreach (PendingResponse item in Queue) { if (item == null) { continue; } item.Origin = origin; if ((Object)(object)target != (Object)null) { item.Target = target; } UpgradePendingToContext(item, target, tier, forceCars: true); return; } PendingResponse pendingResponse = BuildPending(origin, target, tier, delayed: true, late: false, forceCars: true); pendingResponse.Delay = Mathf.Max(pendingResponse.Delay, 4f); Enqueue(pendingResponse); } private static void UpgradePendingToContext(PendingResponse pending, Player target, HeatTier tier, bool forceCars) { if (pending == null) { return; } bool flag = forceCars || WantsVehicleChase(target); ResponsePersonality b = PickPersonality(tier, pending.IsDelayedWave, pending.IsLateWave, flag); pending.Personality = StrongerPersonality(pending.Personality, b); if (flag || IsCarWave(pending.Personality)) { if (!IsCarWave(pending.Personality)) { pending.Personality = CarPersonalityFor(tier); } pending.Cars = Mathf.Max(1, Mathf.Max(pending.Cars, DesiredStreetCars(tier, pending.Personality))); pending.Foot = 0; } else { pending.Foot = Mathf.Max(pending.Foot, DesiredFootUnits(pending.Personality)); } pending.Theme = LoadoutService.ThemeFor(pending.Personality); } private static bool QueueHasFollowUp() { foreach (PendingResponse item in Queue) { if (item != null && (item.IsDelayedWave || item.IsLateWave)) { return true; } } return false; } private static void Enqueue(PendingResponse pending) { //IL_02b1: Unknown result type (might be due to invalid IL or missing references) //IL_02b6: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) if (pending == null) { return; } foreach (PendingResponse item in Queue) { if (item == null) { continue; } bool flag = IsCarWave(item.Personality); bool flag2 = IsCarWave(pending.Personality); if (flag != flag2) { if (flag2 || HeatTracker.Stars >= 3) { item.Personality = StrongerPersonality(item.Personality, flag2 ? pending.Personality : CarPersonalityFor(HeatTracker.CurrentTier)); if (!IsCarWave(item.Personality)) { item.Personality = CarPersonalityFor(HeatTracker.CurrentTier); } item.Cars = Mathf.Max(1, Mathf.Max(item.Cars, pending.Cars)); item.Foot = 0; item.Theme = LoadoutService.ThemeFor(item.Personality); item.Origin = pending.Origin; if ((Object)(object)pending.Target != (Object)null) { item.Target = pending.Target; } item.Delay = Mathf.Max(item.Delay, pending.Delay); item.IsDelayedWave = item.IsDelayedWave || pending.IsDelayedWave; item.IsLateWave = item.IsLateWave || pending.IsLateWave; } return; } if (!pending.IsDelayedWave && !pending.IsLateWave && (item.IsDelayedWave || item.IsLateWave) && flag2 == flag) { item.Cars = Mathf.Max(item.Cars, pending.Cars); item.Foot = Mathf.Max(item.Foot, pending.Foot); item.Personality = StrongerPersonality(item.Personality, pending.Personality); item.Theme = LoadoutService.ThemeFor(item.Personality); item.Origin = pending.Origin; if ((Object)(object)pending.Target != (Object)null) { item.Target = pending.Target; } if (IsCarWave(item.Personality)) { item.Cars = Mathf.Max(1, item.Cars); item.Foot = 0; } return; } item.Cars = Mathf.Max(item.Cars, pending.Cars); item.Foot = Mathf.Max(item.Foot, pending.Foot); item.Personality = StrongerPersonality(item.Personality, pending.Personality); item.Theme = LoadoutService.ThemeFor(item.Personality); item.Origin = pending.Origin; if ((Object)(object)pending.Target != (Object)null) { item.Target = pending.Target; } if (pending.IsDelayedWave || pending.IsLateWave) { item.IsDelayedWave = item.IsDelayedWave || pending.IsDelayedWave; item.IsLateWave = item.IsLateWave || pending.IsLateWave; item.Delay = Mathf.Max(item.Delay, pending.Delay); } if (IsCarWave(item.Personality)) { item.Cars = Mathf.Max(1, item.Cars); item.Foot = 0; } return; } int num = ((CountLiveCrew() >= 2) ? 1 : Mathf.Max(2, Mathf.Min(3, ModConfig.MaxPendingVehicleResponses.Value))); if (Queue.Count < num) { Queue.Enqueue(pending); } } private static PendingResponse BuildPending(Vector3 origin, Player target, HeatTier tier, bool delayed, bool late, bool forceCars = false) { //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) bool preferCars = forceCars || WantsVehicleChase(target); ResponsePersonality personality = PickPersonality(tier, delayed, late, preferCars); int num = 0; int num2 = 0; float num3 = ((delayed || late) ? DelayedWaveDelay() : HeatStagger(tier)); if (IsCarWave(personality)) { num = RollCars(tier, personality); if (num <= 0) { num = 1; num2 = 0; num3 = Mathf.Max(num3, 6f); delayed = true; } else { num2 = 0; } } else { num2 = DesiredFootUnits(personality); } return new PendingResponse { Cars = num, Foot = num2, Personality = personality, Theme = LoadoutService.ThemeFor(personality), Delay = num3, Origin = origin, Target = target, IsDelayedWave = delayed, IsLateWave = late }; } private static bool WantsVehicleChase(Player target) { if (PlayerInVehicle(target)) { return true; } if ((Object)(object)target != (Object)null) { try { if (target.TimeSinceVehicleExit < 12f) { return true; } } catch { } } if (HeatTracker.Stars >= 5) { return true; } return !IsNearPoliceStation(target, 100f); } private static bool PlayerInVehicle(Player target) { if ((Object)(object)target == (Object)null) { return false; } try { if (target.IsInVehicle) { return true; } } catch { } try { if ((Object)(object)target.CurrentVehicle != (Object)null) { return true; } } catch { } return false; } private static bool IsNearPoliceStation(Player target, float maxDist) { //IL_0018: 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_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)target == (Object)null) { return false; } try { Vector3 position = ((Component)target).transform.position; PoliceStation closestStation = OfficerPool.GetClosestStation(position); if ((Object)(object)closestStation == (Object)null) { return false; } Transform val = null; try { val = closestStation.SpawnPoint; } catch { } if ((Object)(object)val == (Object)null) { val = ((Component)closestStation).transform; } return Vector3.Distance(position, val.position) <= maxDist; } catch { return false; } } private static float DelayedWaveDelay() { if (HeatTracker.Stars >= 4) { return Random.Range(3f, 6f); } if (HeatTracker.Stars >= 3) { return Random.Range(4f, 8f); } if (HeatTracker.Stars >= 2) { return Random.Range(5f, 9f); } return Random.Range(6f, 10f); } private static float HeatStagger(HeatTier tier) { if (HeatTracker.Stars >= 4) { return Random.Range(0.5f, 1.4f); } if (HeatTracker.Stars >= 2) { return Random.Range(0.8f, 2f); } return Random.Range(1f, 2.5f); } private static bool IsCarWave(ResponsePersonality personality) { switch (personality) { case ResponsePersonality.Standard: case ResponsePersonality.Swarm: case ResponsePersonality.Panic: case ResponsePersonality.AllHands: case ResponsePersonality.Understaffed: case ResponsePersonality.Aggressive: case ResponsePersonality.K9Unit: case ResponsePersonality.SpecialResponse: case ResponsePersonality.RifleTeam: case ResponsePersonality.ShotgunTeam: case ResponsePersonality.Juggernaut: return true; default: return false; } } private static ResponsePersonality FootFallback(HeatTier tier) { return ResponsePersonality.FootSweep; } private static ResponsePersonality CarPersonalityFor(HeatTier tier) { if (tier >= HeatTier.Juggernaut && JuggernautSquad.Enabled) { return ResponsePersonality.Juggernaut; } if (tier >= HeatTier.Manhunt) { return ResponsePersonality.SpecialResponse; } if (tier >= HeatTier.Panic) { return SwatRoll(); } if (tier >= HeatTier.Surge) { return (Random.value < 0.55f) ? ResponsePersonality.RifleTeam : ResponsePersonality.ShotgunTeam; } if (tier >= HeatTier.Heat) { return (Random.value < 0.45f) ? ResponsePersonality.RifleTeam : ResponsePersonality.Standard; } return ResponsePersonality.Standard; } private static ResponsePersonality PickPersonality(HeatTier tier, bool delayed, bool late, bool preferCars) { if (tier >= HeatTier.Juggernaut && JuggernautSquad.Enabled) { return ResponsePersonality.Juggernaut; } if (HeatTracker.RevengePending) { HeatTracker.RevengePending = false; return CarPersonalityFor(tier); } if (preferCars || tier >= HeatTier.Manhunt) { if (late || delayed) { if (tier >= HeatTier.Manhunt) { return ResponsePersonality.SpecialResponse; } if (tier >= HeatTier.Panic) { return (Random.value < 0.35f) ? ResponsePersonality.K9Unit : SwatRoll(); } if (tier >= HeatTier.Surge) { return (Random.value < 0.3f) ? ResponsePersonality.K9Unit : SwatRoll(); } return CarPersonalityFor(tier); } if (tier >= HeatTier.Manhunt) { return ResponsePersonality.SpecialResponse; } if (tier >= HeatTier.Panic) { float value = Random.value; if (value < 0.22f) { return ResponsePersonality.K9Unit; } if (value < 0.48f) { return ResponsePersonality.RifleTeam; } if (value < 0.74f) { return ResponsePersonality.ShotgunTeam; } return ResponsePersonality.SpecialResponse; } if (tier >= HeatTier.Surge) { float value2 = Random.value; if (value2 < 0.18f) { return ResponsePersonality.Swarm; } if (value2 < 0.36f) { return ResponsePersonality.K9Unit; } if (value2 < 0.68f) { return ResponsePersonality.RifleTeam; } return ResponsePersonality.ShotgunTeam; } return CarPersonalityFor(tier); } if (late) { return (tier >= HeatTier.Surge) ? ResponsePersonality.FootSweep : ResponsePersonality.FootSweep; } if (delayed) { if (tier >= HeatTier.Panic) { return (Random.value < 0.45f) ? ResponsePersonality.FootSweep : SwatRoll(); } if (tier >= HeatTier.Surge) { return (Random.value < 0.55f) ? ResponsePersonality.FootSweep : ResponsePersonality.RifleTeam; } return (!(Random.value < 0.55f)) ? ResponsePersonality.FootSweep : ResponsePersonality.Standard; } float value3 = Random.value; if (tier >= HeatTier.Panic) { if (value3 < 0.4f) { return ResponsePersonality.FootSweep; } if (value3 < 0.65f) { return ResponsePersonality.RifleTeam; } return ResponsePersonality.ShotgunTeam; } if (tier >= HeatTier.Surge) { if (value3 < 0.45f) { return ResponsePersonality.FootSweep; } if (value3 < 0.7f) { return ResponsePersonality.RifleTeam; } return ResponsePersonality.Tactical; } if (tier >= HeatTier.Heat) { if (value3 < 0.4f) { return ResponsePersonality.Standard; } if (value3 < 0.7f) { return ResponsePersonality.FootSweep; } return ResponsePersonality.RifleTeam; } return (value3 < 0.55f) ? ResponsePersonality.Understaffed : ResponsePersonality.FootSweep; } private static ResponsePersonality RollPersonality(HeatTier tier, bool delayed, bool late) { return PickPersonality(tier, delayed, late, WantsVehicleChase(ResolveTarget()) || HeatTracker.Stars >= 3); } private static ResponsePersonality SwatRoll() { float value = Random.value; if (value < 0.4f) { return ResponsePersonality.RifleTeam; } if (value < 0.8f) { return ResponsePersonality.ShotgunTeam; } return ResponsePersonality.SpecialResponse; } private static ResponsePersonality StrongerPersonality(ResponsePersonality a, ResponsePersonality b) { return (PersonalityRank(b) > PersonalityRank(a)) ? b : a; } private static int PersonalityRank(ResponsePersonality personality) { if (1 == 0) { } int result = personality switch { ResponsePersonality.Juggernaut => 10, ResponsePersonality.SpecialResponse => 9, ResponsePersonality.RifleTeam => 8, ResponsePersonality.ShotgunTeam => 8, ResponsePersonality.K9Unit => 7, ResponsePersonality.Panic => 6, ResponsePersonality.AllHands => 5, ResponsePersonality.Aggressive => 4, ResponsePersonality.Tactical => 4, ResponsePersonality.Swarm => 3, ResponsePersonality.FootSweep => 2, ResponsePersonality.Standard => 1, _ => 0, }; if (1 == 0) { } return result; } private static int ConcurrentCap() { return Mathf.Clamp(ModConfig.MaxConcurrentResponseCars.Value, 1, 3); } private static int DesiredStreetCars(HeatTier tier, ResponsePersonality personality) { switch (personality) { case ResponsePersonality.Understaffed: return 1; case ResponsePersonality.Juggernaut: return 2; default: if (IsSwat(personality) || personality == ResponsePersonality.K9Unit) { return (HeatTracker.Stars < 4) ? 1 : 2; } if (HeatTracker.Stars <= 2) { return 1; } return Mathf.Min(2, ConcurrentCap()); } } private static int RollCars(HeatTier tier, ResponsePersonality personality) { if (!IsCarWave(personality)) { return 0; } int num = CountLiveResponseCars(); int num2 = Mathf.Max(0, ConcurrentCap() - num); if (num2 == 0) { return 0; } int num3 = DesiredStreetCars(tier, personality); return Mathf.Clamp(Mathf.Min(num3, num2), 0, ConcurrentCap()); } private static int CountLiveCrew() { int num = 0; foreach (PoliceOfficer item in ActiveCrew) { if (OfficerPool.IsUsable(item)) { num++; } } return num; } private static int CountLiveResponseCars() { int num = 0; for (int num2 = ActiveVehicles.Count - 1; num2 >= 0; num2--) { LandVehicle val = ActiveVehicles[num2]; if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).gameObject == (Object)null) { ActiveVehicles.RemoveAt(num2); } else if (VehicleHasLivingResponseCrew(val)) { num++; } } return num; } private static bool VehicleHasLivingResponseCrew(LandVehicle vehicle) { if ((Object)(object)vehicle == (Object)null) { return false; } if (OfficerPool.CountLivingOccupants(vehicle) > 0) { return true; } int instanceID = ((Object)vehicle).GetInstanceID(); if (VehicleCrewIds.TryGetValue(instanceID, out var value) && value != null && value.Count > 0) { foreach (PoliceOfficer item in ActiveCrew) { if ((Object)(object)item == (Object)null || !OfficerPool.IsLive(item) || !value.Contains(((Object)item).GetInstanceID())) { continue; } return true; } return false; } foreach (PoliceOfficer item2 in ActiveCrew) { if ((Object)(object)item2 == (Object)null || !OfficerPool.IsLive(item2)) { continue; } try { if ((Object)(object)((NPC)item2).CurrentVehicle == (Object)(object)vehicle || (Object)(object)item2.AssignedVehicle == (Object)(object)vehicle) { return true; } } catch { } } return false; } private static bool VehicleCrewConfirmedDead(LandVehicle vehicle) { if ((Object)(object)vehicle == (Object)null) { return true; } if (OfficerPool.CountLivingOccupants(vehicle) > 0) { return false; } int instanceID = ((Object)vehicle).GetInstanceID(); if (!VehicleCrewIds.TryGetValue(instanceID, out var value) || value == null || value.Count == 0) { return false; } foreach (PoliceOfficer item in ActiveCrew) { if ((Object)(object)item == (Object)null || !OfficerPool.IsLive(item) || !value.Contains(((Object)item).GetInstanceID())) { continue; } return false; } return true; } private static void RememberVehicleCrew(LandVehicle vehicle, List crew) { if ((Object)(object)vehicle == (Object)null || crew == null || crew.Count == 0) { return; } HashSet hashSet = new HashSet(); for (int i = 0; i < crew.Count; i++) { if ((Object)(object)crew[i] != (Object)null) { hashSet.Add(((Object)crew[i]).GetInstanceID()); } } VehicleCrewIds[((Object)vehicle).GetInstanceID()] = hashSet; } private static IEnumerator ProcessQueue() { _running = true; try { while (Queue.Count > 0) { float waitedFight = 0f; while (CountLiveCrew() >= 3 && waitedFight < 45f) { if (Queue.Count == 0) { yield break; } yield return (object)new WaitForSeconds(0.75f); waitedFight += 0.75f; } if (Queue.Count == 0) { break; } PendingResponse pending = Queue.Dequeue(); if (pending != null) { if (pending.Delay > 0f) { yield return (object)new WaitForSeconds(pending.Delay); } float waited = 0f; bool carWave = pending.Cars > 0 && IsCarWave(pending.Personality); while (carWave && CountLiveResponseCars() >= ConcurrentCap() && waited < 12f) { ReclaimFinishedResponseVehicles(); yield return (object)new WaitForSeconds(0.5f); waited += 0.5f; } yield return Dispatch(pending); float gap = WaveGap(); if (gap > 0f) { yield return (object)new WaitForSeconds(gap); } } } } finally { _running = false; } } private static float WaveGap() { if (Queue.Count == 0) { return 0f; } float num = Mathf.Max(8f, ModConfig.WaveGapSeconds.Value); if (CountLiveCrew() >= 2) { return Mathf.Max(num, 14f); } if (HeatTracker.Stars >= 5) { return Mathf.Min(num, 8f); } if (HeatTracker.Stars >= 4) { return Mathf.Min(num, 10f); } if (HeatTracker.Stars >= 3) { return Mathf.Min(num, 12f); } return num; } private static IEnumerator Dispatch(PendingResponse pending) { if (HeatTracker.IgnoreWanted) { yield break; } if ((Object)(object)pending.Target == (Object)null) { pending.Target = ResolveTarget(); } if ((Object)(object)pending.Target == (Object)null) { yield break; } UpgradePendingToContext(pending, pending.Target, HeatTracker.CurrentTier, WantsVehicleChase(pending.Target)); ReclaimFinishedResponseVehicles(); bool carWave = pending.Cars > 0 && IsCarWave(pending.Personality); if (carWave && CountLiveResponseCars() >= ConcurrentCap()) { float hold = 0f; while (CountLiveResponseCars() >= ConcurrentCap() && hold < 20f) { ReclaimFinishedResponseVehicles(); yield return (object)new WaitForSeconds(0.5f); hold += 0.5f; } if (CountLiveResponseCars() >= ConcurrentCap()) { pending.Delay = Mathf.Max(6f, pending.Delay); pending.IsDelayedWave = true; Enqueue(pending); yield break; } } WorldHooks.OnResponseDispatched(pending.Personality); OfficerPool.EnsureCapacity(); PoliceStation station = OfficerPool.GetClosestStation(pending.Origin); int spawnedCars = 0; int spawnedFoot = 0; int spawnedK9 = 0; int carSlots = (carWave ? Mathf.Min(ConcurrentCap(), Mathf.Max(1, pending.Cars)) : 0); int footWanted = ((!carWave) ? Mathf.Clamp(pending.Foot, 0, IsSwat(pending.Personality) ? 4 : 3) : 0); if (carSlots <= 0 && footWanted <= 0) { yield break; } int concurrent = ConcurrentCap(); int remainingPeople = ((pending.Personality == ResponsePersonality.Juggernaut) ? JuggernautSquad.CrewWanted() : (-1)); int juggsLeft = ((pending.Personality == ResponsePersonality.Juggernaut) ? JuggernautSquad.JuggernautsThisWave() : 0); if (pending.Personality == ResponsePersonality.Juggernaut) { JuggernautSquad.BeginWaveOutfit(); } for (int i = 0; i < carSlots; i++) { if (CountLiveResponseCars() >= concurrent) { break; } if (remainingPeople == 0) { break; } LandVehicle vehicle = OfficerPool.TryDeployVehicle(station, i, carSlots); if ((Object)(object)vehicle == (Object)null) { ReclaimFinishedResponseVehicles(); yield return (object)new WaitForSeconds(0.5f); vehicle = OfficerPool.TryDeployVehicle(station, i, carSlots); } if ((Object)(object)vehicle == (Object)null) { continue; } ActiveVehicles.Add(vehicle); VehicleChaseSlot[((Object)vehicle).GetInstanceID()] = spawnedCars; VehicleDeployedAt[((Object)vehicle).GetInstanceID()] = Time.unscaledTime; OfficerPool.EnsureVehicleVisible(vehicle); int crewSize = CrewForVehicle(vehicle, pending.Personality); if (remainingPeople >= 0) { crewSize = Mathf.Min(crewSize, remainingPeople); } int seats = VehicleSeats(vehicle, crewSize); bool k9Car = WantVehicleK9(pending.Personality); int officerCap = (k9Car ? Mathf.Min(crewSize, Mathf.Max(1, seats - 1)) : crewSize); List crew = new List(); int used = 0; Vector3 leaseNear = (((Object)(object)station != (Object)null) ? ((Component)station).transform.position : pending.Origin); int n = 0; while (crew.Count < officerCap && used < seats && n < seats + 6) { PoliceOfficer member = OfficerPool.LeaseForResponse(leaseNear); if ((Object)(object)member == (Object)null) { OfficerPool.EnsureCapacity(); yield return (object)new WaitForSeconds(0.15f); member = OfficerPool.LeaseForResponse(leaseNear); } if ((Object)(object)member == (Object)null) { break; } int cost = 1 + K9Compat.ExtraSeats(member); if (used + cost > seats) { OfficerPool.FreeLease(member); } else { crew.Add(member); used += cost; } n++; } if (crew.Count == 0) { ActiveVehicles.Remove(vehicle); VehicleChaseSlot.Remove(((Object)vehicle).GetInstanceID()); VehicleDeployedAt.Remove(((Object)vehicle).GetInstanceID()); OfficerPool.ReturnVehicle(station, vehicle); break; } OutfitVehicleCrew(crew, pending, ref juggsLeft); if (remainingPeople >= 0) { remainingPeople -= crew.Count; } RememberVehicleCrew(vehicle, crew); for (int j = 0; j < crew.Count; j++) { if (!ActiveCrew.Contains(crew[j])) { ActiveCrew.Add(crew[j]); } TryEnterVehicle(crew[j], vehicle); } yield return (object)new WaitForSeconds(0.35f); OfficerPool.EnsureVehicleVisible(vehicle); for (int k = 0; k < crew.Count; k++) { BeginVehicleHunt(crew[k], pending.Target, vehicle); K9Compat.SeatIfRiding(crew[k]); } if (k9Car) { PoliceOfficer handler = crew[0]; for (int l = 0; l < crew.Count; l++) { if (K9Compat.IsHandler(crew[l])) { handler = crew[l]; break; } } if (K9Compat.TryAttach(handler, force: true)) { spawnedK9++; } K9Compat.SeatIfRiding(handler); } spawnedCars++; if (IsSwat(pending.Personality) && crew.Count < 3 && carSlots < concurrent) { carSlots = Mathf.Min(concurrent, carSlots + 1); } if (i < carSlots - 1) { yield return (object)new WaitForSeconds(2.25f); } } if (spawnedCars == 0 && carWave) { if (ModConfig.DebugLogging.Value) { MelonLogger.Warning($"Car wave {pending.Personality} got no cruisers — requeueing instead of foot."); } pending.Cars = Mathf.Max(1, pending.Cars); pending.Foot = 0; pending.Delay = Mathf.Max(12f, pending.Delay); pending.IsDelayedWave = true; Enqueue(pending); yield break; } Vector3 footLeaseNear = (((Object)(object)station != (Object)null) ? ((Component)station).transform.position : pending.Origin); for (int m = 0; m < footWanted; m++) { PoliceOfficer extra = OfficerPool.LeaseForResponse(footLeaseNear); if ((Object)(object)extra == (Object)null) { ReleaseDownedCrewLeases(); extra = OfficerPool.LeaseForResponse(footLeaseNear); } if ((Object)(object)extra == (Object)null) { break; } if (!ActiveCrew.Contains(extra)) { ActiveCrew.Add(extra); } OutfitOfficer(extra, pending, null, ref juggsLeft); OfficerPool.DeployResponseFoot(extra, station, 40 + m); spawnedFoot += SendOnFoot(extra, pending.Target, pending.Personality, ref spawnedK9); yield return (object)new WaitForSeconds(1f); } EscalatePursuit(pending.Target, pending.Personality); if (spawnedCars == 0 && spawnedFoot == 0) { if (ModConfig.DebugLogging.Value) { MelonLogger.Warning($"Dispatch produced nobody ({pending.Personality}) — station pool may be empty."); } yield break; } _lastDispatchTime = Time.unscaledTime; MelonLogger.Msg($"Dispatched {pending.Personality} ({spawnedCars} cars, {spawnedFoot} foot, {spawnedK9} K9, {pending.Theme}) at {HeatTracker.StarLabel}."); } private static int CrewWanted(ResponsePersonality personality) { switch (personality) { case ResponsePersonality.Juggernaut: return JuggernautSquad.CrewWanted(); case ResponsePersonality.K9Unit: if (K9Compat.CanDeploy) { return 1; } break; } if (!IsSwat(personality)) { return Mathf.Clamp(ModConfig.OfficersPerCruiser.Value, 1, 2); } return (HeatTracker.Stars >= 4) ? 4 : 3; } private static bool WantVehicleK9(ResponsePersonality personality) { return personality == ResponsePersonality.K9Unit && K9Compat.CanDeploy; } private static int VehicleSeats(LandVehicle vehicle, int fallback) { try { if ((Object)(object)vehicle != (Object)null) { return Mathf.Max(1, vehicle.Capacity); } } catch { } return Mathf.Max(1, fallback); } private static int CrewForVehicle(LandVehicle vehicle, ResponsePersonality personality) { int num = 2; try { if ((Object)(object)vehicle != (Object)null) { num = Mathf.Max(1, vehicle.Capacity); } } catch { num = 2; } int num2 = CrewWanted(personality); if (!IsSwat(personality)) { return Mathf.Clamp(num2, 1, Mathf.Min(2, num)); } return Mathf.Clamp(num2, 2, num); } private static int DesiredFootUnits(ResponsePersonality personality) { if (IsCarWave(personality)) { return 0; } if (IsSwat(personality)) { return (HeatTracker.Stars >= 4) ? 4 : 3; } return (HeatTracker.Stars >= 3) ? 3 : 2; } private static void OutfitVehicleCrew(List crew, PendingResponse pending, ref int juggsLeft) { if (crew == null || crew.Count == 0) { return; } WeaponBudget budget = (IsSwat(pending.Personality) ? WeaponBudget.ForSwat(crew.Count) : WeaponBudget.ForVehicle()); foreach (PoliceOfficer item in crew) { OutfitOfficer(item, pending, budget, ref juggsLeft); } } private static void OutfitOfficer(PoliceOfficer officer, PendingResponse pending, WeaponBudget budget, ref int juggsLeft) { bool flag = pending.Personality == ResponsePersonality.Juggernaut && juggsLeft > 0; if (flag) { juggsLeft--; } LoadoutService.Assign(officer, flag ? OfficerArchetype.Elite : LoadoutService.RollArchetype(HeatTracker.CurrentTier, pending.Personality), pending.Personality, HeatTracker.CurrentTier, pending.Theme, budget, flag); if (flag || (pending.Personality == ResponsePersonality.SpecialResponse && HeatTracker.Stars >= 4)) { SmokeTactics.Arm(officer); } ActiveCrew.Add(officer); } private static int SendOnFoot(PoliceOfficer officer, Player target, ResponsePersonality personality, ref int k9) { if ((Object)(object)officer == (Object)null) { return 0; } BeginFootHunt(officer, target); bool flag = personality == ResponsePersonality.K9Unit; if (k9 == 0 && flag && K9Compat.TryAttach(officer, force: true)) { k9++; } else if (k9 == 0 && personality == ResponsePersonality.FootSweep && K9Compat.TryAttach(officer)) { k9++; } return 1; } private static bool IsSwat(ResponsePersonality personality) { if ((uint)(personality - 9) <= 3u) { return true; } return false; } private static void BeginFootHunt(PoliceOfficer officer, Player target) { if ((Object)(object)officer == (Object)null || (Object)(object)target == (Object)null) { return; } try { officer.BeginFootPursuit_Networked(target.PlayerCode, true); } catch { try { officer.BeginFootPursuit_Networked(target.PlayerCode, true); } catch { GameAccess.Call(officer, "BeginFootPursuit", target.PlayerCode); } } } private static void AlertNearbyUnits() { //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_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) if (HeatTracker.Stars < 1 || Time.unscaledTime < _nextNearbyAlert) { return; } _nextNearbyAlert = Time.unscaledTime + 2.25f; Player val = ResolveTarget(); if ((Object)(object)val == (Object)null || HeatTracker.ShouldIgnore(val)) { return; } Vector3 position; try { position = ((Component)val).transform.position; } catch { return; } float num = ((ModConfig.LocalAlertMinRadius != null) ? ModConfig.LocalAlertMinRadius.Value : 80f); float num2 = ((ModConfig.LocalAlertMaxRadius != null) ? ModConfig.LocalAlertMaxRadius.Value : 120f); if (num2 < num) { num2 = num; } float num3 = Mathf.Clamp(num + (float)HeatTracker.Stars * 6f, num, num2); int num4 = ((ModConfig.LocalAlertMaxCops != null) ? Mathf.Clamp(ModConfig.LocalAlertMaxCops.Value, 0, 6) : 2); int num5 = Mathf.Clamp((HeatTracker.Stars <= 2) ? 1 : num4, 0, num4); if (num5 <= 0) { return; } int num6 = CountLiveLocalSupport(); if (num6 >= num5) { return; } int num7 = 0; int num8 = num5 - num6; foreach (PoliceOfficer item in OfficerPool.EnumerateOfficers()) { if (num7 >= num8) { break; } if ((Object)(object)item == (Object)null || !OfficerPool.IsUsable(item) || OfficerPool.IsLeased(item) || OfficerPool.IsAtStation(item)) { continue; } float num9; try { num9 = Vector3.Distance(((Component)item).transform.position, position); } catch { continue; } if (num9 > num3) { continue; } try { if ((Object)(object)item.VehiclePursuitBehaviour != (Object)null && ((Behaviour)item.VehiclePursuitBehaviour).Active && (Object)(object)item.VehiclePursuitBehaviour.Target == (Object)(object)val) { continue; } } catch { } try { if ((Object)(object)item.PursuitBehaviour != (Object)null && ((Behaviour)item.PursuitBehaviour).Active && (Object)(object)item.PursuitBehaviour.TargetPlayer == (Object)(object)val) { continue; } } catch { } LandVehicle val2 = null; try { val2 = ((NPC)item).CurrentVehicle; } catch { val2 = null; } if (OfficerPool.ClaimForChase(item)) { LocalSupport.Add(((Object)item).GetInstanceID()); if ((Object)(object)val2 != (Object)null) { BeginVehicleHunt(item, val, val2, beginAsSighted: true); } else { BeginFootHunt(item, val); } try { EscalatePursuit(val, ResponsePersonality.Aggressive); } catch { } num7++; } } } private static int CountLiveLocalSupport() { int num = 0; foreach (int item in LocalSupport) { foreach (PoliceOfficer item2 in OfficerPool.EnumerateOfficers()) { if ((Object)(object)item2 == (Object)null || ((Object)item2).GetInstanceID() != item) { continue; } if (OfficerPool.IsUsable(item2)) { num++; } break; } } return num; } private static void ReapLocalSupport() { if (LocalSupport.Count == 0) { return; } int[] array = LocalSupport.ToArray(); foreach (int num in array) { PoliceOfficer val = null; foreach (PoliceOfficer item in OfficerPool.EnumerateOfficers()) { if ((Object)(object)item != (Object)null && ((Object)item).GetInstanceID() == num) { val = item; break; } } if ((Object)(object)val == (Object)null || !OfficerPool.IsUsable(val)) { if ((Object)(object)val != (Object)null) { OfficerPool.FreeLease(val); } LocalSupport.Remove(num); } } } private static void ClearLocalSupport() { int[] array = LocalSupport.ToArray(); foreach (int num in array) { foreach (PoliceOfficer item in OfficerPool.EnumerateOfficers()) { if ((Object)(object)item != (Object)null && ((Object)item).GetInstanceID() == num) { OfficerPool.FreeLease(item); break; } } } LocalSupport.Clear(); } public static bool TryGetChaseSlot(LandVehicle vehicle, out int slot) { slot = 0; return (Object)(object)vehicle != (Object)null && VehicleChaseSlot.TryGetValue(((Object)vehicle).GetInstanceID(), out slot); } public static void KeepVehicleChaseAlive(VehiclePursuitBehaviour pursuit) { if ((Object)(object)pursuit == (Object)null) { return; } PoliceOfficer val = ((Component)pursuit).GetComponent() ?? ((Component)pursuit).GetComponentInParent(); if (!OfficerPool.IsLeased(val)) { return; } try { val.IgnorePlayers = false; } catch { } try { NPCAwareness awareness = ((NPC)val).Awareness; if (awareness != null) { awareness.SetAwarenessActive(true); } } catch { } GameAccess.Set(pursuit, "visionEventReceived", true); try { float num = GameAccess.Get(pursuit, "timeSinceLastSighting"); if (num > 8f) { GameAccess.Set(pursuit, "timeSinceLastSighting", 4f); } } catch { } } private static void RefreshVehicleChases() { if (ActiveCrew.Count == 0 || Time.unscaledTime < _nextChaseRefresh) { return; } _nextChaseRefresh = Time.unscaledTime + 2.5f; foreach (PoliceOfficer item in ActiveCrew) { if (OfficerPool.IsUsable(item) && !((Object)(object)((NPC)item).CurrentVehicle == (Object)null)) { VehiclePursuitBehaviour vehiclePursuitBehaviour = item.VehiclePursuitBehaviour; if (!((Object)(object)vehiclePursuitBehaviour == (Object)null) && ((Behaviour)vehiclePursuitBehaviour).Active) { KeepVehicleChaseAlive(vehiclePursuitBehaviour); } } } } private static void BeginVehicleHunt(PoliceOfficer officer, Player target, LandVehicle vehicle, bool beginAsSighted = false) { if ((Object)(object)officer == (Object)null || (Object)(object)target == (Object)null) { return; } try { officer.IgnorePlayers = false; } catch { } try { NetworkObject val = (((Object)(object)vehicle != (Object)null) ? ((NetworkBehaviour)vehicle).NetworkObject : ((NetworkBehaviour)officer).NetworkObject); if ((Object)(object)val == (Object)null) { val = (((Object)(object)vehicle != (Object)null) ? ((Component)vehicle).GetComponent() : ((Component)officer).GetComponent()); } officer.BeginVehiclePursuit_Networked(target.PlayerCode, val, beginAsSighted); } catch { BeginFootHunt(officer, target); } } private static void TryEnterVehicle(PoliceOfficer officer, LandVehicle vehicle) { if ((Object)(object)officer == (Object)null || (Object)(object)vehicle == (Object)null) { return; } try { officer.IgnorePlayers = false; } catch { } try { officer.AssignedVehicle = vehicle; } catch { GameAccess.Set(officer, "AssignedVehicle", vehicle); } VehiclePatrolBehaviour vehiclePatrolBehaviour = officer.VehiclePatrolBehaviour; if ((Object)(object)vehiclePatrolBehaviour != (Object)null) { try { vehiclePatrolBehaviour.Vehicle = vehicle; } catch { } try { ((Behaviour)vehiclePatrolBehaviour).Disable(); } catch { GameAccess.Call(vehiclePatrolBehaviour, "Disable"); } } try { ((NPC)officer).EnterVehicle((NetworkConnection)null, vehicle); } catch { GameAccess.Call(officer, "EnterVehicle", null, vehicle); } try { NPCAwareness awareness = ((NPC)officer).Awareness; if (awareness != null) { awareness.SetAwarenessActive(true); } } catch { } } private static void EscalatePursuit(Player target, ResponsePersonality personality) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: 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_0082: Unknown result type (might be due to invalid IL or missing references) if (!HeatTracker.ShouldIgnore(target) && HeatTracker.StarsFor(target) > 0 && !((Object)(object)target?.CrimeData == (Object)null)) { EPursuitLevel val = (EPursuitLevel)3; if (ModConfig.LethalOnPanic.Value && (IsSwat(personality) || personality == ResponsePersonality.Panic || HeatTracker.StarsFor(target) >= 4)) { val = (EPursuitLevel)4; } if (target.CrimeData.CurrentPursuitLevel < val) { target.CrimeData.SetPursuitLevel(val); } } } private static Player ResolveTarget(PoliceOfficer fromOfficer = null) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: 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) //IL_0019: 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_003a: 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) Vector3 near = Vector3.zero; try { if ((Object)(object)fromOfficer != (Object)null) { near = ((Component)fromOfficer).transform.position; } else if ((Object)(object)Player.Local != (Object)null) { near = ((Component)Player.Local).transform.position; } } catch { } return PlayerQuery.MostWanted(near); } private static void ReapFinishedCrew() { //IL_01e5: Unknown result type (might be due to invalid IL or missing references) if (ActiveCrew.Count == 0 && ActiveVehicles.Count == 0) { return; } bool flag = false; PoliceOfficer[] array = ActiveCrew.ToArray(); foreach (PoliceOfficer val in array) { if ((Object)(object)val == (Object)null) { ActiveCrew.Remove(val); continue; } if (!OfficerPool.IsUsable(val)) { OfficerPool.FreeLease(val); } int instanceID = ((Object)val).GetInstanceID(); if (OfficerPool.IsUsable(val)) { flag = true; CorpseSince.Remove(instanceID); continue; } if (!CorpseSince.ContainsKey(instanceID)) { CorpseSince[instanceID] = Time.unscaledTime; } if (!(Time.unscaledTime - CorpseSince[instanceID] < 75f)) { OfficerPool.Release(val); ActiveCrew.Remove(val); CorpseSince.Remove(instanceID); } } if (!flag && Time.unscaledTime - _lastDispatchTime > 5f) { bool flag2 = false; for (int j = 0; j < ActiveVehicles.Count; j++) { if (VehicleHasLivingResponseCrew(ActiveVehicles[j])) { flag2 = true; break; } } if (flag2) { return; } HeatTracker.RevengePending = true; ReclaimFinishedResponseVehicles(forceAll: true); if (!HeatTracker.IgnoreWanted && Queue.Count == 0 && !_running && HeatTracker.Stars >= 1) { Player val2 = ResolveTarget(); if ((Object)(object)val2 != (Object)null) { PendingResponse pendingResponse = BuildPending(((Component)val2).transform.position, val2, HeatTracker.CurrentTier, delayed: false, late: true, forceCars: true); pendingResponse.Delay = Mathf.Max(2.5f, pendingResponse.Delay); Enqueue(pendingResponse); } } } else if (Time.unscaledTime >= _nextVehicleReclaim) { _nextVehicleReclaim = Time.unscaledTime + 5f; ReclaimFinishedResponseVehicles(); } } private static void ReclaimFinishedResponseVehicles(bool forceAll = false) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) if (ActiveVehicles.Count > 0) { PoliceStation closestStation = OfficerPool.GetClosestStation(Vector3.zero); for (int num = ActiveVehicles.Count - 1; num >= 0; num--) { LandVehicle val = ActiveVehicles[num]; if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).gameObject == (Object)null) { ActiveVehicles.RemoveAt(num); } else { int instanceID = ((Object)val).GetInstanceID(); if (!VehicleHasLivingResponseCrew(val)) { float num2 = 999f; if (VehicleDeployedAt.TryGetValue(instanceID, out var value)) { num2 = Time.unscaledTime - value; } if ((!(num2 < 16f) || VehicleCrewConfirmedDead(val)) && (forceAll || !(num2 < 6f)) && (forceAll || VehicleCrewConfirmedDead(val) || !(num2 < 20f))) { VehicleChaseSlot.Remove(instanceID); VehicleDeployedAt.Remove(instanceID); VehicleCrewIds.Remove(instanceID); OfficerPool.ReturnVehicle(closestStation, val); ActiveVehicles.RemoveAt(num); } } } } } if (forceAll && !_running) { OfficerPool.RecoverStrandedVehicles(ActiveVehicles, force: true); } } private static void EnsureActiveVehiclesVisible() { for (int i = 0; i < ActiveVehicles.Count; i++) { LandVehicle val = ActiveVehicles[i]; if (!((Object)(object)val == (Object)null) && VehicleHasLivingResponseCrew(val)) { OfficerPool.EnsureVehicleVisible(val); } } } private static void ReleaseDownedCrewLeases() { foreach (PoliceOfficer item in ActiveCrew) { if (!((Object)(object)item == (Object)null) && !OfficerPool.IsUsable(item)) { OfficerPool.FreeLease(item); } } } } internal static class SmokeTactics { public static void Arm(PoliceOfficer officer) { if (!((Object)(object)officer == (Object)null)) { MelonCoroutines.Start(ThrowLoop(officer)); } } private static IEnumerator ThrowLoop(PoliceOfficer officer) { yield return (object)new WaitForSeconds(Random.Range(3f, 6f)); int throws = 0; while ((Object)(object)officer != (Object)null && OfficerPool.IsUsable(officer) && throws < 3) { Player target = PlayerQuery.MostWanted(((Component)officer).transform.position); if ((Object)(object)target != (Object)null) { float dist = Vector3.Distance(((Component)officer).transform.position, ((Component)target).transform.position); if (dist > 7f && dist < 26f) { Vector3 dest = Vector3.Lerp(((Component)officer).transform.position, ((Component)target).transform.position, 0.6f); dest.y += 0.35f; SpawnCloud(dest); throws++; } } yield return (object)new WaitForSeconds(Random.Range(18f, 28f)); } } private static void SpawnCloud(Vector3 pos) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_0012: 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_007e: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("PRO_Smoke"); val.transform.position = pos; Color tint = default(Color); ((Color)(ref tint))..ctor(0.62f, 0.62f, 0.66f, 0.42f); for (int i = 0; i < 6; i++) { GameObject val2 = GameObject.CreatePrimitive((PrimitiveType)0); Object.Destroy((Object)(object)val2.GetComponent()); ((Object)val2).name = "puff"; val2.transform.SetParent(val.transform, false); val2.transform.localPosition = Random.insideUnitSphere * 1.4f; val2.transform.localScale = Vector3.one * Random.Range(1.8f, 3.2f); MeshRenderer component = val2.GetComponent(); if (!((Object)(object)component == (Object)null)) { ((Renderer)component).shadowCastingMode = (ShadowCastingMode)0; ((Renderer)component).receiveShadows = false; Material val3 = MakeSmokeMaterial(tint); if ((Object)(object)val3 != (Object)null) { ((Renderer)component).material = val3; } } } MelonCoroutines.Start(AnimateCloud(val)); } private static IEnumerator AnimateCloud(GameObject root) { if ((Object)(object)root == (Object)null) { yield break; } float life = 11f; float t = 0f; Vector3 start = root.transform.localScale; while ((Object)(object)root != (Object)null && t < life) { t += Time.deltaTime; float u = t / life; root.transform.localScale = start * (1f + u * 1.8f); float alpha = ((u < 0.2f) ? (u / 0.2f * 0.45f) : Mathf.Lerp(0.45f, 0f, (u - 0.2f) / 0.8f)); MeshRenderer[] componentsInChildren = root.GetComponentsInChildren(); foreach (MeshRenderer renderer in componentsInChildren) { if (!((Object)(object)renderer == (Object)null) && !((Object)(object)((Renderer)renderer).material == (Object)null)) { Color c = ((Renderer)renderer).material.color; c.a = alpha; ((Renderer)renderer).material.color = c; } } yield return null; } if ((Object)(object)root != (Object)null) { Object.Destroy((Object)(object)root); } } private static Material MakeSmokeMaterial(Color tint) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: 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_0054: Expected O, but got Unknown Shader val = Shader.Find("Sprites/Default") ?? Shader.Find("Universal Render Pipeline/Unlit") ?? Shader.Find("Unlit/Color") ?? Shader.Find("Standard"); if ((Object)(object)val == (Object)null) { return null; } Material val2 = new Material(val) { color = tint }; val2.SetInt("_SrcBlend", 5); val2.SetInt("_DstBlend", 10); val2.SetInt("_ZWrite", 0); val2.EnableKeyword("_SURFACE_TYPE_TRANSPARENT"); val2.renderQueue = 3000; return val2; } } internal static class WeaponTactics { public static float CuffRange => (ModConfig.CuffRange != null) ? Mathf.Max(1f, ModConfig.CuffRange.Value) : 2.75f; public static WeaponClass Kind(PoliceOfficer officer) { if ((Object)(object)officer == (Object)null) { return WeaponClass.Pistol; } if (LoadoutService.TryGetAssigned(officer, out var path)) { return LoadoutService.Classify(path); } CombatBehaviour val = ((Component)officer).GetComponent() ?? ((Component)officer).GetComponentInChildren(); AvatarWeapon val2 = (((Object)(object)val != (Object)null) ? GameAccessCurrent(val) : null); if ((Object)(object)val2 != (Object)null && !string.IsNullOrEmpty(((AvatarEquippable)val2).AssetPath)) { return LoadoutService.Classify(((AvatarEquippable)val2).AssetPath); } PursuitBehaviour val3 = officer.PursuitBehaviour ?? ((Component)officer).GetComponent() ?? ((Component)officer).GetComponentInChildren(); if ((Object)(object)val3?.Weapon_Gun != (Object)null && !string.IsNullOrEmpty(((AvatarEquippable)val3.Weapon_Gun).AssetPath)) { return LoadoutService.Classify(((AvatarEquippable)val3.Weapon_Gun).AssetPath); } return WeaponClass.Pistol; } public static WeaponClass Kind(CombatBehaviour combat) { PoliceOfficer val = OfficerOf(combat); if ((Object)(object)val != (Object)null) { return Kind(val); } AvatarWeapon val2 = GameAccessCurrent(combat); if ((Object)(object)val2 != (Object)null && !string.IsNullOrEmpty(((AvatarEquippable)val2).AssetPath)) { return LoadoutService.Classify(((AvatarEquippable)val2).AssetPath); } return WeaponClass.Pistol; } public static bool IsLongGun(WeaponClass kind) { if ((uint)(kind - 1) <= 5u) { return true; } return false; } public static float IdealRange(WeaponClass kind) { if (1 == 0) { } float result = kind switch { WeaponClass.Sniper => Entry(ModConfig.IdealRangeSniper, 7.5f), WeaponClass.Rifle => Entry(ModConfig.IdealRangeRifle, 5.5f), WeaponClass.Explosive => Entry(ModConfig.IdealRangeExplosive, 6.5f), WeaponClass.Heavy => Entry(ModConfig.IdealRangeHeavy, 5.5f), WeaponClass.Shotgun => Entry(ModConfig.IdealRangeShotgun, 3.5f), WeaponClass.Smg => Entry(ModConfig.IdealRangeSmg, 4f), _ => Entry(ModConfig.IdealRangePistol, 3.5f), }; if (1 == 0) { } return result; } public static bool TryIdealRange(CombatBehaviour combat, out float range) { //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) range = 0f; PoliceOfficer val = OfficerOf(combat); if ((Object)(object)val == (Object)null) { return false; } WeaponClass kind = Kind(combat); float num = (range = IdealRange(kind)); Player val2 = null; try { PursuitBehaviour val3 = (PursuitBehaviour)(object)((combat is PursuitBehaviour) ? combat : null); if (val3 != null) { val2 = val3.TargetPlayer; } } catch { val2 = null; } if ((Object)(object)val2 == (Object)null) { try { ICombatTargetable target = combat.Target; if (target != null && target.IsPlayer) { val2 = target.AsPlayer; } } catch { val2 = null; } } if ((Object)(object)val2 == (Object)null) { return true; } Vector3 val4 = ((Component)val2).transform.position; try { if ((Object)(object)val2.Avatar != (Object)null) { val4 = val2.Avatar.CenterPoint; } } catch { } float num2 = Vector3.Distance(((Component)combat).transform.position, val4); if (num2 < num) { range = Mathf.Max(Entry(ModConfig.HoldGroundMin, 2.2f), num2); } return true; } public static void ApplyUseRanges(AvatarWeapon weapon, WeaponClass kind) { if ((Object)(object)weapon == (Object)null) { return; } try { GetUseRange(kind, out var min, out var max); if (weapon.MaxUseRange < max * 0.6f) { weapon.MaxUseRange = max; } if (weapon.MinUseRange > min || weapon.MinUseRange <= 0.01f) { weapon.MinUseRange = min; } } catch { } } public static float ArrestRange(WeaponClass kind) { return CuffRange; } public static float MinShotInterval(WeaponClass kind) { if (1 == 0) { } float result = kind switch { WeaponClass.Sniper => Entry(ModConfig.ShotIntervalSniper, 0.95f), WeaponClass.Rifle => Entry(ModConfig.ShotIntervalRifle, 0.62f), WeaponClass.Explosive => Entry(ModConfig.ShotIntervalExplosive, 1.35f), WeaponClass.Heavy => Entry(ModConfig.ShotIntervalHeavy, 0.38f), WeaponClass.Shotgun => Entry(ModConfig.ShotIntervalShotgun, 0.55f), WeaponClass.Smg => Entry(ModConfig.ShotIntervalSmg, 0.22f), _ => Entry(ModConfig.ShotIntervalPistol, 0.32f), }; if (1 == 0) { } return result; } public static bool WantsGunCombat(WeaponClass kind) { return IsLongGun(kind); } public static bool WantsArrest(WeaponClass kind) { return true; } public static bool ShouldShootInsteadOfBaton(PursuitBehaviour pursuit) { if (!IsOfficer((CombatBehaviour)(object)pursuit)) { return false; } if (CanArrestNow(pursuit)) { return false; } return IsLongGun(Kind((CombatBehaviour)(object)pursuit)); } public static bool CanPolice(PoliceOfficer officer) { return OfficerPool.IsLive(officer); } public static bool CanPolice(PursuitBehaviour pursuit) { return CanPolice(OfficerOf((CombatBehaviour)(object)pursuit)); } public static bool CanArrestNow(PursuitBehaviour pursuit) { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)pursuit == (Object)null || !CanPolice(pursuit)) { return false; } PoliceOfficer val = OfficerOf((CombatBehaviour)(object)pursuit); if ((Object)(object)val == (Object)null) { return false; } Player val2 = null; try { val2 = pursuit.TargetPlayer; } catch { val2 = null; } if ((Object)(object)val2 == (Object)null) { return false; } float num = Vector3.Distance(((Component)pursuit).transform.position, ((Object)(object)val2.Avatar != (Object)null) ? val2.Avatar.CenterPoint : ((Component)val2).transform.position); return num < ArrestRange(Kind(val)); } public static void ApplyCombatStance(PursuitBehaviour pursuit) { if ((Object)(object)pursuit == (Object)null || (Object)(object)OfficerOf((CombatBehaviour)(object)pursuit) == (Object)null) { return; } WeaponClass weaponClass = Kind((CombatBehaviour)(object)pursuit); if (1 == 0) { } float num = weaponClass switch { WeaponClass.Sniper => 0.62f, WeaponClass.Rifle => 0.72f, WeaponClass.Explosive => 0.68f, WeaponClass.Heavy => 0.7f, WeaponClass.Shotgun => 0.82f, WeaponClass.Smg => 0.86f, _ => 0.74f, }; if (1 == 0) { } float num2 = num; try { ((CombatBehaviour)pursuit).SetMovementSpeed(num2, "combat", 8); } catch { } } public static void TuneWeapon(PoliceOfficer officer, CombatBehaviour combat) { if ((Object)(object)combat == (Object)null) { return; } AvatarWeapon val = GameAccessCurrent(combat); if ((Object)(object)val == (Object)null) { return; } WeaponClass weaponClass = Kind(officer); ApplyUseRanges(val, weaponClass); try { float num = MinShotInterval(weaponClass) * 0.9f; if (val.CooldownDuration < num) { val.CooldownDuration = num; } } catch { } AvatarRangedWeapon result = (AvatarRangedWeapon)(object)((val is AvatarRangedWeapon) ? val : null); if ((Object)(object)result == (Object)null && !Utils.Is(val, out result)) { return; } try { switch (weaponClass) { case WeaponClass.Sniper: if (result.MaxFireRate > 1.1f) { result.MaxFireRate = 1.1f; } result.AimTime_Min = Mathf.Max(result.AimTime_Min, 0.35f); result.AimTime_Max = Mathf.Max(result.AimTime_Max, 0.7f); return; case WeaponClass.Rifle: if (result.MaxFireRate > 2.2f) { result.MaxFireRate = 2.2f; } result.AimTime_Min = Mathf.Max(result.AimTime_Min, 0.2f); result.AimTime_Max = Mathf.Max(result.AimTime_Max, 0.45f); return; case WeaponClass.Explosive: if (result.MaxFireRate > 0.8f) { result.MaxFireRate = 0.8f; } result.AimTime_Min = Mathf.Max(result.AimTime_Min, 0.3f); result.AimTime_Max = Mathf.Max(result.AimTime_Max, 0.55f); return; case WeaponClass.Smg: if (result.MaxFireRate > 8f) { result.MaxFireRate = 8f; return; } break; } if (weaponClass == WeaponClass.Shotgun && result.MaxFireRate > 1.8f) { result.MaxFireRate = 1.8f; } else if (weaponClass == WeaponClass.Pistol && result.MaxFireRate > 3.2f) { result.MaxFireRate = 3.2f; } } catch { } } public static void SlowFire(AvatarWeapon weapon, ref bool ready) { if (!ready || !Utils.Alive((Object)(object)weapon) || !LoadoutService.IsPoliceWeapon(weapon)) { return; } WeaponClass kind = WeaponClass.Pistol; try { if (!string.IsNullOrEmpty(((AvatarEquippable)weapon).AssetPath)) { kind = LoadoutService.Classify(((AvatarEquippable)weapon).AssetPath); } } catch { kind = WeaponClass.Pistol; } float num = MinShotInterval(kind); float num2 = 0f; try { num2 = weapon.CooldownDuration * 1.15f; } catch { } float num3 = Mathf.Max(num, num2); if (num3 <= 0f) { return; } try { if (Time.time - weapon.LastUseTime < num3) { ready = false; } } catch { } } public static PoliceOfficer OfficerOf(CombatBehaviour combat) { if (!Utils.Alive((Object)(object)combat)) { return null; } PursuitBehaviour val = (PursuitBehaviour)(object)((combat is PursuitBehaviour) ? combat : null); if (val != null) { try { PoliceOfficer officer = val.officer; if (Utils.Alive((Object)(object)officer)) { return officer; } } catch { } } try { PoliceOfficer component = ((Component)combat).GetComponent(); if (Utils.Alive((Object)(object)component)) { return component; } } catch { return null; } try { PoliceOfficer componentInParent = ((Component)combat).GetComponentInParent(); return Utils.Alive((Object)(object)componentInParent) ? componentInParent : null; } catch { return null; } } public static bool IsOfficer(CombatBehaviour combat) { return (Object)(object)OfficerOf(combat) != (Object)null; } private static void GetUseRange(WeaponClass kind, out float min, out float max) { switch (kind) { case WeaponClass.Sniper: min = Entry(ModConfig.MinUseSniper, 3f); max = Entry(ModConfig.MaxUseSniper, 28f); break; case WeaponClass.Rifle: min = Entry(ModConfig.MinUseRifle, 1.5f); max = Entry(ModConfig.MaxUseRifle, 22f); break; case WeaponClass.Heavy: min = Entry(ModConfig.MinUseHeavy, 2f); max = Entry(ModConfig.MaxUseHeavy, 20f); break; case WeaponClass.Explosive: min = Entry(ModConfig.MinUseExplosive, 4f); max = Entry(ModConfig.MaxUseExplosive, 26f); break; case WeaponClass.Smg: min = Entry(ModConfig.MinUseSmg, 1.2f); max = Entry(ModConfig.MaxUseSmg, 15f); break; case WeaponClass.Shotgun: min = Entry(ModConfig.MinUseShotgun, 1f); max = Entry(ModConfig.MaxUseShotgun, 12f); break; default: min = Entry(ModConfig.MinUsePistol, 0.8f); max = Entry(ModConfig.MaxUsePistol, 14f); break; } } private static float Entry(MelonPreferences_Entry entry, float fallback) { if (entry == null) { return fallback; } float value = entry.Value; return (value > 0.01f) ? value : fallback; } private static AvatarWeapon GameAccessCurrent(CombatBehaviour combat) { if ((Object)(object)combat == (Object)null) { return null; } try { return combat.currentWeapon; } catch { return GameAccess.Get(combat, "currentWeapon"); } } } } namespace PoliceResponseOverhaul.Recovery { public static class StuckRecovery { private static float _nextCheck; public static void Tick() { if (!ModConfig.StuckRecovery.Value || Time.unscaledTime < _nextCheck) { return; } _nextCheck = Time.unscaledTime + 2.5f; foreach (PoliceOfficer item in OfficerPool.EnumerateOfficers()) { if (OfficerPool.IsLeased(item) && OfficerPool.IsUsable(item)) { TryRecover(item); } } UnstickStationDoor(); UnstickClusteredVehicles(); } private static void TryRecover(PoliceOfficer officer) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) try { LandVehicle currentVehicle = ((NPC)officer).CurrentVehicle; if ((Object)(object)currentVehicle == (Object)null) { return; } Transform transform = ((Component)currentVehicle).transform; float num = Vector3.Dot(transform.up, Vector3.up); bool flag = num < 0.25f; Rigidbody component = ((Component)currentVehicle).GetComponent(); int num2; if (!((Object)(object)component == (Object)null)) { Vector3 velocity = component.velocity; num2 = ((((Vector3)(ref velocity)).sqrMagnitude < 0.15f) ? 1 : 0); } else { num2 = 1; } bool flag2 = (byte)num2 != 0; if (flag && flag2) { Vector3 val = transform.position + transform.right * 2.2f + Vector3.up * 0.5f; NPCMovement movement = ((NPC)officer).Movement; if (movement != null) { movement.Warp(val); } DisableSiren(currentVehicle); if (ModConfig.DebugLogging.Value) { MelonLogger.Msg("Recovered officer from flipped cruiser " + ((Object)currentVehicle).name); } return; } Animator componentInChildren = ((Component)officer).GetComponentInChildren(); int num3; if ((Object)(object)componentInChildren != (Object)null) { if (!componentInChildren.GetBool("Sitting")) { AnimatorStateInfo currentAnimatorStateInfo = componentInChildren.GetCurrentAnimatorStateInfo(0); num3 = (((AnimatorStateInfo)(ref currentAnimatorStateInfo)).IsTag("Sit") ? 1 : 0); } else { num3 = 1; } } else { num3 = 0; } bool flag3 = (byte)num3 != 0; if (flag2 && flag3 && Time.frameCount % 120 == 0) { Vector3 val2 = transform.position + transform.right * 1.6f; NPCMovement movement2 = ((NPC)officer).Movement; if (movement2 != null) { movement2.Warp(val2); } } } catch (Exception ex) { if (ModConfig.DebugLogging.Value) { MelonLogger.Warning("Stuck recovery failed: " + ex.Message); } } } private static void UnstickStationDoor() { OfficerPool.SweepStationDoor(); } private static void UnstickClusteredVehicles() { //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0144: 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_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_0167: 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) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_018c: 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_0196: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) List list = new List(); foreach (PoliceOfficer item in OfficerPool.EnumerateOfficers()) { if (OfficerPool.IsLeased(item)) { LandVehicle currentVehicle = ((NPC)item).CurrentVehicle; if ((Object)(object)currentVehicle != (Object)null && !list.Contains(currentVehicle)) { list.Add(currentVehicle); } } } for (int i = 0; i < list.Count; i++) { LandVehicle val = list[i]; if ((Object)(object)val == (Object)null || !IsStationary(val)) { continue; } int num = 0; for (int j = 0; j < list.Count; j++) { if (i != j && !((Object)(object)list[j] == (Object)null) && Vector3.Distance(((Component)val).transform.position, ((Component)list[j]).transform.position) < 3.2f) { num++; } } if (num != 0) { Vector3 val2 = ((Component)val).transform.forward * (4.5f + (float)num * 2f) + ((Component)val).transform.right * ((i % 2 == 0) ? 3.2f : (-3.2f)); Transform transform = ((Component)val).transform; transform.position += val2 + Vector3.up * 0.15f; Rigidbody component = ((Component)val).GetComponent(); if ((Object)(object)component != (Object)null) { component.velocity = Vector3.zero; component.angularVelocity = Vector3.zero; component.position = ((Component)val).transform.position; } } } } private static bool IsStationary(LandVehicle vehicle) { //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) Rigidbody component = ((Component)vehicle).GetComponent(); int result; if (!((Object)(object)component == (Object)null)) { Vector3 velocity = component.velocity; result = ((((Vector3)(ref velocity)).sqrMagnitude < 0.2f) ? 1 : 0); } else { result = 1; } return (byte)result != 0; } private static void DisableSiren(LandVehicle vehicle) { Behaviour[] componentsInChildren = ((Component)vehicle).GetComponentsInChildren(true); foreach (Behaviour val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } string name = ((object)val).GetType().Name; if (name.IndexOf("PoliceLight", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("Siren", StringComparison.OrdinalIgnoreCase) >= 0) { try { val.enabled = false; } catch { } } } } } } namespace PoliceResponseOverhaul.Pooling { public static class OfficerPool { private static readonly HashSet Leased = new HashSet(); private static readonly HashSet PooledClones = new HashSet(); private static readonly HashSet DeadNative = new HashSet(); private static readonly HashSet ProDeployedVehicles = new HashSet(); private static readonly List AmbientExtras = new List(); private static bool _expanded; private static int _staffed; private static int _spreadCursor; private const int StreetColumns = 4; private const float StreetColSpace = 8.5f; private const float StreetRowSpace = 7.5f; private const float StreetFirstRow = 14f; private const float MinOfficerSpacing = 6.5f; private static PoliceStation[] _stationCache; private static float _stationCacheUntil; private static Transform _launchMarker; private static float _nextStrandedRecover; private static readonly List OfficerSnap = new List(64); private static float _officerSnapUntil; public static int AmbientExtraCount => AmbientExtras.Count; public static bool HasExpanded => _expanded; public static bool IsLeased(PoliceOfficer officer) { return (Object)(object)officer != (Object)null && Leased.Contains(((Object)officer).GetInstanceID()); } public static bool IsPooledClone(PoliceOfficer officer) { return (Object)(object)officer != (Object)null && PooledClones.Contains(((Object)officer).GetInstanceID()); } public static void ClearBuildingOccupancy(NPC npc) { if (!((Object)(object)npc == (Object)null)) { GameAccess.Set(npc, "CurrentBuilding", null); GameAccess.Set(npc, "OccupiedBuilding", null); GameAccess.Set(npc, "BuildingID", string.Empty); GameAccess.Set(npc, "currentBuildingID", string.Empty); GameAccess.Set(npc, "CurrentBuildingID", string.Empty); GameAccess.Set(npc, "isInBuilding", false); GameAccess.Set(npc, "IsInBuilding", false); } } public static void Release(PoliceOfficer officer) { if ((Object)(object)officer == (Object)null) { return; } Leased.Remove(((Object)officer).GetInstanceID()); try { LoadoutService.Restore(officer); NPCHealth component = ((Component)officer).GetComponent(); if ((Object)(object)component != (Object)null && (component.IsDead || component.IsKnockedOut)) { try { component.Revive(); } catch { GameAccess.Call(component, "Revive"); } UnmarkDead(officer); } officer.AutoDeactivate = true; try { officer.Deactivate(); } catch { } } catch (Exception ex) { if (ModConfig.DebugLogging.Value) { MelonLogger.Warning("Failed to return officer: " + ex.Message); } } } public static void FreeLease(PoliceOfficer officer) { if (!((Object)(object)officer == (Object)null)) { Leased.Remove(((Object)officer).GetInstanceID()); } } public static bool ClaimForChase(PoliceOfficer officer) { if ((Object)(object)officer == (Object)null || !IsUsable(officer) || IsLeased(officer)) { return false; } Leased.Add(((Object)officer).GetInstanceID()); try { officer.AutoDeactivate = false; } catch { } try { officer.IgnorePlayers = false; } catch { } try { officer.SetIgnorePlayers(false); } catch { } try { NPCAwareness awareness = ((NPC)officer).Awareness; if (awareness != null) { awareness.SetAwarenessActive(true); } } catch { } ClearBuildingOccupancy((NPC)(object)officer); return true; } public static PoliceOfficer LeaseIdle(Vector3 near) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) PoliceOfficer val = FindCandidate(near, allowPatrol: false); if ((Object)(object)val == (Object)null) { val = PullFromStations(near); } if ((Object)(object)val == (Object)null) { val = FindCandidate(near, allowPatrol: true); } if ((Object)(object)val == (Object)null) { return null; } Leased.Add(((Object)val).GetInstanceID()); val.AutoDeactivate = false; ClearBuildingOccupancy((NPC)(object)val); return val; } public static PoliceOfficer LeaseForResponse(Vector3 stationNear) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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) PoliceOfficer val = PullFromStations(stationNear); if ((Object)(object)val == (Object)null) { val = FindCandidatePreferStation(stationNear, allowPatrol: false); } if ((Object)(object)val == (Object)null) { val = FindCandidatePreferStation(stationNear, allowPatrol: true); } if ((Object)(object)val == (Object)null) { return null; } if (IsNearAnyPlayer(((Component)val).transform.position, 35f)) { try { } catch { } PoliceOfficer val2 = FindCandidatePreferStation(stationNear, allowPatrol: true, avoidPlayers: true); if ((Object)(object)val2 != (Object)null) { val = val2; } else if (!IsAtStation(val) && !IsPooledClone(val)) { return null; } } Leased.Add(((Object)val).GetInstanceID()); val.AutoDeactivate = false; ClearBuildingOccupancy((NPC)(object)val); return val; } private static PoliceOfficer FindCandidatePreferStation(Vector3 near, bool allowPatrol, bool avoidPlayers = false) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) PoliceOfficer result = null; float num = float.MaxValue; foreach (PoliceOfficer item in EnumerateOfficers()) { if (IsUsable(item) && !IsLeased(item) && (allowPatrol || !IsOnAmbientDuty(item)) && (!avoidPlayers || !IsNearAnyPlayer(((Component)item).transform.position, 35f))) { float num2 = Vector3.Distance(((Component)item).transform.position, near); if (IsPooledClone(item)) { num2 -= 1000f; } else if (IsAtStation(item)) { num2 -= 400f; } else if (IsNearAnyPlayer(((Component)item).transform.position, 35f)) { num2 += 500f; } if (num2 < num) { num = num2; result = item; } } } return result; } private static bool IsNearAnyPlayer(Vector3 pos, float minDist) { //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) try { foreach (Player player in Player.PlayerList) { if ((Object)(object)player == (Object)null || !(Vector3.Distance(pos, ((Component)player).transform.position) < minDist)) { continue; } return true; } } catch { try { if ((Object)(object)Player.Local != (Object)null && Vector3.Distance(pos, ((Component)Player.Local).transform.position) < minDist) { return true; } } catch { } } return false; } private static PoliceOfficer FindCandidate(Vector3 near, bool allowPatrol) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) PoliceOfficer result = null; float num = float.MaxValue; foreach (PoliceOfficer item in EnumerateOfficers()) { if (IsUsable(item) && !IsLeased(item) && (allowPatrol || !IsOnAmbientDuty(item))) { float num2 = Vector3.Distance(((Component)item).transform.position, near); float num3 = num2; if (IsPooledClone(item)) { num3 -= 1000f; } else if (IsAtStation(item)) { num3 -= 200f; } if (num3 < num) { num = num3; result = item; } } } return result; } private static PoliceOfficer PullFromStations(Vector3 near) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) PoliceStation closestStation = GetClosestStation(near); PoliceOfficer val = TryPull(closestStation); if ((Object)(object)val != (Object)null) { return val; } foreach (PoliceStation item in EnumerateStations()) { if (!((Object)(object)item == (Object)null) && !((Object)(object)item == (Object)(object)closestStation)) { val = TryPull(item); if ((Object)(object)val != (Object)null) { return val; } } } return null; } private static PoliceOfficer TryPull(PoliceStation station) { if ((Object)(object)station == (Object)null) { return null; } PoliceOfficer val = null; try { val = station.PullOfficer(); } catch { val = null; } if ((Object)(object)val == (Object)null || !IsUsable(val) || IsLeased(val)) { return null; } return val; } private static bool IsOnAmbientDuty(PoliceOfficer officer) { try { if ((Object)(object)officer.PursuitBehaviour != (Object)null && ((Behaviour)officer.PursuitBehaviour).Active) { return true; } if ((Object)(object)officer.VehiclePursuitBehaviour != (Object)null && ((Behaviour)officer.VehiclePursuitBehaviour).Active) { return true; } VehiclePatrolBehaviour vehiclePatrolBehaviour = officer.VehiclePatrolBehaviour; if ((Object)(object)vehiclePatrolBehaviour != (Object)null && ((Behaviour)vehiclePatrolBehaviour).Active) { return true; } FootPatrolBehaviour val = ((Component)officer).GetComponent() ?? ((Component)officer).GetComponentInChildren(true); if ((Object)(object)val != (Object)null && ((Behaviour)val).Active) { return true; } } catch { } return false; } public static bool IsAtDoor(PoliceOfficer officer, PoliceStation station = null) { //IL_0019: 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_005d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)officer == (Object)null) { return false; } if (station == null) { station = GetClosestStation(((Component)officer).transform.position); } if ((Object)(object)station == (Object)null) { return false; } Transform val = (((Object)(object)station.SpawnPoint != (Object)null) ? station.SpawnPoint : ((Component)station).transform); return Vector3.Distance(((Component)officer).transform.position, val.position) < 4.5f; } public static bool IsAtStation(PoliceOfficer officer, PoliceStation station = null) { if ((Object)(object)officer == (Object)null) { return false; } try { if (((NPC)officer).isInBuilding) { return true; } } catch { } return IsAtDoor(officer, station); } public static void DeployFromStation(PoliceOfficer officer, PoliceStation station, int slot, LandVehicle vehicle = null) { //IL_0025: 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) if (!((Object)(object)officer == (Object)null)) { officer.AutoDeactivate = false; ClearBuildingOccupancy((NPC)(object)officer); if (station == null) { station = GetClosestStation(((Component)officer).transform.position); } if (!((Object)(object)vehicle != (Object)null) && TryAssignSpreadPoint(station, officer, out var dest)) { WarpOntoNavMesh(officer, dest); } } } public static void DeployResponseFoot(PoliceOfficer officer, PoliceStation station, int slot) { //IL_0025: 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) if (!((Object)(object)officer == (Object)null)) { officer.AutoDeactivate = false; ClearBuildingOccupancy((NPC)(object)officer); if (station == null) { station = GetClosestStation(((Component)officer).transform.position); } if (TryAssignSpreadPoint(station, officer, out var dest) || TryGetStreetNavPoint(station, slot, out dest, officer)) { WarpOntoNavMesh(officer, dest); } } } public static void WarpOntoNavMesh(PoliceOfficer officer, Vector3 dest) { //IL_0079: 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_0061: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)officer == (Object)null) { return; } try { NPCMovement movement = ((NPC)officer).Movement; if (movement != null) { movement.Warp(dest); } } catch { } try { NavMeshAgent val = ((Component)officer).GetComponent() ?? ((Component)officer).GetComponentInChildren(true); if ((Object)(object)val != (Object)null) { if (!((Behaviour)val).enabled) { ((Behaviour)val).enabled = true; } val.Warp(dest); } } catch { } try { ((Component)officer).transform.position = dest; } catch { } } public static bool TryAssignSpreadPoint(PoliceStation station, PoliceOfficer ignore, out Vector3 dest) { //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) dest = Vector3.zero; for (int i = 0; i < 32; i++) { if (TryGetStreetNavPoint(station, _spreadCursor++, out dest, ignore)) { return true; } } return false; } public static bool TryGetStreetNavPoint(PoliceStation station, int slot, out Vector3 dest, PoliceOfficer ignore = null) { //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_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_0066: 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_006a: 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_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) dest = Vector3.zero; if (!TryGetDoorAxes(station, out var spawn, out var away, out var right)) { return false; } Vector3 position = spawn.position; int num = Mathf.Abs(slot) % 4; int num2 = Mathf.Abs(slot) / 4; float num3 = ((float)num - 1.5f) * 8.5f; float num4 = 14f + (float)num2 * 7.5f; Vector3 val = position + away * num4 + right * num3 + Vector3.up * 0.25f; NavMeshHit val2 = default(NavMeshHit); if (!NavMesh.SamplePosition(val, ref val2, 1.75f, -1)) { return false; } if (Vector3.Distance(((NavMeshHit)(ref val2)).position, position) < 8f) { return false; } if (IsOccupied(((NavMeshHit)(ref val2)).position, 6.5f, ignore)) { return false; } dest = ((NavMeshHit)(ref val2)).position; return true; } private static bool IsOccupied(Vector3 pos, float minDist, PoliceOfficer ignore) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) try { Collider[] array = Physics.OverlapSphere(pos, minDist); for (int i = 0; i < array.Length; i++) { PoliceOfficer val = (((Object)(object)array[i] != (Object)null) ? ((Component)array[i]).GetComponentInParent() : null); if ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)ignore) { return true; } } } catch { } return false; } public static PoliceStation GetClosestStation(Vector3 position) { //IL_0002: 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_005e: Unknown result type (might be due to invalid IL or missing references) try { PoliceStation closestPoliceStation = PoliceStation.GetClosestPoliceStation(position); if ((Object)(object)closestPoliceStation != (Object)null) { return closestPoliceStation; } } catch { } PoliceStation result = null; float num = float.MaxValue; foreach (PoliceStation item in EnumerateStations()) { if (!((Object)(object)item == (Object)null)) { float num2 = Vector3.Distance(((Component)item).transform.position, position); if (num2 < num) { num = num2; result = item; } } } return result; } private static IEnumerable EnumerateStations() { if (_stationCache == null || Time.unscaledTime >= _stationCacheUntil) { _stationCache = Object.FindObjectsOfType(true); _stationCacheUntil = Time.unscaledTime + 8f; } return _stationCache; } public static int CountUnleasedUsable() { int num = 0; foreach (PoliceOfficer item in EnumerateOfficers()) { if (IsUsable(item) && !IsLeased(item)) { num++; } } return num; } public static LandVehicle TryDeployVehicle(PoliceStation station, int slot = 0, int slots = 1) { LandVehicle val = TryDeployFrom(station, slot, slots); if ((Object)(object)val != (Object)null) { return val; } foreach (PoliceStation item in EnumerateStations()) { if (!((Object)(object)item == (Object)null) && !((Object)(object)item == (Object)(object)station)) { val = TryDeployFrom(item, slot, slots); if ((Object)(object)val != (Object)null) { return val; } } } return null; } private static LandVehicle TryDeployFrom(PoliceStation station, int slot, int slots) { if ((Object)(object)station == (Object)null) { return null; } LandVehicle val = null; try { Transform val2 = PickVehicleSpawnPoint(station, slot); if ((Object)(object)val2 != (Object)null) { station.TryDeployVehicle(ref val, val2); } else { Transform[] array = null; try { array = station.VehicleSpawnPoints; } catch { } bool flag = false; if (array != null) { for (int i = 0; i < array.Length; i++) { if (IsVehicleSpawnFree(array[i]) && !IsSpawnCrowdedWithOfficers(array[i])) { flag = true; break; } } } if (flag) { val = station.DeployVehicle(); } else { Transform streetOverflowSpawn = GetStreetOverflowSpawn(station, (array != null && array.Length != 0) ? array[0] : null, slot); if ((Object)(object)streetOverflowSpawn != (Object)null) { station.TryDeployVehicle(ref val, streetOverflowSpawn); } } } } catch { val = null; } if ((Object)(object)val != (Object)null) { ProDeployedVehicles.Add(((Object)val).GetInstanceID()); EnsureVehicleVisible(val); ZeroVehicleMotion(val); } return val; } private static void ZeroVehicleMotion(LandVehicle vehicle) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)vehicle == (Object)null) { return; } try { Rigidbody rb = vehicle.Rb; if ((Object)(object)rb != (Object)null) { rb.velocity = Vector3.zero; rb.angularVelocity = Vector3.zero; } } catch { try { Rigidbody component = ((Component)vehicle).GetComponent(); if ((Object)(object)component != (Object)null) { component.velocity = Vector3.zero; component.angularVelocity = Vector3.zero; } } catch { } } } public static void EnsureVehicleVisible(LandVehicle vehicle) { if ((Object)(object)vehicle == (Object)null) { return; } try { if (vehicle.isParked) { return; } } catch { } try { if (!vehicle.IsVisible) { vehicle.SetVisible(true); } } catch { try { GameAccess.Call(vehicle, "SetVisible", true); } catch { } } try { GameObject val = GameAccess.Get(vehicle, "vehicleModel"); if ((Object)(object)val != (Object)null && !val.activeSelf) { val.SetActive(true); } } catch { } } private static Transform PickVehicleSpawnPoint(PoliceStation station, int slot) { if ((Object)(object)station == (Object)null) { return null; } Transform val = null; try { Transform[] vehicleSpawnPoints = station.VehicleSpawnPoints; if (vehicleSpawnPoints != null && vehicleSpawnPoints.Length != 0) { for (int i = 0; i < vehicleSpawnPoints.Length; i++) { Transform val2 = vehicleSpawnPoints[Math.Abs(slot + i) % vehicleSpawnPoints.Length]; if (!((Object)(object)val2 == (Object)null)) { if ((Object)(object)val == (Object)null) { val = val2; } if (IsVehicleSpawnFree(val2) && !IsSpawnCrowdedWithOfficers(val2)) { return val2; } } } } } catch { } if ((Object)(object)val == (Object)null) { Transform val3 = FindNamedVehicleSpawn(station); if ((Object)(object)val3 != (Object)null && IsVehicleSpawnFree(val3) && !IsSpawnCrowdedWithOfficers(val3)) { return val3; } val = val3; } return GetStreetOverflowSpawn(station, val, slot); } private static bool IsVehicleSpawnFree(Transform spawnPoint) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)spawnPoint == (Object)null) { return false; } try { int num = LayerMask.NameToLayer("Vehicle"); Collider[] array = Physics.OverlapSphere(spawnPoint.position, 3.5f, (num >= 0) ? (1 << num) : (-1)); for (int i = 0; i < array.Length; i++) { if ((Object)(object)array[i] == (Object)null) { continue; } LandVehicle componentInParent = ((Component)array[i]).GetComponentInParent(); if ((Object)(object)componentInParent == (Object)null) { continue; } try { if (componentInParent.isParked) { continue; } } catch { } return false; } return true; } catch { return true; } } private static bool IsSpawnCrowdedWithOfficers(Transform spawnPoint) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)spawnPoint == (Object)null) { return false; } try { Collider[] array = Physics.OverlapSphere(spawnPoint.position, 4.5f); int num = 0; for (int i = 0; i < array.Length; i++) { if (!((Object)(object)array[i] == (Object)null) && (Object)(object)((Component)array[i]).GetComponentInParent() != (Object)null) { num++; if (num >= 2) { return true; } } } } catch { } return false; } private static Transform GetStreetOverflowSpawn(PoliceStation station, Transform bay, int slot) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: 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) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0147: 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_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Expected O, but got Unknown if ((Object)(object)bay == (Object)null && (Object)(object)station == (Object)null) { return null; } Transform val = (((Object)(object)bay != (Object)null) ? bay : ((Component)station).transform); Vector3 val2 = val.forward; val2.y = 0f; if (((Vector3)(ref val2)).sqrMagnitude < 0.01f) { val2 = val.position - (((Object)(object)station != (Object)null) ? ((Component)station).transform.position : val.position); val2.y = 0f; } if (((Vector3)(ref val2)).sqrMagnitude < 0.01f) { val2 = Vector3.forward; } ((Vector3)(ref val2)).Normalize(); Vector3 val3 = Vector3.Cross(Vector3.up, val2); if (((Vector3)(ref val3)).sqrMagnitude < 0.01f) { val3 = val.right; } val3.y = 0f; if (((Vector3)(ref val3)).sqrMagnitude < 0.01f) { val3 = Vector3.right; } ((Vector3)(ref val3)).Normalize(); float num = 22f + (float)Mathf.Abs(slot) * 7f; float num2 = ((slot % 2 == 0) ? (-1f) : 1f) * (3.5f + (float)(Mathf.Abs(slot) / 2) * 2.5f); Vector3 val4 = val.position + val2 * num + val3 * num2; val4.y = val.position.y + 0.15f; Quaternion val5 = Quaternion.LookRotation(val2, Vector3.up); if ((Object)(object)_launchMarker == (Object)null) { GameObject val6 = new GameObject("PRO_VehicleStreetOverflow"); Object.DontDestroyOnLoad((Object)(object)val6); _launchMarker = val6.transform; } _launchMarker.SetPositionAndRotation(val4, val5); return _launchMarker; } private static bool TryGetDoorAxes(PoliceStation station, out Transform spawn, out Vector3 away, out Vector3 right) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //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_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006b: 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_0075: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: 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) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) spawn = null; away = Vector3.forward; right = Vector3.right; if ((Object)(object)station == (Object)null) { return false; } spawn = (((Object)(object)station.SpawnPoint != (Object)null) ? station.SpawnPoint : ((Component)station).transform); if ((Object)(object)spawn == (Object)null) { return false; } away = spawn.position - ((Component)station).transform.position; away.y = 0f; if (((Vector3)(ref away)).sqrMagnitude < 0.25f) { away = spawn.forward; away.y = 0f; } if (((Vector3)(ref away)).sqrMagnitude < 0.01f) { away = Vector3.forward; } ((Vector3)(ref away)).Normalize(); right = Vector3.Cross(Vector3.up, away); if (((Vector3)(ref right)).sqrMagnitude < 0.01f) { right = spawn.right; } right.y = 0f; if (((Vector3)(ref right)).sqrMagnitude < 0.01f) { right = Vector3.right; } ((Vector3)(ref right)).Normalize(); return true; } private static Transform GetLaunchPoint(PoliceStation station, int slot, int slots) { //IL_003e: 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_004e: 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) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: 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_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0114: 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_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_013f: 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_014a: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Expected O, but got Unknown Transform val = FindNamedVehicleSpawn(station) ?? (((Object)(object)station.SpawnPoint != (Object)null) ? station.SpawnPoint : ((Component)station).transform); if ((Object)(object)val == (Object)null) { return null; } Vector3 val2 = val.position - ((Component)station).transform.position; val2.y = 0f; if (((Vector3)(ref val2)).sqrMagnitude < 0.25f) { val2 = val.forward; val2.y = 0f; } if (((Vector3)(ref val2)).sqrMagnitude < 0.01f) { val2 = Vector3.forward; } ((Vector3)(ref val2)).Normalize(); Vector3 val3 = Vector3.Cross(Vector3.up, val2); if (((Vector3)(ref val3)).sqrMagnitude < 0.01f) { val3 = val.right; } val3.y = 0f; ((Vector3)(ref val3)).Normalize(); float num = ((float)slot - (float)(slots - 1) * 0.5f) * 5.5f; Vector3 val4 = val.position + val2 * (12f + (float)slot * 2.5f) + val3 * num; val4.y = val.position.y + 0.2f; Quaternion val5 = Quaternion.LookRotation(val2, Vector3.up); if ((Object)(object)_launchMarker == (Object)null) { GameObject val6 = new GameObject("PRO_VehicleLaunch"); Object.DontDestroyOnLoad((Object)(object)val6); _launchMarker = val6.transform; } _launchMarker.SetPositionAndRotation(val4, val5); return _launchMarker; } private static Transform FindNamedVehicleSpawn(PoliceStation station) { Transform val = GameAccess.Get(station, "VehicleSpawnPoint") ?? GameAccess.Get(station, "VehicleSpawn"); if ((Object)(object)val != (Object)null) { return val; } try { Transform[] vehicleSpawnPoints = station.VehicleSpawnPoints; if (vehicleSpawnPoints != null) { for (int i = 0; i < vehicleSpawnPoints.Length; i++) { if ((Object)(object)vehicleSpawnPoints[i] != (Object)null) { return vehicleSpawnPoints[i]; } } } } catch { } Transform[] componentsInChildren = ((Component)station).GetComponentsInChildren(true); foreach (Transform val2 in componentsInChildren) { if (!((Object)(object)val2 == (Object)null)) { string name = ((Object)val2).name; if (name.IndexOf("VehicleSpawn", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("CarSpawn", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("CruiserSpawn", StringComparison.OrdinalIgnoreCase) >= 0) { return val2; } } } return null; } public static void ReturnVehicle(PoliceStation station, LandVehicle vehicle) { if ((Object)(object)vehicle != (Object)null) { ProDeployedVehicles.Remove(((Object)vehicle).GetInstanceID()); } if ((Object)(object)station == (Object)null || (Object)(object)vehicle == (Object)null) { return; } EjectDeadOccupants(vehicle); try { station.ReturnVehicle(vehicle); } catch { GameAccess.Call(station, "ReturnVehicle", vehicle); } } private static void EjectDeadOccupants(LandVehicle vehicle) { if ((Object)(object)vehicle == (Object)null) { return; } try { NPC[] occupantNPCs = vehicle.OccupantNPCs; if (occupantNPCs == null) { return; } foreach (NPC val in occupantNPCs) { if ((Object)(object)val == (Object)null) { continue; } PoliceOfficer val2 = (PoliceOfficer)(object)((val is PoliceOfficer) ? val : null); if ((Object)(object)val2 != (Object)null && IsLive(val2)) { continue; } try { val.ExitVehicle(); } catch { try { GameAccess.Call(val, "ExitVehicle"); } catch { } } } } catch { } } public static int RecoverStrandedVehicles(ICollection keepAlive = null, bool force = false) { if (ProDeployedVehicles.Count == 0) { return 0; } if (!force && Time.unscaledTime < _nextStrandedRecover) { return 0; } _nextStrandedRecover = Time.unscaledTime + 5f; HashSet hashSet = BuildOccupiedVehicleIds(); int num = 0; foreach (PoliceStation item in EnumerateStations()) { if ((Object)(object)item == (Object)null) { continue; } LandVehicle[] array = null; try { array = item.PoliceVehicles; } catch { array = null; } if (array == null) { continue; } foreach (LandVehicle val in array) { if ((Object)(object)val == (Object)null) { continue; } int instanceID = ((Object)val).GetInstanceID(); if (!ProDeployedVehicles.Contains(instanceID) || (keepAlive != null && keepAlive.Contains(val))) { continue; } if (!IsStationDeployed(item, val)) { ProDeployedVehicles.Remove(instanceID); } else if (!hashSet.Contains(instanceID) && !VehicleOccupied(val)) { try { ReturnVehicle(item, val); num++; } catch { } } } } return num; } public static bool VehicleOccupied(LandVehicle vehicle) { return CountLivingOccupants(vehicle) > 0; } public static int CountLivingOccupants(LandVehicle vehicle) { if ((Object)(object)vehicle == (Object)null) { return 0; } int num = 0; try { NPC[] occupantNPCs = vehicle.OccupantNPCs; if (occupantNPCs != null) { foreach (NPC val in occupantNPCs) { if ((Object)(object)val == (Object)null) { continue; } PoliceOfficer val2 = (PoliceOfficer)(object)((val is PoliceOfficer) ? val : null); if ((Object)(object)val2 != (Object)null) { if (IsLive(val2)) { num++; } } else { num++; } } } } catch { try { object obj2 = GameAccess.Get(vehicle, "OccupantNPCs"); if (obj2 is IEnumerable enumerable) { foreach (object item in enumerable) { if (item == null) { continue; } PoliceOfficer val3 = (PoliceOfficer)((item is PoliceOfficer) ? item : null); if (val3 != null) { if (IsLive(val3)) { num++; } } else { num++; } } } } catch { } } return num; } public static void BeginDeployLease(LandVehicle vehicle) { if ((Object)(object)vehicle != (Object)null) { ProDeployedVehicles.Add(((Object)vehicle).GetInstanceID()); } } public static void CancelDeployLease(LandVehicle vehicle) { if ((Object)(object)vehicle != (Object)null) { ProDeployedVehicles.Remove(((Object)vehicle).GetInstanceID()); } } private static HashSet BuildOccupiedVehicleIds() { HashSet hashSet = new HashSet(); foreach (PoliceOfficer item in EnumerateOfficers()) { if (!IsUsable(item)) { continue; } try { LandVehicle currentVehicle = ((NPC)item).CurrentVehicle; if ((Object)(object)currentVehicle != (Object)null) { hashSet.Add(((Object)currentVehicle).GetInstanceID()); } } catch { } try { LandVehicle assignedVehicle = item.AssignedVehicle; if ((Object)(object)assignedVehicle != (Object)null) { hashSet.Add(((Object)assignedVehicle).GetInstanceID()); } } catch { } } return hashSet; } private static bool IsStationDeployed(PoliceStation station, LandVehicle vehicle) { try { if ((Object)(object)station.PoliceVehicleParkingLot != (Object)null && vehicle.isParked) { return false; } } catch { } try { return !vehicle.isParked; } catch { return false; } } private static bool VehicleHasLivingOccupant(LandVehicle vehicle) { if ((Object)(object)vehicle == (Object)null) { return false; } int instanceID = ((Object)vehicle).GetInstanceID(); return BuildOccupiedVehicleIds().Contains(instanceID); } public static IEnumerable EnumerateOfficers() { if (Time.unscaledTime >= _officerSnapUntil) { RefreshOfficerSnap(); } return OfficerSnap; } private static void RefreshOfficerSnap() { OfficerSnap.Clear(); bool flag = false; try { List officers = PoliceOfficer.Officers; if (officers != null) { foreach (PoliceOfficer item in officers.AsEnumerable()) { if ((Object)(object)item != (Object)null) { OfficerSnap.Add(item); } } flag = true; } } catch { flag = false; } if (!flag) { try { PoliceOfficer[] array = Object.FindObjectsOfType(true); if (array != null) { for (int i = 0; i < array.Length; i++) { if ((Object)(object)array[i] != (Object)null) { OfficerSnap.Add(array[i]); } } } } catch { } } _officerSnapUntil = Time.unscaledTime + 0.25f; } public static void MarkDead(PoliceOfficer officer) { if (TryNative(officer, out var pointer)) { DeadNative.Add(pointer); } } public static void UnmarkDead(PoliceOfficer officer) { if (TryNative(officer, out var pointer)) { DeadNative.Remove(pointer); } } public static bool IsKnownDead(PoliceOfficer officer) { IntPtr pointer; return TryNative(officer, out pointer) && DeadNative.Contains(pointer); } public static bool IsLive(PoliceOfficer officer) { IntPtr pointer; return TryNative(officer, out pointer) && !IsKnownDead(officer); } public static bool IsUsable(PoliceOfficer officer) { if (!IsLive(officer)) { return false; } try { if (!((NPC)officer).isVisible) { try { if ((Object)(object)((NPC)officer).CurrentVehicle != (Object)null) { return true; } } catch { } return false; } } catch { } return true; } private static bool TryNative(PoliceOfficer officer, out IntPtr pointer) { pointer = IntPtr.Zero; if (officer == null) { return false; } try { if ((Object)(object)officer == (Object)null) { return false; } pointer = new IntPtr(((Object)officer).GetInstanceID()); return pointer != IntPtr.Zero; } catch { return false; } } public static int CountLeased() { return Leased.Count; } public static void Clear() { PoliceOfficer[] array = AmbientExtras.ToArray(); foreach (PoliceOfficer val in array) { if (!((Object)(object)val == (Object)null)) { try { Object.Destroy((Object)(object)((Component)val).gameObject); } catch { } } } AmbientExtras.Clear(); Leased.Clear(); PooledClones.Clear(); DeadNative.Clear(); ProDeployedVehicles.Clear(); _expanded = false; _staffed = 0; _spreadCursor = 0; } public static void EnsureCapacity() { _expanded = true; } public static IEnumerator DeployExtraFoot(IReadOnlyList extras) { if (extras == null || extras.Count == 0) { yield break; } PoliceOfficer template = null; for (int i = 0; i < 90; i++) { if (!((Object)(object)template == (Object)null)) { break; } template = FindVisibleTemplate(); if ((Object)(object)template != (Object)null) { break; } yield return (object)new WaitForSeconds(0.5f); } if ((Object)(object)template == (Object)null) { if (ModConfig.DebugLogging.Value) { MelonLogger.Msg("No visible officer to copy for extra patrols yet. Routes stay scheduled."); } yield break; } int spawned = 0; foreach (PatrolInstance patrol in extras) { if ((Object)(object)patrol?.Route == (Object)null || patrol.Route.Waypoints == null || patrol.Route.Waypoints.Length == 0) { continue; } PoliceOfficer clone = CloneVisible(template); if ((Object)(object)clone == (Object)null) { continue; } yield return null; if ((Object)(object)((NPC)clone).Avatar == (Object)null) { Object.Destroy((Object)(object)((Component)clone).gameObject); continue; } PatrolGroup group = new PatrolGroup(patrol.Route); int index = Mathf.Clamp(patrol.Route.StartWaypointIndex, 0, patrol.Route.Waypoints.Length - 1); Transform start = patrol.Route.Waypoints[index]; if ((Object)(object)start != (Object)null) { WarpOntoNavMesh(clone, start.position); } clone.AutoDeactivate = false; if ((Object)(object)clone.belt == (Object)null && (Object)(object)((NPC)clone).Avatar != (Object)null) { clone.belt = ((Component)((NPC)clone).Avatar).GetComponentInChildren(true); } clone.StartFootPatrol(group, true); GameAccess.Set(patrol, "ActiveGroup", group); AmbientExtras.Add(clone); spawned++; _staffed++; yield return null; } _expanded = true; if (spawned > 0) { MelonLogger.Msg($"Started {spawned} extra foot patrols."); } } private static PoliceOfficer FindVisibleTemplate() { foreach (PoliceOfficer item in EnumerateOfficers()) { if (!IsUsable(item) || K9Compat.IsHandler(item) || IsLeased(item) || (Object)(object)((NPC)item).Avatar == (Object)null || IsAtStation(item)) { continue; } return item; } return null; } private static void RebindBehaviours(PoliceOfficer officer) { if ((Object)(object)officer == (Object)null) { return; } NPCBehaviour val = ((Component)officer).GetComponent() ?? ((Component)officer).GetComponentInChildren(true); if ((Object)(object)val == (Object)null) { return; } Behaviour[] componentsInChildren = ((Component)officer).GetComponentsInChildren(true); foreach (Behaviour val2 in componentsInChildren) { if (!((Object)(object)val2 == (Object)null) && (Object)(object)val2.beh == (Object)null) { val2.beh = val; } } } private static PoliceOfficer CloneVisible(PoliceOfficer template) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0013: 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_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0029: 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_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) try { Vector3 val = ((Component)template).transform.position + ((Component)template).transform.right * 2.2f; PoliceOfficer val2 = Object.Instantiate(template, val, ((Component)template).transform.rotation); if ((Object)(object)val2 == (Object)null) { return null; } ((Object)val2).name = ((Object)template).name + " (extra patrol)"; val2.AutoDeactivate = false; RebindBehaviours(val2); ClearBuildingOccupancy((NPC)(object)val2); try { NetworkObject val3 = ((NetworkBehaviour)val2).NetworkObject ?? ((Component)val2).GetComponent(); if ((Object)(object)val3 != (Object)null && !val3.IsSpawned && InstanceFinder.IsServer) { InstanceFinder.ServerManager.Spawn(val3, (NetworkConnection)null, default(Scene)); } } catch { } return val2; } catch (Exception ex) { if (ModConfig.DebugLogging.Value) { MelonLogger.Warning("Failed to clone extra patrol officer: " + ex.Message); } return null; } } public static void SweepStationDoor() { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) PoliceStation val = null; List list = new List(); foreach (PoliceOfficer item in EnumerateOfficers()) { if (IsUsable(item) && !((Object)(object)((NPC)item).CurrentVehicle != (Object)null) && !K9Compat.IsHandler(item)) { if (val == null) { val = GetClosestStation(((Component)item).transform.position); } if (IsAtDoor(item, val)) { list.Add(item); } } } int num = 0; foreach (PoliceOfficer item2 in list) { Vector3 dest; if (!IsPooledClone(item2) && !IsLeased(item2) && (list.Count < 3 || num == 0)) { num++; } else if ((Object)(object)val != (Object)null && TryAssignSpreadPoint(val, item2, out dest)) { WarpOntoNavMesh(item2, dest); } } } public static void NoteExpanded() { _expanded = true; } } } namespace PoliceResponseOverhaul.Patches { public static class CorePatches { public static void Apply(Harmony harmony) { foreach (Type item in SafeHarmony.DiscoverPatchTypes(typeof(CorePatches).Assembly)) { SafeHarmony.TryPatchClass(harmony, item); } } public static IEnumerator ApplyBatched(Harmony harmony, Instance log = null) { yield return SafeHarmony.ApplyBatched(harmony, typeof(CorePatches).Assembly, log); } internal static bool BehaviourNetworkSafe(Behaviour behaviour) { if ((Object)(object)behaviour == (Object)null) { return false; } try { return (Object)(object)behaviour.beh != (Object)null && (Object)(object)behaviour.Npc != (Object)null; } catch { return false; } } } [HarmonyPatch(typeof(Behaviour), "Disable_Networked")] internal static class BehaviourDisableNetworkedGuardPatch { private static bool Prefix(Behaviour __instance) { return CorePatches.BehaviourNetworkSafe(__instance); } } [HarmonyPatch(typeof(Behaviour), "Deactivate_Networked")] internal static class BehaviourDeactivateNetworkedGuardPatch { private static bool Prefix(Behaviour __instance) { return CorePatches.BehaviourNetworkSafe(__instance); } } [HarmonyPatch(typeof(Behaviour), "Disable", new Type[] { })] internal static class BehaviourDisableGuardPatch { private static bool Prefix(Behaviour __instance) { return CorePatches.BehaviourNetworkSafe(__instance); } } [HarmonyPatch(typeof(NPCHealth), "Die")] internal static class OfficerDeathPatch { private static void Prefix(NPCHealth __instance) { PoliceOfficer val = OfficerFromHealth(__instance); if ((Object)(object)val == (Object)null) { return; } try { val.AutoDeactivate = false; } catch { } } private static void Postfix(NPCHealth __instance) { HandleOfficerDeath(__instance); } private static Exception Finalizer(NPCHealth __instance, Exception __exception) { if (__exception == null) { return null; } HandleOfficerDeath(__instance); PoliceOfficer val = OfficerFromHealth(__instance); return ((Object)(object)val != (Object)null) ? null : __exception; } private static void HandleOfficerDeath(NPCHealth health) { PoliceOfficer val = OfficerFromHealth(health); if (!((Object)(object)val == (Object)null)) { try { val.AutoDeactivate = false; } catch { } StopDeadOfficer(val); if (NetworkAuthority.IsAuthoritative && ModConfig.Enabled.Value) { HeatTracker.OnOfficerKilled(val); JuggernautSquad.OnOfficerKilled(val); ResponseDispatcher.OnOfficerKilled(val); FinalResponse.OnOfficerKilled(); } } } internal static void StopDeadOfficer(PoliceOfficer officer) { //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)officer == (Object)null) { return; } OfficerPool.MarkDead(officer); try { officer.AutoDeactivate = false; } catch { } try { PursuitBehaviour val = officer.PursuitBehaviour ?? ((Component)officer).GetComponent() ?? ((Component)officer).GetComponentInChildren(true); if ((Object)(object)val != (Object)null) { try { val.arrestingEnabled = false; } catch { } try { val.ResetArrestProgress(); } catch { } try { ((Behaviour)val).Disable(); } catch { GameAccess.Call(val, "Disable"); } } } catch { } try { Avatar avatar = ((NPC)officer).Avatar; if (avatar != null) { avatar.EnableRagdoll(default(Vector3), default(Vector3)); } } catch { } try { ((NPC)officer).SetVisible(true, false); } catch { GameAccess.Call(officer, "SetVisible", true); } try { Avatar avatar2 = ((NPC)officer).Avatar; if (avatar2 != null) { avatar2.SetVisible(true); } } catch { } } private static PoliceOfficer OfficerFromHealth(NPCHealth health) { if ((Object)(object)health == (Object)null) { return null; } return ((Component)health).GetComponent() ?? ((Component)health).GetComponentInParent(); } } [HarmonyPatch(typeof(NPC), "SetVisible", new Type[] { typeof(bool), typeof(bool) })] internal static class DeadOfficerStayVisiblePatch { private static bool Prefix(NPC __instance, bool visible) { if (visible) { return true; } PoliceOfficer val = (PoliceOfficer)(((object)((__instance is PoliceOfficer) ? __instance : null)) ?? ((object)((Component)__instance).GetComponent())); if ((Object)(object)val == (Object)null) { return true; } NPCHealth component = ((Component)val).GetComponent(); if ((Object)(object)component != (Object)null && (component.IsDead || component.IsKnockedOut)) { return false; } return true; } } [HarmonyPatch(typeof(PoliceOfficer), "Deactivate")] internal static class DeadOfficerNoDeactivatePatch { private static bool Prefix(PoliceOfficer __instance) { if ((Object)(object)__instance == (Object)null || !OfficerPool.IsLeased(__instance)) { return true; } NPCHealth component = ((Component)__instance).GetComponent(); return (Object)(object)component == (Object)null || (!component.IsDead && !component.IsKnockedOut); } } [HarmonyPatch(typeof(PursuitBehaviour), "OnCurrentWeaponChanged")] internal static class PursuitWeaponChangedGuardPatch { private static bool Prefix(PursuitBehaviour __instance) { try { if (!Utils.Alive((Object)(object)__instance)) { return false; } PoliceOfficer officer = __instance.officer; if (!Utils.Alive((Object)(object)officer)) { return false; } if ((Object)(object)officer.belt == (Object)null && Utils.Alive((Object)(object)((NPC)officer).Avatar)) { officer.belt = ((Component)((NPC)officer).Avatar).GetComponentInChildren(true); } return (Object)(object)officer.belt != (Object)null; } catch { return false; } } } [HarmonyPatch(typeof(PursuitBehaviour), "GetIdealRangedWeaponDistance")] internal static class PursuitIdealRangePatch { private static void Postfix(PursuitBehaviour __instance, ref float __result) { if (WeaponTactics.TryIdealRange((CombatBehaviour)(object)__instance, out var range)) { __result = range; } } } [HarmonyPatch(typeof(CombatBehaviour), "GetIdealRangedWeaponDistance")] internal static class CombatIdealRangePatch { private static void Postfix(CombatBehaviour __instance, ref float __result) { if (!(__instance is PursuitBehaviour) && WeaponTactics.TryIdealRange(__instance, out var range)) { __result = range; } } } [HarmonyPatch(typeof(PursuitBehaviour), "UpdateArrestBehaviour")] internal static class PursuitArrestBehaviourPatch { private static bool Prefix(PursuitBehaviour __instance) { if (!WeaponTactics.CanPolice(__instance)) { return false; } if (!WeaponTactics.ShouldShootInsteadOfBaton(__instance)) { return true; } try { __instance.UpdateLethalBehaviour(); } catch { return true; } return false; } } [HarmonyPatch(typeof(PursuitBehaviour), "UpdateNonLethalBehaviour")] internal static class PursuitNonLethalBehaviourPatch { private static bool Prefix(PursuitBehaviour __instance) { if (!WeaponTactics.IsOfficer((CombatBehaviour)(object)__instance)) { return true; } if (!WeaponTactics.CanPolice(__instance)) { return false; } if (!WeaponTactics.WantsGunCombat(WeaponTactics.Kind((CombatBehaviour)(object)__instance))) { return true; } try { __instance.UpdateLethalBehaviour(); } catch { return true; } return false; } } [HarmonyPatch(typeof(PursuitBehaviour), "UpdateLethalBehaviour")] internal static class PursuitLethalBehaviourPatch { private static void Postfix(PursuitBehaviour __instance) { WeaponTactics.ApplyCombatStance(__instance); } } [HarmonyPatch(typeof(PursuitBehaviour), "UpdateArrest")] internal static class PursuitArrestGatePatch { private static bool Prefix(PursuitBehaviour __instance, float __0) { //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) if (!WeaponTactics.IsOfficer((CombatBehaviour)(object)__instance)) { return true; } if (!WeaponTactics.CanPolice(__instance)) { try { __instance.timeWithinArrestRange = Mathf.Max(0f, __instance.timeWithinArrestRange - __0); } catch { } return false; } Player val = null; try { val = __instance.TargetPlayer; } catch { } if ((Object)(object)val == (Object)null) { return false; } try { GameAccess.Set(__instance, "arrestingEnabled", true); } catch { } Vector3 val2 = ((Component)val).transform.position; try { if ((Object)(object)val.Avatar != (Object)null) { val2 = val.Avatar.CenterPoint; } } catch { } float num = Vector3.Distance(((Component)__instance).transform.position, val2); bool flag = true; try { flag = ((CombatBehaviour)__instance).IsTargetRecentlyVisible; } catch { } bool flag2 = true; try { flag2 = GameAccess.Get(__instance, "arrestingEnabled"); } catch { } if (num < WeaponTactics.CuffRange && flag2 && flag) { __instance.timeWithinArrestRange += __0; if (__instance.timeWithinArrestRange > 0.5f) { try { GameAccess.Set(__instance, "wasInArrestCircleLastFrame", true); } catch { } } } else { try { if (GameAccess.Get(__instance, "wasInArrestCircleLastFrame")) { int num2 = GameAccess.Get(__instance, "leaveArrestCircleCount"); GameAccess.Set(__instance, "leaveArrestCircleCount", num2 + 1); GameAccess.Set(__instance, "wasInArrestCircleLastFrame", false); } } catch { } __instance.timeWithinArrestRange = Mathf.Max(0f, __instance.timeWithinArrestRange - __0); } try { if (((NetworkBehaviour)val).IsOwner) { float num3 = __instance.timeWithinArrestRange / 1.75f; if (num3 > val.CrimeData.CurrentArrestProgress) { val.CrimeData.SetArrestProgress(num3); } } } catch { } return false; } } [HarmonyPatch(typeof(AvatarWeapon), "IsReadyToAttack")] internal static class OfficerSlowFirePatch { private static void Postfix(AvatarWeapon __instance, ref bool __result) { WeaponTactics.SlowFire(__instance, ref __result); } } [HarmonyPatch(typeof(NPCHealth), "KnockOut")] internal static class KnockoutBlockPatch { private static bool Prefix(NPCHealth __instance) { return !FinalResponse.ShouldBlockKnockout(__instance); } private static void Postfix(NPCHealth __instance) { PoliceOfficer val = ((Component)__instance).GetComponent() ?? ((Component)__instance).GetComponentInParent(); if ((Object)(object)val != (Object)null) { OfficerPool.MarkDead(val); ResponseDispatcher.OnOfficerDown(val); } } } [HarmonyPatch(typeof(PlayerHealth), "TakeDamage")] internal static class ArmorDamagePatch { private static void Prefix(PlayerHealth __instance, ref float damage) { if (ModConfig.ArmorEnabled.Value) { Player player = ((Component)__instance).GetComponent() ?? ((Component)__instance).GetComponentInParent(); damage = BodyArmor.ModifyDamage(player, damage); } } } [HarmonyPatch(typeof(NPCHealth), "TakeDamage")] internal static class OfficerArmorDamagePatch { private static void Prefix(NPCHealth __instance, ref float damage) { if (ModConfig.ArmorEnabled.Value) { NPC npc = __instance.npc; PoliceOfficer val = (PoliceOfficer)(object)((npc is PoliceOfficer) ? npc : null); if (!((Object)(object)val == (Object)null)) { damage = BodyArmor.ModifyOfficerDamage(val, damage); } } } } [HarmonyPatch(typeof(PoliceOfficer), "Start")] internal static class OfficerStartArmorPatch { private static void Postfix(PoliceOfficer __instance) { BodyArmor.EnsureStreetArmor(__instance); } } [HarmonyPatch(typeof(PoliceOfficer), "StartFootPatrol")] internal static class OfficerPatrolArmorPatch { private static void Postfix(PoliceOfficer __instance) { BodyArmor.EnsureStreetArmor(__instance); } } [HarmonyPatch(typeof(Registry), "Start")] internal static class ArmorRegistryReadyPatch { private static void Postfix() { BodyArmor.OnRegistryReady(); } } [HarmonyPatch(typeof(PlayerHealth), "Die")] internal static class PlayerDeathPatch { private static void Postfix(PlayerHealth __instance) { Player player = ((Component)__instance).GetComponent() ?? ((Component)__instance).GetComponentInParent(); BodyArmor.OnPlayerDied(player); HeatTracker.OnPlayerDiedOrArrested(player); } } [HarmonyPatch(typeof(Player), "Arrest_Client")] internal static class PlayerArrestPatch { private static void Postfix(Player __instance) { HeatTracker.OnPlayerDiedOrArrested(__instance); } } [HarmonyPatch(typeof(PlayerCrimeData), "OnPlayerFreed")] internal static class PlayerFreedPatch { private static void Postfix(PlayerCrimeData __instance) { Player player = null; try { player = (((Object)(object)__instance != (Object)null) ? __instance.Player : null); } catch { } HeatTracker.OnPlayerDiedOrArrested(player); } } [HarmonyPatch(typeof(LawActivitySettings), "Evaluate")] internal static class LawEvaluatePatch { private static void Postfix(LawActivitySettings __instance) { if (PatchGate.IsLive && NetworkAuthority.IsAuthoritative) { AmbientLaw.OnSettingsEvaluate(__instance); } } } [HarmonyPatch(typeof(ShopInterface), "Awake")] internal static class ShopAwakePatch { private static void Postfix(ShopInterface __instance) { BodyArmor.TryInjectShop(__instance); } } [HarmonyPatch(typeof(ShopInterface), "Open")] internal static class ShopOpenPatch { private static void Postfix(ShopInterface __instance) { BodyArmor.TryInjectShop(__instance); } } [HarmonyPatch(typeof(ShopInterface), "SetIsOpen")] internal static class ShopSetIsOpenPatch { private static void Postfix(ShopInterface __instance, bool open) { if (open) { BodyArmor.TryInjectShop(__instance); } } } [HarmonyPatch(typeof(ListingUI), "Initialize")] internal static class ArmorListingNamePatch { private static void Postfix(ListingUI __instance, ShopListing listing) { BodyArmor.ApplyListingDisplayName(__instance, listing); } } [HarmonyPatch(typeof(Cart), "Buy")] internal static class ArmorCartBuyPatch { private static void Prefix(Cart __instance, out bool __state) { __state = CartContainsArmor(__instance); } private static void Postfix(bool __state) { if (__state) { Player local = Player.Local; if (!((Object)(object)local == (Object)null)) { BodyArmor.MarkOwner(local); MelonLogger.Msg("Body Armor purchase recorded. Keep a vest equipped in Outerwear for damage reduction."); } } } private static bool CartContainsArmor(Cart cart) { if ((Object)(object)cart == (Object)null || !ModConfig.ArmorEnabled.Value) { return false; } try { ClothingShopListing armorListing = BodyArmor.ArmorListing; if (armorListing != null && cart.GetCartCount((ShopListing)(object)armorListing) > 0) { return true; } } catch { } try { object obj2 = GameAccess.Get(cart, "cartEntries"); if (!(obj2 is IEnumerable enumerable)) { return false; } foreach (object item in enumerable) { ShopListing val = null; try { val = GameAccess.Get(item, "Listing"); } catch { continue; } if (BodyArmor.IsOurListing(val)) { return true; } } } catch { } return false; } } [HarmonyPatch(typeof(CallPoliceBehaviour), "FinalizeCall")] internal static class CivilianRadioPatch { private static void Postfix() { if (NetworkAuthority.IsAuthoritative) { WorldHooks.OnPoliceCalled(); } } } [HarmonyPatch(typeof(PatrolInstance), "StartPatrol")] internal static class PatrolStartCapPatch { private static bool Prefix(PatrolInstance __instance) { if (!ModConfig.Enabled.Value || __instance == null) { return true; } try { __instance.MaxMembers = Mathf.Min(Mathf.Max(1, __instance.MaxMembers), 2); __instance.MinMembers = Mathf.Clamp(__instance.MinMembers, 1, __instance.MaxMembers); if (AmbientLaw.RouteHasActiveGroup(__instance.Route, __instance)) { return false; } } catch { } return true; } } [HarmonyPatch(typeof(LawManager), "StartFootpatrol")] internal static class FootPatrolMemberCapPatch { private static bool Prefix(FootPatrolRoute route, ref int requestedMembers) { if (!ModConfig.Enabled.Value) { return true; } requestedMembers = Mathf.Clamp(requestedMembers, 1, 2); int num = AmbientLaw.CountOfficersOnRoute(route); if (num >= 2) { return false; } requestedMembers = Mathf.Min(requestedMembers, 2 - num); return requestedMembers > 0; } } [HarmonyPatch(typeof(NPC), "ExitBuilding", new Type[] { typeof(NPCEnterableBuilding) })] internal static class ExitBuildingNullGuardPatch { private static bool Prefix(NPC __instance, NPCEnterableBuilding building) { if ((Object)(object)__instance == (Object)null || (Object)(object)building == (Object)null) { return false; } try { if ((Object)(object)__instance.Avatar == (Object)null) { return false; } StaticDoor[] doors = building.Doors; if (doors == null || doors.Length == 0 || (Object)(object)doors[0] == (Object)null) { return false; } StaticDoor val = __instance.LastEnteredDoor; if ((Object)(object)val == (Object)null) { val = doors[0]; } if ((Object)(object)val == (Object)null || (Object)(object)val.AccessPoint == (Object)null) { return false; } PoliceOfficer val2 = (PoliceOfficer)(((object)((__instance is PoliceOfficer) ? __instance : null)) ?? ((object)((Component)__instance).GetComponent())); if ((Object)(object)val2 != (Object)null && (OfficerPool.IsLeased(val2) || OfficerPool.IsPooledClone(val2))) { return false; } } catch { return false; } return true; } } [HarmonyPatch(typeof(NPC), "SetVisible", new Type[] { typeof(bool), typeof(bool) })] internal static class SetVisibleNullAvatarPatch { private static bool Prefix(NPC __instance) { return (Object)(object)__instance != (Object)null && (Object)(object)__instance.Avatar != (Object)null; } } [HarmonyPatch] internal static class OfficerActivatePatch { private static bool Prepare() { return TargetMethod() != null; } private static MethodBase TargetMethod() { return AccessTools.DeclaredMethod(typeof(PoliceOfficer), "Activate", (Type[])null, (Type[])null) ?? AccessTools.DeclaredMethod(typeof(PoliceOfficer), "BeginBodySearch_Networked", (Type[])null, (Type[])null); } private static bool Prefix(PoliceOfficer __instance) { if (OfficerPool.IsPooledClone(__instance) && !OfficerPool.IsLeased(__instance)) { return false; } if (OfficerPool.IsPooledClone(__instance)) { OfficerPool.ClearBuildingOccupancy((NPC)(object)__instance); } return true; } private static void Postfix(PoliceOfficer __instance) { try { WorldHooks.ApplyBodySearchScaling(__instance); } catch { } BodyArmor.EnsureStreetArmor(__instance); } } [HarmonyPatch] internal static class OfficerPursuitLoadoutPatch { private static bool Prepare() { return TargetMethods().Any(); } private static IEnumerable TargetMethods() { string[] names = new string[4] { "BeginFootPursuit", "BeginFootPursuit_Networked", "BeginVehiclePursuit", "BeginVehiclePursuit_Networked" }; string[] array = names; foreach (string name in array) { MethodInfo method = AccessTools.DeclaredMethod(typeof(PoliceOfficer), name, (Type[])null, (Type[])null); if (method != null) { yield return method; } } } private static void Postfix(PoliceOfficer __instance) { if (!PatchGate.IsLive || !NetworkAuthority.IsAuthoritative || !ModConfig.Enabled.Value) { return; } try { LoadoutService.EnsureCombatLoadout(__instance); } catch (Exception) { } } } [HarmonyPatch(typeof(CombatBehaviour), "StartCombat")] internal static class CombatStartLoadoutPatch { private static void Postfix(CombatBehaviour __instance) { if (!PatchGate.IsLive || !NetworkAuthority.IsAuthoritative || !ModConfig.Enabled.Value) { return; } try { LoadoutService.ReapplyAssigned(__instance); } catch (Exception) { } } } [HarmonyPatch] internal static class VehiclePursuitChasePatch { private static bool Prepare() { return false; } private static MethodBase TargetMethod() { return AccessTools.DeclaredMethod(typeof(VehiclePursuitBehaviour), "UpdateDestination", (Type[])null, (Type[])null); } private static void Prefix(VehiclePursuitBehaviour __instance) { } } [HarmonyPatch] internal static class VehicleChasePointPatch { private static bool Prepare() { return TargetMethod() != null; } private static MethodBase TargetMethod() { return AccessTools.DeclaredMethod(typeof(VehiclePursuitBehaviour), "GetPlayerChasePoint", (Type[])null, (Type[])null); } private static void Postfix(VehiclePursuitBehaviour __instance, ref Vector3 __result) { //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: 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_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_0147: 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_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0168: 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) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) LandVehicle val = GameAccess.Get(__instance, "vehicle"); if ((Object)(object)val == (Object)null || !ResponseDispatcher.TryGetChaseSlot(val, out var slot)) { return; } Vector3 val2 = __result; Vector3 val3 = Vector3.forward; try { Player val4 = GameAccess.Get(__instance, "Target"); if ((Object)(object)val4 != (Object)null) { Vector3 val5 = Vector3.zero; try { val5 = (((Object)(object)val4.VelocityCalculator != (Object)null) ? val4.VelocityCalculator.Velocity : Vector3.zero); } catch { } val5.y = 0f; if (((Vector3)(ref val5)).sqrMagnitude > 0.25f) { val3 = ((Vector3)(ref val5)).normalized; } else { val3 = val2 - ((Component)val).transform.position; val3.y = 0f; if (((Vector3)(ref val3)).sqrMagnitude < 0.01f) { val3 = ((Component)val).transform.forward; } ((Vector3)(ref val3)).Normalize(); } } } catch { val3 = ((Component)val).transform.forward; val3.y = 0f; if (((Vector3)(ref val3)).sqrMagnitude < 0.01f) { val3 = Vector3.forward; } else { ((Vector3)(ref val3)).Normalize(); } } Vector3 val6 = Vector3.Cross(Vector3.up, val3); if (((Vector3)(ref val6)).sqrMagnitude < 0.01f) { val6 = Vector3.right; } ((Vector3)(ref val6)).Normalize(); float num = ((slot % 2 == 0) ? (-1f) : 1f); float num2 = 7f + (float)(slot / 2) * 4f; __result = val2 + val6 * num * num2 - val3 * ((float)slot * 2.5f); } } [HarmonyPatch] internal static class VehiclePursuitObstaclePatch { private static bool Prepare() { return TargetMethod() != null; } private static MethodBase TargetMethod() { return AccessTools.DeclaredMethod(typeof(VehiclePursuitBehaviour), "SetAggressiveDriving", (Type[])null, (Type[])null); } private static void Postfix(VehiclePursuitBehaviour __instance, bool aggressive) { //IL_009a: Unknown result type (might be due to invalid IL or missing references) if (!aggressive) { return; } LandVehicle val = null; try { val = __instance.vehicle; } catch { val = GameAccess.Get(__instance, "vehicle"); } if ((Object)(object)val == (Object)null) { return; } PoliceOfficer officer = null; try { officer = ((Component)__instance).GetComponent() ?? ((Component)__instance).GetComponentInParent(); } catch { } if (!OfficerPool.IsLeased(officer)) { return; } try { VehicleAgent agent = val.Agent; if (!((Object)(object)agent == (Object)null) && agent.Flags != null) { agent.Flags.ObstacleMode = (EObstacleMode)0; agent.Flags.UseRoads = true; if (agent.Flags.OverriddenSpeed > 55f) { agent.Flags.OverriddenSpeed = 55f; } } } catch { } } } [HarmonyPatch] internal static class TimeoutPursuitPatch { private static bool Prepare() { return TargetMethod() != null; } private static MethodBase TargetMethod() { return AccessTools.DeclaredMethod(typeof(PlayerCrimeData), "TimeoutPursuit", (Type[])null, (Type[])null); } private static bool Prefix(PlayerCrimeData __instance) { Player val = null; try { val = (((Object)(object)__instance != (Object)null) ? __instance.Player : null); } catch { val = null; } if (HeatTracker.ShouldIgnore(val) || HeatTracker.StarsFor(val) <= 0) { return true; } return !ModConfig.LethalOnPanic.Value || HeatTracker.StarsFor(val) < 4; } } [HarmonyPatch(typeof(Console))] internal static class ConsoleCommandPatch { private static bool Prepare() { return typeof(Console).GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public).Any((MethodInfo method) => method.Name == "SubmitCommand"); } private static IEnumerable TargetMethods() { MethodInfo[] methods = typeof(Console).GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public); foreach (MethodInfo method in methods) { if (!(method.Name != "SubmitCommand")) { ParameterInfo[] parameters = method.GetParameters(); if (parameters.Length == 1 && parameters[0].ParameterType == typeof(string)) { yield return method; } } } } private static bool Prefix(string __0) { try { if (string.IsNullOrWhiteSpace(__0) || !__0.TrimStart().StartsWith("overhaul", StringComparison.OrdinalIgnoreCase)) { return true; } return !OverhaulCommands.TryHandle(__0); } catch { return true; } } } [HarmonyPatch(typeof(CombatBehaviour), "RpcLogic___ClearWeapon_2166136261")] internal static class ClearWeaponSafePatch { private static bool Prepare() { return AccessTools.DeclaredMethod(typeof(CombatBehaviour), "RpcLogic___ClearWeapon_2166136261", (Type[])null, (Type[])null) != null; } private static bool Prefix(CombatBehaviour __instance) { if ((Object)(object)__instance == (Object)null) { return false; } CombatWeaponGuards.EnsureHitEvent((AvatarWeapon)(object)__instance.VirtualPunchWeapon); AvatarWeapon val = null; try { val = __instance.currentWeapon; } catch { try { val = GameAccess.Get(__instance, "currentWeapon"); } catch { } } if (!Utils.Alive((Object)(object)val)) { try { GameAccess.Set(__instance, "currentWeapon", null); } catch { } return false; } CombatWeaponGuards.EnsureHitEvent(val); return true; } private static Exception Finalizer(Exception __exception) { return null; } } [HarmonyPatch(typeof(CombatBehaviour), "RpcLogic___SetWeapon_3615296227")] internal static class SetWeaponSafePatch { private static bool Prepare() { return AccessTools.DeclaredMethod(typeof(CombatBehaviour), "RpcLogic___SetWeapon_3615296227", (Type[])null, (Type[])null) != null; } private static bool Prefix(CombatBehaviour __instance, string weaponPath) { //IL_0239: Unknown result type (might be due to invalid IL or missing references) //IL_0240: Expected O, but got Unknown if ((Object)(object)__instance == (Object)null) { return false; } try { CombatWeaponGuards.EnsureHitEvent((AvatarWeapon)(object)__instance.VirtualPunchWeapon); AvatarWeapon val = null; try { val = __instance.currentWeapon; } catch { try { val = GameAccess.Get(__instance, "currentWeapon"); } catch { } } if (Utils.Alive((Object)(object)val)) { string a = null; try { a = ((AvatarEquippable)val).AssetPath; } catch { } if (!string.IsNullOrEmpty(weaponPath) && string.Equals(a, weaponPath, StringComparison.Ordinal)) { return false; } try { __instance.ClearWeapon(); } catch { try { AccessTools.Method(typeof(CombatBehaviour), "RpcLogic___ClearWeapon_2166136261", (Type[])null, (Type[])null)?.Invoke(__instance, null); } catch { GameAccess.Set(__instance, "currentWeapon", null); } } } if (string.IsNullOrEmpty(weaponPath)) { return false; } AvatarEquippable val2 = null; try { NPC npc = ((Behaviour)__instance).Npc; if ((Object)(object)npc != (Object)null) { val2 = npc.SetEquippable_Return(weaponPath); } } catch (Exception ex) { if (ModConfig.DebugLogging.Value) { MelonLogger.Warning("SetWeapon SetEquippable_Return(" + weaponPath + "): " + ex.Message); } } AvatarWeapon val3 = (AvatarWeapon)(object)((val2 is AvatarWeapon) ? val2 : null); if (!Utils.Alive((Object)(object)val3) && Utils.Alive((Object)(object)val2)) { try { val3 = ((Component)val2).GetComponent() ?? ((Component)val2).GetComponentInChildren(true); } catch { } } if (!Utils.Alive((Object)(object)val3)) { GameAccess.Set(__instance, "currentWeapon", null); return false; } CombatWeaponGuards.EnsureHitEvent(val3); if (string.IsNullOrEmpty(((AvatarEquippable)val3).AssetPath)) { try { ((AvatarEquippable)val3).AssetPath = weaponPath; } catch { GameAccess.Set(val3, "AssetPath", weaponPath); } } GameAccess.Set(__instance, "currentWeapon", val3); try { MethodInfo methodInfo = AccessTools.Method(typeof(CombatBehaviour), "SucessfulHit", (Type[])null, (Type[])null); if (methodInfo != null && val3.onSuccessfulHit != null) { UnityAction val4 = (UnityAction)Delegate.CreateDelegate(typeof(UnityAction), __instance, methodInfo); val3.onSuccessfulHit.AddListener(val4); } } catch { } try { __instance.OnCurrentWeaponChanged(val3); } catch { try { GameAccess.Call(__instance, "OnCurrentWeaponChanged", val3); } catch { } } return false; } catch (Exception ex2) { MelonLogger.Warning("SetWeapon safe path failed: " + ex2.Message); return false; } } private static Exception Finalizer(Exception __exception) { return null; } } internal static class CombatWeaponGuards { internal static void EnsureHitEvent(AvatarWeapon weapon) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Expected O, but got Unknown //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown if (!Utils.Alive((Object)(object)weapon)) { return; } try { if (weapon.onSuccessfulHit != null) { return; } } catch { } try { weapon.onSuccessfulHit = new UnityEvent(); } catch { try { GameAccess.Set(weapon, "onSuccessfulHit", (object)new UnityEvent()); } catch { } } } } } namespace PoliceResponseOverhaul.Loadout { public enum WeaponClass { Pistol, Shotgun, Rifle, Heavy, Smg, Sniper, Explosive } public sealed class WeaponBudget { public int Rifles; public int Shotguns; public readonly int MaxRifles; public readonly int MaxShotguns; public bool CanRifle => Rifles < MaxRifles; public bool CanShotgun => Shotguns < MaxShotguns; public WeaponBudget(int maxRifles = 99, int maxShotguns = 99) { MaxRifles = maxRifles; MaxShotguns = maxShotguns; } public static WeaponBudget ForVehicle() { return new WeaponBudget(1, 1); } public static WeaponBudget ForSwat(int crew) { int num = Mathf.Max(1, crew); return new WeaponBudget(num, num); } public void Record(WeaponClass kind) { if (((uint)(kind - 2) <= 1u || (uint)(kind - 5) <= 1u) ? true : false) { Rifles++; } if ((kind == WeaponClass.Shotgun || kind == WeaponClass.Smg) ? true : false) { Shotguns++; } } } public static class LoadoutService { private static readonly Dictionary OriginalWeapons = new Dictionary(); private static readonly Dictionary OriginalGuns = new Dictionary(); private static readonly Dictionary OriginalHealth = new Dictionary(); private static readonly Dictionary OriginalGiveUp = new Dictionary(); private static readonly Dictionary AssignedWeapons = new Dictionary(); private static readonly Dictionary OfficerGunTemplates = new Dictionary(); private static readonly HashSet PoliceWeaponIds = new HashSet(); private static bool? _moreGunsPresent; private static bool _moreGunsLogged; private static Type _moreGunsWeaponBase; public static bool MoreGunsAvailable { get { if (_moreGunsPresent == true) { return true; } Type type = MoreGunsWeaponBase(); if (type == null) { if (!_moreGunsLogged) { _moreGunsLogged = true; MelonLogger.Msg("More Guns not detected. Officers will use vanilla pistols and pump shotguns."); } return false; } if (FindWeapon("ak47") != null || FindWeapon("minigun") != null || FindWeapon("smg") != null || FindWeapon("sniper") != null || FindWeapon("rpg") != null || WeaponsFinishedLoading(type)) { if (_moreGunsPresent != true) { _moreGunsPresent = true; MelonLogger.Msg("More Guns detected. Response and street patrols can equip custom weapons."); } return true; } return false; } } public static void Reset() { OriginalWeapons.Clear(); OriginalGuns.Clear(); OriginalHealth.Clear(); OriginalGiveUp.Clear(); AssignedWeapons.Clear(); foreach (AvatarWeapon value in OfficerGunTemplates.Values) { if (!((Object)(object)value == (Object)null)) { try { Object.Destroy((Object)(object)((Component)value).gameObject); } catch { } } } OfficerGunTemplates.Clear(); PoliceWeaponIds.Clear(); _moreGunsPresent = null; _moreGunsLogged = false; _moreGunsWeaponBase = null; } public static void EnsureStreetLoadout(PoliceOfficer officer) { if (!((Object)(object)officer == (Object)null) && !HasLoadout(officer) && PatchGate.IsLive && ModConfig.AmbientMoreGuns.Value) { MelonCoroutines.Start(AssignStreetWhenReady(officer)); } } private static IEnumerator AssignStreetWhenReady(PoliceOfficer officer) { float waited = 0f; while (!MoreGunsAvailable && waited < 45f) { if (!Utils.Alive((Object)(object)officer)) { yield break; } waited += 0.5f; yield return (object)new WaitForSeconds(0.5f); } if (!Utils.Alive((Object)(object)officer) || HasLoadout(officer) || !MoreGunsAvailable) { yield break; } try { Assign(officer, OfficerArchetype.Veteran, ResponsePersonality.Standard, HeatTier.Calm, LoadoutTheme.Mixed, new WeaponBudget(1, 1)); } catch (Exception ex) { MelonLogger.Warning("Street loadout failed: " + ex.Message); } } public static bool HasLoadout(PoliceOfficer officer) { return (Object)(object)officer != (Object)null && AssignedWeapons.ContainsKey(((Object)officer).GetInstanceID()); } public static bool TryGetAssigned(PoliceOfficer officer, out string path) { path = null; return (Object)(object)officer != (Object)null && AssignedWeapons.TryGetValue(((Object)officer).GetInstanceID(), out path) && !string.IsNullOrEmpty(path); } public static void EnsureCombatLoadout(PoliceOfficer officer) { if ((Object)(object)officer == (Object)null || HasLoadout(officer) || !PatchGate.IsLive) { return; } try { Assign(officer, RollArchetype(HeatTracker.CurrentTier, ResponsePersonality.Standard), ResponsePersonality.Standard, HeatTracker.CurrentTier); } catch (Exception ex) { MelonLogger.Warning("Combat loadout failed: " + ex.Message); } } public static bool IsPoliceWeapon(AvatarWeapon weapon) { if (!Utils.Alive((Object)(object)weapon)) { return false; } try { return PoliceWeaponIds.Contains(((Object)weapon).GetInstanceID()); } catch { return false; } } public static void ReapplyAssigned(CombatBehaviour combat) { if (Utils.Alive((Object)(object)combat)) { PoliceOfficer officer = WeaponTactics.OfficerOf(combat); if (TryGetAssigned(officer, out var path) && !AlreadyHolding(combat, path)) { ApplyWeapon(officer, combat, path); } } } public static void Assign(PoliceOfficer officer, OfficerArchetype archetype, ResponsePersonality personality, HeatTier tier, LoadoutTheme theme = LoadoutTheme.Mixed, WeaponBudget budget = null, bool juggernaut = false) { if ((Object)(object)officer == (Object)null) { return; } int instanceID = ((Object)officer).GetInstanceID(); CombatBehaviour val = ((Component)officer).GetComponent() ?? ((Component)officer).GetComponentInChildren(); NPCHealth val2 = ((Component)officer).GetComponent() ?? ((Component)officer).GetComponentInChildren(); if ((Object)(object)val != (Object)null && !OriginalGiveUp.ContainsKey(instanceID)) { OriginalGiveUp[instanceID] = val.GiveUpRange; } if ((Object)(object)val2 != (Object)null && !OriginalHealth.ContainsKey(instanceID) && TryReadBaselineHealth(val2, out var baseline)) { OriginalHealth[instanceID] = baseline; } if (1 == 0) { } float num = archetype switch { OfficerArchetype.Rookie => 0.85f, OfficerArchetype.Enforcer => 1.35f, OfficerArchetype.Elite => 2.2f, OfficerArchetype.Marksman => 1.05f, _ => 1.1f, }; if (1 == 0) { } float num2 = num; if (juggernaut) { num2 = 2f; } else if (personality == ResponsePersonality.Swarm) { num2 += 0.2f; } if ((Object)(object)val2 != (Object)null && OriginalHealth.TryGetValue(instanceID, out var value)) { GameAccess.Set(val2, "MaxHealth", value * num2); GameAccess.Set(val2, "Health", value * num2); } if ((Object)(object)val != (Object)null) { bool flag = juggernaut; bool flag2 = flag; if (!flag2) { bool flag3 = (((uint)(personality - 2) <= 2u || (uint)(personality - 8) <= 4u) ? true : false); flag2 = flag3; } bool flag4 = flag2 || HeatTracker.Stars >= 4; val.GiveUpRange = (flag4 ? 9999f : Mathf.Max(val.GiveUpRange, 40f)); val.DefaultMovementSpeed = ((archetype == OfficerArchetype.Enforcer) ? (val.DefaultMovementSpeed * 0.9f) : (val.DefaultMovementSpeed * 1.05f)); } if (budget == null) { budget = new WeaponBudget(); } string text = (juggernaut ? JuggernautWeapon() : RollWeapon(archetype, personality, tier, theme, budget)); budget.Record(Classify(text)); AssignedWeapons[instanceID] = text; BodyArmor.EquipOfficer(officer, juggernaut || BodyArmor.ShouldWearArmor(officer, archetype, personality, text)); if (juggernaut) { JuggernautSquad.Mark(officer); BodyArmor.ApplyJuggernautLook(officer); } ApplyWeapon(officer, val, text); MelonCoroutines.Start(ReapplyWeapon(officer, text)); if (ModConfig.DebugLogging.Value) { MelonLogger.Msg($"Assigned {text} ({archetype}/{personality}/{tier})"); } } public static void Restore(PoliceOfficer officer) { if (!((Object)(object)officer == (Object)null)) { int instanceID = ((Object)officer).GetInstanceID(); CombatBehaviour val = ((Component)officer).GetComponent() ?? ((Component)officer).GetComponentInChildren(); NPCHealth val2 = ((Component)officer).GetComponent() ?? ((Component)officer).GetComponentInChildren(); PursuitBehaviour pursuit = GetPursuit(officer); if ((Object)(object)val != (Object)null && OriginalGiveUp.TryGetValue(instanceID, out var value)) { val.GiveUpRange = value; } if ((Object)(object)val2 != (Object)null && OriginalHealth.TryGetValue(instanceID, out var value2)) { GameAccess.Set(val2, "Health", value2); } if ((Object)(object)pursuit != (Object)null && OriginalGuns.TryGetValue(instanceID, out var value3) && (Object)(object)value3 != (Object)null) { pursuit.Weapon_Gun = value3; } DestroyOfficerGunTemplate(instanceID); BodyArmor.EquipOfficer(officer, armored: false); if (OriginalWeapons.TryGetValue(instanceID, out var value4)) { ApplyWeapon(officer, val, value4); } ForgetPoliceWeapons(instanceID); OriginalWeapons.Remove(instanceID); OriginalGuns.Remove(instanceID); OriginalHealth.Remove(instanceID); OriginalGiveUp.Remove(instanceID); AssignedWeapons.Remove(instanceID); } } public static OfficerArchetype RollArchetype(HeatTier tier, ResponsePersonality personality) { switch (personality) { case ResponsePersonality.Juggernaut: return (Random.value < 0.5f) ? OfficerArchetype.Elite : OfficerArchetype.Marksman; case ResponsePersonality.RifleTeam: return (Random.value < 0.4f) ? OfficerArchetype.Elite : OfficerArchetype.Marksman; case ResponsePersonality.ShotgunTeam: return (Random.value < 0.4f) ? OfficerArchetype.Elite : OfficerArchetype.Enforcer; case ResponsePersonality.SpecialResponse: return (Random.value < 0.55f) ? OfficerArchetype.Elite : OfficerArchetype.Enforcer; default: { float num = ModConfig.EliteOfficerChance.Value; if (personality == ResponsePersonality.AllHands) { num = Mathf.Max(num, 0.2f); } if (HeatTracker.Stars >= 5) { num = Mathf.Max(num, 0.22f); } if (Random.value < num) { return OfficerArchetype.Elite; } float value = Random.value; if (value < 0.2f) { return OfficerArchetype.Rookie; } if (value < 0.45f) { return OfficerArchetype.Marksman; } if (value < 0.65f) { return OfficerArchetype.Enforcer; } return OfficerArchetype.Veteran; } } } public static LoadoutTheme ThemeFor(ResponsePersonality personality) { if (personality == ResponsePersonality.Juggernaut || personality == ResponsePersonality.RifleTeam) { return LoadoutTheme.Rifles; } return personality switch { ResponsePersonality.ShotgunTeam => LoadoutTheme.Shotguns, ResponsePersonality.SpecialResponse => (Random.value < 0.5f) ? LoadoutTheme.Rifles : LoadoutTheme.Shotguns, _ => LoadoutTheme.Mixed, }; } private static string JuggernautWeapon() { if (MoreGunsAvailable && JuggernautSquad.WaveSize >= 4 && FindWeapon("rpg") != null && JuggernautSquad.ClaimRpgSlot()) { return AvatarPath(FirstId(ModConfig.RpgIds.Value, "rpg")); } if (MoreGunsAvailable) { return AvatarPath(FirstId(ModConfig.HeavyIds.Value, "minigun")); } return AvatarPath(FirstId(ModConfig.RifleIds.Value, "ak47")) ?? VanillaShotgunPath(); } private static string RollWeapon(OfficerArchetype archetype, ResponsePersonality personality, HeatTier tier, LoadoutTheme theme, WeaponBudget budget) { switch (theme) { case LoadoutTheme.Rifles: if (personality != ResponsePersonality.Juggernaut && Random.value >= 0.85f) { return VanillaPistolPath(); } return PickRifle(archetype, tier, budget) ?? FallbackLongGun(budget); case LoadoutTheme.Shotguns: if (Random.value < 0.85f && budget.CanShotgun) { if (MoreGunsAvailable && Random.value < ModConfig.SmgChanceNormal.Value && FindWeapon("smg") != null) { return AvatarPath(FirstId(ModConfig.SmgIds.Value, "smg")); } return VanillaShotgunPath(); } return VanillaPistolPath(); default: { bool flag = (((uint)(personality - 2) <= 2u || personality == ResponsePersonality.Aggressive) ? true : false); bool flag2 = flag; float num = 0f; if (HeatTracker.Stars >= 4) { num += 0.05f; } if (HeatTracker.Stars >= 5) { num += 0.05f; } if (HeatTrackerMaybeRevenge()) { num += 0.1f; } float num2 = Mathf.Clamp01((flag2 ? ModConfig.RifleChanceTactical.Value : ModConfig.RifleChanceNormal.Value) + num); float num3 = Mathf.Clamp01((flag2 ? ModConfig.SniperChanceTactical.Value : ModConfig.SniperChanceNormal.Value) + num); if (budget.CanRifle && MoreGunsAvailable) { bool flag3 = archetype == OfficerArchetype.Elite && Random.value < ModConfig.MinigunChanceElite.Value; if (!flag3 && (personality == ResponsePersonality.Panic || tier >= HeatTier.Panic) && Random.value < ModConfig.MinigunChancePanic.Value) { flag3 = true; } if (flag3) { return AvatarPath(FirstId(ModConfig.HeavyIds.Value, "minigun")); } if (Random.value <= num2) { return AvatarPath(FirstId(ModConfig.RifleIds.Value, "ak47")); } if (Random.value <= num3 && FindWeapon("sniper") != null) { return AvatarPath(FirstId(ModConfig.SniperIds.Value, "sniper")); } } float num4 = ShotgunChance(archetype, personality, tier); if (!budget.CanShotgun) { num4 = 0f; } if (Random.value <= num4) { return VanillaShotgunPath(); } float num5 = SmgChance(archetype, personality, tier); if (!budget.CanShotgun) { num5 = 0f; } if (MoreGunsAvailable && Random.value <= num5 && FindWeapon("smg") != null) { return AvatarPath(FirstId(ModConfig.SmgIds.Value, "smg")); } return VanillaPistolPath(); } } } private static string PickRifle(OfficerArchetype archetype, HeatTier tier, WeaponBudget budget) { if (!budget.CanRifle || !MoreGunsAvailable) { return null; } bool flag = archetype == OfficerArchetype.Elite && Random.value < ModConfig.MinigunChanceElite.Value; if (!flag && tier >= HeatTier.Panic && Random.value < ModConfig.MinigunChancePanic.Value) { flag = true; } if (flag) { return AvatarPath(FirstId(ModConfig.HeavyIds.Value, "minigun")); } if ((archetype == OfficerArchetype.Marksman || archetype == OfficerArchetype.Elite) && FindWeapon("sniper") != null && Random.value < ModConfig.SniperChanceNormal.Value) { return AvatarPath(FirstId(ModConfig.SniperIds.Value, "sniper")); } return AvatarPath(FirstId(ModConfig.RifleIds.Value, "ak47")); } private static string FallbackLongGun(WeaponBudget budget) { if (budget.CanShotgun) { return VanillaShotgunPath(); } return VanillaPistolPath(); } public static WeaponClass Classify(string path) { if (string.IsNullOrEmpty(path)) { return WeaponClass.Pistol; } if (path.IndexOf("rpg", StringComparison.OrdinalIgnoreCase) >= 0) { return WeaponClass.Explosive; } if (path.IndexOf("minigun", StringComparison.OrdinalIgnoreCase) >= 0) { return WeaponClass.Heavy; } if (path.IndexOf("sniper", StringComparison.OrdinalIgnoreCase) >= 0) { return WeaponClass.Sniper; } if (path.IndexOf("smg", StringComparison.OrdinalIgnoreCase) >= 0) { return WeaponClass.Smg; } if (path.IndexOf("ak47", StringComparison.OrdinalIgnoreCase) >= 0 || path.IndexOf("rifle", StringComparison.OrdinalIgnoreCase) >= 0 || path.IndexOf("m4", StringComparison.OrdinalIgnoreCase) >= 0) { return WeaponClass.Rifle; } if (path.IndexOf("shotgun", StringComparison.OrdinalIgnoreCase) >= 0) { return WeaponClass.Shotgun; } return WeaponClass.Pistol; } private static float ShotgunChance(OfficerArchetype archetype, ResponsePersonality personality, HeatTier tier) { float num = ((archetype == OfficerArchetype.Enforcer) ? ModConfig.ShotgunChanceEnforcer.Value : ModConfig.ShotgunChanceNormal.Value); if (archetype == OfficerArchetype.Rookie) { num *= 0.55f; } bool flag = (((uint)(personality - 2) <= 1u || personality == ResponsePersonality.Aggressive) ? true : false); if (flag || HeatTracker.Stars >= 4) { num += 0.08f; } return Mathf.Clamp01(num); } private static float SmgChance(OfficerArchetype archetype, ResponsePersonality personality, HeatTier tier) { float num = ((archetype == OfficerArchetype.Enforcer) ? ModConfig.SmgChanceEnforcer.Value : ModConfig.SmgChanceNormal.Value); if (archetype == OfficerArchetype.Rookie) { num *= 0.55f; } bool flag = (((uint)(personality - 2) <= 1u || personality == ResponsePersonality.Aggressive) ? true : false); if (flag || HeatTracker.Stars >= 4) { num += 0.08f; } return Mathf.Clamp01(num); } private static bool HeatTrackerMaybeRevenge() { return HeatTracker.RevengePending; } private static IEnumerator ReapplyWeapon(PoliceOfficer officer, string path) { for (int attempt = 0; attempt < 3; attempt++) { yield return (object)new WaitForSeconds((attempt == 0) ? 0.4f : 0.75f); if (!Utils.Alive((Object)(object)officer) || OfficerPool.IsKnownDead(officer)) { break; } CombatBehaviour combat = ((Component)officer).GetComponent() ?? ((Component)officer).GetComponentInChildren(); if (AlreadyHolding(combat, path) || AvatarHolding(officer, path)) { BindAssignedGun(officer, combat, path); break; } ApplyWeapon(officer, combat, path); if (AlreadyHolding(combat, path) || AvatarHolding(officer, path)) { break; } } } public static void GiveVanillaShotgun(PoliceOfficer officer) { if (!((Object)(object)officer == (Object)null) && !HasLoadout(officer)) { CombatBehaviour combat = ((Component)officer).GetComponent() ?? ((Component)officer).GetComponentInChildren(); ApplyWeapon(officer, combat, VanillaShotgunPath()); } } private static void ApplyWeapon(PoliceOfficer officer, CombatBehaviour combat, string path) { if (!Utils.Alive((Object)(object)officer) || string.IsNullOrEmpty(path) || OfficerPool.IsKnownDead(officer)) { return; } if (!Utils.Alive((Object)(object)combat)) { combat = null; } if (AlreadyHolding(combat, path) || AvatarHolding(officer, path)) { BindAssignedGun(officer, combat, path); SyncPickpocketWeapon(officer, path); return; } CaptureOriginalGun(officer); BindPursuitGunPath(officer, path); if ((Object)(object)combat != (Object)null) { try { combat.SetWeapon(path); } catch (Exception ex) { if (ModConfig.DebugLogging.Value) { MelonLogger.Warning("SetWeapon(" + path + ") failed: " + ex.Message); } } } AvatarWeapon val = (((Object)(object)combat != (Object)null) ? GameAccess.Get(combat, "currentWeapon") : null); if ((Object)(object)val == (Object)null && Utils.Alive((Object)(object)((NPC)officer).Avatar)) { try { ((NPC)officer).Avatar.SetEquippable(path); } catch { } val = AdoptAvatarWeapon(officer, combat, path); } BindAssignedGun(officer, combat, path); StripExtraHandWeapons(officer); WeaponTactics.TuneWeapon(officer, combat); SyncPickpocketWeapon(officer, path); } private static bool AlreadyHolding(CombatBehaviour combat, string path) { if (!Utils.Alive((Object)(object)combat) || string.IsNullOrEmpty(path)) { return false; } try { AvatarWeapon currentWeapon = combat.currentWeapon; return (Object)(object)currentWeapon != (Object)null && !string.IsNullOrEmpty(((AvatarEquippable)currentWeapon).AssetPath) && string.Equals(((AvatarEquippable)currentWeapon).AssetPath, path, StringComparison.OrdinalIgnoreCase); } catch { return false; } } private static bool AvatarHolding(PoliceOfficer officer, string path) { if (!Utils.Alive((Object)(object)((officer != null) ? ((NPC)officer).Avatar : null)) || string.IsNullOrEmpty(path)) { return false; } try { AvatarEquippable currentEquippable = ((NPC)officer).Avatar.CurrentEquippable; return (Object)(object)currentEquippable != (Object)null && !string.IsNullOrEmpty(currentEquippable.AssetPath) && string.Equals(currentEquippable.AssetPath, path, StringComparison.OrdinalIgnoreCase); } catch { return false; } } private static void CaptureOriginalGun(PoliceOfficer officer) { int instanceID = ((Object)officer).GetInstanceID(); PursuitBehaviour pursuit = GetPursuit(officer); if (!OriginalGuns.ContainsKey(instanceID) && (Object)(object)pursuit != (Object)null) { OriginalGuns[instanceID] = pursuit.Weapon_Gun; } if (!OriginalWeapons.ContainsKey(instanceID)) { string value = VanillaPistolPath(); AvatarWeapon val = (((Object)(object)pursuit != (Object)null) ? pursuit.Weapon_Gun : null); if ((Object)(object)val != (Object)null && !string.IsNullOrEmpty(((AvatarEquippable)val).AssetPath)) { value = ((AvatarEquippable)val).AssetPath; } OriginalWeapons[instanceID] = value; } } private static void BindPursuitGunPath(PoliceOfficer officer, string path) { if ((Object)(object)officer == (Object)null || string.IsNullOrEmpty(path)) { return; } PursuitBehaviour pursuit = GetPursuit(officer); if ((Object)(object)pursuit == (Object)null) { return; } int instanceID = ((Object)officer).GetInstanceID(); AvatarWeapon val = null; if (OriginalGuns.TryGetValue(instanceID, out var value) && Utils.Alive((Object)(object)value)) { val = value; } else if (Utils.Alive((Object)(object)pursuit.Weapon_Gun)) { val = pursuit.Weapon_Gun; } if (!((Object)(object)val == (Object)null)) { if (!OriginalGuns.ContainsKey(instanceID) && Utils.Alive((Object)(object)val)) { OriginalGuns[instanceID] = val; } AvatarWeapon orCreateOfficerGunTemplate = GetOrCreateOfficerGunTemplate(instanceID, val, path); if (!((Object)(object)orCreateOfficerGunTemplate == (Object)null) && pursuit.Weapon_Gun != orCreateOfficerGunTemplate) { pursuit.Weapon_Gun = orCreateOfficerGunTemplate; } } } private static AvatarWeapon GetOrCreateOfficerGunTemplate(int officerId, AvatarWeapon source, string path) { //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) if (OfficerGunTemplates.TryGetValue(officerId, out var value) && Utils.Alive((Object)(object)value)) { try { if (!string.Equals(((AvatarEquippable)value).AssetPath, path, StringComparison.OrdinalIgnoreCase)) { ((AvatarEquippable)value).AssetPath = path; } } catch { GameAccess.Set(value, "AssetPath", path); } return value; } AvatarWeapon val = source; if (OriginalGuns.TryGetValue(officerId, out var value2) && Utils.Alive((Object)(object)value2)) { val = value2; } AvatarWeapon val2 = null; try { val2 = Object.Instantiate(val); ((Object)val2).name = $"{((Object)val).name}_PRO_{officerId}"; ((Object)((Component)val2).gameObject).hideFlags = (HideFlags)61; Scene scene = ((Component)val2).gameObject.scene; if (((Scene)(ref scene)).IsValid()) { } ((AvatarEquippable)val2).AssetPath = path; } catch (Exception ex) { if (ModConfig.DebugLogging.Value) { MelonLogger.Warning("Officer gun template clone failed: " + ex.Message); } return null; } OfficerGunTemplates[officerId] = val2; return val2; } private static void DestroyOfficerGunTemplate(int officerId) { if (!OfficerGunTemplates.TryGetValue(officerId, out var value)) { return; } OfficerGunTemplates.Remove(officerId); if ((Object)(object)value == (Object)null) { return; } try { Object.Destroy((Object)(object)((Component)value).gameObject); } catch { } } private static void BindAssignedGun(PoliceOfficer officer, CombatBehaviour combat, string path) { BindPursuitGunPath(officer, path); AvatarWeapon val = (((Object)(object)combat != (Object)null) ? GameAccess.Get(combat, "currentWeapon") : null); if ((Object)(object)val != (Object)null && string.IsNullOrEmpty(((AvatarEquippable)val).AssetPath)) { try { ((AvatarEquippable)val).AssetPath = path; } catch { GameAccess.Set(val, "AssetPath", path); } } AvatarWeapon val2 = null; PursuitBehaviour pursuit = GetPursuit(officer); val2 = ((!((Object)(object)pursuit != (Object)null) || !Utils.Alive((Object)(object)pursuit.Weapon_Gun)) ? val : pursuit.Weapon_Gun); if ((Object)(object)combat != (Object)null && (Object)(object)val2 != (Object)null) { try { combat.SetDefaultWeapon(val2); } catch { GameAccess.Set(combat, "_defaultWeapon", val2); } } if ((Object)(object)val != (Object)null) { try { PoliceWeaponIds.Add(((Object)val).GetInstanceID()); } catch { } } } private static AvatarWeapon AdoptAvatarWeapon(PoliceOfficer officer, CombatBehaviour combat, string path) { //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Expected O, but got Unknown //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Expected O, but got Unknown if (!Utils.Alive((Object)(object)((officer != null) ? ((NPC)officer).Avatar : null))) { return null; } AvatarEquippable val = null; try { val = ((NPC)officer).Avatar.CurrentEquippable; } catch { return null; } if (!Utils.Alive((Object)(object)val)) { return null; } AvatarWeapon val2 = (AvatarWeapon)(object)((val is AvatarWeapon) ? val : null); if ((Object)(object)val2 == (Object)null) { try { val2 = ((Component)val).GetComponent(); } catch { } } if (!Utils.Alive((Object)(object)val2)) { return null; } if (string.IsNullOrEmpty(((AvatarEquippable)val2).AssetPath)) { try { ((AvatarEquippable)val2).AssetPath = path; } catch { GameAccess.Set(val2, "AssetPath", path); } } try { if (val2.onSuccessfulHit == null) { val2.onSuccessfulHit = new UnityEvent(); } } catch { try { GameAccess.Set(val2, "onSuccessfulHit", (object)new UnityEvent()); } catch { } } if ((Object)(object)combat != (Object)null) { GameAccess.Set(combat, "currentWeapon", val2); } return val2; } private static void StripExtraHandWeapons(PoliceOfficer officer) { if (!Utils.Alive((Object)(object)((officer != null) ? ((NPC)officer).Avatar : null))) { return; } AvatarEquippable val = null; try { val = ((NPC)officer).Avatar.CurrentEquippable; } catch { return; } AvatarWeapon[] array = null; try { array = ((Component)((NPC)officer).Avatar).GetComponentsInChildren(true); } catch { return; } if (array == null || array.Length <= 1) { return; } foreach (AvatarWeapon val2 in array) { if (!Utils.Alive((Object)(object)val2) || ((Object)(object)val != (Object)null && ((object)val2 == val || ((Component)val2).transform.IsChildOf(((Component)val).transform)))) { continue; } try { Object.Destroy((Object)(object)((Component)val2).gameObject); } catch { try { ((AvatarEquippable)val2).Unequip(); } catch { } } } } private static void SyncPickpocketWeapon(PoliceOfficer officer, string avatarPath) { if (!Utils.Alive((Object)(object)officer) || string.IsNullOrEmpty(avatarPath)) { return; } string text = ItemIdFromAvatarPath(avatarPath); if (string.IsNullOrEmpty(text)) { return; } NPCInventory val = null; try { val = ((NPC)officer).Inventory; } catch { val = null; } if (val == null) { val = ((Component)officer).GetComponent() ?? ((Component)officer).GetComponentInChildren(true); } if (((val != null) ? val.ItemSlots : null) != null) { ReplaceInventoryItem(val, text, IsGunItemId, "gun"); string text2 = ResolveMagIdForGun(text); if (!string.IsNullOrEmpty(text2)) { ReplaceInventoryItem(val, text2, IsMagazineItemId, "mag"); } else { ClearInventoryItems(val, IsMagazineItemId); } } } private static void ReplaceInventoryItem(NPCInventory inventory, string itemId, Func slotMatcher, string label) { if (((inventory != null) ? inventory.ItemSlots : null) == null || string.IsNullOrEmpty(itemId) || slotMatcher == null) { return; } ItemDefinition val = ResolveItemDefinition(itemId); if ((Object)(object)val == (Object)null) { if (ModConfig.DebugLogging.Value) { MelonLogger.Warning("Pickpocket sync: no registry item '" + itemId + "' (" + label + ")."); } return; } ItemInstance val2 = CreateDefaultInstance(val, AmmoStackSize(itemId)); if (val2 == null) { return; } bool flag = false; try { foreach (ItemSlot itemSlot in inventory.ItemSlots) { if (itemSlot == null || itemSlot.Quantity <= 0) { continue; } ItemInstance itemInstance = itemSlot.ItemInstance; if ((Object)(object)((itemInstance != null) ? itemInstance.Definition : null) == (Object)null) { continue; } string text = ReadItemId(itemSlot.ItemInstance.Definition); if (slotMatcher(text)) { if (string.Equals(text, itemId, StringComparison.OrdinalIgnoreCase)) { flag = true; break; } if (TrySetSlotItem(inventory, itemSlot, val2)) { flag = true; break; } } } } catch (Exception ex) { if (ModConfig.DebugLogging.Value) { MelonLogger.Warning("Pickpocket " + label + " replace failed: " + ex.Message); } } if (flag) { return; } try { inventory.InsertItem(val2, true); } catch { try { GameAccess.Call(inventory, "InsertItem", val2, true); } catch (Exception ex2) { if (ModConfig.DebugLogging.Value) { MelonLogger.Warning("Pickpocket " + label + " insert failed: " + ex2.Message); } } } } private static void ClearInventoryItems(NPCInventory inventory, Func slotMatcher) { if (((inventory != null) ? inventory.ItemSlots : null) == null || slotMatcher == null) { return; } try { foreach (ItemSlot itemSlot in inventory.ItemSlots) { if (itemSlot == null || itemSlot.Quantity <= 0) { continue; } ItemInstance itemInstance = itemSlot.ItemInstance; if (!((Object)(object)((itemInstance != null) ? itemInstance.Definition : null) == (Object)null) && slotMatcher(ReadItemId(itemSlot.ItemInstance.Definition))) { try { itemSlot.ClearStoredInstance(false); } catch { GameAccess.Call(itemSlot, "ClearStoredInstance", false); } } } } catch { } } private static ItemInstance CreateDefaultInstance(ItemDefinition definition, int quantity = 1) { if ((Object)(object)definition == (Object)null) { return null; } int num = Mathf.Max(1, quantity); try { return definition.GetDefaultInstance(num); } catch { try { return GameAccess.Call(definition, "GetDefaultInstance", new object[1] { num }); } catch { return null; } } } private static int AmmoStackSize(string itemId) { if (string.Equals(itemId, "shotgunshell", StringComparison.OrdinalIgnoreCase)) { return 12; } return 1; } private static bool TrySetSlotItem(NPCInventory inventory, ItemSlot slot, ItemInstance instance) { try { slot.SetStoredItem(instance, false); return true; } catch { try { GameAccess.Call(slot, "SetStoredItem", instance, false); return true; } catch { try { slot.ClearStoredInstance(false); } catch { GameAccess.Call(slot, "ClearStoredInstance", false); } try { inventory.InsertItem(instance, true); return true; } catch { try { GameAccess.Call(inventory, "InsertItem", instance, true); return true; } catch { return false; } } } } } private static string ResolveMagIdForGun(string gunId) { if (string.IsNullOrEmpty(gunId)) { return null; } ItemDefinition val = ResolveItemDefinition(gunId); if ((Object)(object)val != (Object)null) { try { object obj = GameAccess.Get(val, "Equippable"); if (obj != null) { ItemDefinition definition = GameAccess.Get(obj, "Magazine"); string text = ReadItemId(definition); if (!string.IsNullOrEmpty(text) && (Object)(object)ResolveItemDefinition(text) != (Object)null) { return text; } } } catch { } } if (IsVanillaPumpShotgunId(gunId) && (Object)(object)ResolveItemDefinition("shotgunshell") != (Object)null) { return "shotgunshell"; } string text2 = gunId + "mag"; if ((Object)(object)ResolveItemDefinition(text2) != (Object)null) { return text2; } if (string.Equals(gunId, "PumpShotgun", StringComparison.OrdinalIgnoreCase)) { string text3 = "pumpshotgunmag"; if ((Object)(object)ResolveItemDefinition(text3) != (Object)null) { return text3; } } return null; } private static bool IsVanillaPumpShotgunId(string id) { if (string.IsNullOrEmpty(id)) { return false; } if (string.Equals(id, "PumpShotgun", StringComparison.OrdinalIgnoreCase) || string.Equals(id, "pumpshotgun", StringComparison.OrdinalIgnoreCase) || string.Equals(id, "shotgun", StringComparison.OrdinalIgnoreCase)) { return true; } return MatchesConfiguredId(id, ModConfig.VanillaShotgunId.Value); } private static ItemDefinition ResolveItemDefinition(string id) { if (string.IsNullOrEmpty(id)) { return null; } try { if ((Object)(object)Singleton.Instance != (Object)null) { ItemDefinition val = Singleton.Instance._GetItem(id, false); if ((Object)(object)val != (Object)null) { return val; } } } catch { } try { return Registry.GetItem(id); } catch { } try { return GameAccess.Call(Singleton.Instance, "_GetItem", new object[2] { id, false }); } catch { return null; } } private static string ItemIdFromAvatarPath(string path) { if (string.IsNullOrEmpty(path)) { return null; } string text = path; int num = path.LastIndexOf('/'); if (num >= 0 && num < path.Length - 1) { text = path.Substring(num + 1); } if (string.Equals(text, "AK47", StringComparison.OrdinalIgnoreCase) || string.Equals(text, "ak47", StringComparison.OrdinalIgnoreCase)) { return FirstId(ModConfig.RifleIds.Value, "ak47"); } if (string.Equals(text, "MiniGun", StringComparison.OrdinalIgnoreCase) || string.Equals(text, "minigun", StringComparison.OrdinalIgnoreCase)) { return FirstId(ModConfig.HeavyIds.Value, "minigun"); } if (string.Equals(text, "SMG", StringComparison.OrdinalIgnoreCase) || string.Equals(text, "smg", StringComparison.OrdinalIgnoreCase)) { return FirstId(ModConfig.SmgIds.Value, "smg"); } if (string.Equals(text, "Sniper", StringComparison.OrdinalIgnoreCase) || string.Equals(text, "sniper", StringComparison.OrdinalIgnoreCase)) { return FirstId(ModConfig.SniperIds.Value, "sniper"); } if (string.Equals(text, "RPG", StringComparison.OrdinalIgnoreCase) || string.Equals(text, "rpg", StringComparison.OrdinalIgnoreCase)) { return FirstId(ModConfig.RpgIds.Value, "rpg"); } if (string.Equals(text, "M1911", StringComparison.OrdinalIgnoreCase) || string.Equals(text, "m1911", StringComparison.OrdinalIgnoreCase)) { return string.IsNullOrWhiteSpace(ModConfig.VanillaPistolId.Value) ? "m1911" : ModConfig.VanillaPistolId.Value; } if (string.Equals(text, "PumpShotgun", StringComparison.OrdinalIgnoreCase) || string.Equals(text, "shotgun", StringComparison.OrdinalIgnoreCase) || string.Equals(text, "pumpshotgun", StringComparison.OrdinalIgnoreCase)) { string text2 = ModConfig.VanillaShotgunId.Value; if (string.IsNullOrWhiteSpace(text2)) { text2 = "PumpShotgun"; } if ((Object)(object)ResolveItemDefinition(text2) != (Object)null) { return text2; } if ((Object)(object)ResolveItemDefinition("pumpshotgun") != (Object)null) { return "pumpshotgun"; } if ((Object)(object)ResolveItemDefinition("PumpShotgun") != (Object)null) { return "PumpShotgun"; } return text2; } return text; } private static bool IsGunItemId(string id) { if (string.IsNullOrEmpty(id)) { return false; } if (string.Equals(id, "m1911", StringComparison.OrdinalIgnoreCase) || string.Equals(id, "PumpShotgun", StringComparison.OrdinalIgnoreCase) || string.Equals(id, "shotgun", StringComparison.OrdinalIgnoreCase) || string.Equals(id, "ak47", StringComparison.OrdinalIgnoreCase) || string.Equals(id, "minigun", StringComparison.OrdinalIgnoreCase) || string.Equals(id, "smg", StringComparison.OrdinalIgnoreCase) || string.Equals(id, "sniper", StringComparison.OrdinalIgnoreCase) || string.Equals(id, "rpg", StringComparison.OrdinalIgnoreCase)) { return true; } return MatchesConfiguredId(id, ModConfig.RifleIds.Value) || MatchesConfiguredId(id, ModConfig.HeavyIds.Value) || MatchesConfiguredId(id, ModConfig.SmgIds.Value) || MatchesConfiguredId(id, ModConfig.SniperIds.Value) || MatchesConfiguredId(id, ModConfig.RpgIds.Value) || MatchesConfiguredId(id, ModConfig.VanillaPistolId.Value) || MatchesConfiguredId(id, ModConfig.VanillaShotgunId.Value); } private static bool IsMagazineItemId(string id) { if (string.IsNullOrEmpty(id)) { return false; } if (id.EndsWith("mag", StringComparison.OrdinalIgnoreCase)) { return true; } if (id.IndexOf("magazine", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (string.Equals(id, "shotgunshell", StringComparison.OrdinalIgnoreCase) || string.Equals(id, "shotgunshells", StringComparison.OrdinalIgnoreCase)) { return true; } string gunId = (string.IsNullOrWhiteSpace(ModConfig.VanillaPistolId.Value) ? "m1911" : ModConfig.VanillaPistolId.Value); string text = ResolveMagIdForGun(gunId); return !string.IsNullOrEmpty(text) && string.Equals(id, text, StringComparison.OrdinalIgnoreCase); } private static bool MatchesConfiguredId(string id, string csv) { if (string.IsNullOrWhiteSpace(csv)) { return false; } string[] array = csv.Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries); string[] array2 = array; foreach (string text in array2) { if (string.Equals(text.Trim(), id, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private static string ReadItemId(ItemDefinition definition) { if ((Object)(object)definition == (Object)null) { return null; } try { if (!string.IsNullOrEmpty(((BaseItemDefinition)definition).ID)) { return ((BaseItemDefinition)definition).ID; } } catch { } string text = GameAccess.Get(definition, "ID"); return string.IsNullOrEmpty(text) ? GameAccess.Get(definition, "id") : text; } private static void ForgetPoliceWeapons(int officerId) { if (OriginalGuns.TryGetValue(officerId, out var value) && (Object)(object)value != (Object)null) { try { PoliceWeaponIds.Remove(((Object)value).GetInstanceID()); } catch { } } } private static PursuitBehaviour GetPursuit(PoliceOfficer officer) { if ((Object)(object)officer == (Object)null) { return null; } return officer.PursuitBehaviour ?? ((Component)officer).GetComponent() ?? ((Component)officer).GetComponentInChildren(); } private static bool TryReadBaselineHealth(NPCHealth health, out float baseline) { baseline = 0f; if ((Object)(object)health == (Object)null) { return false; } try { baseline = health.MaxHealth; if (baseline > 0f) { return true; } } catch { } try { baseline = health.Health; } catch { baseline = 100f; } if (baseline <= 0f) { baseline = 100f; } return true; } private static string VanillaPistolPath() { string value = ModConfig.VanillaPistolId.Value; if (string.Equals(value, "m1911", StringComparison.OrdinalIgnoreCase)) { return "Avatar/Equippables/M1911"; } return "Avatar/Equippables/" + value; } private static string VanillaShotgunPath() { string text = ModConfig.VanillaShotgunId.Value; if (string.IsNullOrWhiteSpace(text)) { text = "PumpShotgun"; } if (text.StartsWith("Avatar/", StringComparison.OrdinalIgnoreCase)) { return text; } return "Avatar/Equippables/" + text; } private static string AvatarPath(string moreGunsId) { if (string.Equals(moreGunsId, "ak47", StringComparison.OrdinalIgnoreCase)) { return "Avatar/Equippables/AK47"; } if (string.Equals(moreGunsId, "minigun", StringComparison.OrdinalIgnoreCase)) { return "Avatar/Equippables/MiniGun"; } if (string.Equals(moreGunsId, "smg", StringComparison.OrdinalIgnoreCase)) { return "Avatar/Equippables/SMG"; } if (string.Equals(moreGunsId, "sniper", StringComparison.OrdinalIgnoreCase)) { return "Avatar/Equippables/Sniper"; } if (string.Equals(moreGunsId, "rpg", StringComparison.OrdinalIgnoreCase)) { return "Avatar/Equippables/RPG"; } return "Avatar/Equippables/" + moreGunsId; } private static string FirstId(string csv, string fallback) { if (string.IsNullOrWhiteSpace(csv)) { return fallback; } string[] array = csv.Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries); return (array.Length == 0) ? fallback : array[0].Trim(); } private static Type MoreGunsWeaponBase() { if (_moreGunsWeaponBase != null) { return _moreGunsWeaponBase; } _moreGunsWeaponBase = GameAccess.FindLoadedType("MoreGuns.Guns.WeaponBase", "MoreGuns", "MoreGunsMono"); return _moreGunsWeaponBase; } private static bool WeaponsFinishedLoading(Type weaponBase) { try { object obj = GameAccess.GetStatic(weaponBase, "allWeapons"); int num = 0; if (obj is ICollection collection) { num = collection.Count; } else if (obj != null) { num = GameAccess.Get(obj, "Count"); } return num > 0 && GameAccess.GetStatic(weaponBase, "AllWeaponsLoaded"); } catch { return false; } } private static object FindWeapon(string id) { if (string.IsNullOrEmpty(id)) { return null; } try { Type type = MoreGunsWeaponBase(); if (type == null) { return null; } object obj = GameAccess.GetStatic(type, "weaponsByName"); if (obj != null) { MethodInfo[] methods = obj.GetType().GetMethods(); foreach (MethodInfo methodInfo in methods) { if (methodInfo.Name != "TryGetValue" || methodInfo.GetParameters().Length != 2) { continue; } object[] array = new object[2] { id, null }; try { if ((bool)methodInfo.Invoke(obj, array) && array[1] != null) { return array[1]; } } catch { } } try { if (GameAccess.Get(obj, "Keys") is IEnumerable enumerable) { foreach (object item in enumerable) { string text = (item as string) ?? item?.ToString(); if (!string.Equals(text, id, StringComparison.OrdinalIgnoreCase)) { continue; } MethodInfo[] methods2 = obj.GetType().GetMethods(); foreach (MethodInfo methodInfo2 in methods2) { if (methodInfo2.Name != "TryGetValue" || methodInfo2.GetParameters().Length != 2) { continue; } object[] array2 = new object[2] { text, null }; try { if ((bool)methodInfo2.Invoke(obj, array2) && array2[1] != null) { return array2[1]; } } catch { } } } } } catch { } } object obj5 = GameAccess.GetStatic(type, "allWeapons"); if (obj5 is IEnumerable enumerable2) { foreach (object item2 in enumerable2) { if (item2 != null) { string a = GameAccess.Get(item2, "ID"); if (string.Equals(a, id, StringComparison.OrdinalIgnoreCase)) { return item2; } } } } } catch { } return null; } } } namespace PoliceResponseOverhaul.Helpers { internal static class GameAccess { private static readonly Dictionary<(Type Type, string Name), MemberInfo> Cache = new Dictionary<(Type, string), MemberInfo>(); private const BindingFlags Flags = BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; public static void Call(object instance, string method, params object[] args) { if (instance != null) { ResolveMethod(instance.GetType(), method, args)?.Invoke(instance, args); } } public static T Call(object instance, string method, params object[] args) { if (instance == null) { return default(T); } MethodInfo methodInfo = ResolveMethod(instance.GetType(), method, args); if (methodInfo == null) { return default(T); } return (methodInfo.Invoke(instance, args) is T val) ? val : default(T); } public static object CallStatic(Type type, string method, params object[] args) { return type.GetMethod(method, BindingFlags.IgnoreCase | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.Invoke(null, args); } public static void Set(object instance, string member, object value) { if (instance == null) { return; } MemberInfo memberInfo = Resolve(instance.GetType(), member); MemberInfo memberInfo2 = memberInfo; if (!(memberInfo2 is FieldInfo fieldInfo)) { if (memberInfo2 is PropertyInfo { CanWrite: not false } propertyInfo) { propertyInfo.SetValue(instance, value); } } else { fieldInfo.SetValue(instance, value); } } public static T Get(object instance, string member) { if (instance == null) { return default(T); } return (Read(Resolve(instance.GetType(), member), instance) is T val) ? val : default(T); } public static void SetStatic(Type type, string member, object value) { MemberInfo memberInfo = Resolve(type, member); MemberInfo memberInfo2 = memberInfo; if (!(memberInfo2 is FieldInfo fieldInfo)) { if (memberInfo2 is PropertyInfo { CanWrite: not false } propertyInfo) { propertyInfo.SetValue(null, value); } } else { fieldInfo.SetValue(null, value); } } public static T GetStatic(Type type, string member) { return (Read(Resolve(type, member), null) is T val) ? val : default(T); } public static void CreateListingUI(ShopInterface shop, ShopListing listing) { Call(shop, "CreateListingUI", listing); } public static void RefreshShownItems(ShopInterface shop) { Call(shop, "RefreshShownItems"); } public static Type FindLoadedType(string fullName, params string[] assemblyNames) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { string name = assembly.GetName().Name; if (assemblyNames != null && assemblyNames.Length != 0) { bool flag = false; foreach (string b in assemblyNames) { if (string.Equals(name, b, StringComparison.OrdinalIgnoreCase)) { flag = true; break; } } if (!flag) { continue; } } Type type = assembly.GetType(fullName, throwOnError: false); if (type != null) { return type; } } return null; } private static object Read(MemberInfo member, object instance) { if (1 == 0) { } object result = ((member is FieldInfo fieldInfo) ? fieldInfo.GetValue(instance) : ((!(member is PropertyInfo propertyInfo)) ? null : propertyInfo.GetValue(instance))); if (1 == 0) { } return result; } private static MemberInfo Resolve(Type type, string name) { (Type, string) key = (type, name); if (Cache.TryGetValue(key, out var value)) { return value; } MemberInfo memberInfo = FindOnType(type, name); if (memberInfo == null && type.BaseType != null) { memberInfo = FindOnType(type.BaseType, name); } Cache[key] = memberInfo; return memberInfo; } private static MethodInfo ResolveMethod(Type type, string name, object[] args) { if (args == null) { args = Array.Empty(); } MethodInfo methodInfo = null; MethodInfo[] methods = type.GetMethods(BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo2 in methods) { if (methodInfo2.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) { ParameterInfo[] parameters = methodInfo2.GetParameters(); if (parameters.Length == args.Length) { methodInfo = methodInfo2; break; } } } return methodInfo ?? type.GetMethod(name, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } private static MemberInfo FindOnType(Type type, string name) { PropertyInfo propertyInfo = type.GetProperty(name, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetProperty(name, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); if (propertyInfo != null) { return propertyInfo; } return type.GetField(name, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetField(name, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); } } public static class MelonLoggerExtensions { public static void Debug(this Instance logger, string message, bool stacktrace = true) { } private static string GetLoggerName(Instance logger) { return typeof(Instance).GetField("Name", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(logger) as string; } private static void InvokeNativeMsg(object namesectionColor, object textColor, string nameSection, string message) { typeof(MelonLogger).GetMethod("NativeMsg", BindingFlags.Static | BindingFlags.NonPublic)?.Invoke(null, new object[5] { namesectionColor, textColor, nameSection, message ?? "null", false }); } private static string GetCallerInfo() { StackTrace stackTrace = new StackTrace(); for (int i = 2; i < stackTrace.FrameCount; i++) { StackFrame frame = stackTrace.GetFrame(i); MethodBase method = frame.GetMethod(); if (!(method?.DeclaringType == null)) { return method.DeclaringType.FullName + "." + method.Name; } } return "unknown"; } } internal static class PlayerQuery { private static List _cached; private static float _cachedAt = -1f; public static List All() { if (_cached != null && Time.unscaledTime - _cachedAt < 0.35f) { return _cached; } List list = new List(); HashSet seen = new HashSet(); try { List playerList = Player.PlayerList; if (playerList != null) { int count = playerList.Count; for (int i = 0; i < count; i++) { Add(list, seen, playerList[i]); } } } catch { } if (list.Count == 0) { try { if ((Object)(object)Player.Local != (Object)null) { Add(list, seen, Player.Local); } } catch { } } _cached = list; _cachedAt = Time.unscaledTime; return list; } public static Player LocalOrFirst() { try { if ((Object)(object)Player.Local != (Object)null) { return Player.Local; } } catch { } List list = All(); return (list.Count > 0) ? list[0] : null; } public static string Code(Player player) { if ((Object)(object)player == (Object)null) { return null; } try { string playerCode = player.PlayerCode; if (!string.IsNullOrEmpty(playerCode)) { return playerCode; } } catch { } return ((Object)player).GetInstanceID().ToString(); } public static Player MostWanted(Vector3 near = default(Vector3)) { //IL_004d: 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_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0056: 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_005c: Unknown result type (might be due to invalid IL or missing references) List list = All(); Player val = null; int num = 0; float num2 = float.MaxValue; Player val2 = null; float num3 = float.MaxValue; foreach (Player item in list) { if (!((Object)(object)item == (Object)null)) { Vector3 position = ((Component)item).transform.position; Vector3 val3 = position - near; float sqrMagnitude = ((Vector3)(ref val3)).sqrMagnitude; if (sqrMagnitude < num3) { num3 = sqrMagnitude; val2 = item; } int num4 = PursuitLevel(item); if (!IsArrested(item) && num4 >= 2 && (num4 > num || (num4 == num && sqrMagnitude < num2))) { num = num4; num2 = sqrMagnitude; val = item; } } } return val ?? val2 ?? LocalOrFirst(); } public static bool AnyoneWanted() { foreach (Player item in All()) { if (IsArrested(item) || PursuitLevel(item) < 2) { continue; } return true; } return false; } public static int PursuitLevel(Player player) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected I4, but got Unknown try { if ((Object)(object)player?.CrimeData == (Object)null) { return 0; } return (int)player.CrimeData.CurrentPursuitLevel; } catch { return 0; } } public static int HighestPursuitLevel() { int num = 0; foreach (Player item in All()) { if (!IsArrested(item)) { int num2 = PursuitLevel(item); if (num2 > num) { num = num2; } } } return num; } public static int Count() { int count = All().Count; return (count <= 0) ? 1 : count; } public static bool IsArrested(Player player) { try { return (Object)(object)player != (Object)null && player.IsArrested; } catch { return false; } } private static void Add(List result, HashSet seen, Player player) { if (!((Object)(object)player == (Object)null)) { int instanceID = ((Object)player).GetInstanceID(); if (seen.Add(instanceID)) { result.Add(player); } } } } internal static class PatchGate { public static bool IsLive { get; private set; } public static void Close() { IsLive = false; } public static void Open() { IsLive = true; } } internal static class SafeHarmony { public static List DiscoverPatchTypes(Assembly assembly) { List list = new List(); Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(assembly); foreach (Type type in typesFromAssembly) { if (!(type == null) && type.IsClass && !type.IsGenericTypeDefinition && type.GetCustomAttributes(typeof(HarmonyPatch), inherit: true).Length != 0) { list.Add(type); } } list.Sort((Type a, Type b) => SideEffectWeight(a).CompareTo(SideEffectWeight(b))); return list; } private static int SideEffectWeight(Type type) { string name = type.Name; if (name.IndexOf("LawEvaluate", StringComparison.Ordinal) >= 0) { return 100; } if (name.IndexOf("Shop", StringComparison.Ordinal) >= 0) { return 90; } if (name.IndexOf("Registry", StringComparison.Ordinal) >= 0) { return 80; } if (name.IndexOf("OfficerStart", StringComparison.Ordinal) >= 0) { return 70; } if (name.IndexOf("StartPatrol", StringComparison.Ordinal) >= 0) { return 70; } if (name.IndexOf("StartFoot", StringComparison.Ordinal) >= 0) { return 70; } if (name.IndexOf("ClearWeapon", StringComparison.Ordinal) >= 0) { return 60; } if (name.IndexOf("SetWeapon", StringComparison.Ordinal) >= 0) { return 60; } return 0; } public static bool TryPatchClass(Harmony harmony, Type patchClass, Instance log = null) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) try { new PatchClassProcessor(harmony, patchClass).Patch(); return true; } catch (Exception ex) { string text = "Harmony class " + patchClass.Name + " failed: " + ex.Message; if (log != null) { log.Error(text); } else { MelonLogger.Error(text); } return false; } } public static IEnumerator ApplyBatched(Harmony harmony, Assembly assembly, Instance log = null) { PatchGate.Close(); List types = DiscoverPatchTypes(assembly); int ok = 0; for (int i = 0; i < types.Count; i++) { if (TryPatchClass(harmony, types[i], log)) { ok++; } yield return null; yield return null; yield return null; } if (log != null) { log.Msg($"Harmony applied {ok}/{types.Count} patch classes (batched)."); } yield return (object)new WaitForSecondsRealtime(3f); PatchGate.Open(); if (log != null) { log.Msg("Harmony patch gate open — gameplay hooks live."); } } } public static class Il2CppListExtensions { public static IEnumerable AsEnumerable(this List list) { return list ?? new List(); } } public static class Utils { private static readonly Instance Logger = new Instance("Police Response Overhaul-Utils"); public static T FindObjectByName(string objectName) where T : Object { try { T[] array = Resources.FindObjectsOfTypeAll(); foreach (T val in array) { if (!(((Object)val).name != objectName)) { Logger.Debug("Found " + typeof(T).Name + " '" + objectName + "' directly in loaded objects"); return val; } } return default(T); } catch (Exception ex) { Logger.Error("Error finding " + typeof(T).Name + " '" + objectName + "': " + ex.Message); return default(T); } } public static List GetAllComponentsInChildrenRecursive(GameObject obj) where T : Component { List list = new List(); if ((Object)(object)obj == (Object)null) { return list; } T[] components = obj.GetComponents(); if (components.Length != 0) { list.AddRange(components); } for (int i = 0; i < obj.transform.childCount; i++) { Transform child = obj.transform.GetChild(i); list.AddRange(GetAllComponentsInChildrenRecursive(((Component)child).gameObject)); } return list; } public static bool Is(object obj, out T result) where T : class { if (obj is T val) { result = val; return true; } result = null; return false; } public static bool Alive(Object obj) { if (obj == null) { return false; } try { return obj != (Object)null; } catch { return false; } } public static IEnumerator WaitForPlayer(IEnumerator routine) { while ((Object)(object)Player.Local == (Object)null || (Object)(object)((Component)Player.Local).gameObject == (Object)null) { yield return null; } MelonCoroutines.Start(routine); } public static IEnumerator WaitForNetwork(IEnumerator routine) { while (!InstanceFinder.IsServer && !InstanceFinder.IsClient) { yield return null; } MelonCoroutines.Start(routine); } public static IEnumerator WaitForNotNull(object obj, float timeout = float.NaN, Action onTimeout = null, Action onFinish = null) { float startTime = Time.time; while (obj == null) { if (!float.IsNaN(timeout) && Time.time - startTime > timeout) { onTimeout?.Invoke(); yield break; } yield return null; } onFinish?.Invoke(); } public static IEnumerator WaitForNetworkSingleton(IEnumerator coroutine) where T : NetworkSingleton { while (!NetworkSingleton.InstanceExists) { yield return null; } yield return coroutine; } } } namespace PoliceResponseOverhaul.Heat { public enum WantedPhase { Clear, Searching, Chasing } public static class HeatTracker { private const float SightLostSeconds = 4.5f; private static readonly List RecentKillTimes = new List(); private static readonly Dictionary PlayerStars = new Dictionary(); private static readonly Dictionary KillsAtCurrentStar = new Dictionary(); private static readonly Dictionary RemotePhase = new Dictionary(); private static readonly Dictionary IgnoreUntil = new Dictionary(); private static float _nextDecayAt; private static float _ignoreWantedUntil; private static int _lastDay = -1; private static int _lastLoggedStars = -1; public static int MaxStars => JuggernautSquad.Enabled ? 6 : 5; public static int DailyKills { get; private set; } public static int Streak { get; private set; } public static bool RevengePending { get; set; } public static int Stars { get; private set; } public static HeatTier CurrentTier => (HeatTier)Stars; public static bool IgnoreWanted => Time.unscaledTime < _ignoreWantedUntil; public static string StarLabel { get { if (Stars <= 0) { return "0★"; } int num = ((Stars >= 6) ? 6 : 5); return new string('★', Stars) + new string('☆', Mathf.Max(0, num - Stars)); } } public static void ResetSession() { DailyKills = 0; Streak = 0; RevengePending = false; Stars = 0; _lastLoggedStars = -1; RecentKillTimes.Clear(); PlayerStars.Clear(); KillsAtCurrentStar.Clear(); RemotePhase.Clear(); IgnoreUntil.Clear(); _lastDay = -1; _ignoreWantedUntil = 0f; _nextDecayAt = Time.unscaledTime + ModConfig.HeatDecayStepSeconds.Value; JuggernautSquad.Reset(); SteamLobbySync.Publish(); } public static void ApplyRemoteSnapshot(HeatTier tier, int kills, int streak) { DailyKills = Mathf.Max(0, kills); Streak = Mathf.Max(0, streak); if (NetworkAuthority.IsAuthoritative) { Stars = Mathf.Clamp((int)tier, 0, MaxStars); } } public static void ApplyRemoteWanted(string packed) { if (NetworkAuthority.IsAuthoritative) { return; } PlayerStars.Clear(); RemotePhase.Clear(); if (string.IsNullOrEmpty(packed)) { return; } string[] array = packed.Split(','); foreach (string text in array) { if (string.IsNullOrEmpty(text)) { continue; } string[] array2 = text.Split(':'); if (array2.Length < 2) { continue; } string text2 = array2[0]; if (!string.IsNullOrEmpty(text2) && int.TryParse(array2[1], out var result)) { PlayerStars[text2] = Mathf.Clamp(result, 0, MaxStars); if (array2.Length >= 3 && int.TryParse(array2[2], out var result2)) { RemotePhase[text2] = (WantedPhase)Mathf.Clamp(result2, 0, 2); } } } } public static string PackWanted() { List list = new List(); foreach (Player item in PlayerQuery.All()) { string text = Sanitize(PlayerQuery.Code(item)); if (!string.IsNullOrEmpty(text)) { int num = StarsFor(item); WantedPhase wantedPhase = PhaseFor(item); if (num > 0 || wantedPhase != WantedPhase.Clear) { list.Add($"{text}:{num}:{(int)wantedPhase}"); } } } return string.Join(",", list); } public static void Tick() { TryRolloverDay(); PruneOldKills(); SyncPlayerHeat(); MaybeDecayStars(); } public static void OnOfficerKilled(PoliceOfficer officer = null) { DailyKills = Mathf.Min(ModConfig.DailyKillCap.Value, DailyKills + 1); Streak++; RecentKillTimes.Add(Time.unscaledTime); Player player = BlamePlayer(officer); int current = StarsFor(player); int stars = ClimbOneTier(player, current); SetPlayerStars(player, stars); SetStars(MaxPlayerStars(), "officer kill"); if (ModConfig.DebugLogging.Value) { MelonLogger.Msg($"Officer killed. Daily={DailyKills} Streak={Streak} Stars={Stars} player={PlayerQuery.Code(player)}"); } } public static void OnPlayerDiedOrArrested(Player player = null) { ClearVanillaWanted(player); SetPlayerStars(player, 0); if ((Object)(object)player != (Object)null) { IgnoreUntil[PlayerQuery.Code(player) ?? ""] = Time.unscaledTime + 25f; } if (AnyoneElseWanted(player)) { SetStars(MaxPlayerStars(), "other player still wanted"); SteamLobbySync.Publish(); return; } if (ModConfig.ResetStreakOnDeath.Value) { Streak = 0; RecentKillTimes.Clear(); } ClearStarProgress(PlayerQuery.Code(player)); SetStars(0, "death/arrest"); _ignoreWantedUntil = Time.unscaledTime + 25f; if (NetworkAuthority.IsAuthoritative) { JuggernautSquad.Reset(); ResponseDispatcher.StandDown(); } } public static bool ShouldIgnore(Player player) { if (IgnoreWanted) { return true; } string text = PlayerQuery.Code(player); if (string.IsNullOrEmpty(text)) { return false; } float value; return IgnoreUntil.TryGetValue(text, out value) && Time.unscaledTime < value; } public static int StarsFor(Player player) { if (PhaseFor(player) == WantedPhase.Clear) { return 0; } string text = PlayerQuery.Code(player); int value = 0; if (!string.IsNullOrEmpty(text)) { PlayerStars.TryGetValue(text, out value); } return Mathf.Clamp(Mathf.Max(value, 1), 0, MaxStars); } public static WantedPhase PhaseFor(Player player) { WantedPhase wantedPhase = ComputePhase(player); if (wantedPhase == WantedPhase.Clear) { return WantedPhase.Clear; } if (!NetworkAuthority.IsAuthoritative) { string text = PlayerQuery.Code(player); if (!string.IsNullOrEmpty(text) && RemotePhase.TryGetValue(text, out var value) && value != WantedPhase.Clear) { return value; } } return wantedPhase; } private static WantedPhase ComputePhase(Player player) { if ((Object)(object)player == (Object)null || PlayerQuery.IsArrested(player) || ShouldIgnore(player)) { return WantedPhase.Clear; } int num = PlayerQuery.PursuitLevel(player); if (num <= 0) { return WantedPhase.Clear; } if (num == 1) { return WantedPhase.Searching; } float num2 = 999f; try { if ((Object)(object)player.CrimeData != (Object)null) { num2 = player.CrimeData.TimeSinceSighted; } } catch { num2 = 999f; } if (num2 <= 4.5f) { return WantedPhase.Chasing; } return WantedPhase.Searching; } public static Player LocalPlayer() { return PlayerQuery.LocalOrFirst(); } private static void SyncPlayerHeat() { HashSet hashSet = new HashSet(); foreach (Player item in PlayerQuery.All()) { string text = PlayerQuery.Code(item); if (!string.IsNullOrEmpty(text)) { hashSet.Add(text); int value; if (PhaseFor(item) == WantedPhase.Clear) { PlayerStars[text] = 0; KillsAtCurrentStar.Remove(text); } else if (!PlayerStars.TryGetValue(text, out value) || value <= 0) { PlayerStars[text] = 1; } } } string[] array = PlayerStars.Keys.ToArray(); foreach (string text2 in array) { if (!hashSet.Contains(text2)) { PlayerStars.Remove(text2); } } int num = MaxPlayerStars(); if (num != Stars) { SetStars(num, (num == 0) ? "lost the cops" : "wanted"); } else { SteamLobbySync.Publish(); } } private static int MaxPlayerStars() { int num = 0; foreach (KeyValuePair playerStar in PlayerStars) { if (playerStar.Value > num) { num = playerStar.Value; } } return num; } private static void SetPlayerStars(Player player, int stars) { string text = PlayerQuery.Code(player); if (!string.IsNullOrEmpty(text)) { int num = Mathf.Clamp(stars, 0, MaxStars); PlayerStars.TryGetValue(text, out var value); PlayerStars[text] = num; if (num != value) { KillsAtCurrentStar[text] = 0; } } } private static int ClimbOneTier(Player player, int current) { int maxStars = MaxStars; if (current >= maxStars) { return maxStars; } string text = PlayerQuery.Code(player); int value = 1; if (!string.IsNullOrEmpty(text)) { KillsAtCurrentStar.TryGetValue(text, out value); value++; KillsAtCurrentStar[text] = value; } if (value < KillsNeededToClimb(current)) { return current; } int num = current + 1; if (num >= 6 && !JuggernautSquad.Enabled) { num = 5; } return Mathf.Min(maxStars, num); } private static int KillsNeededToClimb(int stars) { if (1 == 0) { } int result = stars switch { 0 => 1, 1 => 1, 2 => 2, 3 => 2, 4 => 3, _ => 4, }; if (1 == 0) { } return result; } private static void ClearStarProgress(string code) { if (string.IsNullOrEmpty(code)) { KillsAtCurrentStar.Clear(); } else { KillsAtCurrentStar.Remove(code); } } private static Player BlamePlayer(PoliceOfficer officer) { //IL_0057: 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_0048: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)officer != (Object)null) { try { Player val = (((Object)(object)officer.VehiclePursuitBehaviour != (Object)null) ? officer.VehiclePursuitBehaviour.Target : null); if ((Object)(object)val != (Object)null) { return val; } } catch { } return PlayerQuery.MostWanted(((Component)officer).transform.position); } return PlayerQuery.MostWanted(); } private static void ClearVanillaWanted(Player player) { if ((Object)(object)player != (Object)null) { ClearPlayerWanted(player); } } private static void ClearPlayerWanted(Player player) { if ((Object)(object)player?.CrimeData == (Object)null) { return; } try { player.CrimeData.SetPursuitLevel((EPursuitLevel)0); player.CrimeData.ClearCrimes(); } catch { } } private static bool AnyoneElseWanted(Player except) { foreach (Player item in PlayerQuery.All()) { if ((Object)(object)item == (Object)null || (Object)(object)item == (Object)(object)except || PlayerQuery.IsArrested(item) || PlayerQuery.PursuitLevel(item) < 2) { continue; } return true; } return false; } public static void SetStars(int stars, string reason = null) { stars = Mathf.Clamp(stars, 0, MaxStars); if (Stars == stars) { SteamLobbySync.Publish(); return; } Stars = stars; if (string.Equals(reason, "command", StringComparison.Ordinal)) { if (stars <= 0) { PlayerStars.Clear(); } else { SetPlayerStars(LocalPlayer(), stars); } } _nextDecayAt = Time.unscaledTime + ModConfig.HeatDecayStepSeconds.Value; SteamLobbySync.Publish(); if (Stars != _lastLoggedStars) { _lastLoggedStars = Stars; string text = (string.IsNullOrEmpty(reason) ? "" : (" (" + reason + ")")); MelonLogger.Msg("Wanted level " + StarLabel + text + "."); } } public static int CountRecentKills() { PruneOldKills(); return RecentKillTimes.Count; } public static int ConnectedPlayers() { return PlayerQuery.Count(); } public static bool IsNight() { try { TimeManager val = (NetworkSingleton.InstanceExists ? NetworkSingleton.Instance : Object.FindObjectOfType(true)); if ((Object)(object)val == (Object)null) { return false; } int currentTime = val.CurrentTime; return currentTime >= 2100 || currentTime < 600; } catch { return false; } } public static bool IsWanted() { return PlayerQuery.AnyoneWanted(); } public static bool ShouldShowStars() { return StarsFor(LocalPlayer()) > 0; } private static void MaybeDecayStars() { if (MaxPlayerStars() > 0) { _nextDecayAt = Time.unscaledTime + ModConfig.HeatDecayStepSeconds.Value; } else if (Stars > 0) { if (IsHunting()) { _nextDecayAt = Time.unscaledTime + ModConfig.HeatDecayStepSeconds.Value; } else { SetStars(0, "lost the cops"); } } } private static bool IsHunting() { if (IgnoreWanted) { return false; } return IsWanted() || CountRecentKills() > 0 || ResponseDispatcher.HasActiveResponse; } private static string Sanitize(string code) { if (string.IsNullOrEmpty(code)) { return ""; } return code.Replace(",", "").Replace(":", ""); } private static void PruneOldKills() { float window = ModConfig.KillStreakSurgeWindowSeconds.Value; float now = Time.unscaledTime; RecentKillTimes.RemoveAll((float t) => now - t > window); } private static void TryRolloverDay() { int num = 0; try { TimeManager val = (NetworkSingleton.InstanceExists ? NetworkSingleton.Instance : Object.FindObjectOfType(true)); if ((Object)(object)val != (Object)null) { num = val.ElapsedDays; } } catch { } if (_lastDay < 0) { _lastDay = num; } else if (num != _lastDay) { _lastDay = num; if (ModConfig.PersistDailyKillsUntilRollover.Value) { DailyKills = 0; Streak = 0; RevengePending = false; RecentKillTimes.Clear(); PlayerStars.Clear(); RemotePhase.Clear(); SetStars(0, "new day"); MelonLogger.Msg("Daily police-kill cycle reset."); } } } } } namespace PoliceResponseOverhaul.Config { public enum HeatTier { Calm, Alert, Heat, Surge, Panic, Manhunt, Juggernaut } public enum ResponsePersonality { Standard, Swarm, Tactical, Panic, AllHands, Understaffed, Aggressive, FootSweep, K9Unit, SpecialResponse, RifleTeam, ShotgunTeam, Juggernaut } public enum LoadoutTheme { Mixed, Rifles, Shotguns } public enum OfficerArchetype { Rookie, Veteran, Marksman, Enforcer, Elite } public static class ModConfig { public static MelonPreferences_Category General; public static MelonPreferences_Category Ambient; public static MelonPreferences_Category Heat; public static MelonPreferences_Category Response; public static MelonPreferences_Category Loadout; public static MelonPreferences_Category Armor; public static MelonPreferences_Category Cartel; public static MelonPreferences_Category World; public static MelonPreferences_Category Tactics; public static MelonPreferences_Entry Enabled; public static MelonPreferences_Entry DebugLogging; public static MelonPreferences_Entry F6WaypointHelper; public static MelonPreferences_Entry SteamLobbySyncEnabled; public static MelonPreferences_Entry ExtraFootPatrols; public static MelonPreferences_Entry ExtraVehiclePatrols; public static MelonPreferences_Entry ExtraSentries; public static MelonPreferences_Entry ExtraCheckpointStaffing; public static MelonPreferences_Entry CloneVanillaFootPatrols; public static MelonPreferences_Entry AmbientMoreGuns; public static MelonPreferences_Entry VanillaMinMembers; public static MelonPreferences_Entry VanillaMaxMembers; public static MelonPreferences_Entry HeatMaxMembers; public static MelonPreferences_Entry ScaleLawIntensity; public static MelonPreferences_Entry HeatDecayStepSeconds; public static MelonPreferences_Entry KillStreakSurgeDeaths; public static MelonPreferences_Entry KillStreakSurgeWindowSeconds; public static MelonPreferences_Entry DailyKillCap; public static MelonPreferences_Entry ResetStreakOnDeath; public static MelonPreferences_Entry PersistDailyKillsUntilRollover; public static MelonPreferences_Entry JuggernautsEnabled; public static MelonPreferences_Entry DeathsPerVehicleResponse; public static MelonPreferences_Entry MaxPatrolsPerResponse; public static MelonPreferences_Entry MaxPendingVehicleResponses; public static MelonPreferences_Entry OfficersPerCruiser; public static MelonPreferences_Entry PatrolStaggerMinSeconds; public static MelonPreferences_Entry PatrolStaggerMaxSeconds; public static MelonPreferences_Entry DelayedWaveChance; public static MelonPreferences_Entry LateCalmWaveChance; public static MelonPreferences_Entry MaxConcurrentResponseCars; public static MelonPreferences_Entry WaveGapSeconds; public static MelonPreferences_Entry ReserveOfficers; public static MelonPreferences_Entry RifleChanceNormal; public static MelonPreferences_Entry RifleChanceTactical; public static MelonPreferences_Entry SniperChanceNormal; public static MelonPreferences_Entry SniperChanceTactical; public static MelonPreferences_Entry MinigunChanceElite; public static MelonPreferences_Entry MinigunChancePanic; public static MelonPreferences_Entry EliteOfficerChance; public static MelonPreferences_Entry VanillaPistolId; public static MelonPreferences_Entry VanillaShotgunId; public static MelonPreferences_Entry ShotgunChanceNormal; public static MelonPreferences_Entry ShotgunChanceEnforcer; public static MelonPreferences_Entry SmgChanceNormal; public static MelonPreferences_Entry SmgChanceEnforcer; public static MelonPreferences_Entry RifleIds; public static MelonPreferences_Entry HeavyIds; public static MelonPreferences_Entry SmgIds; public static MelonPreferences_Entry SniperIds; public static MelonPreferences_Entry RpgIds; public static MelonPreferences_Entry ArmorEnabled; public static MelonPreferences_Entry ArmorItemId; public static MelonPreferences_Entry ArmorItemName; public static MelonPreferences_Entry ArmorPrice; public static MelonPreferences_Entry ArmorDamageMultiplier; public static MelonPreferences_Entry ArmorShopName; public static MelonPreferences_Entry OfficerArmorClothingId; public static MelonPreferences_Entry FinalResponseEnabled; public static MelonPreferences_Entry FinalResponseInterval; public static MelonPreferences_Entry FinalResponseTiers; public static MelonPreferences_Entry CartelInfluenceBump; public static MelonPreferences_Entry CivilianRadio; public static MelonPreferences_Entry BodySearchScaling; public static MelonPreferences_Entry LethalOnPanic; public static MelonPreferences_Entry PoliceChatter; public static MelonPreferences_Entry HardCurfewOnPanic; public static MelonPreferences_Entry HeatHud; public static MelonPreferences_Entry StuckRecovery; public static MelonPreferences_Entry K9Enabled; public static MelonPreferences_Entry K9FootChance; public static MelonPreferences_Entry CuffRange; public static MelonPreferences_Entry HoldGroundMin; public static MelonPreferences_Entry IdealRangePistol; public static MelonPreferences_Entry IdealRangeSmg; public static MelonPreferences_Entry IdealRangeShotgun; public static MelonPreferences_Entry IdealRangeRifle; public static MelonPreferences_Entry IdealRangeSniper; public static MelonPreferences_Entry IdealRangeHeavy; public static MelonPreferences_Entry IdealRangeExplosive; public static MelonPreferences_Entry MaxUsePistol; public static MelonPreferences_Entry MaxUseSmg; public static MelonPreferences_Entry MaxUseShotgun; public static MelonPreferences_Entry MaxUseRifle; public static MelonPreferences_Entry MaxUseSniper; public static MelonPreferences_Entry MaxUseHeavy; public static MelonPreferences_Entry MaxUseExplosive; public static MelonPreferences_Entry MinUsePistol; public static MelonPreferences_Entry MinUseSmg; public static MelonPreferences_Entry MinUseShotgun; public static MelonPreferences_Entry MinUseRifle; public static MelonPreferences_Entry MinUseSniper; public static MelonPreferences_Entry MinUseHeavy; public static MelonPreferences_Entry MinUseExplosive; public static MelonPreferences_Entry ShotIntervalPistol; public static MelonPreferences_Entry ShotIntervalSmg; public static MelonPreferences_Entry ShotIntervalShotgun; public static MelonPreferences_Entry ShotIntervalRifle; public static MelonPreferences_Entry ShotIntervalSniper; public static MelonPreferences_Entry ShotIntervalHeavy; public static MelonPreferences_Entry ShotIntervalExplosive; public static MelonPreferences_Entry LocalAlertMinRadius; public static MelonPreferences_Entry LocalAlertMaxRadius; public static MelonPreferences_Entry LocalAlertMaxCops; public static void Initialize() { General = MelonPreferences.CreateCategory("PoliceResponseOverhaul_General", "Police Response Overhaul - General"); Ambient = MelonPreferences.CreateCategory("PoliceResponseOverhaul_Ambient", "Police Response Overhaul - Ambient"); Heat = MelonPreferences.CreateCategory("PoliceResponseOverhaul_Heat", "Police Response Overhaul - Wanted Level"); Response = MelonPreferences.CreateCategory("PoliceResponseOverhaul_Response", "Police Response Overhaul - Response"); Loadout = MelonPreferences.CreateCategory("PoliceResponseOverhaul_Loadout", "Police Response Overhaul - Loadout"); Armor = MelonPreferences.CreateCategory("PoliceResponseOverhaul_Armor", "Police Response Overhaul - Armor"); Cartel = MelonPreferences.CreateCategory("PoliceResponseOverhaul_Cartel", "Police Response Overhaul - Cartel"); World = MelonPreferences.CreateCategory("PoliceResponseOverhaul_World", "Police Response Overhaul - World"); Tactics = MelonPreferences.CreateCategory("PoliceResponseOverhaul_Tactics", "Police Response Overhaul - Combat Tactics"); Enabled = General.CreateEntry("Enabled", true, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); DebugLogging = General.CreateEntry("DebugLogging", false, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); F6WaypointHelper = General.CreateEntry("F6WaypointHelper", true, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); SteamLobbySyncEnabled = General.CreateEntry("SteamLobbySync", true, "If SteamNetworkLib is installed, publish each player's wanted stars to the Steam lobby. Each client draws their own stars. Lobby keys only — does not pump P2P (safe with K9 Patrol).", (string)null, false, false, (ValueValidator)null, (string)null); ExtraFootPatrols = Ambient.CreateEntry("ExtraFootPatrols", true, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); ExtraVehiclePatrols = Ambient.CreateEntry("ExtraVehiclePatrols", true, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); ExtraSentries = Ambient.CreateEntry("ExtraSentries", true, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); ExtraCheckpointStaffing = Ambient.CreateEntry("ExtraCheckpointStaffing", false, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); CloneVanillaFootPatrols = Ambient.CreateEntry("CloneVanillaFootPatrols", true, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); AmbientMoreGuns = Ambient.CreateEntry("AmbientMoreGuns", true, "Let cloned / street foot patrols roll More Guns weapons (AK, SMG, sniper, etc.) instead of only vanilla pistols/shotguns. Needs More Guns installed.", (string)null, false, false, (ValueValidator)null, (string)null); VanillaMinMembers = Ambient.CreateEntry("VanillaMinMembers", 1, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); VanillaMaxMembers = Ambient.CreateEntry("VanillaMaxMembers", 2, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); HeatMaxMembers = Ambient.CreateEntry("HeatMaxMembers", 3, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); ScaleLawIntensity = Ambient.CreateEntry("ScaleLawIntensity", true, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); HeatDecayStepSeconds = Heat.CreateEntry("HeatDecayStepSeconds", 45f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); KillStreakSurgeDeaths = Heat.CreateEntry("KillStreakSurgeDeaths", 2, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); KillStreakSurgeWindowSeconds = Heat.CreateEntry("KillStreakSurgeWindowSeconds", 30f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); DailyKillCap = Heat.CreateEntry("DailyKillCap", 50, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); ResetStreakOnDeath = Heat.CreateEntry("ResetStreakOnDeath", true, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); PersistDailyKillsUntilRollover = Heat.CreateEntry("PersistDailyKillsUntilRollover", true, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); JuggernautsEnabled = Heat.CreateEntry("JuggernautsEnabled", true, "Allow a 6th gold star after a serious 5★ fight. 6★ sends a squad of 4: minigun juggernauts replace rifle escorts as you kill them (1+3, then 2+2, 3+1, then 4). At four juggernauts, one carries an RPG. Juggernauts toss smoke.", (string)null, false, false, (ValueValidator)null, (string)null); DeathsPerVehicleResponse = Response.CreateEntry("DeathsPerVehicleResponse", 1, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); MaxPatrolsPerResponse = Response.CreateEntry("MaxPatrolsPerResponse", 6, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); MaxPendingVehicleResponses = Response.CreateEntry("MaxPendingVehicleResponses", 6, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); OfficersPerCruiser = Response.CreateEntry("OfficersPerCruiser", 2, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); PatrolStaggerMinSeconds = Response.CreateEntry("PatrolStaggerMinSeconds", 0.15f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); PatrolStaggerMaxSeconds = Response.CreateEntry("PatrolStaggerMaxSeconds", 0.7f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); DelayedWaveChance = Response.CreateEntry("DelayedWaveChance", 0.65f, "Chance a kill also queues a follow-up wave. Applies from 1★.", (string)null, false, false, (ValueValidator)null, (string)null); ReplaceIf(DelayedWaveChance, 0.55f, 0.65f); LateCalmWaveChance = Response.CreateEntry("LateCalmWaveChance", 0.22f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); MaxConcurrentResponseCars = Response.CreateEntry("MaxConcurrentResponseCars", 2, "Cap on response cars in the street at once (1-3).", (string)null, false, false, (ValueValidator)null, (string)null); WaveGapSeconds = Response.CreateEntry("WaveGapSeconds", 12f, "Base seconds between backup waves. Higher stars cut this further (down to ~3.5s at 5★).", (string)null, false, false, (ValueValidator)null, (string)null); ReplaceIf(WaveGapSeconds, 45f, 12f); ReplaceIf(WaveGapSeconds, 18f, 12f); ReserveOfficers = Response.CreateEntry("ReserveOfficers", 8, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); RifleChanceNormal = Loadout.CreateEntry("RifleChanceNormal", 0.38f, "Chance a mixed-crew officer gets a rifle. Needs More Guns. Dedicated rifle SWAT teams ignore this.", (string)null, false, false, (ValueValidator)null, (string)null); RifleChanceTactical = Loadout.CreateEntry("RifleChanceTactical", 0.55f, "Rifle chance on tactical / panic mixed waves. Dedicated rifle teams ignore this.", (string)null, false, false, (ValueValidator)null, (string)null); SniperChanceNormal = Loadout.CreateEntry("SniperChanceNormal", 0.38f, "Chance a mixed-crew officer gets a sniper after the rifle roll fails. Needs More Guns.", (string)null, false, false, (ValueValidator)null, (string)null); SniperChanceTactical = Loadout.CreateEntry("SniperChanceTactical", 0.55f, "Sniper chance on tactical / panic mixed waves. Needs More Guns.", (string)null, false, false, (ValueValidator)null, (string)null); MinigunChanceElite = Loadout.CreateEntry("MinigunChanceElite", 0.15f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); MinigunChancePanic = Loadout.CreateEntry("MinigunChancePanic", 0.08f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); EliteOfficerChance = Loadout.CreateEntry("EliteOfficerChance", 0.08f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); VanillaPistolId = Loadout.CreateEntry("VanillaPistolId", "m1911", (string)null, (string)null, false, false, (ValueValidator)null, (string)null); VanillaShotgunId = Loadout.CreateEntry("VanillaShotgunId", "PumpShotgun", "Vanilla pump shotgun avatar id (Avatar/Equippables/PumpShotgun).", (string)null, false, false, (ValueValidator)null, (string)null); ShotgunChanceNormal = Loadout.CreateEntry("ShotgunChanceNormal", 0.38f, "Chance a mixed-crew officer gets a shotgun after rifle/sniper rolls fail.", (string)null, false, false, (ValueValidator)null, (string)null); ShotgunChanceEnforcer = Loadout.CreateEntry("ShotgunChanceEnforcer", 0.58f, "Shotgun chance for Enforcer archetypes on mixed crews. Shotgun SWAT teams ignore this.", (string)null, false, false, (ValueValidator)null, (string)null); SmgChanceNormal = Loadout.CreateEntry("SmgChanceNormal", 0.38f, "Chance a mixed-crew officer gets an SMG after shotgun roll fails. Needs More Guns.", (string)null, false, false, (ValueValidator)null, (string)null); SmgChanceEnforcer = Loadout.CreateEntry("SmgChanceEnforcer", 0.58f, "SMG chance for Enforcer archetypes on mixed crews. Needs More Guns.", (string)null, false, false, (ValueValidator)null, (string)null); MigrateOldLoadoutDefaults(); RifleIds = Loadout.CreateEntry("RifleIds", "ak47", (string)null, (string)null, false, false, (ValueValidator)null, (string)null); HeavyIds = Loadout.CreateEntry("HeavyIds", "minigun", (string)null, (string)null, false, false, (ValueValidator)null, (string)null); SmgIds = Loadout.CreateEntry("SmgIds", "smg", "More Guns SMG id for mixed crews and shotgun-style rush teams.", (string)null, false, false, (ValueValidator)null, (string)null); SniperIds = Loadout.CreateEntry("SniperIds", "sniper", "More Guns sniper id for mixed crews and marksman / rifle teams.", (string)null, false, false, (ValueValidator)null, (string)null); RpgIds = Loadout.CreateEntry("RpgIds", "rpg", "More Guns RPG id. One juggernaut gets this when the squad reaches four.", (string)null, false, false, (ValueValidator)null, (string)null); ArmorEnabled = Armor.CreateEntry("ArmorEnabled", true, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); ArmorItemId = Armor.CreateEntry("ArmorItemId", "pro_bodyarmor", (string)null, (string)null, false, false, (ValueValidator)null, (string)null); ArmorItemName = Armor.CreateEntry("ArmorItemName", "Body Armor", (string)null, (string)null, false, false, (ValueValidator)null, (string)null); ArmorPrice = Armor.CreateEntry("ArmorPrice", 12500f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); ArmorDamageMultiplier = Armor.CreateEntry("ArmorDamageMultiplier", 0.5f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); ArmorShopName = Armor.CreateEntry("ArmorShopName", "Thrifty Threads", (string)null, (string)null, false, false, (ValueValidator)null, (string)null); OfficerArmorClothingId = Armor.CreateEntry("OfficerArmorClothingId", "tacticalvest", "Vanilla clothing id worn by armored police (Thrifty Threads Tactical Vest). Fallback is vest.", (string)null, false, false, (ValueValidator)null, (string)null); FinalResponseEnabled = Cartel.CreateEntry("FinalResponseEnabled", true, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); FinalResponseInterval = Cartel.CreateEntry("FinalResponseInterval", 10, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); FinalResponseTiers = Cartel.CreateEntry("FinalResponseTiers", 5, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); CartelInfluenceBump = Cartel.CreateEntry("CartelInfluenceBump", false, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); CivilianRadio = World.CreateEntry("CivilianRadio", true, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); BodySearchScaling = World.CreateEntry("BodySearchScaling", true, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); LethalOnPanic = World.CreateEntry("LethalOnPanic", true, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); PoliceChatter = World.CreateEntry("PoliceChatter", true, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); HardCurfewOnPanic = World.CreateEntry("HardCurfewOnPanic", false, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); HeatHud = World.CreateEntry("HeatHud", true, "Draw GTA-style wanted stars in the top-right for the local player. Solid white while you are being chased, flashing while cops search, hidden when you are no longer wanted.", (string)null, false, false, (ValueValidator)null, (string)null); StuckRecovery = World.CreateEntry("StuckRecovery", true, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); K9Enabled = World.CreateEntry("K9Enabled", true, "If K9 Patrol is installed, attach dogs to K9 and some foot response officers. Does nothing without that mod.", (string)null, false, false, (ValueValidator)null, (string)null); K9FootChance = World.CreateEntry("K9FootChance", 0.6f, "Chance each foot response officer gets a K9. Already-paired patrol dogs are kept. A K9 in a car takes a seat and stays inside.", (string)null, false, false, (ValueValidator)null, (string)null); CuffRange = Tactics.CreateEntry("CuffRange", 2.75f, "Meters — any pursuing officer this close builds arrest progress (vanilla default).", (string)null, false, false, (ValueValidator)null, (string)null); HoldGroundMin = Tactics.CreateEntry("HoldGroundMin", 2.2f, "When the player closes inside IdealRange, cops hold at least this far instead of running away.", (string)null, false, false, (ValueValidator)null, (string)null); IdealRangePistol = Tactics.CreateEntry("IdealRangePistol", 3.5f, "Preferred stand-off distance (m) with a pistol.", (string)null, false, false, (ValueValidator)null, (string)null); IdealRangeSmg = Tactics.CreateEntry("IdealRangeSmg", 4f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); IdealRangeShotgun = Tactics.CreateEntry("IdealRangeShotgun", 3.5f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); IdealRangeRifle = Tactics.CreateEntry("IdealRangeRifle", 5.5f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); IdealRangeSniper = Tactics.CreateEntry("IdealRangeSniper", 7.5f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); IdealRangeHeavy = Tactics.CreateEntry("IdealRangeHeavy", 5.5f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); IdealRangeExplosive = Tactics.CreateEntry("IdealRangeExplosive", 6.5f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); MaxUsePistol = Tactics.CreateEntry("MaxUsePistol", 14f, "Max AI shoot range (m) for pistols when remapped guns are too low.", (string)null, false, false, (ValueValidator)null, (string)null); MaxUseSmg = Tactics.CreateEntry("MaxUseSmg", 15f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); MaxUseShotgun = Tactics.CreateEntry("MaxUseShotgun", 12f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); MaxUseRifle = Tactics.CreateEntry("MaxUseRifle", 22f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); MaxUseSniper = Tactics.CreateEntry("MaxUseSniper", 28f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); MaxUseHeavy = Tactics.CreateEntry("MaxUseHeavy", 20f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); MaxUseExplosive = Tactics.CreateEntry("MaxUseExplosive", 26f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); MinUsePistol = Tactics.CreateEntry("MinUsePistol", 0.8f, "Min AI shoot range (m).", (string)null, false, false, (ValueValidator)null, (string)null); MinUseSmg = Tactics.CreateEntry("MinUseSmg", 1.2f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); MinUseShotgun = Tactics.CreateEntry("MinUseShotgun", 1f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); MinUseRifle = Tactics.CreateEntry("MinUseRifle", 1.5f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); MinUseSniper = Tactics.CreateEntry("MinUseSniper", 3f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); MinUseHeavy = Tactics.CreateEntry("MinUseHeavy", 2f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); MinUseExplosive = Tactics.CreateEntry("MinUseExplosive", 4f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); ShotIntervalPistol = Tactics.CreateEntry("ShotIntervalPistol", 0.32f, "Minimum seconds between shots for police pistols.", (string)null, false, false, (ValueValidator)null, (string)null); ShotIntervalSmg = Tactics.CreateEntry("ShotIntervalSmg", 0.22f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); ShotIntervalShotgun = Tactics.CreateEntry("ShotIntervalShotgun", 0.55f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); ShotIntervalRifle = Tactics.CreateEntry("ShotIntervalRifle", 0.62f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); ShotIntervalSniper = Tactics.CreateEntry("ShotIntervalSniper", 0.95f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); ShotIntervalHeavy = Tactics.CreateEntry("ShotIntervalHeavy", 0.38f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); ShotIntervalExplosive = Tactics.CreateEntry("ShotIntervalExplosive", 1.35f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null); LocalAlertMinRadius = Tactics.CreateEntry("LocalAlertMinRadius", 80f, "Meters — ambient street cops start getting drafted into the chase within this radius at low stars.", (string)null, false, false, (ValueValidator)null, (string)null); LocalAlertMaxRadius = Tactics.CreateEntry("LocalAlertMaxRadius", 120f, "Meters — max radius for ambient local alerts at high stars. Response cars still handle longer range.", (string)null, false, false, (ValueValidator)null, (string)null); LocalAlertMaxCops = Tactics.CreateEntry("LocalAlertMaxCops", 2, "Max ambient street cops pulled into a chase at once (does not block response car waves).", (string)null, false, false, (ValueValidator)null, (string)null); MelonPreferences.Save(); } private static void MigrateOldLoadoutDefaults() { ReplaceIf(RifleChanceNormal, 0.6f, 0.38f); ReplaceIf(RifleChanceNormal, 0.18f, 0.38f); ReplaceIf(RifleChanceTactical, 1f, 0.55f); ReplaceIf(RifleChanceTactical, 0.35f, 0.55f); ReplaceIf(ShotgunChanceNormal, 0.55f, 0.38f); ReplaceIf(ShotgunChanceEnforcer, 0.8f, 0.58f); } private static void ReplaceIf(MelonPreferences_Entry entry, float oldDefault, float next) { if (entry != null) { float value = entry.Value; if (value > oldDefault - 0.0001f && value < oldDefault + 0.0001f) { entry.Value = next; } } } } public class RoutesFile { [JsonProperty("extraFootPatrols")] public List ExtraFootPatrols = new List(); [JsonProperty("extraVehiclePatrols")] public List ExtraVehiclePatrols = new List(); [JsonProperty("extraSentries")] public List ExtraSentries = new List(); } public class SerializedFootPatrol { public string name = "Extra Loop"; public int startTime = 800; public int endTime = 2200; public int minMembers = 1; public int maxMembers = 2; public int intensityRequirement = 0; public bool onlyIfCurfew = false; public List days = new List { "mon", "tue", "wed", "thu", "fri", "sat", "sun" }; public List waypoints = new List(); } public class SerializedVehiclePatrol { public string name = "Extra Vehicle Loop"; public int startTime = 2000; public int intensityRequirement = 1; public bool onlyIfCurfew = false; public List days = new List { "mon", "tue", "wed", "thu", "fri", "sat", "sun" }; public List waypoints = new List(); } public class SerializedSentry { public string name = "Extra Sentry"; public int startTime = 2100; public int endTime = 500; public int minMembers = 1; public int maxMembers = 2; public int intensityRequirement = 1; public List locations = new List(); } public class SerializedWaypoint { public float x; public float y; public float z; } public static class RoutesConfig { public static string DirectoryPath => Path.Combine(MelonEnvironment.UserDataDirectory, "PoliceResponseOverhaul"); public static string FilePath => Path.Combine(DirectoryPath, "routes.json"); public static RoutesFile Current { get; private set; } = new RoutesFile(); public static void EnsureLoaded() { try { Directory.CreateDirectory(DirectoryPath); if (!File.Exists(FilePath)) { Current = CreateDefault(); File.WriteAllText(FilePath, JsonConvert.SerializeObject((object)Current, (Formatting)1)); MelonLogger.Msg("Wrote default extra routes to " + FilePath); } else { Current = JsonConvert.DeserializeObject(File.ReadAllText(FilePath)) ?? CreateDefault(); } } catch (Exception arg) { MelonLogger.Error($"Failed to load routes.json: {arg}"); Current = CreateDefault(); } } private static RoutesFile CreateDefault() { return new RoutesFile { ExtraFootPatrols = new List(), ExtraVehiclePatrols = new List(), ExtraSentries = new List() }; } public static Vector3 ToVector(SerializedWaypoint waypoint) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: 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) return new Vector3(waypoint.x, waypoint.y, waypoint.z); } } } namespace PoliceResponseOverhaul.Compat { public static class K9Compat { private sealed class SeatedDog { public GameObject Dog; public bool AgentWasEnabled; public bool NpcWasEnabled; public readonly List DisabledColliders = new List(); public readonly List HiddenRenderers = new List(); } private static readonly string[] AssemblyNames = new string[5] { "K9 Patrol", "K9_Patrol", "K9Patrol", "K9Patrol_Mono", "K9Patrol_IL2Cpp" }; private static bool _resolved; private static bool _logged; private static Type _manager; private static Type _controller; private static Type _k9Npc; private static Type _netSync; private static MethodInfo _isAssigned; private static MethodInfo _initialize; private static MethodInfo _serverSpawn; private static FieldInfo _units; private static float _nextSeatPass; private static readonly Dictionary Seated = new Dictionary(); private static readonly HashSet PendingAttach = new HashSet(); public static bool Available { get { Resolve(); return _controller != null && _initialize != null; } } public static bool CanDeploy => ModConfig.K9Enabled.Value && Available; public static void Reset() { int[] array = Seated.Keys.ToArray(); foreach (int handlerId in array) { ReleaseSeat(handlerId, warpOut: false); } _resolved = false; _logged = false; _manager = null; _controller = null; _k9Npc = null; _netSync = null; _isAssigned = null; _initialize = null; _serverSpawn = null; _units = null; PendingAttach.Clear(); Seated.Clear(); } public static bool IsHandler(PoliceOfficer officer) { if ((Object)(object)officer == (Object)null) { return false; } Resolve(); return IsAssigned(officer); } public static int ExtraSeats(PoliceOfficer officer) { return IsHandler(officer) ? 1 : 0; } public static bool TryAttach(PoliceOfficer officer, bool force = false) { if ((Object)(object)officer == (Object)null || !ModConfig.K9Enabled.Value) { return false; } if (!Available) { return false; } if (IsAssigned(officer)) { SeatIfRiding(officer); return true; } if (GroupOrNearbyHasHandler(officer)) { return false; } if (!force && Random.value > ModConfig.K9FootChance.Value) { return false; } int instanceID = ((Object)officer).GetInstanceID(); if (!PendingAttach.Add(instanceID)) { return true; } MelonCoroutines.Start(AttachWhenReady(officer)); return true; } public static void Tick() { if (!CanDeploy || Time.unscaledTime < _nextSeatPass) { return; } _nextSeatPass = Time.unscaledTime + 0.2f; List list = SnapshotUnits(); HashSet hashSet = new HashSet(); foreach (object item in list) { PoliceOfficer val = GameAccess.Get(item, "Officer"); if (!((Object)(object)val == (Object)null)) { hashSet.Add(((Object)val).GetInstanceID()); SeatIfRiding(val); } } int[] array = Seated.Keys.ToArray(); foreach (int num in array) { if (!hashSet.Contains(num)) { ReleaseSeat(num, warpOut: false); } } } public static void SeatIfRiding(PoliceOfficer officer) { if (!((Object)(object)officer == (Object)null)) { LandVehicle val = null; try { val = ((NPC)officer).CurrentVehicle; } catch { val = null; } if ((Object)(object)val == (Object)null) { ReleaseSeat(((Object)officer).GetInstanceID(), warpOut: true); } else { KeepSeated(officer, val); } } } private static IEnumerator AttachWhenReady(PoliceOfficer officer) { int id = (((Object)(object)officer != (Object)null) ? ((Object)officer).GetInstanceID() : 0); try { for (int i = 0; i < 16; i++) { yield return (object)new WaitForSeconds(0.4f); if ((Object)(object)officer == (Object)null || !OfficerPool.IsLeased(officer)) { break; } if (IsAssigned(officer)) { SeatIfRiding(officer); break; } bool inCar; try { inCar = (Object)(object)((NPC)officer).CurrentVehicle != (Object)null; } catch { inCar = false; } if (inCar || IsOnNavMesh(officer)) { Attach(officer); yield return null; yield return null; SeatIfRiding(officer); break; } } } finally { PendingAttach.Remove(id); } } private static bool IsOnNavMesh(PoliceOfficer officer) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) try { NavMeshAgent val = ((Component)officer).GetComponent() ?? ((Component)officer).GetComponentInChildren(true); if ((Object)(object)val != (Object)null) { return ((Behaviour)val).enabled && val.isOnNavMesh; } NavMeshHit val2 = default(NavMeshHit); return NavMesh.SamplePosition(((Component)officer).transform.position, ref val2, 1.25f, -1); } catch { return false; } } public static bool GroupOrNearbyHasHandler(PoliceOfficer officer) { //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)officer == (Object)null) { return false; } Resolve(); PatrolGroup val = GetGroup(officer); if (val?.Members != null) { try { int count = val.Members.Count; for (int i = 0; i < count; i++) { NPC val2 = val.Members[i]; PoliceOfficer val3 = (PoliceOfficer)(((object)((val2 is PoliceOfficer) ? val2 : null)) ?? ((object)((val2 != null) ? ((Component)val2).GetComponent() : null))); if (!((Object)(object)val3 == (Object)null) && !((Object)(object)val3 == (Object)(object)officer) && IsAssigned(val3)) { return true; } } } catch { } } Vector3 position = ((Component)officer).transform.position; foreach (PoliceOfficer item in OfficerPool.EnumerateOfficers()) { if ((Object)(object)item == (Object)null || (Object)(object)item == (Object)(object)officer || !IsAssigned(item) || !(Vector3.Distance(position, ((Component)item).transform.position) < 10f)) { continue; } return true; } return false; } private static PatrolGroup GetGroup(PoliceOfficer officer) { try { FootPatrolBehaviour val = ((Component)officer).GetComponent() ?? ((Component)officer).GetComponentInChildren(true); return (val != null) ? val.Group : null; } catch { return null; } } private static void Resolve() { if (_resolved) { return; } _resolved = true; _manager = FindK9Type("K9_Patrol.Source.Managers.K9Manager"); _controller = FindK9Type("K9_Patrol.Source.Controllers.K9UnitController"); _k9Npc = FindK9Type("K9_Patrol.Source.NPCs.K9NPC"); _netSync = FindK9Type("K9_Patrol.Source.Networking.K9NetSync"); if (_manager != null) { _isAssigned = _manager.GetMethod("IsOfficerAssigned", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _units = _manager.GetField("_units", BindingFlags.Static | BindingFlags.NonPublic); } if (_controller != null) { _initialize = _controller.GetMethod("Initialize", BindingFlags.Instance | BindingFlags.Public); } if (_netSync != null) { _serverSpawn = _netSync.GetMethod("ServerSpawn", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } if (!_logged) { _logged = true; if (_controller != null && _initialize != null) { MelonLogger.Msg("K9 Patrol detected. Response units can deploy with dogs."); } } } private static Type FindK9Type(string fullName) { Type type = GameAccess.FindLoadedType(fullName, AssemblyNames); if (type != null) { return type; } Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { string text = assembly.GetName().Name ?? ""; if (text.IndexOf("K9", StringComparison.OrdinalIgnoreCase) >= 0 && (text.IndexOf("Patrol", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("K9_", StringComparison.OrdinalIgnoreCase) >= 0)) { type = assembly.GetType(fullName, throwOnError: false); if (type != null) { return type; } } } return null; } private static bool IsAssigned(PoliceOfficer officer) { try { if (_isAssigned == null) { return false; } object obj = _isAssigned.Invoke(null, new object[1] { officer }); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } catch { return false; } } private static bool Attach(PoliceOfficer officer) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown //IL_0029: Unknown result type (might be due to invalid IL or missing references) try { GameObject val = new GameObject($"PRO_K9Unit_{((Object)officer).GetInstanceID()}"); val.transform.position = ((Component)officer).transform.position; Component val2 = AddComponent(val, _controller); if ((Object)(object)val2 == (Object)null) { Object.Destroy((Object)(object)val); return false; } _initialize.Invoke(val2, new object[1] { officer }); TryRegisterUnit(val2); try { _serverSpawn?.Invoke(null, new object[1] { officer }); } catch { } return true; } catch (Exception ex) { if (ModConfig.DebugLogging.Value) { MelonLogger.Warning("K9 attach failed: " + ex.Message); } return false; } } private static void KeepSeated(PoliceOfficer officer, LandVehicle vehicle) { //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) GameObject val = FindDog(officer); if ((Object)(object)val == (Object)null || (Object)(object)vehicle == (Object)null) { return; } int instanceID = ((Object)officer).GetInstanceID(); if (!Seated.TryGetValue(instanceID, out var value) || (Object)(object)value.Dog != (Object)(object)val) { if (value != null) { RestoreDogMotion(value); } value = CaptureDog(val); Seated[instanceID] = value; } FreezeDog(val, value); HideDogModel(val, value); try { if ((Object)(object)val.transform.parent != (Object)null) { val.transform.SetParent((Transform)null, true); } } catch { } Vector3 val2 = ((Component)vehicle).transform.TransformPoint(0f, -0.4f, -0.35f); val.transform.SetPositionAndRotation(val2, ((Component)vehicle).transform.rotation); SetSit(val, sit: true); } private static void ReleaseSeat(int handlerId, bool warpOut) { //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0114: 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_0156: Unknown result type (might be due to invalid IL or missing references) if (!Seated.TryGetValue(handlerId, out var value)) { return; } Seated.Remove(handlerId); GameObject dog = value.Dog; if ((Object)(object)dog == (Object)null) { return; } PoliceOfficer val = null; foreach (PoliceOfficer item in OfficerPool.EnumerateOfficers()) { if ((Object)(object)item != (Object)null && ((Object)item).GetInstanceID() == handlerId) { val = item; break; } } try { dog.transform.SetParent((Transform)null, true); } catch { } RestoreDogMotion(value); SetSit(dog, sit: false); if (!warpOut || (Object)(object)val == (Object)null) { return; } Vector3 val2 = ((Component)val).transform.position + ((Component)val).transform.right * 0.9f; try { NavMeshHit val3 = default(NavMeshHit); if (NavMesh.SamplePosition(val2, ref val3, 2.5f, -1)) { val2 = ((NavMeshHit)(ref val3)).position; } dog.transform.position = val2; NavMeshAgent val4 = dog.GetComponent() ?? dog.GetComponentInChildren(true); if ((Object)(object)val4 != (Object)null && ((Behaviour)val4).enabled) { val4.Warp(val2); } } catch { } } private static SeatedDog CaptureDog(GameObject dog) { SeatedDog seatedDog = new SeatedDog { Dog = dog }; NavMeshAgent val = dog.GetComponent() ?? dog.GetComponentInChildren(true); seatedDog.AgentWasEnabled = (Object)(object)val != (Object)null && ((Behaviour)val).enabled; Behaviour k9Npc = GetK9Npc(dog); seatedDog.NpcWasEnabled = (Object)(object)k9Npc != (Object)null && k9Npc.enabled; return seatedDog; } private static void FreezeDog(GameObject dog, SeatedDog seated) { NavMeshAgent val = dog.GetComponent() ?? dog.GetComponentInChildren(true); if ((Object)(object)val != (Object)null) { try { val.isStopped = true; } catch { } ((Behaviour)val).enabled = false; } Behaviour k9Npc = GetK9Npc(dog); if ((Object)(object)k9Npc != (Object)null) { k9Npc.enabled = false; } if (seated.DisabledColliders.Count > 0) { return; } Collider[] componentsInChildren = dog.GetComponentsInChildren(true); foreach (Collider val2 in componentsInChildren) { if ((Object)(object)val2 == (Object)null || !val2.enabled || val2.isTrigger) { continue; } try { LandVehicle componentInParent = ((Component)val2).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null) { Transform transform = dog.transform; if (!((Component)val2).transform.IsChildOf(transform) && (Object)(object)((Component)val2).transform != (Object)(object)transform) { continue; } } } catch { } val2.enabled = false; seated.DisabledColliders.Add(val2); } } private static void HideDogModel(GameObject dog, SeatedDog seated) { if ((Object)(object)dog == (Object)null || seated == null || seated.HiddenRenderers.Count > 0) { return; } Renderer[] componentsInChildren = dog.GetComponentsInChildren(true); foreach (Renderer val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && val.enabled) { val.enabled = false; seated.HiddenRenderers.Add(val); } } } private static void RestoreDogMotion(SeatedDog seated) { if ((Object)(object)seated?.Dog == (Object)null) { return; } GameObject dog = seated.Dog; foreach (Collider disabledCollider in seated.DisabledColliders) { if ((Object)(object)disabledCollider != (Object)null) { disabledCollider.enabled = true; } } seated.DisabledColliders.Clear(); foreach (Renderer hiddenRenderer in seated.HiddenRenderers) { if ((Object)(object)hiddenRenderer != (Object)null) { hiddenRenderer.enabled = true; } } seated.HiddenRenderers.Clear(); Behaviour k9Npc = GetK9Npc(dog); if ((Object)(object)k9Npc != (Object)null) { k9Npc.enabled = seated.NpcWasEnabled; } NavMeshAgent val = dog.GetComponent() ?? dog.GetComponentInChildren(true); if ((Object)(object)val != (Object)null) { ((Behaviour)val).enabled = seated.AgentWasEnabled; } } private static void SetSit(GameObject dog, bool sit) { try { Animator componentInChildren = dog.GetComponentInChildren(true); if (!((Object)(object)componentInChildren == (Object)null)) { componentInChildren.SetBool("sit", sit); if (sit) { componentInChildren.SetBool("isWalking", false); } } } catch { } } private static Transform FindDogSeat(LandVehicle vehicle, PoliceOfficer handler) { try { VehicleSeat firstFreeSeat = vehicle.GetFirstFreeSeat(); if ((Object)(object)firstFreeSeat != (Object)null && !firstFreeSeat.isDriverSeat) { return ((Component)firstFreeSeat).transform; } } catch { } try { VehicleSeat[] seats = vehicle.Seats; if (seats == null) { return null; } Transform val = null; for (int i = 0; i < seats.Length; i++) { VehicleSeat val2 = seats[i]; if (!((Object)(object)val2 == (Object)null) && !val2.isDriverSeat) { if (val == null) { val = ((Component)val2).transform; } if (!SeatTakenByOfficer(vehicle, i, handler)) { return ((Component)val2).transform; } } } return val; } catch { return null; } } private static bool SeatTakenByOfficer(LandVehicle vehicle, int index, PoliceOfficer handler) { try { NPC[] occupantNPCs = vehicle.OccupantNPCs; if (occupantNPCs == null || index < 0 || index >= occupantNPCs.Length) { return false; } NPC val = occupantNPCs[index]; return (Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)handler; } catch { return false; } } private static GameObject FindDog(PoliceOfficer officer) { if ((Object)(object)officer == (Object)null) { return null; } Resolve(); GameObject found = null; ForEachUnit(delegate(object unit) { if (!((Object)(object)found != (Object)null)) { PoliceOfficer val = GameAccess.Get(unit, "Officer"); if (!((Object)(object)val != (Object)(object)officer)) { found = GameAccess.Get(unit, "Dog"); if ((Object)(object)found == (Object)null) { object obj = GameAccess.Get(unit, "K9DogNPC"); Component val2 = (Component)((obj is Component) ? obj : null); if (val2 != null) { found = val2.gameObject; } } } } }); return found; } private static Behaviour GetK9Npc(GameObject dog) { if ((Object)(object)dog == (Object)null) { return null; } Component componentOf = GetComponentOf(dog, _k9Npc); return (Behaviour)(object)((componentOf is Behaviour) ? componentOf : null); } private static List SnapshotUnits() { List list = new List(); ForEachUnit(list.Add); return list; } private static void ForEachUnit(Action visit) { if (_units == null || visit == null) { return; } object value; try { value = _units.GetValue(null); } catch { return; } if (value == null) { return; } try { PropertyInfo property = value.GetType().GetProperty("Count", BindingFlags.Instance | BindingFlags.Public); PropertyInfo property2 = value.GetType().GetProperty("Item", BindingFlags.Instance | BindingFlags.Public); if (property != null && property2 != null) { int num = Convert.ToInt32(property.GetValue(value)); for (int i = 0; i < num; i++) { object value2 = property2.GetValue(value, new object[1] { i }); if (value2 != null) { visit(value2); } } return; } } catch { } if (!(value is IList list)) { return; } foreach (object item in list) { if (item != null) { visit(item); } } } private static void TryRegisterUnit(object unit) { if (_units == null || unit == null) { return; } try { object value = _units.GetValue(null); if (value is IList list) { if (!list.Contains(unit)) { list.Add(unit); } } else { GameAccess.Call(value, "Add", unit); } } catch { } } private static Component GetComponentOf(GameObject go, Type type) { if ((Object)(object)go == (Object)null || type == null) { return null; } try { MethodInfo method = typeof(GameObject).GetMethod("GetComponent", new Type[1] { typeof(Type) }); if (method != null) { object obj = method.Invoke(go, new object[1] { type }); Component val = (Component)((obj is Component) ? obj : null); if (val != null) { return val; } } } catch { } try { Component[] componentsInChildren = go.GetComponentsInChildren(true); foreach (Component val2 in componentsInChildren) { if (!((Object)(object)val2 == (Object)null)) { Type type2 = ((object)val2).GetType(); if (type.IsAssignableFrom(type2) || type2.Name == type.Name) { return val2; } } } } catch { } return null; } private static Component AddComponent(GameObject go, Type type) { if ((Object)(object)go == (Object)null || type == null) { return null; } try { MethodInfo methodInfo = typeof(GameObject).GetMethods(BindingFlags.Instance | BindingFlags.Public).FirstOrDefault((MethodInfo methodInfo2) => methodInfo2.Name == "AddComponent" && methodInfo2.IsGenericMethodDefinition && methodInfo2.GetParameters().Length == 0); if (methodInfo != null) { object? obj = methodInfo.MakeGenericMethod(type).Invoke(go, null); return (Component)((obj is Component) ? obj : null); } } catch { } try { MethodInfo method = typeof(GameObject).GetMethod("AddComponent", new Type[1] { typeof(Type) }); if (method != null) { object? obj3 = method.Invoke(go, new object[1] { type }); return (Component)((obj3 is Component) ? obj3 : null); } } catch { } return null; } } public static class SteamLobbySync { private static readonly string[] AssemblyNames = new string[5] { "SteamNetworkLib", "SteamNetworkLib-Mono", "SteamNetworkLib-Il2Cpp", "SteamNetworkLib_Mono", "SteamNetworkLib_IL2CPP" }; private const string KeyHeat = "pro.heat"; private const string KeyWanted = "pro.wanted"; private const string KeyKills = "pro.kills"; private const string KeyStreak = "pro.streak"; private static Type _clientType; private static ConstructorInfo _ctor; private static MethodInfo _tryInitialize; private static MethodInfo _setLobbyData; private static MethodInfo _getLobbyData; private static MethodInfo _dispose; private static PropertyInfo _isInitialized; private static PropertyInfo _isHost; private static PropertyInfo _isInLobby; private static PropertyInfo _versionCheckEnabled; private static object _client; private static bool _resolved; private static bool _loggedMissing; private static bool _loggedReady; private static float _nextInitAttempt; private static float _nextPullAt; private static int _lastHeat = int.MinValue; private static int _lastKills = int.MinValue; private static int _lastStreak = int.MinValue; private static string _lastWanted = ""; public static void Reset() { _lastHeat = int.MinValue; _lastKills = int.MinValue; _lastStreak = int.MinValue; _lastWanted = ""; _nextPullAt = 0f; } public static void Shutdown() { Reset(); try { _dispose?.Invoke(_client, null); } catch { } _client = null; _resolved = false; _loggedMissing = false; _loggedReady = false; _clientType = null; _ctor = null; _tryInitialize = null; _setLobbyData = null; _getLobbyData = null; _dispose = null; _isInitialized = null; _isHost = null; _isInLobby = null; _versionCheckEnabled = null; } public static void Tick() { if (ModConfig.SteamLobbySyncEnabled.Value && EnsureClient() && IsInitialized() && IsInLobby()) { if (NetworkAuthority.IsAuthoritative) { Publish(); } else if (!(Time.unscaledTime < _nextPullAt)) { _nextPullAt = Time.unscaledTime + 0.75f; Pull(); } } } public static void Publish() { if (ModConfig.SteamLobbySyncEnabled.Value && NetworkAuthority.IsAuthoritative && EnsureClient() && IsInitialized() && IsHost() && IsInLobby()) { int stars = HeatTracker.Stars; int dailyKills = HeatTracker.DailyKills; int streak = HeatTracker.Streak; string text = HeatTracker.PackWanted() ?? ""; if ((stars != _lastHeat || dailyKills != _lastKills || streak != _lastStreak || !(text == _lastWanted)) && SetData("pro.heat", stars.ToString(CultureInfo.InvariantCulture))) { SetData("pro.wanted", text); SetData("pro.kills", dailyKills.ToString(CultureInfo.InvariantCulture)); SetData("pro.streak", streak.ToString(CultureInfo.InvariantCulture)); _lastHeat = stars; _lastKills = dailyKills; _lastStreak = streak; _lastWanted = text; } } } private static void Pull() { string data = GetData("pro.wanted"); string data2 = GetData("pro.heat"); if (!string.IsNullOrEmpty(data) || !string.IsNullOrEmpty(data2)) { HeatTracker.ApplyRemoteWanted(data); int kills = ParseInt(GetData("pro.kills"), HeatTracker.DailyKills); int streak = ParseInt(GetData("pro.streak"), HeatTracker.Streak); HeatTracker.ApplyRemoteSnapshot(HeatTracker.CurrentTier, kills, streak); } } private static bool EnsureClient() { Resolve(); if (_clientType == null) { return false; } if (_client != null && IsInitialized()) { return true; } if (Time.unscaledTime < _nextInitAttempt) { return _client != null; } if (_client == null) { try { _client = _ctor.Invoke(null); if (_versionCheckEnabled != null && _versionCheckEnabled.CanWrite) { _versionCheckEnabled.SetValue(_client, false); } } catch (Exception ex) { if (ModConfig.DebugLogging.Value) { MelonLogger.Warning("SteamNetworkLib client create failed: " + ex.Message); } _nextInitAttempt = Time.unscaledTime + 3f; return false; } } try { object obj = _tryInitialize.Invoke(_client, null); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0) { if (!_loggedReady) { _loggedReady = true; MelonLogger.Msg("SteamNetworkLib detected. Wanted-level sync uses lobby data only (no P2P pump)."); } return true; } } catch (Exception ex2) { if (ModConfig.DebugLogging.Value) { MelonLogger.Warning("SteamNetworkLib TryInitialize failed: " + ex2.Message); } } _nextInitAttempt = Time.unscaledTime + 3f; return false; } private static void Resolve() { if (_resolved) { return; } _resolved = true; _clientType = GameAccess.FindLoadedType("SteamNetworkLib.SteamNetworkClient", AssemblyNames); if (_clientType == null) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { string text = assembly.GetName().Name ?? ""; if (text.IndexOf("SteamNetworkLib", StringComparison.OrdinalIgnoreCase) >= 0) { _clientType = assembly.GetType("SteamNetworkLib.SteamNetworkClient", throwOnError: false); if (_clientType != null) { break; } } } } if (_clientType == null) { if (!_loggedMissing) { _loggedMissing = true; MelonLogger.Msg("SteamNetworkLib not found. Co-op still uses FishNet; client wanted HUD stays local until the host has the library."); } return; } _ctor = _clientType.GetConstructor(Type.EmptyTypes); _tryInitialize = _clientType.GetMethod("TryInitialize", Type.EmptyTypes); _setLobbyData = _clientType.GetMethod("SetLobbyData", new Type[2] { typeof(string), typeof(string) }); _getLobbyData = _clientType.GetMethod("GetLobbyData", new Type[1] { typeof(string) }); _dispose = _clientType.GetMethod("Dispose", Type.EmptyTypes); _isInitialized = _clientType.GetProperty("IsInitialized"); _isHost = _clientType.GetProperty("IsHost"); _isInLobby = _clientType.GetProperty("IsInLobby"); _versionCheckEnabled = _clientType.GetProperty("VersionCheckEnabled"); if (_ctor == null || _tryInitialize == null || _setLobbyData == null || _getLobbyData == null) { MelonLogger.Warning("SteamNetworkLib loaded but lobby methods were missing. Wanted-level lobby sync disabled."); _clientType = null; } } private static bool IsInitialized() { return ReadBool(_isInitialized); } private static bool IsHost() { return ReadBool(_isHost); } private static bool IsInLobby() { return ReadBool(_isInLobby); } private static bool ReadBool(PropertyInfo property) { try { object obj = property?.GetValue(_client); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } catch { return false; } } private static bool SetData(string key, string value) { try { _setLobbyData.Invoke(_client, new object[2] { key, value }); return true; } catch (Exception ex) { if (ModConfig.DebugLogging.Value) { MelonLogger.Warning("Steam lobby SetLobbyData failed: " + (ex.InnerException?.Message ?? ex.Message)); } return false; } } private static string GetData(string key) { try { return _getLobbyData.Invoke(_client, new object[1] { key }) as string; } catch { return null; } } private static int ParseInt(string raw, int fallback) { if (int.TryParse(raw, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return result; } return fallback; } } } namespace PoliceResponseOverhaul.Cartel { public static class FinalResponse { private static readonly List Active = new List(); private static readonly HashSet Immune = new HashSet(); private static int _lastTriggeredTier; private static float _nextRetarget; public static void Reset() { Active.Clear(); Immune.Clear(); _lastTriggeredTier = 0; } public static void OnOfficerKilled() { if (NetworkAuthority.IsAuthoritative && ModConfig.FinalResponseEnabled.Value) { int num = Mathf.Max(1, ModConfig.FinalResponseInterval.Value); int num2 = Mathf.Clamp(HeatTracker.DailyKills / num, 0, ModConfig.FinalResponseTiers.Value); if (num2 > 0 && num2 > _lastTriggeredTier && HeatTracker.DailyKills % num == 0) { _lastTriggeredTier = num2; MelonCoroutines.Start(SpawnTier(num2)); } } } public static void ForceTier(int tier) { MelonCoroutines.Start(SpawnTier(Mathf.Clamp(tier, 1, ModConfig.FinalResponseTiers.Value))); } public static void Tick() { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) if (HeatTracker.IgnoreWanted || HeatTracker.Stars <= 0 || Active.Count == 0 || Time.unscaledTime < _nextRetarget) { return; } _nextRetarget = Time.unscaledTime + 2f; Player val = PlayerQuery.MostWanted(); if ((Object)(object)val == (Object)null) { return; } CartelGoon[] array = Active.ToArray(); foreach (CartelGoon val2 in array) { if ((Object)(object)val2 == (Object)null) { Active.Remove(val2); continue; } NPCHealth component = ((Component)val2).GetComponent(); if ((Object)(object)component != (Object)null && component.IsDead) { ReturnGoon(val2); continue; } CombatBehaviour val3 = ((Component)val2).GetComponent() ?? ((Component)val2).GetComponentInChildren(); if ((Object)(object)val3 != (Object)null) { val3.GiveUpRange = 9999f; try { val3.SetTarget(((NetworkBehaviour)val).NetworkObject); } catch { } try { val3.SetTargetAndEnable_Server(((NetworkBehaviour)val).NetworkObject); } catch { } } } } public static bool ShouldBlockKnockout(NPCHealth health) { return (Object)(object)health != (Object)null && Immune.Contains(((Object)health).GetInstanceID()); } private static IEnumerator SpawnTier(int tier) { yield return (object)new WaitForSeconds(1.2f); GoonPool pool = Object.FindObjectOfType(true); if ((Object)(object)pool == (Object)null) { MelonLogger.Warning("GoonPool not found; Final Response skipped."); yield break; } int count = SquadSize(tier); Player target = PlayerQuery.MostWanted(); Vector3 origin = ResolveSpawnOrigin(pool, target); MelonLogger.Msg($"Final Response tier {tier} — spawning {count} Cartel goon(s)."); TryInfluenceBump(); try { List spawned = pool.SpawnMultipleGoons(origin, count, true); if (spawned != null && spawned.Count > 0) { for (int i = 0; i < spawned.Count; i++) { CartelGoon goon = spawned[i]; if (!((Object)(object)goon == (Object)null)) { Configure(goon, tier, i == 0); if (!Active.Contains(goon)) { Active.Add(goon); } } } yield break; } } catch (Exception ex) { Exception ex2 = ex; MelonLogger.Warning("SpawnMultipleGoons failed: " + ex2.Message); } for (int j = 0; j < count; j++) { CartelGoon goon2 = SpawnOne(pool, origin + RandomOffset(j)); if (!((Object)(object)goon2 == (Object)null)) { Configure(goon2, tier, j == 0); Active.Add(goon2); yield return (object)new WaitForSeconds(0.35f); } } } private static Vector3 ResolveSpawnOrigin(GoonPool pool, Player target) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: 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_0081: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0033: 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_0064: Unknown result type (might be due to invalid IL or missing references) Vector3 val = (((Object)(object)target != (Object)null) ? ((Component)target).transform.position : Vector3.zero); try { NPCEnterableBuilding nearestExitBuilding = pool.GetNearestExitBuilding(val); if ((Object)(object)nearestExitBuilding != (Object)null) { StaticDoor closestDoor = nearestExitBuilding.GetClosestDoor(val, true); if ((Object)(object)closestDoor != (Object)null && (Object)(object)closestDoor.AccessPoint != (Object)null) { return closestDoor.AccessPoint.position; } } } catch { } return val + RandomOffset(0); } private static int SquadSize(int tier) { int num = HeatTracker.ConnectedPlayers(); if (num <= 1) { return 1; } if (1 == 0) { } int result = tier switch { 1 => 2, 2 => 2, 3 => 3, 4 => 4, _ => 5, }; if (1 == 0) { } return result; } private static void TryInfluenceBump() { if (!ModConfig.CartelInfluenceBump.Value) { return; } try { Assembly assembly = typeof(CartelGoon).Assembly; Type type = assembly.GetType("Il2CppScheduleOne.Cartel.Cartel") ?? assembly.GetType("ScheduleOne.Cartel.Cartel") ?? assembly.GetType("Il2CppScheduleOne.Cartel.CartelManager") ?? assembly.GetType("ScheduleOne.Cartel.CartelManager"); if (!(type == null)) { object instance = GameAccess.GetStatic(type, "Instance") ?? GameAccess.CallStatic(type, "get_Instance"); GameAccess.Call(instance, "ChangeInfluence", 1f); GameAccess.Call(instance, "AddInfluence", 1f); } } catch { } } private static CartelGoon SpawnOne(GoonPool pool, Vector3 near) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)pool == (Object)null) { return null; } try { return pool.SpawnGoon(near); } catch (Exception ex) { MelonLogger.Warning("SpawnGoon failed: " + ex.Message); return null; } } private static void Configure(CartelGoon goon, int tier, bool lead) { //IL_00ff: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)goon == (Object)null) { return; } NPCHealth component = ((Component)goon).GetComponent(); CombatBehaviour val = ((Component)goon).GetComponent() ?? ((Component)goon).GetComponentInChildren(); float num = 120f + (float)tier * 55f; if (lead) { num += 40f; } if ((Object)(object)component != (Object)null) { GameAccess.Set(component, "MaxHealth", num); GameAccess.Set(component, "Health", num); if (lead) { Immune.Add(((Object)component).GetInstanceID()); } } float moveSpeedMultiplier = 1.2f + (float)tier * 0.1f; try { if ((Object)(object)((NPC)goon).Movement != (Object)null) { ((NPC)goon).Movement.MoveSpeedMultiplier = moveSpeedMultiplier; } } catch { } if ((Object)(object)val != (Object)null) { val.GiveUpRange = 9999f; try { val.SetWeapon("Avatar/Equippables/M1911"); } catch { } Player val2 = PlayerQuery.MostWanted(((Component)goon).transform.position); if ((Object)(object)val2 != (Object)null) { try { val.SetTargetAndEnable_Server(((NetworkBehaviour)val2).NetworkObject); } catch { } } } InsertCash(goon, tier); } private static void InsertCash(CartelGoon goon, int tier) { try { int num = 8 + tier * 6; NPCInventory val = ((Component)goon).GetComponent() ?? ((Component)goon).GetComponentInChildren(true); ItemDefinition val2 = null; try { val2 = Registry.GetItem("cash"); } catch { } if ((Object)(object)val2 == (Object)null && (Object)(object)Singleton.Instance != (Object)null) { try { val2 = Singleton.Instance._GetItem("cash", false); } catch { val2 = GameAccess.Call(Singleton.Instance, "GetItem", new object[1] { "cash" }); } } if ((Object)(object)val2 == (Object)null || (Object)(object)val == (Object)null) { return; } ItemInstance val3 = null; try { val3 = val2.GetDefaultInstance(num * 100); } catch { val3 = GameAccess.Call(val2, "GetDefaultInstance", new object[1] { num * 100 }); } if (val3 == null) { return; } try { val.InsertItem(val3, true); } catch { GameAccess.Call(val, "InsertItem", val3); } } catch { } } private static void ReturnGoon(CartelGoon goon) { Active.Remove(goon); NPCHealth component = ((Component)goon).GetComponent(); if ((Object)(object)component != (Object)null) { Immune.Remove(((Object)component).GetInstanceID()); } try { if ((Object)(object)goon != (Object)null) { goon.Despawn(); } } catch { GoonPool val = Object.FindObjectOfType(true); if ((Object)(object)val != (Object)null) { try { val.ReturnToPool(goon); return; } catch { GameAccess.Call(val, "ReturnToPool", goon); return; } } } } private static Vector3 RandomOffset(int index) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) float num = (float)index * 70f * (MathF.PI / 180f); return new Vector3(Mathf.Cos(num), 0f, Mathf.Sin(num)) * (6f + (float)index); } } } namespace PoliceResponseOverhaul.Authority { public static class NetworkAuthority { public static bool IsAuthoritative { get { try { NetworkManager networkManager = InstanceFinder.NetworkManager; if ((Object)(object)networkManager == (Object)null) { return false; } if (networkManager.IsServer) { return true; } if (!networkManager.IsServer && !networkManager.IsClient) { return true; } return false; } catch { return false; } } } public static bool IsClientOnly { get { try { NetworkManager networkManager = InstanceFinder.NetworkManager; return (Object)(object)networkManager != (Object)null && networkManager.IsClient && !networkManager.IsServer; } catch { return false; } } } } } namespace PoliceResponseOverhaul.Armor { public static class BodyArmor { private static ClothingDefinition _definition; private static ClothingDefinition _officerVest; private static ClothingShopListing _armorListing; private static readonly HashSet ArmorOwners = new HashSet(); private static readonly HashSet InjectedShopIds = new HashSet(); private static bool _shopInjected; private static bool _loggedMissingTemplate; private static bool _loggedArmorAdded; private static bool _vestSearchDone; private static int _armorUiShopId; private const string ListingMarker = "pro_bodyarmor"; private static readonly HashSet EquippedOfficers = new HashSet(); private static readonly HashSet VestApplied = new HashSet(); private static readonly Dictionary WasCombined = new Dictionary(); private static readonly Dictionary VestSlot = new Dictionary(); public static string ItemId => ModConfig.ArmorItemId.Value; public static ClothingShopListing ArmorListing => _armorListing; public static void Reset() { EquippedOfficers.Clear(); VestApplied.Clear(); WasCombined.Clear(); VestSlot.Clear(); ArmorOwners.Clear(); InjectedShopIds.Clear(); _armorListing = null; _definition = null; _shopInjected = false; _loggedMissingTemplate = false; _loggedArmorAdded = false; _officerVest = null; _vestSearchDone = false; _armorUiShopId = 0; } public static bool IsEquipped(Player player) { if ((Object)(object)player == (Object)null || !ModConfig.ArmorEnabled.Value) { return false; } return IsOwner(player) && WearingArmor(player); } public static void OnPlayerDied(Player player) { if (!((Object)(object)player == (Object)null) && ModConfig.ArmorEnabled.Value) { Consume(player); UnmarkOwner(player); } } public static bool IsOurListing(ShopListing listing) { if (listing == null) { return false; } if (_armorListing != null && (object)listing == _armorListing) { return true; } try { string text = null; try { text = listing.PurchasedQuantityVariableName; } catch { text = GameAccess.Get(listing, "PurchasedQuantityVariableName"); } if (string.Equals(text, "pro_bodyarmor", StringComparison.OrdinalIgnoreCase)) { return true; } string text2 = null; try { text2 = (((Object)(object)listing.Item != (Object)null) ? ((BaseItemDefinition)listing.Item).ID : null); } catch { text2 = ReadId(listing.Item); } if (string.Equals(text2, ItemId, StringComparison.OrdinalIgnoreCase)) { return true; } if (string.Equals(listing.name, ModConfig.ArmorItemName.Value, StringComparison.OrdinalIgnoreCase) && listing.OverridePrice && Mathf.Abs(listing.OverriddenPrice - ModConfig.ArmorPrice.Value) < 0.01f) { return true; } } catch { } return false; } public static void MarkOwner(Player player) { string text = PlayerQuery.Code(player); if (!string.IsNullOrEmpty(text)) { ArmorOwners.Add(text); } } public static void UnmarkOwner(Player player) { string text = PlayerQuery.Code(player); if (!string.IsNullOrEmpty(text)) { ArmorOwners.Remove(text); } } public static bool IsOwner(Player player) { string text = PlayerQuery.Code(player); return !string.IsNullOrEmpty(text) && ArmorOwners.Contains(text); } public static float ModifyDamage(Player player, float damage) { if (!ModConfig.ArmorEnabled.Value || (Object)(object)player == (Object)null) { return damage; } if (!IsEquipped(player)) { return damage; } return damage * ModConfig.ArmorDamageMultiplier.Value; } public static bool OfficerHasArmor(PoliceOfficer officer) { return (Object)(object)officer != (Object)null && EquippedOfficers.Contains(((Object)officer).GetInstanceID()); } public static bool ShouldWearArmor(PoliceOfficer officer, OfficerArchetype archetype, ResponsePersonality personality, string weaponPath) { if (!ModConfig.ArmorEnabled.Value || (Object)(object)officer == (Object)null) { return false; } if ((uint)(personality - 9) <= 3u) { return Random.value < 0.82f; } float num = (IsLongGun(weaponPath) ? 0.38f : 0.12f); if (archetype == OfficerArchetype.Rookie) { num *= 0.5f; } return Random.value < num; } public static void EnsureStreetArmor(PoliceOfficer officer) { if ((Object)(object)officer == (Object)null || !PatchGate.IsLive) { return; } LoadoutService.EnsureStreetLoadout(officer); if (!ModConfig.ArmorEnabled.Value) { return; } if (OfficerHasArmor(officer)) { TryApplyVest(officer); return; } bool flag = StableRoll(officer) < 12; EquipOfficer(officer, flag); if ((!ModConfig.AmbientMoreGuns.Value || !LoadoutService.MoreGunsAvailable) && flag && StableRoll(officer) >= 25) { LoadoutService.GiveVanillaShotgun(officer); } } public static void EquipOfficer(PoliceOfficer officer, bool armored) { if ((Object)(object)officer == (Object)null) { return; } int instanceID = ((Object)officer).GetInstanceID(); if (armored) { bool flag = EquippedOfficers.Add(instanceID); TryApplyVest(officer); if (flag && (Object)(object)OfficerVest() != (Object)null) { MelonCoroutines.Start(RetryVest(officer)); } } else { EquippedOfficers.Remove(instanceID); TryRemoveVest(officer); } } public static void ApplyJuggernautLook(PoliceOfficer officer) { //IL_003a: 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_0042: 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) if ((Object)(object)officer == (Object)null) { return; } EquipOfficer(officer, armored: true); ClothingDefinition val = FindJuggernautHead(); if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(val.ClothingAssetPath)) { Color actualColor = ClothingColorExtensions.GetActualColor((EClothingColor)4); if (!TryApplyVestAccessory(officer, val, actualColor)) { TryApplyExtraLayer(officer, val, actualColor); } } } public static float ModifyOfficerDamage(PoliceOfficer officer, float damage) { if (!ModConfig.ArmorEnabled.Value || (Object)(object)officer == (Object)null) { return damage; } if (!OfficerHasArmor(officer)) { return damage; } return damage * ModConfig.ArmorDamageMultiplier.Value; } private static IEnumerator RetryVest(PoliceOfficer officer) { yield return (object)new WaitForSeconds(0.25f); if ((Object)(object)officer != (Object)null && OfficerHasArmor(officer)) { TryApplyVest(officer); } yield return (object)new WaitForSeconds(0.75f); if ((Object)(object)officer != (Object)null && OfficerHasArmor(officer)) { TryApplyVest(officer); } } private static bool IsLongGun(string path) { if (string.IsNullOrEmpty(path)) { return false; } return path.IndexOf("shotgun", StringComparison.OrdinalIgnoreCase) >= 0 || path.IndexOf("ak47", StringComparison.OrdinalIgnoreCase) >= 0 || path.IndexOf("rifle", StringComparison.OrdinalIgnoreCase) >= 0 || path.IndexOf("minigun", StringComparison.OrdinalIgnoreCase) >= 0 || path.IndexOf("smg", StringComparison.OrdinalIgnoreCase) >= 0 || path.IndexOf("sniper", StringComparison.OrdinalIgnoreCase) >= 0 || path.IndexOf("rpg", StringComparison.OrdinalIgnoreCase) >= 0; } private static int StableRoll(PoliceOfficer officer) { try { NetworkObject val = ((NetworkBehaviour)officer).NetworkObject ?? ((Component)officer).GetComponent(); if ((Object)(object)val != (Object)null) { return (int)((long)val.ObjectId * 17L % 100); } } catch { } return Math.Abs(((Object)officer).GetInstanceID() * 17) % 100; } private static void TryApplyVest(PoliceOfficer officer) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)officer == (Object)null) { return; } int instanceID = ((Object)officer).GetInstanceID(); if (VestApplied.Contains(instanceID)) { return; } Avatar avatar = ((NPC)officer).Avatar; if ((Object)(object)avatar == (Object)null) { return; } ClothingDefinition val = OfficerVest(); if ((Object)(object)val == (Object)null || string.IsNullOrEmpty(val.ClothingAssetPath)) { return; } try { Color actualColor = ClothingColorExtensions.GetActualColor((EClothingColor)4); int slot; if (TryApplyVestAccessory(officer, val, actualColor)) { VestApplied.Add(instanceID); } else if (PrepareLayeredBody(officer, instanceID, out slot)) { avatar.SetBodyLayer(slot, val.ClothingAssetPath, actualColor); VestSlot[instanceID] = slot; VestApplied.Add(instanceID); if (ModConfig.DebugLogging.Value) { MelonLogger.Msg($"Officer {((Object)officer).name} wearing {ReadId(val)} on layer {slot}."); } } } catch (Exception ex) { if (ModConfig.DebugLogging.Value) { MelonLogger.Warning("Officer vest apply failed: " + ex.Message); } } } private static bool TryApplyVestAccessory(PoliceOfficer officer, ClothingDefinition clothing, Color tint) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_00b1: Unknown result type (might be due to invalid IL or missing references) if ((int)clothing.ApplicationType != 2) { return false; } Avatar avatar = ((NPC)officer).Avatar; if ((Object)(object)avatar == (Object)null) { return false; } Object val = Resources.Load(clothing.ClothingAssetPath); if (val == (Object)null || (Object)(object)avatar.HipBone == (Object)null) { return false; } Accessory result = (Accessory)(object)((val is Accessory) ? val : null); if ((Object)(object)result == (Object)null && !Utils.Is(val, out result)) { return false; } Accessory val2 = Object.Instantiate(result, avatar.HipBone); if ((Object)(object)val2 == (Object)null) { return false; } try { val2.ApplyColor(tint); } catch { } return true; } private static bool PrepareLayeredBody(PoliceOfficer officer, int officerId, out int slot) { slot = 8; Avatar avatar = ((NPC)officer).Avatar; if ((Object)(object)avatar == (Object)null) { return false; } AvatarSettings currentSettings = avatar.CurrentSettings; int num = 0; try { if (currentSettings?.BodyLayerSettings != null) { num = currentSettings.BodyLayerSettings.Count; } } catch { num = 0; } bool flag = false; try { flag = avatar.UseCombinedLayer; } catch { } try { flag = flag || GameAccess.Get(avatar, "usingCombinedLayer"); } catch { } if (flag) { if (num <= 0) { return false; } WasCombined[officerId] = true; try { avatar.UseCombinedLayer = false; } catch { } GameAccess.Set(avatar, "usingCombinedLayer", false); avatar.ApplyBodyLayerSettings(currentSettings, -1); } slot = Mathf.Clamp(num + 1, 1, 8); return true; } private static void TryRemoveVest(PoliceOfficer officer) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)officer == (Object)null) { return; } int instanceID = ((Object)officer).GetInstanceID(); if (!VestApplied.Remove(instanceID)) { return; } Avatar avatar = ((NPC)officer).Avatar; if ((Object)(object)avatar == (Object)null) { return; } try { if (VestSlot.TryGetValue(instanceID, out var value)) { avatar.SetBodyLayer(value, string.Empty, Color.white); } if (WasCombined.TryGetValue(instanceID, out var value2) && value2 && (Object)(object)avatar.CurrentSettings != (Object)null) { try { avatar.UseCombinedLayer = true; } catch { } GameAccess.Set(avatar, "usingCombinedLayer", true); avatar.ApplyBodyLayerSettings(avatar.CurrentSettings, -1); } } catch { } VestSlot.Remove(instanceID); WasCombined.Remove(instanceID); } private static ClothingDefinition OfficerVest() { if (_vestSearchDone) { return _officerVest; } _officerVest = FindVestDefinition(); return _officerVest; } private static void TryApplyExtraLayer(PoliceOfficer officer, ClothingDefinition clothing, Color tint) { //IL_007b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)officer == (Object)null || (Object)(object)clothing == (Object)null) { return; } int instanceID = ((Object)officer).GetInstanceID(); Avatar avatar = ((NPC)officer).Avatar; if ((Object)(object)avatar == (Object)null || !PrepareLayeredBody(officer, instanceID, out var slot)) { return; } if (VestSlot.TryGetValue(instanceID, out var value)) { slot = Mathf.Clamp(Mathf.Max(slot, value + 1), 1, 8); } try { avatar.SetBodyLayer(slot, clothing.ClothingAssetPath, tint); } catch { } } private static ClothingDefinition FindJuggernautHead() { string[] array = new string[6] { "swathelmet", "tacticalhelmet", "helmet", "balaclava", "gasmask", "tacticalmask" }; string[] array2 = array; foreach (string id in array2) { ClothingDefinition clothing = GetClothing(id); if ((Object)(object)clothing != (Object)null) { return clothing; } } try { Registry instance = Singleton.Instance; List list = ((instance != null) ? instance.GetAllItems() : null); if (list == null) { return null; } int count = list.Count; for (int j = 0; j < count; j++) { ClothingDefinition val = AsClothing(list[j]); if (!((Object)(object)val == (Object)null)) { string text = ReadId(val) ?? string.Empty; string text2 = ((BaseItemDefinition)val).Name ?? string.Empty; if (text.IndexOf("helmet", StringComparison.OrdinalIgnoreCase) >= 0 || text2.IndexOf("helmet", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("balaclava", StringComparison.OrdinalIgnoreCase) >= 0 || text2.IndexOf("balaclava", StringComparison.OrdinalIgnoreCase) >= 0) { return val; } } } } catch { } return null; } private static ClothingDefinition FindVestDefinition() { string[] array = new string[3] { (ModConfig.OfficerArmorClothingId != null) ? ModConfig.OfficerArmorClothingId.Value : "tacticalvest", "tacticalvest", "vest" }; string[] array2 = array; foreach (string text in array2) { if (!string.IsNullOrWhiteSpace(text)) { ClothingDefinition clothing = GetClothing(text.Trim()); if ((Object)(object)clothing != (Object)null) { _vestSearchDone = true; return clothing; } } } ClothingDefinition val = ScanRegistryForVest(); if ((Object)(object)val != (Object)null || RegistryIsLive()) { _vestSearchDone = true; } return val; } private static bool RegistryIsLive() { try { return (Object)(object)Singleton.Instance != (Object)null && (Object)(object)Singleton.Instance._GetItem("cash", false) != (Object)null; } catch { return false; } } private static ClothingDefinition AsClothing(ItemDefinition item) { if ((Object)(object)item == (Object)null) { return null; } ClothingDefinition val = (ClothingDefinition)(object)((item is ClothingDefinition) ? item : null); if (val != null) { return val; } return null; } private static ClothingDefinition GetClothing(string id) { if (string.IsNullOrEmpty(id) || (Object)(object)Singleton.Instance == (Object)null) { return null; } try { return AsClothing(Singleton.Instance._GetItem(id, false)); } catch { try { return AsClothing(GameAccess.Call(Singleton.Instance, "_GetItem", new object[2] { id, false })); } catch { return null; } } } private static ClothingDefinition ScanRegistryForVest() { //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Invalid comparison between Unknown and I4 try { Registry instance = Singleton.Instance; List list = ((instance != null) ? instance.GetAllItems() : null); if (list == null) { return null; } ClothingDefinition val = null; int count = list.Count; for (int i = 0; i < count; i++) { ClothingDefinition val2 = AsClothing(list[i]); if (!((Object)(object)val2 == (Object)null)) { string text = ReadId(val2) ?? string.Empty; string text2 = ((BaseItemDefinition)val2).Name ?? string.Empty; if (text.IndexOf("tacticalvest", StringComparison.OrdinalIgnoreCase) >= 0 || text2.IndexOf("tactical vest", StringComparison.OrdinalIgnoreCase) >= 0) { return val2; } if ((int)val2.Slot == 4 && (text.IndexOf("vest", StringComparison.OrdinalIgnoreCase) >= 0 || text2.IndexOf("vest", StringComparison.OrdinalIgnoreCase) >= 0) && val == null) { val = val2; } } } return val; } catch { return null; } } public static void OnRegistryReady() { if (PatchGate.IsLive) { TryInjectAllShops(); } } public static void TryInjectAllShops() { if (!PatchGate.IsLive || !ModConfig.ArmorEnabled.Value || _shopInjected) { return; } try { List allShops = ShopInterface.AllShops; if (allShops == null) { return; } foreach (ShopInterface item in allShops) { TryInjectShop(item); } } catch { } } public static void TryInjectShop(ShopInterface shop) { if (!PatchGate.IsLive || !ModConfig.ArmorEnabled.Value || (Object)(object)shop == (Object)null) { return; } string text = ((Object)((Component)shop).gameObject).name + " " + ((Object)shop).name; if (!text.Contains("Clothing", StringComparison.OrdinalIgnoreCase) && !text.Contains("Thrifty", StringComparison.OrdinalIgnoreCase) && !text.Contains(ModConfig.ArmorShopName.Value, StringComparison.OrdinalIgnoreCase)) { return; } EnsureDefinition(shop); if ((Object)(object)_definition == (Object)null) { bool flag = false; try { flag = shop.IsOpen; } catch { } if (flag && !_loggedMissingTemplate) { _loggedMissingTemplate = true; MelonLogger.Warning("Could not find a vanilla vest for Body Armor. Thrifty Threads listing skipped."); } return; } try { if (shop.Listings == null) { return; } int instanceID = ((Object)shop).GetInstanceID(); if (InjectedShopIds.Contains(instanceID) && _armorListing != null) { EnsureListingUI(shop, (ShopListing)(object)_armorListing); return; } ClothingShopListing val = null; ClothingShopListing val2 = null; List list = new List(); try { ShopListing listing = shop.GetListing(ItemId); val = AsClothingListing(listing); } catch { } foreach (ShopListing listing2 in shop.Listings) { if ((Object)(object)listing2?.Item == (Object)null) { continue; } if (IsOurListing(listing2)) { ClothingShopListing val3 = AsClothingListing(listing2); if (val == null) { val = val3; } else if ((object)val != listing2) { list.Add(listing2); } } else if (val2 == null && IsVanillaVestDefinition((ItemDefinition)(object)AsClothing((ItemDefinition)(object)listing2.Item))) { val2 = AsClothingListing(listing2); } } foreach (ShopListing item in list) { TryRemoveListing(shop.Listings, item); } if (val != null) { ApplyArmorListingFields(val, val2); _armorListing = val; _shopInjected = true; InjectedShopIds.Add(instanceID); if (list.Count > 0) { MelonLogger.Msg($"Removed {list.Count} duplicate Body Armor listing(s) from {((Object)shop).name}."); } EnsureListingUI(shop, (ShopListing)(object)val); return; } ClothingShopListing val4 = BuildArmorListing(val2); if (val4 == null || (Object)(object)((ShopListing)val4).Item == (Object)null) { return; } shop.Listings.Add((ShopListing)(object)val4); try { ((ShopListing)val4).Initialize(shop); } catch { try { GameAccess.Call(val4, "Initialize", shop); } catch { } } EnsureListingUI(shop, (ShopListing)(object)val4); _armorListing = val4; _shopInjected = true; InjectedShopIds.Add(instanceID); if (!_loggedArmorAdded) { _loggedArmorAdded = true; MelonLogger.Msg($"Body armor added to {((Object)shop).name} for ${ModConfig.ArmorPrice.Value:0}."); } } catch (Exception ex) { MelonLogger.Warning("Body armor shop inject failed: " + ex.Message); } } private static ClothingShopListing AsClothingListing(ShopListing listing) { if (listing == null) { return null; } ClothingShopListing val = (ClothingShopListing)(object)((listing is ClothingShopListing) ? listing : null); if (val != null) { return val; } return null; } private static void TryRemoveListing(object listings, ShopListing listing) { if (listings == null || listing == null) { return; } try { GameAccess.Call(listings, "Remove", listing); } catch { try { GameAccess.Call(listings, "Remove", listing); } catch { } } } private static void EnsureListingUI(ShopInterface shop, ShopListing listing) { if ((Object)(object)shop == (Object)null || listing == null) { return; } int instanceID = ((Object)shop).GetInstanceID(); if (ShopHasListingUI(shop, listing)) { _armorUiShopId = instanceID; ApplyListingDisplayNameForShop(shop, listing); return; } try { GameAccess.CreateListingUI(shop, listing); } catch (Exception ex) { MelonLogger.Warning("Body armor listing UI failed: " + ex.Message); } if (ShopHasListingUI(shop, listing)) { _armorUiShopId = instanceID; ApplyListingDisplayNameForShop(shop, listing); return; } try { GameAccess.RefreshShownItems(shop); } catch (Exception ex2) { MelonLogger.Warning("Body armor listing refresh failed: " + ex2.Message); } if (ShopHasListingUI(shop, listing)) { _armorUiShopId = instanceID; ApplyListingDisplayNameForShop(shop, listing); } else if (_armorUiShopId != instanceID) { MelonLogger.Warning("Body armor listing is in " + ((Object)shop).name + " but no UI row was created yet. Re-open the shop if it is missing."); } } private static bool ShopHasListingUI(ShopInterface shop, ShopListing listing) { try { object obj = GameAccess.Get(shop, "listingUIs") ?? GameAccess.Get(shop, "ListingUIs") ?? GameAccess.Get(shop, "listingsUI") ?? GameAccess.Get(shop, "_listingUIs"); if (obj is IEnumerable enumerable) { foreach (object item in enumerable) { if (ListingUIMatches(item, listing)) { return true; } } } } catch { } try { ListingUI[] componentsInChildren = ((Component)shop).GetComponentsInChildren(true); if (componentsInChildren == null) { return false; } ListingUI[] array = componentsInChildren; foreach (ListingUI val in array) { if ((Object)(object)val != (Object)null && (val.Listing == listing || IsOurListing(val.Listing))) { return true; } } } catch { } return false; } private static bool ListingUIMatches(object entry, ShopListing listing) { if (entry == null || listing == null) { return false; } ListingUI val = (ListingUI)((entry is ListingUI) ? entry : null); if (val != null) { return val.Listing == listing || IsOurListing(val.Listing); } try { ShopListing val2 = GameAccess.Get(entry, "Listing"); return val2 == listing || IsOurListing(val2); } catch { return false; } } private static void ApplyListingDisplayNameForShop(ShopInterface shop, ShopListing listing) { try { object obj = GameAccess.Get(shop, "listingUIs") ?? GameAccess.Get(shop, "ListingUIs") ?? GameAccess.Get(shop, "listingsUI"); if (!(obj is IEnumerable enumerable)) { return; } foreach (object item in enumerable) { ListingUI val = (ListingUI)((item is ListingUI) ? item : null); ShopListing val2 = (((Object)(object)val != (Object)null) ? val.Listing : GameAccess.Get(item, "Listing")); if (val2 != listing && !IsOurListing(val2)) { continue; } if ((Object)(object)val != (Object)null) { ApplyListingDisplayName(val, val2); } else if (item != null) { object obj2 = GameAccess.Get(item, "NameLabel"); if (obj2 != null) { GameAccess.Set(obj2, "text", ModConfig.ArmorItemName.Value); } } } } catch { } } private static void ApplyArmorListingFields(ClothingShopListing listing, ClothingShopListing vestTemplate) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) if (listing == null) { return; } ((ShopListing)listing).name = ModConfig.ArmorItemName.Value; ((ShopListing)listing).Item = (StorableItemDefinition)(((object)_definition) ?? ((object)((ShopListing)listing).Item)); ((ShopListing)listing).OverridePrice = true; ((ShopListing)listing).OverriddenPrice = ModConfig.ArmorPrice.Value; ((ShopListing)listing).LimitedStock = false; ((ShopListing)listing).TrackPurchases = false; ((ShopListing)listing).PurchasedQuantityVariableName = "pro_bodyarmor"; if (vestTemplate != null) { listing.Color = vestTemplate.Color; } else if ((Object)(object)_definition != (Object)null) { try { listing.Color = _definition.DefaultColor; } catch { } } } private static ClothingShopListing BuildArmorListing(ClothingShopListing vestTemplate) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown if ((Object)(object)_definition == (Object)null) { return null; } ClothingShopListing val = new ClothingShopListing { Item = (StorableItemDefinition)(object)_definition, CanBeDelivered = (((ShopListing)(vestTemplate?)).CanBeDelivered ?? false) }; ApplyArmorListingFields(val, vestTemplate); return val; } public static void ApplyListingDisplayName(ListingUI ui, ShopListing listing) { if ((Object)(object)ui?.NameLabel == (Object)null || !IsOurListing(listing)) { return; } try { ((TMP_Text)ui.NameLabel).text = ModConfig.ArmorItemName.Value; } catch { } } public static void GiveToLocal() { Player local = Player.Local; EnsureDefinition(null); if ((Object)(object)local == (Object)null || (Object)(object)_definition == (Object)null) { MelonLogger.Warning("Could not grant body armor. The clothing registry is not ready yet."); return; } try { ClothingInstance val = CreateArmorInstance(); if (val == null) { return; } MarkOwner(local); PlayerClothing clothing = local.Clothing; if ((Object)(object)clothing != (Object)null) { clothing.InsertClothing(val); MelonLogger.Msg("Equipped Body Armor in your Outerwear slot."); return; } PlayerInventory inventory = GetInventory(local); if ((Object)(object)inventory != (Object)null) { inventory.AddItemToInventory((ItemInstance)(object)val); MelonLogger.Msg("Added Body Armor to your inventory. Equip it in your Outerwear clothing slot."); } } catch (Exception ex) { MelonLogger.Warning("Give armor failed: " + ex.Message); } } private static ClothingInstance CreateArmorInstance(EClothingColor? color = null) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002d: 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_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003d: 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) if ((Object)(object)_definition == (Object)null) { return null; } EClothingColor val = (EClothingColor)(((??)color) ?? DefaultArmorColor()); try { ClothingInstance val2 = new ClothingInstance((ItemDefinition)(object)_definition, 1, val); if (val2 != null) { return val2; } } catch { } try { ItemInstance defaultInstance = ((ItemDefinition)_definition).GetDefaultInstance(1); ClothingInstance val3 = (ClothingInstance)(object)((defaultInstance is ClothingInstance) ? defaultInstance : null); if (val3 != null) { try { val3.Color = val; } catch { } } return val3; } catch { return null; } } private static EClothingColor DefaultArmorColor() { //IL_0027: 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) //IL_002a: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)_definition != (Object)null) { return _definition.DefaultColor; } } catch { } return (EClothingColor)0; } private static bool WearingArmor(Player player) { PlayerClothing clothing = player.Clothing; if (((clothing != null) ? clothing.ItemSlots : null) == null) { return false; } foreach (ItemSlot itemSlot in clothing.ItemSlots) { if (itemSlot != null && itemSlot.Quantity > 0) { ItemInstance itemInstance = itemSlot.ItemInstance; if (IsArmorDefinition((itemInstance != null) ? itemInstance.Definition : null)) { return true; } } } return false; } private static bool IsArmorDefinition(ItemDefinition definition) { string text = ReadId(definition); if (string.IsNullOrEmpty(text)) { return false; } return string.Equals(text, ItemId, StringComparison.OrdinalIgnoreCase); } private static bool IsVestDefinition(ItemDefinition definition) { return IsArmorDefinition(definition) || IsVanillaVestDefinition(definition); } private static bool IsVanillaVestDefinition(ItemDefinition definition) { string text = ReadId(definition); if (string.IsNullOrEmpty(text)) { return false; } return string.Equals(text, "tacticalvest", StringComparison.OrdinalIgnoreCase) || string.Equals(text, "vest", StringComparison.OrdinalIgnoreCase); } private static void Consume(Player player) { try { PlayerClothing clothing = player.Clothing; if (((clothing != null) ? clothing.ItemSlots : null) != null) { foreach (ItemSlot itemSlot in clothing.ItemSlots) { ClearIfArmor(itemSlot); } } PlayerInventory inventory = GetInventory(player); if ((Object)(object)inventory != (Object)null) { TryRemoveVestFromInventory(inventory, ItemId); TryRemoveVestFromInventory(inventory, "tacticalvest"); TryRemoveVestFromInventory(inventory, "vest"); } } catch { } } private static void TryRemoveVestFromInventory(PlayerInventory inventory, string id) { if ((Object)(object)inventory == (Object)null || string.IsNullOrEmpty(id)) { return; } try { uint amountOfItem = inventory.GetAmountOfItem(id); if (amountOfItem != 0) { inventory.RemoveAmountOfItem(id, amountOfItem); } } catch { } } private static void ClearIfArmor(ItemSlot slot) { if (slot == null || slot.Quantity <= 0) { return; } ItemInstance itemInstance = slot.ItemInstance; if (!IsArmorDefinition((itemInstance != null) ? itemInstance.Definition : null)) { return; } try { slot.ClearStoredInstance(false); } catch { GameAccess.Call(slot, "ClearStoredInstance", false); } } private static PlayerInventory GetInventory(Player player) { if ((Object)(object)player == (Object)null) { return null; } PlayerInventory val = ((Component)player).GetComponent() ?? ((Component)player).GetComponentInChildren(true); if ((Object)(object)val != (Object)null) { return val; } try { if (PlayerSingleton.InstanceExists) { return PlayerSingleton.Instance; } } catch { } return null; } private static void EnsureDefinition(ShopInterface shop) { if ((Object)(object)_definition != (Object)null) { return; } ClothingDefinition val = FindVestDefinition() ?? FindVestFromShop(shop); if ((Object)(object)val == (Object)null) { try { val = GetClothing("tacticalvest") ?? GetClothing("vest"); } catch { val = null; } } if ((Object)(object)val == (Object)null) { return; } ClothingDefinition val2 = CloneVestDefinition(val); if ((Object)(object)val2 == (Object)null) { MelonLogger.Warning("Failed to clone tactical vest for Body Armor."); return; } _definition = val2; try { if ((Object)(object)Singleton.Instance != (Object)null && !Registry.ItemExists(ItemId)) { Singleton.Instance.AddToRegistry((ItemDefinition)(object)val2); } } catch { try { GameAccess.Call(Singleton.Instance, "AddToRegistry", val2); } catch { } } } private static ClothingDefinition CloneVestDefinition(ClothingDefinition template) { if ((Object)(object)template == (Object)null) { return null; } ClothingDefinition val = null; try { val = Object.Instantiate(template); } catch (Exception ex) { if (ModConfig.DebugLogging.Value) { MelonLogger.Warning("Vest Instantiate failed: " + ex.Message); } } if ((Object)(object)val == (Object)null) { return null; } try { ((Object)val).name = ItemId; ((BaseItemDefinition)val).ID = ItemId; ((BaseItemDefinition)val).Name = ModConfig.ArmorItemName.Value; ((BaseItemDefinition)val).Description = "Ballistic vest. Wear it in your Outerwear clothing slot to cut incoming damage in half. Lost on death."; ((StorableItemDefinition)val).BasePurchasePrice = (int)ModConfig.ArmorPrice.Value; } catch (Exception ex2) { MelonLogger.Warning("Failed to configure Body Armor definition: " + ex2.Message); return null; } CopyShopCategories(template, val); return val; } private static void CopyShopCategories(ClothingDefinition template, ClothingDefinition clone) { if ((Object)(object)template == (Object)null || (Object)(object)clone == (Object)null) { return; } try { if (((StorableItemDefinition)clone).ShopCategories == null && ((StorableItemDefinition)template).ShopCategories != null) { ((StorableItemDefinition)clone).ShopCategories = ((StorableItemDefinition)template).ShopCategories; } } catch { } } private static ClothingDefinition FindVestFromShop(ShopInterface shop) { //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Invalid comparison between Unknown and I4 object obj = GameAccess.Get(shop, "Listings"); if (!(obj is IEnumerable enumerable)) { return null; } ClothingDefinition val = null; foreach (object item in enumerable) { ClothingDefinition val2 = null; ShopListing val3 = (ShopListing)((item is ShopListing) ? item : null); if (val3 != null) { val2 = AsClothing((ItemDefinition)(object)val3.Item); } if ((Object)(object)val2 == (Object)null) { val2 = AsClothing(GameAccess.Get(item, "Item")); } if (!((Object)(object)val2 == (Object)null)) { string a = ReadId(val2); if (string.Equals(a, "tacticalvest", StringComparison.OrdinalIgnoreCase) || string.Equals(a, "vest", StringComparison.OrdinalIgnoreCase) || (int)val2.Slot == 4) { return val2; } if (val == null) { val = val2; } } } return val; } private static string ReadId(object definition) { if (definition == null) { return null; } string text = GameAccess.Get(definition, "ID"); return string.IsNullOrEmpty(text) ? GameAccess.Get(definition, "id") : text; } } } namespace PoliceResponseOverhaul.Ambient { public static class AmbientLaw { private static bool _injected; private static float _nextTick; private static readonly List ExtraFoot = new List(); private static readonly List ExtraVehicles = new List(); private static readonly List ExtraSentries = new List(); public static int CountOfficersOnRoute(FootPatrolRoute route) { if ((Object)(object)route == (Object)null) { return 0; } int num = 0; foreach (PoliceOfficer item in OfficerPool.EnumerateOfficers()) { if (OfficerPool.IsUsable(item)) { FootPatrolBehaviour val = ((Component)item).GetComponent() ?? ((Component)item).GetComponentInChildren(true); if (!((Object)(object)val == (Object)null) && ((Behaviour)val).Active && val.Group != null && (Object)(object)val.Group.Route == (Object)(object)route) { num++; } } } return num; } public static bool RouteHasActiveGroup(FootPatrolRoute route, PatrolInstance except = null) { if ((Object)(object)route == (Object)null) { return false; } try { LawManager val = (Singleton.InstanceExists ? Singleton.Instance : Object.FindObjectOfType(true)); LawActivitySettings val2 = ((val != null) ? ((Component)val).GetComponent() : null) ?? ((val != null) ? ((Component)val).GetComponentInChildren(true) : null); if (val2?.Patrols != null) { PatrolInstance[] patrols = val2.Patrols; foreach (PatrolInstance val3 in patrols) { if (val3 != null && val3 != except && !((Object)(object)val3.Route != (Object)(object)route) && val3.ActiveGroup != null && val3.ActiveGroup.Members != null && val3.ActiveGroup.Members.Count > 0) { return true; } } } } catch { } return CountOfficersOnRoute(route) > 0; } public static void Reset() { _injected = false; _nextTick = 0f; ExtraFoot.Clear(); ExtraVehicles.Clear(); ExtraSentries.Clear(); } public static void Tick() { if (!PatchGate.IsLive || !ModConfig.Enabled.Value || Time.unscaledTime < _nextTick) { return; } _nextTick = Time.unscaledTime + 8f; LawManager val = (Singleton.InstanceExists ? Singleton.Instance : Object.FindObjectOfType(true)); if (!((Object)(object)val == (Object)null)) { LawActivitySettings val2 = ((Component)val).GetComponent() ?? ((Component)val).GetComponentInChildren(true); if (val2 != null) { OnSettingsEvaluate(val2); } } } public static void OnSettingsEvaluate(LawActivitySettings settings) { if (settings == null || !ModConfig.Enabled.Value || !PatchGate.IsLive) { return; } try { RewriteVanillaSizes(settings); if (!_injected) { InjectExtras(settings); } ApplyHeatToExtras(); ScaleIntensity(); } catch (Exception arg) { MelonLogger.Error($"Ambient law update failed: {arg}"); } } private static void RewriteVanillaSizes(LawActivitySettings settings) { int num = Mathf.Max(1, ModConfig.VanillaMinMembers.Value); int num2 = ((HeatTracker.CurrentTier >= HeatTier.Surge) ? Mathf.Max(num, ModConfig.HeatMaxMembers.Value) : Mathf.Max(num, ModConfig.VanillaMaxMembers.Value)); if (HeatTracker.IsNight() && HeatTracker.CurrentTier >= HeatTier.Heat) { num2 = Mathf.Max(num2, ModConfig.HeatMaxMembers.Value); } num2 = Mathf.Min(num2, 2); if (settings.Patrols != null) { PatrolInstance[] patrols = settings.Patrols; foreach (PatrolInstance val in patrols) { if (val != null) { val.MinMembers = num; val.MaxMembers = num2; } } } if (settings.Sentries != null) { SentryInstance[] sentries = settings.Sentries; foreach (SentryInstance val2 in sentries) { if (val2 != null) { val2.MinMembers = num; val2.MaxMembers = num2; } } } if (!ModConfig.ExtraCheckpointStaffing.Value || settings.Checkpoints == null) { return; } CheckpointInstance[] checkpoints = settings.Checkpoints; foreach (CheckpointInstance val3 in checkpoints) { if (val3 != null) { val3.MinMembers = num; val3.MaxMembers = num2; } } } private static void InjectExtras(LawActivitySettings settings) { //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_0226: Unknown result type (might be due to invalid IL or missing references) //IL_0233: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Expected O, but got Unknown //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0141: 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_0160: Expected O, but got Unknown RoutesConfig.EnsureLoaded(); GameObject obj = GameObject.Find("PatrolRoutes"); Transform val = ((obj != null) ? obj.transform : null) ?? EnsureRouteRoot("PoliceResponseOverhaulFootRoutes"); GameObject obj2 = GameObject.Find("VehiclePatrolRoutes"); Transform val2 = ((obj2 != null) ? obj2.transform : null) ?? EnsureRouteRoot("PoliceResponseOverhaulVehicleRoutes"); PatrolInstance[] patrols = settings.Patrols; VehiclePatrolInstance[] vehiclePatrols = settings.VehiclePatrols; SentryInstance[] sentries = settings.Sentries; if (ModConfig.ExtraFootPatrols.Value && (Object)(object)val != (Object)null) { if (ModConfig.CloneVanillaFootPatrols.Value && patrols != null) { CloneVanillaFootShifts(patrols, val); } foreach (SerializedFootPatrol extraFootPatrol in RoutesConfig.Current.ExtraFootPatrols) { FootPatrolRoute val3 = BuildFootRoute(val, extraFootPatrol); if (!((Object)(object)val3 == (Object)null)) { ExtraFoot.Add(new PatrolInstance { Route = val3, StartTime = extraFootPatrol.startTime, EndTime = extraFootPatrol.endTime, MinMembers = Mathf.Max(1, extraFootPatrol.minMembers), MaxMembers = Mathf.Min(2, Mathf.Max(extraFootPatrol.minMembers, extraFootPatrol.maxMembers)), IntensityRequirement = extraFootPatrol.intensityRequirement, OnlyIfCurfewEnabled = extraFootPatrol.onlyIfCurfew }); } } if (ExtraFoot.Count > 0) { settings.Patrols = ToNativeArray(Concat(patrols, ExtraFoot)); } } if (ModConfig.ExtraVehiclePatrols.Value && (Object)(object)val2 != (Object)null && vehiclePatrols != null) { foreach (SerializedVehiclePatrol extraVehiclePatrol in RoutesConfig.Current.ExtraVehiclePatrols) { VehiclePatrolRoute val4 = BuildVehicleRoute(val2, extraVehiclePatrol); if (!((Object)(object)val4 == (Object)null)) { ExtraVehicles.Add(new VehiclePatrolInstance { Route = val4, StartTime = extraVehiclePatrol.startTime, IntensityRequirement = extraVehiclePatrol.intensityRequirement, OnlyIfCurfewEnabled = extraVehiclePatrol.onlyIfCurfew }); } } if (ExtraVehicles.Count == 0 && vehiclePatrols.Length != 0) { CloneFirstVanillaVehicle(vehiclePatrols[0], val2); } if (ExtraVehicles.Count > 0) { settings.VehiclePatrols = ToNativeArray(Concat(vehiclePatrols, ExtraVehicles)); } } if (ModConfig.ExtraSentries.Value && sentries != null && sentries.Length != 0) { SentryInstance first = sentries[0]; foreach (SerializedSentry extraSentry in RoutesConfig.Current.ExtraSentries) { ExtraSentries.Add(CopySentry(first, extraSentry.startTime, extraSentry.endTime, extraSentry.intensityRequirement, extraSentry.minMembers, extraSentry.maxMembers)); } if (ExtraSentries.Count == 0) { ExtraSentries.Add(CopySentry(first, 2100, 500, 0, 1, 2)); } if (ExtraSentries.Count > 0) { settings.Sentries = ToNativeArray(Concat(sentries, ExtraSentries)); } } _injected = true; int num = ExtraFoot.Count + ExtraVehicles.Count + ExtraSentries.Count; PatrolInstance[] patrols2 = settings.Patrols; int num2 = ((patrols2 != null) ? patrols2.Length : 0); MelonLogger.Msg($"Ambient extras injected. Foot={ExtraFoot.Count} Vehicle={ExtraVehicles.Count} Sentry={ExtraSentries.Count} PatrolsList={num2}"); if (num > 0) { MelonCoroutines.Start(StaffThenStart()); } } private static IEnumerator StaffThenStart() { yield return OfficerPool.DeployExtraFoot(ExtraFoot); if (ExtraFoot.Count > 0 && OfficerPool.AmbientExtraCount == 0) { yield return (object)new WaitForSeconds(45f); if (OfficerPool.AmbientExtraCount == 0) { yield return OfficerPool.DeployExtraFoot(ExtraFoot); } } } private static void CloneVanillaFootShifts(PatrolInstance[] vanilla, Transform patrolRoot) { //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_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Expected O, but got Unknown foreach (PatrolInstance val in vanilla) { if (!((Object)(object)val?.Route == (Object)null) && val.Route.Waypoints != null && val.Route.Waypoints.Length != 0) { FootPatrolRoute val2 = DuplicateFootRoute(patrolRoot, val.Route); if (!((Object)(object)val2 == (Object)null)) { ExtraFoot.Add(new PatrolInstance { Route = val2, StartTime = val.StartTime, EndTime = val.EndTime, MinMembers = 1, MaxMembers = 2, IntensityRequirement = 0, OnlyIfCurfewEnabled = false }); } } } } private static void CloneFirstVanillaVehicle(VehiclePatrolInstance source, Transform vehicleRoot) { //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_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown if (!((Object)(object)source?.Route == (Object)null)) { VehiclePatrolRoute val = DuplicateVehicleRoute(vehicleRoot, source.Route); if (!((Object)(object)val == (Object)null)) { ExtraVehicles.Add(new VehiclePatrolInstance { Route = val, StartTime = source.StartTime, IntensityRequirement = 0, OnlyIfCurfewEnabled = false }); } } } private static SentryInstance CopySentry(SentryInstance first, int start, int end, int intensity, int minMembers, int maxMembers) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0029: 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_0037: 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_004c: Expected O, but got Unknown SentryInstance val = new SentryInstance { MinMembers = Mathf.Max(1, minMembers), MaxMembers = Mathf.Min(2, Mathf.Max(minMembers, maxMembers)), StartTime = start, EndTime = end, IntensityRequirement = Mathf.Max(0, intensity), OnlyIfCurfewEnabled = false }; val._potentialLocations = first._potentialLocations; return val; } private static FootPatrolRoute DuplicateFootRoute(Transform parent, FootPatrolRoute source) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Expected O, but got Unknown //IL_00d2: 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) try { GameObject val = new GameObject(source.RouteName + " (extra)"); val.transform.SetParent(parent); FootPatrolRoute val2 = val.AddComponent(); val2.RouteName = source.RouteName + " (extra)"; Transform[] waypoints = source.Waypoints; val2.StartWaypointIndex = ((waypoints.Length > 1) ? ((source.StartWaypointIndex + waypoints.Length / 2) % waypoints.Length) : source.StartWaypointIndex); Transform[] array = (Transform[])(object)new Transform[waypoints.Length]; for (int i = 0; i < waypoints.Length; i++) { Transform val3 = waypoints[i]; GameObject val4 = new GameObject((i == 0) ? "Waypoint" : $"Waypoint ({i})"); val4.transform.SetParent(val.transform); val4.transform.position = (((Object)(object)val3 != (Object)null) ? val3.position : Vector3.zero); array[i] = val4.transform; } SetWaypoints(val2, array); try { val2.UpdateWaypoints(); } catch { } return val2; } catch (Exception ex) { if (ModConfig.DebugLogging.Value) { MelonLogger.Warning("Failed to duplicate foot route: " + ex.Message); } return null; } } private static VehiclePatrolRoute DuplicateVehicleRoute(Transform parent, VehiclePatrolRoute source) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Expected O, but got Unknown //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) try { if (source.Waypoints == null || source.Waypoints.Length == 0) { return null; } GameObject val = new GameObject(source.RouteName + " (extra)"); val.transform.SetParent(parent); VehiclePatrolRoute val2 = val.AddComponent(); val2.RouteName = source.RouteName + " (extra)"; val2.StartWaypointIndex = source.StartWaypointIndex; Transform[] waypoints = source.Waypoints; Transform[] array = (Transform[])(object)new Transform[waypoints.Length]; for (int i = 0; i < waypoints.Length; i++) { Transform val3 = waypoints[i]; GameObject val4 = new GameObject((i == 0) ? "Waypoint" : $"Waypoint ({i})"); val4.transform.SetParent(val.transform); val4.transform.position = (((Object)(object)val3 != (Object)null) ? val3.position : Vector3.zero); array[i] = val4.transform; } SetWaypoints(val2, array); return val2; } catch (Exception ex) { if (ModConfig.DebugLogging.Value) { MelonLogger.Warning("Failed to duplicate vehicle route: " + ex.Message); } return null; } } private static void ApplyHeatToExtras() { int num = ((HeatTracker.CurrentTier >= HeatTier.Heat) ? ModConfig.HeatMaxMembers.Value : ModConfig.VanillaMaxMembers.Value); foreach (PatrolInstance item in ExtraFoot) { if (item != null) { item.MaxMembers = Mathf.Min(2, Mathf.Max(item.MinMembers, num)); } } } private static void ScaleIntensity() { if (!ModConfig.ScaleLawIntensity.Value) { return; } int stars = HeatTracker.Stars; if (1 == 0) { } float num = stars switch { 5 => 3f, 4 => 2.5f, 3 => 2f, 2 => 1.5f, _ => 1f, }; if (1 == 0) { } float num2 = num; try { LawController val = (Singleton.InstanceExists ? Singleton.Instance : Object.FindObjectOfType(true)); if ((Object)(object)val != (Object)null) { val.SetInternalIntensity(num2); val.LE_Intensity = Mathf.RoundToInt(num2); } int num3 = 1; int num4 = 2; GameAccess.SetStatic(typeof(LawManager), "OfficerDispatchMin", num3); GameAccess.SetStatic(typeof(LawManager), "OfficerDispatchMax", num4); } catch { } } private static Transform EnsureRouteRoot(string name) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) GameObject val = GameObject.Find(name); if ((Object)(object)val != (Object)null) { return val.transform; } return new GameObject(name).transform; } private static FootPatrolRoute BuildFootRoute(Transform parent, SerializedFootPatrol extra) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Expected O, but got Unknown //IL_00d7: Unknown result type (might be due to invalid IL or missing references) if (extra.waypoints == null || extra.waypoints.Count == 0) { return null; } GameObject val = new GameObject(extra.name); val.transform.SetParent(parent); val.transform.position = RoutesConfig.ToVector(extra.waypoints[0]); FootPatrolRoute val2 = val.AddComponent(); val2.RouteName = extra.name; val2.StartWaypointIndex = 0; Transform[] array = (Transform[])(object)new Transform[extra.waypoints.Count]; for (int i = 0; i < extra.waypoints.Count; i++) { GameObject val3 = new GameObject((i == 0) ? "Waypoint" : $"Waypoint ({i})"); val3.transform.SetParent(val.transform); val3.transform.position = RoutesConfig.ToVector(extra.waypoints[i]); array[i] = val3.transform; } SetWaypoints(val2, array); try { val2.UpdateWaypoints(); } catch { } return val2; } private static VehiclePatrolRoute BuildVehicleRoute(Transform parent, SerializedVehiclePatrol extra) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Expected O, but got Unknown //IL_00ba: Unknown result type (might be due to invalid IL or missing references) if (extra.waypoints == null || extra.waypoints.Count == 0) { return null; } GameObject val = new GameObject(extra.name); val.transform.SetParent(parent); VehiclePatrolRoute val2 = val.AddComponent(); val2.RouteName = extra.name; val2.StartWaypointIndex = 0; Transform[] array = (Transform[])(object)new Transform[extra.waypoints.Count]; for (int i = 0; i < extra.waypoints.Count; i++) { GameObject val3 = new GameObject((i == 0) ? "Waypoint" : $"Waypoint ({i})"); val3.transform.SetParent(val.transform); val3.transform.position = RoutesConfig.ToVector(extra.waypoints[i]); array[i] = val3.transform; } SetWaypoints(val2, array); return val2; } private static int ShiftTime(int time, int add) { int num = time / 100; int num2 = time % 100; int num3 = (num * 60 + num2 + add) % 1440; if (num3 < 0) { num3 += 1440; } return num3 / 60 * 100 + num3 % 60; } private static void SetWaypoints(FootPatrolRoute route, Transform[] points) { route.Waypoints = points; } private static void SetWaypoints(VehiclePatrolRoute route, Transform[] points) { route.Waypoints = points; } private static T[] Concat(T[] original, List extra) { int num = ((original != null) ? original.Length : 0); T[] array = new T[num + extra.Count]; if (num > 0) { Array.Copy(original, array, num); } extra.CopyTo(array, num); return array; } private static T[] ToNativeArray(T[] items) { return items; } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }