Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of CoordinatedPolice v0.8.1
Mods/CoordinatedPolice.dll
Decompiled 2 weeks ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Diagnostics; using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using CoordinatedPolice; using HarmonyLib; using Il2CppFishNet; using Il2CppFishNet.Connection; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppScheduleOne.AvatarFramework.Equipping; using Il2CppScheduleOne.Combat; using Il2CppScheduleOne.DevUtilities; using Il2CppScheduleOne.GameTime; using Il2CppScheduleOne.Law; using Il2CppScheduleOne.Map; using Il2CppScheduleOne.NPCs; using Il2CppScheduleOne.NPCs.Behaviour; using Il2CppScheduleOne.Persistence; using Il2CppScheduleOne.PlayerScripts; using Il2CppScheduleOne.Police; using Il2CppSystem; using Il2CppSystem.Reflection; using MelonLoader; using MelonLoader.Preferences; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.AI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Main), "Coordinated Police", "0.8.1", "holyfurries", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("CoordinatedPolice")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.8.1.0")] [assembly: AssemblyInformationalVersion("0.8.1+4ce08d5f863ae500429d0387c630cf8e90c5a8bc")] [assembly: AssemblyProduct("CoordinatedPolice")] [assembly: AssemblyTitle("CoordinatedPolice")] [assembly: AssemblyVersion("0.8.1.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace CoordinatedPolice { internal static class ConeCop { private static GameObject? hat; private static Mesh? mesh; private static Material? orange; private static Material? white; private static float next_seconds; private static bool failed; public static void reset() { if ((Object)(object)hat != (Object)null) { Object.Destroy((Object)(object)hat); } if ((Object)(object)mesh != (Object)null) { Object.Destroy((Object)(object)mesh); } if ((Object)(object)orange != (Object)null) { Object.Destroy((Object)(object)orange); } if ((Object)(object)white != (Object)null) { Object.Destroy((Object)(object)white); } hat = null; mesh = null; orange = null; white = null; next_seconds = 0f; failed = false; } public static void tick() { //IL_017d: 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_01a5: Unknown result type (might be due to invalid IL or missing references) if (failed || Time.unscaledTime < next_seconds || !Singleton<LoadManager>.InstanceExists || !Singleton<LoadManager>.Instance.IsGameLoaded || !NetworkSingleton<TimeManager>.InstanceExists) { return; } next_seconds = Time.unscaledTime + 5f; try { PoliceOfficer val = null; uint num = uint.MaxValue; int num2 = Math.Min(128, PoliceOfficer.Officers.Count); for (int i = 0; i < num2; i++) { PoliceOfficer val2 = PoliceOfficer.Officers[i]; if (!((Object)(object)val2 == (Object)null) && !string.IsNullOrEmpty(((NPC)val2).ID)) { uint num3 = DailyChance.score(((NPC)val2).ID, NetworkSingleton<TimeManager>.Instance.ElapsedDays); if (num3 % 256 == 0 && num3 < num) { num = num3; val = val2; } } } if ((Object)(object)val == (Object)null || (Object)(object)((NPC)val).Avatar == (Object)null || (Object)(object)((NPC)val).Avatar.HeadBone == (Object)null) { if ((Object)(object)hat != (Object)null) { hat.SetActive(false); } return; } if ((Object)(object)hat == (Object)null) { create(); } Transform headBone = ((NPC)val).Avatar.HeadBone; if ((Object)(object)hat.transform.parent != (Object)(object)headBone) { hat.layer = ((Component)((NPC)val).Avatar).gameObject.layer; hat.transform.SetParent(headBone, false); hat.transform.localPosition = new Vector3(0f, 0.12f, 0f); hat.transform.localRotation = Quaternion.identity; hat.transform.localScale = Vector3.one; MelonLogger.Msg("Police: An officer has questionable headwear today."); } hat.SetActive(true); } catch (Exception ex) { reset(); failed = true; MelonLogger.Warning("Police: Cone cosmetic disabled for this scene: " + ex.Message); } } private static void create() { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_0068: 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_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_0207: Unknown result type (might be due to invalid IL or missing references) //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_0244: Unknown result type (might be due to invalid IL or missing references) //IL_025b: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_027f: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Unknown result type (might be due to invalid IL or missing references) //IL_0290: Unknown result type (might be due to invalid IL or missing references) //IL_029c: Expected O, but got Unknown //IL_02d7: Unknown result type (might be due to invalid IL or missing references) //IL_02e1: Expected O, but got Unknown //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) Shader obj = Shader.Find("Universal Render Pipeline/Unlit") ?? Shader.Find("Unlit/Color") ?? Shader.Find("Standard"); if ((Object)(object)obj == (Object)null) { throw new InvalidOperationException("No cone material shader available."); } orange = new Material(obj); white = new Material(obj); orange.color = new Color(1f, 0.3f, 0.03f, 1f); white.color = Color.white; if (orange.HasProperty("_BaseColor")) { orange.SetColor("_BaseColor", orange.color); } if (white.HasProperty("_BaseColor")) { white.SetColor("_BaseColor", white.color); } Vector3[] array = (Vector3[])(object)new Vector3[68]; int[] array2 = new int[198]; int[] array3 = new int[96]; int num = 0; for (int i = 0; i < 4; i++) { float num2 = (float)i * 0.12f; float num3 = 0.15f * (1f - (float)i / 3f) + 0.012f; for (int j = 0; j < 16; j++) { float x = (float)j * (float)Math.PI / 8f; array[i * 16 + j] = new Vector3(MathF.Cos(x) * num3, num2, MathF.Sin(x) * num3); if (i != 3) { int num4 = i * 16 + j; int num5 = i * 16 + (j + 1) % 16; int[] obj2 = ((i == 1) ? array3 : array2); int num6 = ((i == 1) ? (j * 6) : num); obj2[num6] = num4; obj2[num6 + 1] = num4 + 16; obj2[num6 + 2] = num5; obj2[num6 + 3] = num5; obj2[num6 + 4] = num4 + 16; obj2[num6 + 5] = num5 + 16; if (i != 1) { num += 6; } } } } array[64] = new Vector3(-0.2f, 0f, -0.2f); array[65] = new Vector3(-0.2f, 0f, 0.2f); array[66] = new Vector3(0.2f, 0f, 0.2f); array[67] = new Vector3(0.2f, 0f, -0.2f); Array.Copy(new int[6] { 64, 65, 66, 64, 66, 67 }, 0, array2, num, 6); mesh = new Mesh { vertices = Il2CppStructArray<Vector3>.op_Implicit(array), subMeshCount = 2 }; mesh.SetTriangles(Il2CppStructArray<int>.op_Implicit(array2), 0); mesh.SetTriangles(Il2CppStructArray<int>.op_Implicit(array3), 1); mesh.RecalculateNormals(); mesh.RecalculateBounds(); hat = new GameObject("CoordinatedPolice.Cone"); hat.AddComponent<MeshFilter>().sharedMesh = mesh; ((Renderer)hat.AddComponent<MeshRenderer>()).sharedMaterials = Il2CppReferenceArray<Material>.op_Implicit((Material[])(object)new Material[2] { orange, white }); } } internal static class DailyChance { public static uint score(string identity, int day) { if (identity.Length > 128 || day < 0) { return uint.MaxValue; } uint num = 2166136261u; foreach (char c in identity) { num = (num ^ c) * 16777619; } num = (num ^ (uint)day) * 16777619; num ^= num >> 16; num *= 2146121005; return num ^ (num >> 15); } } internal static class DistrictAllocation { public static void allocate(int total, ReadOnlySpan<bool> eligible, ReadOnlySpan<int> weights, Span<int> targets) { if (eligible.Length != 6 || weights.Length != 6 || targets.Length != 6) { throw new ArgumentException("Exactly six districts are required."); } if (total < 0 || total > 64) { throw new ArgumentOutOfRangeException("total"); } targets.Clear(); int num = 0; for (int i = 0; i < 6; i++) { if (weights[i] < 0 || weights[i] > 10) { throw new ArgumentOutOfRangeException("weights"); } if (eligible[i]) { num += weights[i]; } } if (num == 0) { return; } Span<int> span = stackalloc int[6]; int num2 = 0; for (int j = 0; j < 6; j++) { int num3 = (eligible[j] ? (total * weights[j]) : 0); targets[j] = num3 / num; span[j] = num3 % num; num2 += targets[j]; } for (int k = num2; k < total; k++) { int num4 = -1; for (int l = 0; l < 6; l++) { if (eligible[l] && weights[l] > 0 && (num4 < 0 || span[l] > span[num4])) { num4 = l; } } if (num4 < 0) { throw new InvalidOperationException("No eligible district for remaining patrol allocation."); } targets[num4]++; span[num4] = -1; } } public static int choose_route(ReadOnlySpan<int> districts, ReadOnlySpan<int> members, ReadOnlySpan<bool> available, ReadOnlySpan<int> actual, ReadOnlySpan<int> targets, int cursor) { if (districts.Length > 64 || districts.Length != members.Length || districts.Length != available.Length || actual.Length != 6 || targets.Length != 6) { throw new ArgumentException("Invalid district route data."); } int num = -1; int num2 = 0; for (int i = 0; i < districts.Length; i++) { int num3 = (Math.Max(cursor, 0) % districts.Length + i) % districts.Length; int num4 = districts[num3]; if (available[num3] && num4 >= 0 && num4 < 6) { int num5 = targets[num4] - actual[num4]; if (num5 > 0 && (num < 0 || (num5 >= num2 && (num5 != num2 || members[num3] < members[num])))) { num = num3; num2 = num5; } } } return num; } } internal readonly record struct PursuitAssignment(OfficerRole role, Vector2 offset, bool redirect); internal static class Interception { public static PursuitAssignment assign(ReadOnlySpan<Vector2> offsets, int index, Vector2 velocity) { if (offsets.Length > 128 || index < 0 || index >= offsets.Length) { throw new ArgumentOutOfRangeException("index"); } Vector2 vector = offsets[index]; float num = vector.LengthSquared(); if (!float.IsFinite(num)) { return new PursuitAssignment(OfficerRole.Chaser, default(Vector2), redirect: false); } int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; float num6 = velocity.Length(); int num7; Vector2 vector2; if (float.IsFinite(num6) && num6 >= 0.75f) { num7 = ((num6 <= 12f) ? 1 : 0); if (num7 != 0) { vector2 = velocity / num6; goto IL_009d; } } else { num7 = 0; } vector2 = Vector2.UnitX; goto IL_009d; IL_009d: Vector2 vector3 = vector2; Vector2 vector4 = new Vector2(vector3.Y, 0f - vector3.X); float num8 = Vector2.Dot(vector, vector4); bool flag = num7 != 0 && eligible(vector, vector3, vector4); int num9 = approach_sector(vector); for (int i = 0; i < offsets.Length; i++) { float num10 = offsets[i].LengthSquared(); if (!float.IsFinite(num10)) { continue; } num2++; if (num10 < num || (num10 == num && i < index)) { num3++; if (approach_sector(offsets[i]) == num9 && num10 >= 144f) { num4++; } if (flag && eligible(offsets[i], vector3, vector4) && Vector2.Dot(offsets[i], vector4) < 0f == num8 < 0f) { num5++; } } } if (num3 < ((num2 < 5) ? 1 : 2)) { return new PursuitAssignment(OfficerRole.Chaser, default(Vector2), redirect: false); } if (num < 144f) { return new PursuitAssignment(OfficerRole.Support, default(Vector2), redirect: false); } if (flag && num5 < 4) { float num11 = Math.Clamp(num6 * 1.5f, 3f, 10f); Vector2 offset = vector3 * num11 + vector4 * ((num8 < 0f) ? (-1f) : 1f) * (4f + (float)num5 * 3f); return new PursuitAssignment(OfficerRole.Interceptor, offset, redirect: true); } if (num4 >= 16) { return new PursuitAssignment(OfficerRole.Support, default(Vector2), redirect: false); } float x = (float)num9 * (float)Math.PI / 4f + (float)(num4 % 3 - 1) * (float)Math.PI / 12f; float num12 = 6f + (float)(num4 / 3) * 3f; Vector2 offset2 = new Vector2(MathF.Cos(x), MathF.Sin(x)) * num12; return new PursuitAssignment(OfficerRole.Support, offset2, MathF.Sqrt(num) > num12 + 3f); } private static bool eligible(Vector2 relative, Vector2 direction, Vector2 side) { if (relative.LengthSquared() < 144f) { return false; } float num = Vector2.Dot(relative, direction); if (!(num >= 0f)) { if (num >= -4f) { return Math.Abs(Vector2.Dot(relative, side)) >= 6f; } return false; } return true; } private static int approach_sector(Vector2 relative) { return ((int)MathF.Round(MathF.Atan2(relative.Y, relative.X) * 4f / (float)Math.PI) + 8) % 8; } } public sealed class Main : MelonMod { private struct SearchCache { public string? player_code; public int slot; public Vector3 origin; public Vector3 destination; public float expires_seconds; public bool valid; } private struct ResponseLog { public string? player_code; public ResponsePhase phase; public ResponseSeverity severity; public int active_limit; public float next_seconds; } private const int player_limit = 16; private const int officer_limit = 128; private static readonly ResponseState[] responses = new ResponseState[16]; private static readonly PoliceOfficer?[] officers = (PoliceOfficer?[])(object)new PoliceOfficer[128]; private static readonly float[] redirects_resume_seconds = new float[128]; private static readonly float[] intercept_seconds = new float[128]; private static readonly Vector3[] intercept_origins = (Vector3[])(object)new Vector3[128]; private static readonly Vector3[] intercept_positions = (Vector3[])(object)new Vector3[128]; private static readonly OfficerRole[] pursuit_roles = new OfficerRole[128]; private static readonly bool[] intercept_valid = new bool[128]; private static readonly string?[] intercept_targets = new string[128]; private static readonly StuckRecovery[] recoveries = new StuckRecovery[128]; private static readonly SearchCache[] searches = new SearchCache[128]; private static readonly ResponseLog[] response_logs = new ResponseLog[16]; private bool? logged_server; private int logged_players = -1; private static int officer_count; private static bool running; private float next_tick; private static int search_queries_count; private static float search_queries_reset_seconds; internal static bool authoritative { get { if (running) { return InstanceFinder.IsServer; } return false; } } public override void OnInitializeMelon() { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Expected O, but got Unknown //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Expected O, but got Unknown //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Expected O, but got Unknown PoliceConfiguration.load(); for (int i = 0; i < responses.Length; i++) { responses[i] = new ResponseState(); } for (int j = 0; j < recoveries.Length; j++) { recoveries[j] = new StuckRecovery(); } ((MelonBase)this).HarmonyInstance.Patch((MethodBase)AccessTools.Method(typeof(NPCMovement), "SetDestination", new Type[1] { typeof(Vector3) }, (Type[])null), new HarmonyMethod(typeof(Main), "redirect_destination", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((MelonBase)this).HarmonyInstance.Patch((MethodBase)AccessTools.Method(typeof(PoliceStation), "Dispatch", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Main), "coordinate_dispatch", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((MelonBase)this).HarmonyInstance.Patch((MethodBase)AccessTools.Method(typeof(CombatBehaviour), "GetNextSearchLocation", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Main), "redirect_search", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); PoliceTactics.install(((MelonBase)this).HarmonyInstance); } public override void OnSceneWasInitialized(int buildIndex, string sceneName) { if (sceneName != "Main") { return; } running = true; foreach (MelonMod registeredMelon in MelonTypeBase<MelonMod>.RegisteredMelons) { if (((MelonBase)registeredMelon).Info.Name.Contains("PoliceResponseOverhaul", StringComparison.OrdinalIgnoreCase) || ((MelonBase)registeredMelon).Info.Name.Contains("Police Response Overhaul", StringComparison.OrdinalIgnoreCase) || ((MelonBase)registeredMelon).Info.Name.Contains("HardcorePolice", StringComparison.OrdinalIgnoreCase)) { running = false; ((MelonBase)this).LoggerInstance.Error("Police: Disable other police overhauls before testing Coordinated Police."); } } } public override void OnSceneWasUnloaded(int buildIndex, string sceneName) { if (!(sceneName != "Main")) { running = false; officer_count = 0; logged_server = null; logged_players = -1; Array.Clear(response_logs, 0, response_logs.Length); ConeCop.reset(); PoliceTactics.reset(); PolicePopulation.reset(); PoliceNavigation.reset(); Array.Clear(redirects_resume_seconds, 0, redirects_resume_seconds.Length); Array.Clear(officers, 0, officers.Length); Array.Clear(intercept_seconds, 0, intercept_seconds.Length); Array.Clear(intercept_targets, 0, intercept_targets.Length); Array.Clear(searches, 0, searches.Length); ResponseState[] array = responses; for (int i = 0; i < array.Length; i++) { array[i].reset(); } StuckRecovery[] array2 = recoveries; for (int i = 0; i < array2.Length; i++) { array2[i].reset(); } next_tick = 0f; search_queries_count = 0; search_queries_reset_seconds = 0f; } } public override void OnUpdate() { //IL_0268: Unknown result type (might be due to invalid IL or missing references) //IL_026e: Invalid comparison between Unknown and I4 if (!running || Time.time < next_tick) { return; } next_tick = Time.time + 1f; try { bool isServer = InstanceFinder.IsServer; int count = Player.PlayerList.Count; if (logged_server != isServer || logged_players != count) { ((MelonBase)this).LoggerInstance.Msg($"Police: Network authority={(isServer ? "host" : "client/waiting")}, players={count}, scaling_players={Math.Clamp(count, 1, 4)}. {(isServer ? "Response decisions run here." : "Response decisions require the host; this peer does not dispatch police.")}"); logged_server = isServer; logged_players = count; } ConeCop.tick(); PolicePopulation.tick(); if (!isServer) { return; } officer_count = Math.Min(PoliceOfficer.Officers.Count, 128); for (int i = 0; i < officer_count; i++) { PoliceOfficer val = PoliceOfficer.Officers[i]; if ((Object)(object)officers[i] != (Object)(object)val) { redirects_resume_seconds[i] = 0f; intercept_seconds[i] = 0f; intercept_valid[i] = false; recoveries[i].reset(); searches[i] = default(SearchCache); } officers[i] = val; } recover_stuck_officers(); int num = Math.Min(Player.PlayerList.Count, 16); for (int j = 0; j < responses.Length; j++) { bool flag = false; for (int k = 0; k < num; k++) { Player val2 = Player.PlayerList[k]; if ((Object)(object)val2 != (Object)null && val2.PlayerCode == responses[j].player_code) { flag = true; } } if (!flag) { responses[j].reset(); } } for (int l = 0; l < num; l++) { Player val3 = Player.PlayerList[l]; if (!((Object)(object)val3 == (Object)null)) { bool create = (Object)(object)val3.CrimeData != (Object)null && (int)val3.CrimeData.CurrentPursuitLevel > 0; ResponseState responseState = get_response(val3.PlayerCode, create); if (responseState != null) { update_response(val3, responseState); } } } log_responses(); } catch (Exception value) { running = false; ((MelonBase)this).LoggerInstance.Error($"Police: Disabled after a game API failure: {value}"); } } private static void log_responses() { //IL_00e0: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < responses.Length; i++) { ResponseState responseState = responses[i]; ref ResponseLog reference = ref response_logs[i]; if (reference.player_code != responseState.player_code && !string.IsNullOrEmpty(reference.player_code)) { MelonLogger.Msg("Police: Response ended player=" + reference.player_code + "."); } if (responseState.phase == ResponsePhase.Inactive) { reference = default(ResponseLog); } else { if (!(reference.player_code != responseState.player_code) && reference.phase == responseState.phase && reference.severity == responseState.severity && reference.active_limit == responseState.rules.active_limit && Time.time < reference.next_seconds) { continue; } Vector3 last_known_position = responseState.last_known_position; PoliceStation closestPoliceStation = PoliceStation.GetClosestPoliceStation(new Vector3(last_known_position.X, last_known_position.Y, last_known_position.Z)); int value = ((!((Object)(object)closestPoliceStation == (Object)null)) ? closestPoliceStation.OfficerPool.Count : 0); int value2 = 0; bool value3 = false; int num = Math.Min(Player.PlayerList.Count, 16); for (int j = 0; j < num; j++) { Player val = Player.PlayerList[j]; if (!((Object)(object)val == (Object)null) && !(val.PlayerCode != responseState.player_code)) { (value2, value3, _, _) = read_pursuit(val); break; } } MelonLogger.Msg($"Police: Response player={responseState.player_code}, tier={responseState.severity}, phase={responseState.phase}, visible={value3}, active={value2}/{responseState.rules.active_limit}, allowance_used={responseState.dispatch_count}/{responseState.rules.dispatch_limit}, next_call_seconds={Math.Max(0f, responseState.dispatch_seconds - Time.time):F1}, station_pool={value}, search_wave_eligible={responseState.can_send_search_wave(Time.time)}."); reference = new ResponseLog { player_code = responseState.player_code, phase = responseState.phase, severity = responseState.severity, active_limit = responseState.rules.active_limit, next_seconds = Time.time + 10f }; } } } private static void recover_stuck_officers() { //IL_0075: 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_007d: 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) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) //IL_02cc: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: 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_0104: 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_010b: Unknown result type (might be due to invalid IL or missing references) int num = 0; for (int i = 0; i < officer_count; i++) { PoliceOfficer val = officers[i]; if (!available(val) || ((!((Behaviour)val.PursuitBehaviour).Active || !((Object)(object)val.PursuitBehaviour.TargetPlayer != (Object)null)) && (!((Object)(object)val.FootPatrolBehaviour != (Object)null) || !((Behaviour)val.FootPatrolBehaviour).Active))) { recoveries[i].reset(); continue; } NPCMovement movement = ((NPC)val).Movement; NavMeshAgent agent = movement.Agent; Vector3 footPosition = movement.FootPosition; Vector3 val2 = movement.CurrentDestination; if ((Object)(object)agent != (Object)null && ((Behaviour)agent).enabled && agent.isOnNavMesh && agent.pathPending) { continue; } int num2; if (movement.HasDestination && !movement.IsPaused && !movement.IsOnLadder && !movement.Disoriented && movement.CanMove() && (Object)(object)agent != (Object)null && ((Behaviour)agent).enabled && agent.isOnNavMesh && !agent.isStopped && !agent.isOnOffMeshLink) { Vector3 val3 = val2 - footPosition; num2 = ((((Vector3)(ref val3)).sqrMagnitude > 4f) ? 1 : 0); } else { num2 = 0; } bool flag = (byte)num2 != 0; if ((num >= 2 && flag) || !recoveries[i].try_repath(Time.time, new Vector3(footPosition.x, footPosition.y, footPosition.z), flag)) { continue; } num++; redirects_resume_seconds[i] = Time.time + 12f; intercept_seconds[i] = 0f; intercept_valid[i] = false; searches[i] = default(SearchCache); PursuitBehaviour pursuitBehaviour = val.PursuitBehaviour; if (((Behaviour)pursuitBehaviour).Active && (Object)(object)pursuitBehaviour.TargetPlayer != (Object)null) { if (((CombatBehaviour)pursuitBehaviour).IsTargetImmediatelyVisible) { val2 = ((Component)pursuitBehaviour.TargetPlayer).transform.position; } else { ResponseState responseState = get_response(pursuitBehaviour.TargetPlayer.PlayerCode, create: false); if (responseState != null && responseState.phase != ResponsePhase.Inactive) { Vector3 last_known_position = responseState.last_known_position; ((Vector3)(ref val2))..ctor(last_known_position.X, last_known_position.Y, last_known_position.Z); } } } if (PoliceNavigation.try_destination(movement, val2, out var destination)) { movement.SetDestination(destination); MelonLogger.Msg($"Police: Navigation recovery requested officer={((NPC)val).ID}, duty={(((Behaviour)pursuitBehaviour).Active ? "pursuit" : "patrol")}, custom_offsets_paused_seconds=12."); } else { MelonLogger.Warning($"Police: Navigation recovery has no complete safe path officer={((NPC)val).ID}, path_status={agent.pathStatus}; custom offsets paused for 12 seconds."); } } } private static void update_response(Player player, ResponseState response) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_0264: 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_0188: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player.CrimeData == (Object)null || !player.IsCurrentlyTargetable || player.IsArrested || (Object)(object)player.Health == (Object)null || !player.Health.IsAlive) { response.reset(); return; } if ((int)player.CrimeData.CurrentPursuitLevel == 0) { if (response.phase != ResponsePhase.Responding || !(Time.time - response.started_seconds < 30f)) { response.reset(); } return; } (int, bool, bool, Vector3) snapshot = read_pursuit(player); observe_response(player, response, snapshot); if ((!snapshot.Item2 && !response.can_send_search_wave(Time.time)) || Time.time < response.dispatch_seconds || response.dispatch_count >= response.rules.dispatch_limit) { return; } if (!snapshot.Item2) { Vector3 last_known_position = response.last_known_position; PoliceStation closestPoliceStation = PoliceStation.GetClosestPoliceStation(new Vector3(last_known_position.X, last_known_position.Y, last_known_position.Z)); if ((Object)(object)closestPoliceStation != (Object)null && closestPoliceStation.OfficerPool.Count > 0) { closestPoliceStation.Dispatch(response.rules.burst_limit, player, (EDispatchType)2, false); } return; } PoliceOfficer val = null; float num = 3600f; for (int i = 0; i < officer_count; i++) { PoliceOfficer val2 = officers[i]; if (available(val2) && !((Behaviour)val2.PursuitBehaviour).Active && (!((Object)(object)val2.VehiclePursuitBehaviour != (Object)null) || !((Behaviour)val2.VehiclePursuitBehaviour).Active) && !((Object)(object)val2.PursuitTarget != (Object)null)) { Vector3 val3 = ((Component)val2).transform.position - snapshot.Item4; float sqrMagnitude = ((Vector3)(ref val3)).sqrMagnitude; if (!(sqrMagnitude >= num)) { val = val2; num = sqrMagnitude; } } } if ((Object)(object)val != (Object)null) { if (response.request_dispatch(Time.time, snapshot.Item1, 1, DispatchSource.Nearby) > 0) { val.BeginFootPursuit_Networked(player.PlayerCode, true); MelonLogger.Msg($"Police: Nearby pursuit requested player={player.PlayerCode}, officers=1, allowance_used={response.dispatch_count}/{response.rules.dispatch_limit}."); } } else { PoliceStation closestPoliceStation2 = PoliceStation.GetClosestPoliceStation(snapshot.Item4); if ((Object)(object)closestPoliceStation2 != (Object)null && closestPoliceStation2.OfficerPool.Count > 0) { closestPoliceStation2.Dispatch(response.rules.burst_limit, player, (EDispatchType)0, true); } } } private static (int count, bool visible, bool searching, Vector3 witness) read_pursuit(Player player) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) int num = 0; bool item = false; bool item2 = false; Vector3 item3 = default(Vector3); int num2 = Math.Min(PoliceOfficer.Officers.Count, 128); for (int i = 0; i < num2; i++) { PoliceOfficer val = PoliceOfficer.Officers[i]; if ((Object)(object)val == (Object)null || !((Component)val).gameObject.activeInHierarchy || !((NPC)val).IsConscious) { continue; } PursuitBehaviour pursuitBehaviour = val.PursuitBehaviour; VehiclePursuitBehaviour vehiclePursuitBehaviour = val.VehiclePursuitBehaviour; bool flag = (Object)(object)pursuitBehaviour != (Object)null && ((Behaviour)pursuitBehaviour).Active && (Object)(object)pursuitBehaviour.TargetPlayer != (Object)null && pursuitBehaviour.TargetPlayer.PlayerCode == player.PlayerCode; bool flag2 = (Object)(object)vehiclePursuitBehaviour != (Object)null && ((Behaviour)vehiclePursuitBehaviour).Active && (Object)(object)vehiclePursuitBehaviour.Target != (Object)null && vehiclePursuitBehaviour.Target.PlayerCode == player.PlayerCode; if (flag || flag2) { num++; if (flag && ((CombatBehaviour)pursuitBehaviour).IsSearching) { item2 = true; } if ((flag && ((CombatBehaviour)pursuitBehaviour).IsTargetImmediatelyVisible) || (flag2 && vehiclePursuitBehaviour.IsTargetImmediatelyVisible)) { item = true; item3 = ((Component)val).transform.position; } } } return (count: num, visible: item, searching: item2, witness: item3); } private static void observe_response(Player player, ResponseState response, (int count, bool visible, bool searching, Vector3 witness) snapshot) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected I4, but got Unknown //IL_0043: 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_0048: 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_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) response.set_player_count(Math.Min(Player.PlayerList.Count, 16)); response.observe_level((int)player.CrimeData.CurrentPursuitLevel); Vector3 val = (snapshot.visible ? ((Component)player).transform.position : player.CrimeData.LastKnownPosition); response.observe(snapshot.visible, snapshot.searching, new Vector3(val.x, val.y, val.z), Time.time); } internal static ResponseState? get_response(string player_code, bool create) { if (string.IsNullOrEmpty(player_code)) { return null; } ResponseState responseState = null; for (int i = 0; i < responses.Length; i++) { ResponseState responseState2 = responses[i]; if (responseState2.player_code == player_code) { return responseState2; } if (responseState == null && responseState2.player_code.Length == 0) { responseState = responseState2; } } if (!create || responseState == null) { return null; } responseState.begin(player_code, Time.time); return responseState; } private static bool coordinate_dispatch(PoliceStation __instance, ref int __0, Player __1, ref EDispatchType __2, ref bool __3) { if (!running || !InstanceFinder.IsServer || (Object)(object)__1 == (Object)null || __0 <= 0) { return true; } try { if ((Object)(object)__1.CrimeData == (Object)null || __1.IsArrested || (Object)(object)__1.Health == (Object)null || !__1.Health.IsAlive) { return false; } ResponseState responseState = get_response(__1.PlayerCode, create: true); if (responseState == null) { return false; } (int, bool, bool, Vector3) snapshot = read_pursuit(__1); observe_response(__1, responseState, snapshot); if (responseState.phase == ResponsePhase.Searching) { __2 = (EDispatchType)2; __3 = false; } int requested_count = Math.Min(__0, __instance.OfficerPool.Count); __0 = responseState.request_dispatch(Time.time, snapshot.Item1, requested_count, DispatchSource.Station); if (__0 == 0) { return false; } if (__0 % 2 != 0) { __2 = (EDispatchType)2; } MelonLogger.Msg($"Police: Station dispatch approved player={__1.PlayerCode}, officers={__0}, type={__2}, sighted={__3}, phase={responseState.phase}, pool_before={__instance.OfficerPool.Count}, allowance_used={responseState.dispatch_count}/{responseState.rules.dispatch_limit}. Arrival is not confirmed."); return true; } catch (Exception value) { running = false; MelonLogger.Error($"Police: Disabled dispatch coordination after a game API failure: {value}"); return false; } } private static bool redirect_search(CombatBehaviour __instance, ref Vector3 __result) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_0188: 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_018f: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_03b5: Unknown result type (might be due to invalid IL or missing references) //IL_03ba: Unknown result type (might be due to invalid IL or missing references) //IL_027a: Unknown result type (might be due to invalid IL or missing references) //IL_037d: Unknown result type (might be due to invalid IL or missing references) //IL_037f: Unknown result type (might be due to invalid IL or missing references) //IL_0346: Unknown result type (might be due to invalid IL or missing references) //IL_034b: Unknown result type (might be due to invalid IL or missing references) //IL_034d: Unknown result type (might be due to invalid IL or missing references) //IL_0352: Unknown result type (might be due to invalid IL or missing references) if (!running || !InstanceFinder.IsServer) { return true; } try { PursuitBehaviour val = ((Il2CppObjectBase)__instance).TryCast<PursuitBehaviour>(); if ((Object)(object)val == (Object)null || !((Behaviour)val).Active) { return true; } Player targetPlayer = val.TargetPlayer; if ((Object)(object)targetPlayer == (Object)null || (Object)(object)targetPlayer.CrimeData == (Object)null || (int)targetPlayer.CrimeData.CurrentPursuitLevel == 0) { return true; } ResponseState responseState = get_response(targetPlayer.PlayerCode, create: true); if (responseState == null) { return true; } (int, bool, bool, Vector3) snapshot = read_pursuit(targetPlayer); observe_response(targetPlayer, responseState, snapshot); if (responseState.phase != ResponsePhase.Searching) { return true; } int num = 0; int num2 = -1; for (int i = 0; i < officer_count; i++) { PoliceOfficer val2 = officers[i]; if (!available(val2)) { continue; } PursuitBehaviour pursuitBehaviour = val2.PursuitBehaviour; if (((Behaviour)pursuitBehaviour).Active && !((Object)(object)pursuitBehaviour.TargetPlayer == (Object)null) && !(pursuitBehaviour.TargetPlayer.PlayerCode != targetPlayer.PlayerCode)) { if (((Il2CppObjectBase)pursuitBehaviour).Pointer == ((Il2CppObjectBase)val).Pointer) { num2 = i; break; } num++; } } if (num2 < 0) { return true; } Vector3 last_known_position = responseState.last_known_position; Vector3 val3 = default(Vector3); ((Vector3)(ref val3))..ctor(last_known_position.X, last_known_position.Y, last_known_position.Z); ref SearchCache reference = ref searches[num2]; Vector3 val4; if (!(reference.player_code != targetPlayer.PlayerCode) && reference.slot == num) { val4 = reference.origin - val3; if (!(((Vector3)(ref val4)).sqrMagnitude > 0.01f) && !(Time.time >= reference.expires_seconds)) { goto IL_03a4; } } if (Time.time >= search_queries_reset_seconds) { search_queries_count = 0; search_queries_reset_seconds = Time.time + 1f; } if (search_queries_count >= 8) { return true; } reference = new SearchCache { player_code = targetPlayer.PlayerCode, slot = num, origin = val3, expires_seconds = Time.time + 6f }; Vector3 candidate = default(Vector3); for (int j = 0; j < 4; j++) { if (search_queries_count >= 8) { break; } search_queries_count++; Vector3 vector = SearchPattern.destination(last_known_position, num, Time.time - responseState.search_started_seconds, j); ((Vector3)(ref candidate))..ctor(vector.X, vector.Y, vector.Z); if (!PoliceNavigation.try_destination(((NPC)officers[num2]).Movement, candidate, out var destination)) { continue; } bool flag = false; for (int k = 0; k < officer_count; k++) { if (k == num2 || !searches[k].valid || searches[k].player_code != targetPlayer.PlayerCode || Time.time >= searches[k].expires_seconds || !available(officers[k])) { continue; } PursuitBehaviour pursuitBehaviour2 = officers[k].PursuitBehaviour; if (((Behaviour)pursuitBehaviour2).Active && !((Object)(object)pursuitBehaviour2.TargetPlayer == (Object)null) && !(pursuitBehaviour2.TargetPlayer.PlayerCode != targetPlayer.PlayerCode)) { val4 = searches[k].destination - destination; if (((Vector3)(ref val4)).sqrMagnitude < 2.25f) { flag = true; } } } if (!flag) { reference.destination = destination; reference.valid = true; break; } } goto IL_03a4; IL_03a4: if (!reference.valid) { return true; } __result = reference.destination; return false; } catch (Exception value) { running = false; MelonLogger.Error($"Police: Disabled search coordination after a game API failure: {value}"); return true; } } internal static void disable(string message) { running = false; MelonLogger.Error(message); } private static bool available(PoliceOfficer? officer) { if ((Object)(object)officer != (Object)null && ((Component)officer).gameObject.activeInHierarchy && ((NPC)officer).IsConscious && !((NPC)officer).IsInVehicle && (Object)(object)officer.PursuitBehaviour != (Object)null) { return (Object)(object)((NPC)officer).Movement != (Object)null; } return false; } internal static OfficerRole pursuit_role(PursuitBehaviour pursuit, int index) { if (index < 0 || index >= officer_count || !available(officers[index]) || ((Il2CppObjectBase)officers[index].PursuitBehaviour).Pointer != ((Il2CppObjectBase)pursuit).Pointer || (Object)(object)pursuit.TargetPlayer == (Object)null || intercept_targets[index] != pursuit.TargetPlayer.PlayerCode || Time.time >= intercept_seconds[index]) { return OfficerRole.Chaser; } return pursuit_roles[index]; } private static void redirect_destination(NPCMovement __instance, ref Vector3 __0) { //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_01f6: Unknown result type (might be due to invalid IL or missing references) //IL_0162: 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_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0308: Unknown result type (might be due to invalid IL or missing references) //IL_0301: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_030d: Unknown result type (might be due to invalid IL or missing references) //IL_0317: Unknown result type (might be due to invalid IL or missing references) //IL_031e: Unknown result type (might be due to invalid IL or missing references) //IL_035a: Unknown result type (might be due to invalid IL or missing references) //IL_035c: Unknown result type (might be due to invalid IL or missing references) //IL_035e: Unknown result type (might be due to invalid IL or missing references) //IL_0363: Unknown result type (might be due to invalid IL or missing references) //IL_0366: Unknown result type (might be due to invalid IL or missing references) //IL_036b: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_0265: Unknown result type (might be due to invalid IL or missing references) //IL_0267: Unknown result type (might be due to invalid IL or missing references) //IL_026c: Unknown result type (might be due to invalid IL or missing references) //IL_026e: Unknown result type (might be due to invalid IL or missing references) //IL_03cc: Unknown result type (might be due to invalid IL or missing references) //IL_03d4: Unknown result type (might be due to invalid IL or missing references) //IL_028a: Unknown result type (might be due to invalid IL or missing references) //IL_0291: Unknown result type (might be due to invalid IL or missing references) //IL_02a2: Unknown result type (might be due to invalid IL or missing references) //IL_02ab: Unknown result type (might be due to invalid IL or missing references) //IL_02b0: Unknown result type (might be due to invalid IL or missing references) //IL_02b5: Unknown result type (might be due to invalid IL or missing references) //IL_02b9: Unknown result type (might be due to invalid IL or missing references) //IL_02c0: Unknown result type (might be due to invalid IL or missing references) //IL_03fb: Unknown result type (might be due to invalid IL or missing references) //IL_0410: Unknown result type (might be due to invalid IL or missing references) //IL_0415: Unknown result type (might be due to invalid IL or missing references) //IL_041a: Unknown result type (might be due to invalid IL or missing references) //IL_039d: Unknown result type (might be due to invalid IL or missing references) //IL_03bc: Unknown result type (might be due to invalid IL or missing references) //IL_03c1: Unknown result type (might be due to invalid IL or missing references) //IL_03c6: Unknown result type (might be due to invalid IL or missing references) //IL_0381: Unknown result type (might be due to invalid IL or missing references) //IL_0386: Unknown result type (might be due to invalid IL or missing references) //IL_0388: Unknown result type (might be due to invalid IL or missing references) //IL_038d: Unknown result type (might be due to invalid IL or missing references) //IL_041e: Unknown result type (might be due to invalid IL or missing references) //IL_0423: Unknown result type (might be due to invalid IL or missing references) //IL_0433: Unknown result type (might be due to invalid IL or missing references) //IL_043a: Unknown result type (might be due to invalid IL or missing references) //IL_04d6: Unknown result type (might be due to invalid IL or missing references) //IL_04db: Unknown result type (might be due to invalid IL or missing references) //IL_04e6: Unknown result type (might be due to invalid IL or missing references) //IL_04e8: Unknown result type (might be due to invalid IL or missing references) //IL_04f6: Unknown result type (might be due to invalid IL or missing references) //IL_04f8: Unknown result type (might be due to invalid IL or missing references) //IL_0492: Unknown result type (might be due to invalid IL or missing references) //IL_0497: Unknown result type (might be due to invalid IL or missing references) //IL_0499: Unknown result type (might be due to invalid IL or missing references) //IL_049e: Unknown result type (might be due to invalid IL or missing references) if (!running || !InstanceFinder.IsServer) { return; } try { int num = -1; PoliceOfficer val = null; for (int i = 0; i < officer_count; i++) { PoliceOfficer val2 = officers[i]; if (available(val2) && !(((Il2CppObjectBase)((NPC)val2).Movement).Pointer != ((Il2CppObjectBase)__instance).Pointer)) { val = val2; num = i; break; } } if ((Object)(object)val == (Object)null) { return; } PursuitBehaviour pursuitBehaviour = val.PursuitBehaviour; Player targetPlayer = pursuitBehaviour.TargetPlayer; if (!((Behaviour)pursuitBehaviour).Active || (Object)(object)targetPlayer == (Object)null || (Object)(object)targetPlayer.CrimeData == (Object)null || (int)targetPlayer.CrimeData.CurrentPursuitLevel == 0) { return; } if (!((CombatBehaviour)pursuitBehaviour).IsTargetImmediatelyVisible && !read_pursuit(targetPlayer).visible) { intercept_valid[num] = false; intercept_seconds[num] = 0f; redirect_search((CombatBehaviour)(object)pursuitBehaviour, ref __0); return; } if (targetPlayer.IsInVehicle || Time.time < redirects_resume_seconds[num]) { return; } Vector3 position = ((Component)targetPlayer).transform.position; Vector3 val3 = __0 - position; if (((Vector3)(ref val3)).sqrMagnitude > 100f) { return; } if (Time.time < intercept_seconds[num]) { if (intercept_valid[num] && intercept_targets[num] == targetPlayer.PlayerCode) { val3 = intercept_origins[num] - __0; if (((Vector3)(ref val3)).sqrMagnitude <= 4f) { __0 = intercept_positions[num]; } } return; } intercept_valid[num] = false; intercept_seconds[num] = Time.time + 1f; Span<Vector2> span = stackalloc Vector2[officer_count]; span.Fill(new Vector2(float.NaN, float.NaN)); Vector2 zero = Vector2.Zero; Vector3 position2 = ((Component)val).transform.position; for (int j = 0; j < officer_count; j++) { PoliceOfficer val4 = officers[j]; if (!available(val4)) { continue; } PursuitBehaviour pursuitBehaviour2 = val4.PursuitBehaviour; if (((Behaviour)pursuitBehaviour2).Active && !((Object)(object)pursuitBehaviour2.TargetPlayer == (Object)null) && !(pursuitBehaviour2.TargetPlayer.PlayerCode != targetPlayer.PlayerCode)) { Vector3 val5 = ((Component)val4).transform.position - position; if (!(Math.Abs(val5.y) > 2f)) { span[j] = new Vector2(val5.x, val5.z); Vector3 val6 = position2 - ((Component)val4).transform.position; zero += PursuitSpacing.separation(new Vector2(val6.x, val6.z), num, j); } } } Vector3 val7 = (((Object)(object)targetPlayer.VelocityCalculator == (Object)null) ? Vector3.zero : targetPlayer.VelocityCalculator.Velocity); PursuitAssignment pursuitAssignment = Interception.assign(span, num, new Vector2(val7.x, val7.z)); pursuit_roles[num] = pursuitAssignment.role; intercept_targets[num] = targetPlayer.PlayerCode; if (pursuitAssignment.role == OfficerRole.Chaser) { return; } Vector3 val8 = position2 - position; Vector3 val9 = __0; if (!pursuitAssignment.redirect) { goto IL_03ca; } if (pursuitAssignment.role == OfficerRole.Interceptor) { val3 = __0 - position; if (!(((Vector3)(ref val3)).sqrMagnitude <= 16f)) { goto IL_03ca; } } val9 = position + new Vector3(pursuitAssignment.offset.X, 0f, pursuitAssignment.offset.Y); goto IL_041c; IL_03ca: Vector2 vector = PursuitSpacing.lateral_offset(zero, new Vector2(0f - val8.x, 0f - val8.z)); if (vector.LengthSquared() < 0.0625f) { return; } val9 += new Vector3(vector.X, 0f, vector.Y); goto IL_041c; IL_041c: if (!PoliceNavigation.try_redirect(__instance, __0, val9, out var destination) || Math.Abs(destination.y - position.y) > 2f) { return; } for (int k = 0; k < officer_count; k++) { if (k != num && intercept_valid[k] && !(Time.time >= intercept_seconds[k]) && !(intercept_targets[k] != targetPlayer.PlayerCode)) { val3 = intercept_positions[k] - destination; if (((Vector3)(ref val3)).sqrMagnitude < 2.25f) { return; } } } intercept_targets[num] = targetPlayer.PlayerCode; intercept_origins[num] = __0; intercept_positions[num] = destination; intercept_valid[num] = true; __0 = destination; } catch (Exception value) { running = false; MelonLogger.Error($"Police: Disabled interception after a game API failure: {value}"); } } } internal static class PoliceConfiguration { public static void load() { MelonPreferences_Category obj = MelonPreferences.CreateCategory("CoordinatedPolice"); MelonPreferences_Entry<int> val = obj.CreateEntry<int>("patrols_per_player", 5, "Foot patrols per connected player (0-16, up to four players)", (string)null, false, false, (ValueValidator)null, (string)null); MelonPreferences_Entry<int> val2 = obj.CreateEntry<int>("reserve_base", 8, "Solo ready reserve target (0-64)", (string)null, false, false, (ValueValidator)null, (string)null); MelonPreferences_Entry<int> val3 = obj.CreateEntry<int>("reserves_per_extra_player", 4, "Extra reserves per additional player (0-16)", (string)null, false, false, (ValueValidator)null, (string)null); MelonPreferences_Entry<float> val4 = obj.CreateEntry<float>("response_size_multiplier", 1f, "Response limits and burst size multiplier (0.25-2)", (string)null, false, false, (ValueValidator)null, (string)null); MelonPreferences_Entry<float> val5 = obj.CreateEntry<float>("reinforcement_time_multiplier", 1f, "Dispatch delays and breaks multiplier (0.25-4; lower is faster)", (string)null, false, false, (ValueValidator)null, (string)null); MelonPreferences_Entry<int> val6 = obj.CreateEntry<int>("northtown_weight", 1, "Northtown patrol share (0-10)", (string)null, false, false, (ValueValidator)null, (string)null); MelonPreferences_Entry<int> val7 = obj.CreateEntry<int>("westville_weight", 1, "Westville patrol share (0-10)", (string)null, false, false, (ValueValidator)null, (string)null); MelonPreferences_Entry<int> val8 = obj.CreateEntry<int>("downtown_weight", 1, "Downtown patrol share (0-10)", (string)null, false, false, (ValueValidator)null, (string)null); MelonPreferences_Entry<int> val9 = obj.CreateEntry<int>("docks_weight", 1, "Docks patrol share (0-10)", (string)null, false, false, (ValueValidator)null, (string)null); MelonPreferences_Entry<int> val10 = obj.CreateEntry<int>("suburbia_weight", 1, "Suburbia patrol share (0-10)", (string)null, false, false, (ValueValidator)null, (string)null); MelonPreferences_Entry<int> val11 = obj.CreateEntry<int>("uptown_weight", 1, "Uptown patrol share (0-10)", (string)null, false, false, (ValueValidator)null, (string)null); PoliceSettings obj2 = new PoliceSettings { patrols_per_player = val.Value, reserve_base = val2.Value, reserves_per_extra_player = val3.Value, response_size = val4.Value, reinforcement_time = val5.Value, northtown_weight = val6.Value, westville_weight = val7.Value, downtown_weight = val8.Value, docks_weight = val9.Value, suburbia_weight = val10.Value, uptown_weight = val11.Value }; PoliceSettings policeSettings = (PoliceSettings.current = obj2.validated()); if (obj2 != policeSettings) { MelonLogger.Warning("Police: Out-of-range settings were clamped; non-finite multipliers use 1."); } val.Value = policeSettings.patrols_per_player; val2.Value = policeSettings.reserve_base; val3.Value = policeSettings.reserves_per_extra_player; val4.Value = policeSettings.response_size; val5.Value = policeSettings.reinforcement_time; val6.Value = policeSettings.northtown_weight; val7.Value = policeSettings.westville_weight; val8.Value = policeSettings.downtown_weight; val9.Value = policeSettings.docks_weight; val10.Value = policeSettings.suburbia_weight; val11.Value = policeSettings.uptown_weight; obj.SaveToFile(false); MelonLogger.Msg($"Police: Settings patrols_per_player={policeSettings.patrols_per_player}, reserves={policeSettings.reserve_base}+{policeSettings.reserves_per_extra_player}/extra player, response_size={policeSettings.response_size}, reinforcement_time={policeSettings.reinforcement_time}. Host settings govern gameplay; restart to apply edits."); } } internal static class PoliceDistricts { public static readonly int[] actual = new int[6]; public static readonly int[] targets = new int[6]; private static readonly FootPatrolRoute?[] routes = (FootPatrolRoute?[])(object)new FootPatrolRoute[64]; private static readonly int[] route_districts = new int[64]; private static readonly int[] route_members = new int[64]; private static readonly bool[] route_available = new bool[64]; private static readonly FootPatrolRoute?[] cached_routes = (FootPatrolRoute?[])(object)new FootPatrolRoute[64]; private static readonly int[] cached_districts = new int[64]; private static int route_count; private static int cursor; public static void reset() { Array.Clear(routes, 0, routes.Length); Array.Clear(cached_routes, 0, cached_routes.Length); Array.Clear(actual, 0, actual.Length); Array.Clear(targets, 0, targets.Length); route_count = 0; cursor = 0; } public static bool prepare(int patrol_target) { //IL_02b5: Unknown result type (might be due to invalid IL or missing references) //IL_02ba: Unknown result type (might be due to invalid IL or missing references) route_count = 0; Array.Clear(routes, 0, routes.Length); Array.Clear(actual, 0, actual.Length); Array.Clear(targets, 0, targets.Length); Array.Clear(route_members, 0, route_members.Length); if (!Singleton<Map>.InstanceExists || !Singleton<LawController>.InstanceExists) { return false; } LawActivitySettings settings = Singleton<LawController>.Instance.GetSettings(); if (settings == null || settings.Patrols == null) { return false; } Span<bool> span = stackalloc bool[6]; span.Clear(); Span<int> span2 = stackalloc int[6]; for (int i = 0; i < 6; i++) { span2[i] = PoliceSettings.current.weight(i); } int num = Math.Min(((Il2CppArrayBase<PatrolInstance>)(object)settings.Patrols).Length, routes.Length); for (int j = 0; j < num; j++) { PatrolInstance val = ((Il2CppArrayBase<PatrolInstance>)(object)settings.Patrols)[j]; if (val == null || (Object)(object)val.Route == (Object)null || val.Route.Waypoints == null || ((Il2CppArrayBase<Transform>)(object)val.Route.Waypoints).Length == 0) { continue; } bool flag = false; for (int k = 0; k < route_count; k++) { if ((Object)(object)routes[k] == (Object)(object)val.Route) { flag = true; } } if (flag) { continue; } int num2 = region_for_route(val.Route); if (num2 >= 0) { MapRegionData regionData = Singleton<Map>.Instance.GetRegionData((EMapRegion)num2); bool flag2 = regionData != null && regionData.IsUnlocked && span2[num2] > 0; routes[route_count] = val.Route; route_districts[route_count] = num2; route_available[route_count++] = flag2; if (flag2) { span[num2] = true; } } } DistrictAllocation.allocate(patrol_target, span, span2, targets); int num3 = Math.Min(PoliceOfficer.Officers.Count, 128); for (int l = 0; l < num3; l++) { PoliceOfficer val2 = PoliceOfficer.Officers[l]; if ((Object)(object)val2 == (Object)null || !((NPC)val2).IsConscious || ((NPC)val2).isInBuilding || (Object)(object)val2.FootPatrolBehaviour == (Object)null || !((Behaviour)val2.FootPatrolBehaviour).Active) { continue; } PatrolGroup val3 = val2.FootPatrolBehaviour.Group; FootPatrolRoute val4 = ((val3 != null) ? val3.Route : null); int num4 = (((Object)(object)val4 != (Object)null) ? region_for_route(val4) : ((int)Singleton<Map>.Instance.GetRegionFromPosition(((Component)val2).transform.position))); if (num4 >= 0 && num4 < 6) { actual[num4]++; } for (int m = 0; m < route_count; m++) { if ((Object)(object)routes[m] == (Object)(object)val4) { route_members[m]++; } } } return true; } public static FootPatrolRoute? next_route(out int district, out int members) { int num = DistrictAllocation.choose_route(route_districts.AsSpan(0, route_count), route_members.AsSpan(0, route_count), route_available.AsSpan(0, route_count), actual, targets, cursor); district = -1; members = 0; if (num < 0) { return null; } cursor = (num + 1) % route_count; route_available[num] = false; district = route_districts[num]; members = route_members[num]; return routes[num]; } public static int region_for_route(FootPatrolRoute route) { //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Expected I4, but got Unknown int num = -1; for (int i = 0; i < cached_routes.Length; i++) { if ((Object)(object)cached_routes[i] == (Object)(object)route) { return cached_districts[i]; } if (num < 0 && (Object)(object)cached_routes[i] == (Object)null) { num = i; } } if (!Singleton<Map>.InstanceExists || route.Waypoints == null || ((Il2CppArrayBase<Transform>)(object)route.Waypoints).Length == 0) { return -1; } Span<int> span = stackalloc int[6]; span.Clear(); int num2 = Math.Min(((Il2CppArrayBase<Transform>)(object)route.Waypoints).Length, 128); int num3 = Math.Min(num2, 8); int num4 = -1; for (int j = 0; j < num3; j++) { Transform val = ((Il2CppArrayBase<Transform>)(object)route.Waypoints)[j * num2 / num3]; if (!((Object)(object)val == (Object)null)) { int num5 = (int)Singleton<Map>.Instance.GetRegionFromPosition(val.position); if (num5 >= 0 && num5 < 6) { span[num5]++; } } } for (int k = 0; k < 6; k++) { if (span[k] > 0 && (num4 < 0 || span[k] > span[num4])) { num4 = k; } } if (num >= 0 && num4 >= 0) { cached_routes[num] = route; cached_districts[num] = num4; } return num4; } public static void log() { MelonLogger.Msg($"Police: District patrols assigned/target Northtown={actual[0]}/{targets[0]}, Westville={actual[1]}/{targets[1]}, Downtown={actual[2]}/{targets[2]}, Docks={actual[3]}/{targets[3]}, Suburbia={actual[4]}/{targets[4]}, Uptown={actual[5]}/{targets[5]}. Targets require unlocked districts with native routes; counts follow assigned routes."); } } internal static class PoliceNavigation { private static NavMeshPath? path; public static void reset() { path = null; } public static bool try_destination(NPCMovement movement, Vector3 candidate, out Vector3 destination) { //IL_0001: 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_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_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_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Expected O, but got Unknown //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: 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) //IL_00c7: Expected O, but got Unknown //IL_00e0: 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_00f1: Unknown result type (might be due to invalid IL or missing references) destination = default(Vector3); NavMeshAgent agent = movement.Agent; if ((Object)(object)agent == (Object)null || !((Behaviour)agent).enabled || !agent.isOnNavMesh || agent.isOnOffMeshLink || !float.IsFinite(((Vector3)(ref candidate)).sqrMagnitude)) { return false; } NavMeshHit val = default(NavMeshHit); if (!NavMesh.SamplePosition(candidate, ref val, 0.75f, agent.areaMask)) { return false; } if (Math.Abs(((NavMeshHit)(ref val)).position.y - candidate.y) > 1f) { return false; } NavMeshQueryFilter val2 = new NavMeshQueryFilter { agentTypeID = agent.agentTypeID, areaMask = agent.areaMask }; NavMeshHit val3 = default(NavMeshHit); if (!NavMesh.FindClosestEdge(((NavMeshHit)(ref val)).position, ref val3, val2) || ((NavMeshHit)(ref val3)).distance < 0.25f) { return false; } if (path == null) { path = new NavMeshPath(); } if (!agent.CalculatePath(((NavMeshHit)(ref val)).position, path) || (int)path.status != 0) { return false; } destination = ((NavMeshHit)(ref val)).position; return true; } public static bool try_redirect(NPCMovement movement, Vector3 original, Vector3 candidate, out Vector3 destination) { //IL_0001: 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_004f: 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) //IL_005f: 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_0078: Expected O, but got Unknown //IL_007a: 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) destination = default(Vector3); NavMeshAgent agent = movement.Agent; if ((Object)(object)agent == (Object)null || !((Behaviour)agent).enabled || !agent.isOnNavMesh || !float.IsFinite(((Vector3)(ref original)).sqrMagnitude)) { return false; } NavMeshHit val = default(NavMeshHit); if (!NavMesh.SamplePosition(original, ref val, 0.75f, agent.areaMask)) { return false; } if (!try_destination(movement, candidate, out destination)) { return false; } NavMeshQueryFilter val2 = new NavMeshQueryFilter { agentTypeID = agent.agentTypeID, areaMask = agent.areaMask }; NavMeshHit val3 = default(NavMeshHit); return !NavMesh.Raycast(((NavMeshHit)(ref val)).position, destination, ref val3, val2); } } internal static class PolicePopulation { private struct OfficerState { public PoliceOfficer? officer; public float defeated_seconds; public bool defeated; public bool owned_patrol; public bool auto_deactivate; public PatrolGroup? group; public PoliceStation? return_station; public float return_seconds; public int return_attempts; } private static readonly OfficerState[] states = new OfficerState[128]; private static float next_seconds; private static float log_seconds; private static bool failed; public static void reset() { for (int i = 0; i < states.Length; i++) { ref OfficerState reference = ref states[i]; if ((Object)(object)reference.officer != (Object)null && reference.owned_patrol) { reference.officer.AutoDeactivate = reference.auto_deactivate; } } Array.Clear(states, 0, states.Length); next_seconds = 0f; log_seconds = 0f; PoliceDistricts.reset(); failed = false; } public static void tick() { //IL_066f: Unknown result type (might be due to invalid IL or missing references) //IL_03bf: Unknown result type (might be due to invalid IL or missing references) //IL_03fd: Unknown result type (might be due to invalid IL or missing references) //IL_040e: Unknown result type (might be due to invalid IL or missing references) //IL_0247: Unknown result type (might be due to invalid IL or missing references) //IL_04e1: Unknown result type (might be due to invalid IL or missing references) if (failed || !Singleton<LoadManager>.InstanceExists || !Singleton<LoadManager>.Instance.IsGameLoaded) { return; } try { if (!InstanceFinder.IsServer) { reconcile_client(); } else { if (Time.time < next_seconds) { return; } next_seconds = Time.time + 5f; int num = Math.Min(Player.PlayerList.Count, 16); if (num == 0) { return; } PopulationRules rules = PopulationRules.for_players(num); int num2 = Math.Min(PoliceOfficer.Officers.Count, states.Length); int num3 = 0; int num4 = 0; for (int i = 0; i < num2; i++) { PoliceOfficer val = PoliceOfficer.Officers[i]; if ((Object)(object)val == (Object)null || (Object)(object)((NPC)val).Health == (Object)null) { continue; } ref OfficerState reference = ref get_state(val); bool flag = ((NPC)val).Health.IsDead || ((NPC)val).Health.IsKnockedOut; if (flag && !reference.defeated) { reference.defeated_seconds = Time.time; } reference.defeated = flag; if ((Object)(object)reference.return_station != (Object)null) { if (reference.return_station.OfficerPool.Contains(val)) { MelonLogger.Msg("Police: Reserve return confirmed officer=" + ((NPC)val).ID + "."); reference.return_station = null; } else if (flag) { reference.return_station = null; } else if ((Object)(object)val.PursuitTarget != (Object)null || ((Object)(object)val.PursuitBehaviour != (Object)null && ((Behaviour)val.PursuitBehaviour).Active) || ((Object)(object)val.VehiclePursuitBehaviour != (Object)null && ((Behaviour)val.VehiclePursuitBehaviour).Active) || ((Object)(object)val.FootPatrolBehaviour != (Object)null && ((Behaviour)val.FootPatrolBehaviour).Active) || ((Object)(object)val.CheckpointBehaviour != (Object)null && ((Behaviour)val.CheckpointBehaviour).Active) || ((Object)(object)val.SentryBehaviour != (Object)null && ((Behaviour)val.SentryBehaviour).Active)) { reference.return_station = null; MelonLogger.Msg("Police: Returned officer already reassigned officer=" + ((NPC)val).ID + "."); } else if (Time.time >= reference.return_seconds && player_distance_squared(((Component)val).transform.position) >= 3600f) { if (reference.return_attempts >= 3) { throw new InvalidOperationException("Officer " + ((NPC)val).ID + " did not return to the station pool after three attempts."); } val.Deactivate(); reference.return_attempts++; reference.return_seconds = Time.time + 5f; } } if (reference.owned_patrol && !flag && reference.group != null && (Object)(object)val.FootPatrolBehaviour != (Object)null && ((Behaviour)val.FootPatrolBehaviour).Active && reference.group.IsGroupReadyToAdvance()) { reference.group.AdvanceGroup(); } if (flag) { num4++; } else if ((Object)(object)val.FootPatrolBehaviour != (Object)null && ((Behaviour)val.FootPatrolBehaviour).Active && !((NPC)val).isInBuilding) { num3++; } } int num5 = count_reserves(); int num6 = 0; for (int j = 0; j < states.Length; j++) { if (num6 >= 2) { break; } if (num5 >= rules.reserve_target) { break; } ref OfficerState reference2 = ref states[j]; PoliceOfficer officer = reference2.officer; if ((Object)(object)officer == (Object)null || !reference2.defeated || (Object)(object)reference2.return_station != (Object)null || (Object)(object)((NPC)officer).Movement == (Object)null || ((NPC)officer).IsInVehicle || (Object)(object)officer.AssignedVehicle != (Object)null) { continue; } PoliceStation closestPoliceStation = PoliceStation.GetClosestPoliceStation(((Component)officer).transform.position); if (!((Object)(object)closestPoliceStation == (Object)null) && !((Object)(object)closestPoliceStation.SpawnPoint == (Object)null) && PopulationRules.can_replenish(reference2.defeated_seconds, Time.time, player_distance_squared(((Component)officer).transform.position), player_distance_squared(closestPoliceStation.SpawnPoint.position))) { if ((Object)(object)officer.FootPatrolBehaviour != (Object)null) { ((Behaviour)officer.FootPatrolBehaviour).Disable_Networked((NetworkConnection)null); } if ((Object)(object)officer.PursuitBehaviour != (Object)null) { ((Behaviour)officer.PursuitBehaviour).Disable_Networked((NetworkConnection)null); } if ((Object)(object)officer.VehiclePatrolBehaviour != (Object)null) { ((Behaviour)officer.VehiclePatrolBehaviour).Disable_Networked((NetworkConnection)null); } if ((Object)(object)officer.BodySearchBehaviour != (Object)null) { ((Behaviour)officer.BodySearchBehaviour).Disable_Networked((NetworkConnection)null); } if ((Object)(object)officer.CheckpointBehaviour != (Object)null) { ((Behaviour)officer.CheckpointBehaviour).Disable_Networked((NetworkConnection)null); } if ((Object)(object)officer.SentryBehaviour != (Object)null) { ((Behaviour)officer.SentryBehaviour).Disable_Networked((NetworkConnection)null); } ((NPC)officer).SetVisible(false, true); ((NPC)officer).Movement.Warp(closestPoliceStation.SpawnPoint.position); ((NPC)officer).Health.SetAfflictedWithLethalEffect(false); ((NPC)officer).Health.Revive(); ((NPC)officer).Health.RestoreHealth(); if (reference2.owned_patrol) { officer.AutoDeactivate = reference2.auto_deactivate; } officer.Deactivate(); reference2.return_station = (closestPoliceStation.OfficerPool.Contains(officer) ? null : closestPoliceStation); reference2.return_seconds = Time.time + 5f; reference2.return_attempts = 1; reference2.owned_patrol = false; reference2.group = null; reference2.defeated = false; num6++; num5 = count_reserves(); MelonLogger.Msg($"Police: Reserve replacement requested officer={((NPC)officer).ID}, pooled={closestPoliceStation.OfficerPool.Contains(officer)}, reserve={num5}/{rules.reserve_target}."); } } bool flag2 = false; for (int k = 0; k < num; k++) { Player val2 = Player.PlayerList[k]; if ((Object)(object)val2 != (Object)null && (Object)(object)val2.CrimeData != (Object)null && (int)val2.CrimeData.CurrentPursuitLevel != 0) { flag2 = true; } } bool flag3 = PoliceDistricts.prepare(rules.patrol_target); if (!flag2 && flag3 && num3 < rules.patrol_target) { deploy_patrols(rules, num3, num5); } else if (!flag2 && flag3 && num3 == rules.patrol_target) { rebalance_patrol(); } if (!flag2 && num3 > rules.patrol_target) { return_patrols(num3 - rules.patrol_target); } if (Time.time < log_seconds) { return; } log_seconds = Time.time + 30f; num5 = count_reserves(); num3 = 0; num4 = 0; for (int l = 0; l < num2; l++) { PoliceOfficer val3 = PoliceOfficer.Officers[l]; if (!((Object)(object)val3 == (Object)null) && !((Object)(object)((NPC)val3).Health == (Object)null)) { if (((NPC)val3).Health.IsDead || ((NPC)val3).Health.IsKnockedOut) { num4++; } else if ((Object)(object)val3.FootPatrolBehaviour != (Object)null && ((Behaviour)val3.FootPatrolBehaviour).Active && !((NPC)val3).isInBuilding) { num3++; } } } if (flag3) { PoliceDistricts.log(); } MelonLogger.Msg($"Police: Population players={num}, foot_patrols={num3}/{rules.patrol_target}, reserves={num5}/{rules.reserve_target}, defeated={num4}, registered={num2}, patrol_staffing_paused={flag2}. Targets depend on native officer availability and safe replacement locations."); } } catch (Exception value) { failed = true; MelonLogger.Error($"Police: Population management disabled for this scene: {value}"); } } private static ref OfficerState get_state(PoliceOfficer officer) { int num = -1; for (int i = 0; i < states.Length; i++) { if ((Object)(object)states[i].officer == (Object)(object)officer) { return ref states[i]; } if ((Object)(object)states[i].officer == (Object)null && num < 0) { num = i; } } if (num < 0) { throw new InvalidOperationException("Police population tracking capacity exhausted."); } states[num] = new OfficerState { officer = officer }; return ref states[num]; } private static int count_reserves() { int num = 0; int num2 = Math.Min(PoliceStation.PoliceStations.Count, 16); for (int i = 0; i < num2; i++) { PoliceStation val = PoliceStation.PoliceStations[i]; if ((Object)(object)val == (Object)null) { continue; } int num3 = Math.Min(val.OfficerPool.Count, 128); for (int j = 0; j < num3; j++) { PoliceOfficer val2 = val.OfficerPool[j]; if ((Object)(object)val2 != (Object)null && ((NPC)val2).IsConscious) { num++; } } } return num; } private static float player_distance_squared(Vector3 position) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_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) float num = float.MaxValue; int num2 = Math.Min(Player.PlayerList.Count, 16); if (num2 == 0 || num2 != Player.PlayerList.Count) { return 0f; } for (int i = 0; i < num2; i++) { Player val = Player.PlayerList[i]; if ((Object)(object)val == (Object)null) { return 0f; } Vector3 val2 = ((Component)val).transform.position - position; float sqrMagnitude = ((Vector3)(ref val2)).sqrMagnitude; if (!float.IsFinite(sqrMagnitude)) { return 0f; } num = Math.Min(num, sqrMagnitude); } return num; } private static void deploy_patrols(PopulationRules rules, int patrol_count, int reserve_count) { //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_0142: 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_0154: Expected O, but got Unknown //IL_018e: Unknown result type (might be due to invalid IL or missing references) int num = Math.Max(4, rules.reserve_target / 2); if (reserve_count <= num || patrol_count >= rules.patrol_target) { return; } for (int i = 0; i < 64; i++) { int district; int members; FootPatrolRoute val = PoliceDistricts.next_route(out district, out members); if ((Object)(object)val == (Object)null) { break; } int num2 = Math.Min(((Il2CppArrayBase<Transform>)(object)val.Waypoints).Length, 128); int num3 = (Math.Clamp(val.StartWaypointIndex, 0, num2 - 1) + members) % num2; Transform val2 = ((Il2CppArrayBase<Transform>)(object)val.Waypoints)[num3]; if ((Object)(object)val2 == (Object)null) { continue; } PoliceStation closestPoliceStation = PoliceStation.GetClosestPoliceStation(val2.position); if ((Object)(object)closestPoliceStation == (Object)null || (Object)(object)closestPoliceStation.SpawnPoint == (Object)null || closestPoliceStation.OfficerPool.Count == 0 || player_distance_squared(closestPoliceStation.SpawnPoint.position) < 3600f) { continue; } PoliceOfficer val3 = closestPoliceStation.PullOfficer(); if ((Object)(object)val3 == (Object)null) { continue; } if (!((NPC)val3).IsConscious || (Object)(object)((NPC)val3).Movement == (Object)null) { throw new InvalidOperationException("Station supplied an unavailable patrol officer."); } ref OfficerState reference = ref get_state(val3); if (!reference.owned_patrol) { reference.auto_deactivate = val3.AutoDeactivate; } reference.owned_patrol = true; reference.group = new PatrolGroup(val) { CurrentWaypoint = num3 }; val3.AutoDeactivate = false; if ((Object)(object)((NPC)val3).CurrentBuilding != (Object)null) { ((NPC)val3).ExitBuilding(((NPC)val3).CurrentBuilding); } val3.Activate(); ((NPC)val3).Movement.Warp(closestPoliceStation.SpawnPoint.position); val3.StartFootPatrol(reference.group, false); patrol_count++; PoliceDistricts.actual[district]++; MelonLogger.Msg($"Police: Patrol assigned officer={((NPC)val3).ID}, district={district}, route={val.RouteName}, waypoint={reference.group.CurrentWaypoint}, foot_patrols={patrol_count}/{rules.patrol_target}. Departing station without route-start warp."); break; } } private static void rebalance_patrol() { //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_024e: Unknown result type (might be due to invalid IL or missing references) //IL_0253: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Expected O, but got Unknown int district; int members; FootPatrolRoute val = PoliceDistricts.next_route(out district, out members); if ((Object)(object)val == (Object)null) { return; } int num = Math.Min(((Il2CppArrayBase<Transform>)(object)val.Waypoints).Length, 128); int num2 = (Math.Clamp(val.StartWaypointIndex, 0, num - 1) + members) % num; Transform val2 = ((Il2CppArrayBase<Transform>)(object)val.Waypoints)[num2]; if ((Object)(object)val2 == (Object)null) { return; } int num3 = 0; for (int i = 0; i < states.Length; i++) { if (num3 >= 2) { break; } ref OfficerState reference = ref states[i]; PoliceOfficer officer = reference.officer; if (!reference.owned_patrol || (Object)(object)officer == (Object)null || !((NPC)officer).IsConscious || (Object)(object)officer.PursuitTarget != (Object)null || ((NPC)officer).IsInVehicle || (Object)(object)officer.AssignedVehicle != (Object)null || (Object)(object)((NPC)officer).Movement == (Object)null || (Object)(object)officer.FootPatrolBehaviour == (Object)null || !((Behaviour)officer.FootPatrolBehaviour).Active || ((NPC)officer).isInBuilding || player_distance_squared(((Component)officer).transform.position) < 3600f || ((Object)(object)officer.PursuitBehaviour != (Object)null && ((Behaviour)officer.PursuitBehaviour).Active) || ((Object)(object)officer.VehiclePursuitBehaviour != (Object)null && ((Behaviour)officer.VehiclePursuitBehaviour).Active) || ((Object)(object)officer.BodySearchBehaviour != (Object)null && ((Behaviour)officer.BodySearchBehaviour).Active) || ((Object)(object)officer.CheckpointBehaviour != (Object)null && ((Behaviour)officer.CheckpointBehaviour).Active) || ((Object)(object)officer.SentryBehaviour != (Object)null && ((Behaviour)officer.SentryBehaviour).Active)) { continue; } PatrolGroup val3 = officer.FootPatrolBehaviour.Group; if (val3 == null || (Object)(object)val3.Route == (Object)null) { continue; } int num4 = PoliceDistricts.region_for_route(val3.Route); if (num4 >= 0 && PoliceDistricts.actual[num4] > PoliceDistricts.targets[num4]) { num3++; if (PoliceNavigation.try_destination(((NPC)officer).Movement, val2.position, out var _)) { ((Behaviour)officer.FootPatrolBehaviour).Disable_Networked((NetworkConnection)null); reference.group = new PatrolGroup(val) { CurrentWaypoint = num2 }; officer.StartFootPatrol(reference.group, false); PoliceDistricts.actual[num4]--; PoliceDistricts.actual[district]++; MelonLogger.Msg($"Police: Patrol reassigned officer={((NPC)officer).ID}, from={num4}, to={district}, route={val.RouteName}. Walking to new route."); break; } } } } private static void return_patrols(int excess_count) { //IL_0087: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < states.Length; i++) { if (excess_count <= 0) { break; } ref OfficerState reference = ref states[i]; PoliceOfficer officer = reference.officer; if (reference.owned_patrol && !((Object)(object)officer == (Object)null) && ((NPC)officer).IsConscious && !((Object)(object)officer.PursuitTarget != (Object)null) && !((NPC)officer).IsInVehicle && !((Object)(object)officer.AssignedVehicle != (Object)null) && !((Object)(object)officer.FootPatrolBehaviour == (Object)null) && ((Behaviour)officer.FootPatrolBehaviour).Active && !(player_distance_squared(((Component)officer).transform.position) < 3600f)) { ((Behaviour)officer.FootPatrolBehaviour).Disable_Networked((NetworkConnection)null); officer.AutoDeactivate = reference.auto_deactivate; officer.Deactivate(); reference.owned_patrol = false; reference.group = null; excess_count--; MelonLogger.Msg("Police: Extra patrol returned officer=" + ((NPC)officer).ID + "."); } } } private static void reconcile_client() { int num = Math.Min(PoliceOfficer.Officers.Count, 128); for (int i = 0; i < num; i++) { PoliceOfficer val = PoliceOfficer.Officers[i]; if (!((Object)(object)val == (Object)null) && !((Object)(object)((NPC)val).Health == (Object)null) && !((Object)(object)((NPC)val).Behaviour == (Object)null)) { NPCHealth health = ((NPC)val).Health; if ((health.IsDead || health.IsKnockedOut) && float.IsFinite(health.Health) && float.IsFinite(health.MaxHealth) && !(health.Health < health.MaxHealth) && !(health.MaxHealth <= 0f) && !((Object)(object)((NPC)val).Behaviour.DeadBehaviour == (Object)null) && !((Object)(object)((NPC)val).Behaviour.UnconsciousBehaviour == (Object)null) && !((Behaviour)((NPC)val).Behaviour.DeadBehaviour).Enabled && !((Behaviour)((NPC)val).Behaviour.UnconsciousBehaviour).Enabled) { health.Revive(); MelonLogger.Msg("Police: Client reconciled restored officer=" + ((NPC)val).ID + " from host health and behaviour state."); } } } } } internal sealed record PoliceSettings { public static PoliceSettings current { get; set; } = new PoliceSettings(); public int patrols_per_player { get; init; } = 5; public int reserve_base { get; init; } = 8; public int reserves_per_extra_player { get; init; } = 4; public float response_size { get; init; } = 1f; public float reinforcement_time { get; init; } = 1f; public int northtown_weight { get; init; } = 1; public int westville_weight { get; init; } = 1; public int downtown_weight { get; init; } = 1; public int docks_weight { get; init; } = 1; public int suburbia_weight { get; init; } = 1; public int uptown_weight { get; init; } = 1; public PoliceSettings validated() { return this with { patrols_per_player = Math.Clamp(patrols_per_player, 0, 16), reserve_base = Math.Clamp(reserve_base, 0, 64), reserves_per_extra_player = Math.Clamp(reserves_per_extra_player, 0, 16), response_size = (float.IsFinite(response_size) ? Math.Clamp(response_size, 0.25f, 2f) : 1f), reinforcement_time = (float.IsFinite(reinforcement_time) ? Math.Clamp(reinforcement_time, 0.25f, 4f) : 1f), northtown_weight = Math.Clamp(northtown_weight, 0, 10), westville_weight = Math.Clamp(westville_weight, 0, 10), downtown_weight = Math.Clamp(downtown_weight, 0, 10), docks_weight = Math.Clamp(docks_weight, 0, 10), suburbia_weight = Math.Clamp(suburbia_weight, 0, 10), uptown_weight = Math.Clamp(uptown_weight, 0, 10) }; } public int weight(int district) { return district switch { 0 => northtown_weight, 1 => westville_weight, 2 => downtown_weight, 3 => docks_weight, 4 => suburbia_weight, 5 => uptown_weight, _ => throw new ArgumentOutOfRangeException("district"), }; } [CompilerGenerated] private PoliceSettings(PoliceSettings original) { patrols_per_player = original.patrols_per_player; reserve_base = original.reserve_base; reserves_per_extra_player = original.reserves_per_extra_player; response_size = original.response_size; reinforcement_time = original.reinforcement_time; northtown_weight = original.northtown_weight; westville_weight = original.westville_weight; downtown_weight = original.downtown_weight; docks_weight = original.docks_weight; suburbia_weight = original.suburbia_weight; uptown_weight = original.uptown_weight; } public PoliceSettings() { } } internal static class PoliceTactics { private readonly record struct ImpactEvent(ResponseState? response, int attack_id, bool lethal, float health_before); private const string shotgun_path = "Avatar/Equippables/PumpShotgun"; private static bool? shotgun_available; public static void install(Harmony harmony) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Expected O, but got Unknown //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Expected O, but got Unknown //IL_00c9: 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) //IL_00eb: Expected O, but got Unknown //IL_00eb: Expected O, but got Unknown harmony.Patch((MethodBase)AccessTools.Method(typeof(CombatBehaviour), "SetWeapon", (Type[])null, (Type[])null), new HarmonyMethod(typeof(PoliceTactics), "vary_weapon", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(PursuitBehaviour), "GetIdealRangedWeaponDistance", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(PoliceTactics), "set_firing_distance", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(PlayerCrimeData), "AddCrime", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(PoliceTactics), "record_crime", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(NPC), "RpcLogic___ReceiveImpact_427288424", (Type[])null, (Type[])null), new HarmonyMethod(typeof(PoliceTactics), "record_impact_before", (Type[])null), new HarmonyMethod(typeof(PoliceTactics), "record_impact_after", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } public static void reset() { shotgun_available = null; } private static bool try_context(CombatBehaviour combat, out PursuitBehaviour? pursuit, out ResponseState? response, out int officer_index) { //IL_0069: 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_00bb: Expected I4, but got Unknown pursuit = null; response = null; officer_index = -1; if (!Main.authoritative) { return false; } pursuit = ((Il2CppObjectBase)combat).TryCast<PursuitBehaviour>(); if ((Object)(object)pursuit == (Object)null || !((Behaviour)pursuit).Active || (Object)(object)pursuit.officer == (Object)null || (Object)(object)pursuit.TargetPlayer == (Object)null) { return false; } Player targetPlayer = pursuit.TargetPlayer; if ((Object)(object)targetPlayer.CrimeData == (Object)null || (int)targetPlayer.CrimeData.CurrentPursuitLevel == 0) { return false; } officer_index = PoliceOfficer.Officers.IndexOf(pursuit.officer); if (officer_index < 0 || officer_index >= 128) { return false; } response = Main.get_response(targetPlayer.PlayerCode, create: true); if (response == null) { return false; } response.observe_level((int)targetPlayer.CrimeData.CurrentPursuitLevel); return true; } private static void vary_weapon(CombatBehaviour __instance, ref string __0) { //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Expected I4, but got Unknown try { if (!try_context(__instance, out PursuitBehaviour pursuit, out ResponseState response, out int officer_index)) { return; } AvatarWeapon weapon_Gun = pursuit.Weapon_Gun; string text = ((weapon_Gun != null) ? ((AvatarEquippable)weapon_Gun).AssetPath : null); AvatarWeapon weapon_Taser = pursuit.Weapon_Taser; string text2 = ((weapon_Taser != null) ? ((AvatarEquippable)weapon_Taser).AssetPath : null); AvatarWeapon weapon_Baton = pursuit.Weapon_Baton; string text3 = ((weapon_Baton != null) ? ((AvatarEquippable)weapon_Baton).AssetPath : null); if (__0 != text && __0 != text2 && __0 != text3) { return; } OfficerWeapon officerWeapon = ResponseRules.weapon(response.severity, (int)pursuit.TargetPlayer.CrimeData.CurrentPursuitLevel, officer_index); if (officerWeapon == OfficerWeapon.Shotgun && !shotgun_available.HasValue) { shotgun_available = Resources.Load<Object>("Avatar/Equippables/PumpShotgun") != (Object)null; if (shotgun_available == false) { MelonLogger.Warning("Police: Shotgun asset unavailable; using standard police guns."); } } string text4 = officerWeapon switch { OfficerWeapon.Baton => text3, OfficerWeapon.Taser => text2, OfficerWeapon.Pistol => text, OfficerWeapon.Shotgun => (shotgun_available == true) ? "Avatar/Equippables/PumpShotgun" : text, _ => null, }; if (!string.IsNullOrEmpty(text4)) { __0 = text4; } } catch (Exception value) { Main.disable($"Police: Weapon selection failed: {value}"); } } private static void set_firing_distance(PursuitBehaviour __instance, ref float __result) { try { if (!try_context((CombatBehaviour)(object)__instance, out PursuitBehaviour pursuit, out ResponseState _, out int officer_index)) { return; } AvatarWeapon currentWeapon = ((CombatBehaviour)pursuit).currentWeapon; if (!((Object)(object)currentWeapon == (Object)null) && !string.IsNullOrEmpty(((AvatarEquippable)currentWeapon).AssetPath)) { int num; if (!(((AvatarEquippable)currentWeapon).AssetPath == "Avatar/Equippables/PumpShotgun")) { string assetPath = ((AvatarEquippable)currentWeapon).AssetPath; AvatarWeapon weapon_Gun = pursuit.Weapon_Gun; num = ((assetPath == ((weapon_Gun != null) ? ((AvatarEquippable)weapon_Gun).AssetPath : null)) ? 3 : 0); } else { num = 4; } OfficerWeapon weapon = (OfficerWeapon)num; float num2 = ResponseRules.firing_distance(Main.pursuit_role(pursuit, officer_index), weapon); float num3 = currentWeapon.MaxUseRange * 0.8f; float num4 = Math.Max(0f, currentWeapon.MinUseRange); if (!(num2 <= 0f) && float.IsFinite(num4) && float.IsFinite(num3) && !(num3 <= num4)) { __result = Math.Clamp(num2, num4, num3); } } } catch (Exception value) { Main.disable($"Police: Combat spacing failed: {value}"); } } private static void record_crime(PlayerCrimeData __instance, Crime __0, int __1) { if (!Main.authoritative || __0 == null || __1 <= 0 || (Object)(object)__instance.Player == (Object)null) { return; } try { Main.get_response(__instance.Player.PlayerCode, create: true)?.observe_crime(((MemberInfo)((Object)__0).GetIl2CppType()).Name); } catch (Exception value) { Main.disable($"Police: Crime severity update failed: {value}"); } } private static void record_impact_before(NPC __instance, Impact __0, out ImpactEvent __state) { //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Invalid comparison between Unknown and I4 //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Invalid comparison between Unknown and I4 __state = default(ImpactEvent); if (!Main.authoritative) { return; } try { PoliceOfficer val = ((Il2CppObjectBase)__instance).TryCast<PoliceOfficer>(); if (!((Object)(object)val == (Object)null) && !((Object)(object)((NPC)val).Health == (Object)null) && !((NPC)val).Health.IsDead && !(__0.ImpactDamage <= 0f) && !((Object)(object)__0.ImpactSource == (Object)null)) { Player component = ((Component)__0.ImpactSource).GetComponent<Player>(); if (!((Object)(object)component == (Object)null)) { ResponseState response = Main.get_response(component.PlayerCode, create: true); EImpactType impactType = __0.ImpactType; bool flag = ((impactType - 2 <= 1 || (int)impactType == 5) ? true : false); bool lethal = flag; __state = new ImpactEvent(response, __0.ImpactID, lethal, ((NPC)val).Health.Health); } } } catch (Exception value) { Main.disable($"Police: Attack attribution failed: {value}"); } } private static void record_impact_after(NPC __instance, ImpactEvent __state) { if (!Main.authoritative || __state.response == null) { return; } try { NPCHealth health = __instance.Health; if (!((Object)(object)health == (Object)null) && (health.IsDead || !(health.Health >= __state.health_before)) && __state.response.record_attack(__state.attack_id, __state.lethal) && health.IsDead) { __state.response.record_kill(); } } catch (Exception value) { Main.disable($"Police: Attack severity update failed: {value}"); } } } internal readonly record struct PopulationRules(int patrol_target, int reserve_target) { public static PopulationRules for_players(int player_count) { int num = Math.Clamp(player_co