Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of PoliceResponseOverhaul Mono v1.1.4
PoliceResponseOverhaul-Mono.dll
Decompiled 7 hours ago
The result has been truncated due to the large size, download it to view full contents!
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<PoliceChatterVO>(true); if (val != null) { val.PlayChatter(); } } catch { } } private static void TryHardCurfew() { try { CurfewManager val = Object.FindObjectOfType<CurfewManager>(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<int> Live = new HashSet<int>(); 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<PendingResponse> Queue = new Queue<PendingResponse>(); private static readonly List<PoliceOfficer> ActiveCrew = new List<PoliceOfficer>(); private static readonly List<LandVehicle> ActiveVehicles = new List<LandVehicle>(); private static readonly Dictionary<int, int> VehicleChaseSlot = new Dictionary<int, int>(); private static readonly Dictionary<int, float> CorpseSince = new Dictionary<int, float>(); 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<int, float> VehicleDeployedAt = new Dictionary<int, float>(); private static readonly Dictionary<int, HashSet<int>> VehicleCrewIds = new Dictionary<int, HashSet<int>>(); private static readonly HashSet<int> LocalSupport = new HashSet<int>(); 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<PoliceOfficer> crew) { if ((Object)(object)vehicle == (Object)null || crew == null || crew.Count == 0) { return; } HashSet<int> hashSet = new HashSet<int>(); 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<PoliceOfficer> crew = new List<PoliceOfficer>(); 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<PoliceOfficer> 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<PoliceOfficer>() ?? ((Component)pursuit).GetComponentInParent<PoliceOfficer>(); 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<float>(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<NetworkObject>() : ((Component)officer).GetComponent<NetworkObject>()); } 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<Collider>()); ((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<MeshRenderer>(); 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<MeshRenderer>(); 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<CombatBehaviour>() ?? ((Component)officer).GetComponentInChildren<CombatBehaviour>(); 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<PursuitBehaviour>() ?? ((Component)officer).GetComponentInChildren<PursuitBehaviour>(); 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<AvatarRangedWeapon>(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<PoliceOfficer>(); if (Utils.Alive((Object)(object)component)) { return component; } } catch { return null; } try { PoliceOfficer componentInParent = ((Component)combat).GetComponentInParent<PoliceOfficer>(); 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<float> 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<AvatarWeapon>(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<Rigidbody>(); 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<Animator>(); 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<LandVehicle> list = new List<LandVehicle>(); 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<Rigidbody>(); 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<Rigidbody>(); 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<Behaviour>(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<int> Leased = new HashSet<int>(); private static readonly HashSet<int> PooledClones = new HashSet<int>(); private static readonly HashSet<IntPtr> DeadNative = new HashSet<IntPtr>(); private static readonly HashSet<int> ProDeployedVehicles = new HashSet<int>(); private static readonly List<PoliceOfficer> AmbientExtras = new List<PoliceOfficer>(); 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<PoliceOfficer> OfficerSnap = new List<PoliceOfficer>(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<NPCHealth>(); 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 o