Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of Difficulty Overhaul v0.1.4
Mods/ScheduleIDifficultyOverhaul.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.Json; using HarmonyLib; using Il2Cpp; using Il2CppFishNet; using Il2CppFishNet.Connection; using Il2CppFishNet.Managing; using Il2CppFishNet.Managing.Object; using Il2CppFishNet.Object; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppScheduleOne; using Il2CppScheduleOne.AvatarFramework; using Il2CppScheduleOne.AvatarFramework.Animation; using Il2CppScheduleOne.AvatarFramework.Equipping; using Il2CppScheduleOne.Building.Doors; using Il2CppScheduleOne.Cartel; using Il2CppScheduleOne.Combat; using Il2CppScheduleOne.Core.Avatar; using Il2CppScheduleOne.Core.Items.Framework; using Il2CppScheduleOne.DevUtilities; using Il2CppScheduleOne.Dialogue; using Il2CppScheduleOne.Doors; using Il2CppScheduleOne.Economy; using Il2CppScheduleOne.Employees; using Il2CppScheduleOne.EntityFramework; using Il2CppScheduleOne.GameTime; using Il2CppScheduleOne.Growing; using Il2CppScheduleOne.Interaction; using Il2CppScheduleOne.ItemFramework; using Il2CppScheduleOne.Levelling; using Il2CppScheduleOne.Map; using Il2CppScheduleOne.NPCs; using Il2CppScheduleOne.NPCs.Actions; using Il2CppScheduleOne.NPCs.Behaviour; using Il2CppScheduleOne.NPCs.Framework; using Il2CppScheduleOne.NPCs.Relation; using Il2CppScheduleOne.NPCs.Responses; using Il2CppScheduleOne.ObjectScripts; using Il2CppScheduleOne.Persistence; using Il2CppScheduleOne.PlayerScripts; using Il2CppScheduleOne.PlayerTasks; using Il2CppScheduleOne.Police; using Il2CppScheduleOne.Product; using Il2CppScheduleOne.Property; using Il2CppScheduleOne.Quests; using Il2CppScheduleOne.StationFramework; using Il2CppScheduleOne.Storage; using Il2CppScheduleOne.UI; using Il2CppScheduleOne.Vehicles; using Il2CppScheduleOne.VoiceOver; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using MelonLoader; using MelonLoader.Preferences; using Microsoft.CodeAnalysis; using ScheduleIDifficultyOverhaul; using UnityEngine; using UnityEngine.AI; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(DifficultyMod), "Schedule I Difficulty Overhaul", "0.1.4", "nacho", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: MelonPlatformDomain(/*Could not decode attribute arguments.*/)] [assembly: HarmonyDontPatchAll] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("ScheduleIDifficultyOverhaul")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.4.0")] [assembly: AssemblyInformationalVersion("0.1.4+c02009702c06e41397470955ed5c61813b6c6995")] [assembly: AssemblyProduct("ScheduleIDifficultyOverhaul")] [assembly: AssemblyTitle("ScheduleIDifficultyOverhaul")] [assembly: AssemblyVersion("0.1.4.0")] [module: RefSafetyRules(11)] 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; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ScheduleIDifficultyOverhaul { internal static class ConsoleCommands { private static Action<DifficultyCommand>? execute; internal static void Initialize(Harmony patches, Action<DifficultyCommand> handler) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Expected O, but got Unknown execute = handler; patches.Patch((MethodBase)AccessTools.Method(typeof(Console), "SubmitCommand", new Type[1] { typeof(string) }, (Type[])null), new HarmonyMethod(typeof(ConsoleCommands), "BeforeTextCommand", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); patches.Patch((MethodBase)AccessTools.Method(typeof(Console), "SubmitCommand", new Type[1] { typeof(List<string>) }, (Type[])null), new HarmonyMethod(typeof(ConsoleCommands), "BeforeArgumentCommand", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static bool BeforeTextCommand(string __0) { return Dispatch(__0); } private static bool BeforeArgumentCommand(List<string> __0) { if (__0 == null || __0.Count == 0) { return true; } List<string> list = new List<string>(); for (int i = 0; i < __0.Count && i < 256; i++) { list.Add(__0[i]); } return Dispatch(string.Join(" ", list)); } private static bool Dispatch(string text) { if (!DifficultyCommands.TryParse(text, out DifficultyCommand command)) { return true; } try { execute?.Invoke(command); } catch (Exception ex) { MelonLogger.Warning("Difficulty command failed: " + ex.GetType().Name + ": " + ex.Message); GameWorld.Notify("Difficulty command could not complete. Check the mod log."); } return false; } internal static void Reset() { execute = null; } } internal enum ContrabandKind { Generic, Product, Seed, MushroomSpawn, SporeSyringe, LiquidMeth } internal static class ContrabandPolicy { internal static bool ShouldSeize(ContrabandKind kind, int legalStatus, int category, string? itemId) { if (kind != ContrabandKind.Generic) { return true; } if (string.Equals(itemId, "cocaleaf", StringComparison.OrdinalIgnoreCase) || string.Equals(itemId, "cocainebase", StringComparison.OrdinalIgnoreCase) || string.Equals(itemId, "liquidmeth", StringComparison.OrdinalIgnoreCase)) { return true; } if (legalStatus >= 1) { if (category != 0 && category != 2) { return category == 9; } return true; } return false; } } internal sealed record DealerRobberyResult(string DealerId, string DealerName, string Faction, long ItemUnits, double Cash, bool InventoryEmpty, bool CashEmpty, int Failures); internal sealed class DealerRobbery { private sealed class Encounter { internal readonly Dealer Dealer; internal readonly string Faction; internal readonly Func<NetworkObject, bool> OwnsAttacker; internal readonly Action<string> Notify; internal bool Active = true; internal bool Applied; internal Encounter(Dealer dealer, string faction, Func<NetworkObject, bool> ownsAttacker, Action<string> notify) { Dealer = dealer; Faction = faction; OwnsAttacker = ownsAttacker; Notify = notify; } } private sealed record ImpactContext(Encounter Encounter, NetworkObject Source, bool WasConscious); private static readonly Dictionary<IntPtr, Encounter> encounters = new Dictionary<IntPtr, Encounter>(); private static bool initialized; [ThreadStatic] private static ImpactContext? impactContext; private readonly Action<string> notify; private Encounter? current; internal static DealerRobberyResult? LastResult { get; private set; } internal static int EventCount { get; private set; } internal DealerRobbery(Action<string> notify) { this.notify = notify; } internal static void Initialize(Harmony harmony) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown //IL_0065: Expected O, but got Unknown //IL_0065: Expected O, but got Unknown //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown if (!initialized) { harmony.Patch((MethodBase)AccessTools.Method(typeof(NPC), "RpcLogic___ReceiveImpact_427288424", (Type[])null, (Type[])null), new HarmonyMethod(typeof(DealerRobbery), "BeforeImpact", (Type[])null), new HarmonyMethod(typeof(DealerRobbery), "AfterImpact", (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(DealerRobbery), "FinalizeImpact", (Type[])null), (HarmonyMethod)null); HarmonyMethod val = new HarmonyMethod(typeof(DealerRobbery), "BeforeDefeat", (Type[])null); harmony.Patch((MethodBase)AccessTools.Method(typeof(NPCHealth), "Die", (Type[])null, (Type[])null), val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(NPCHealth), "KnockOut", (Type[])null, (Type[])null), val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); initialized = true; } } internal void Begin(Dealer? dealer, string faction, Func<NetworkObject, bool> ownsAttacker) { End(); if (!((Object)(object)dealer == (Object)null)) { current = new Encounter(dealer, faction, ownsAttacker, notify); encounters[((Il2CppObjectBase)dealer).Pointer] = current; } } internal void End() { if (current != null) { current.Active = false; if (encounters.TryGetValue(((Il2CppObjectBase)current.Dealer).Pointer, out Encounter value) && value == current) { encounters.Remove(((Il2CppObjectBase)current.Dealer).Pointer); } current = null; } } private static void BeforeImpact(NPC __instance, Impact __0, out ImpactContext? __state) { __state = impactContext; impactContext = null; try { if (!((Object)(object)__instance == (Object)null) && encounters.TryGetValue(((Il2CppObjectBase)__instance).Pointer, out Encounter value) && value.Active && !value.Applied && __0 != null && GameWorld.IsAuthority) { NetworkObject impactSource = __0.ImpactSource; if (!((Object)(object)impactSource == (Object)null) && value.OwnsAttacker(impactSource)) { impactContext = new ImpactContext(value, impactSource, ((NPC)value.Dealer).IsConscious); } } } catch (Exception ex) { MelonLogger.Warning("Gang damage attribution: " + ex.Message); } } private static void AfterImpact(ImpactContext? __state) { impactContext = __state; } private static Exception? FinalizeImpact(Exception? __exception, ImpactContext? __state) { impactContext = __state; return __exception; } private static void BeforeDefeat(NPCHealth __instance) { try { ImpactContext impactContext = DealerRobbery.impactContext; if (!(impactContext == null) && !((Object)(object)__instance == (Object)null)) { Encounter encounter = impactContext.Encounter; Dealer dealer = encounter.Dealer; if (GangPolicy.CanRobDealer(new DealerDefeatEvidence(GameWorld.IsAuthority, Player.PlayerList != null && Player.PlayerList.Count == 1, (Object)(object)dealer != (Object)null && dealer.IsRecruited, encounter.Active, (Object)(object)dealer != (Object)null && (Object)(object)__instance._npc != (Object)null && ((Il2CppObjectBase)__instance._npc).Pointer == ((Il2CppObjectBase)dealer).Pointer, (Object)(object)impactContext.Source != (Object)null && encounter.OwnsAttacker(impactContext.Source), impactContext.WasConscious, !__instance._invincible && !__instance.IsDead && !__instance.IsKnockedOut, encounter.Applied))) { encounter.Applied = true; Seize(encounter); } } } catch (Exception ex) { MelonLogger.Warning("Gang dealer robbery: " + ex.Message); } } private static void Seize(Encounter encounter) { Dealer dealer = encounter.Dealer; List<ItemSlot> slots = new List<ItemSlot>(); HashSet<IntPtr> seen = new HashSet<IntPtr>(); int failures = 0; Collect(delegate { List<ItemSlot> allSlots = dealer.GetAllSlots(); if (allSlots != null) { Enumerator<ItemSlot> enumerator2 = allSlots.GetEnumerator(); while (enumerator2.MoveNext()) { ItemSlot slot = enumerator2.Current; Add(slot); } } }); Collect(delegate { NPCInventory inventory = ((NPC)dealer).Inventory; if (((inventory != null) ? inventory.ItemSlots : null) != null) { Enumerator<ItemSlot> enumerator2 = inventory.ItemSlots.GetEnumerator(); while (enumerator2.MoveNext()) { ItemSlot slot = enumerator2.Current; Add(slot); } } }); Collect(delegate { if (dealer.ItemSlots != null) { Enumerator<ItemSlot> enumerator2 = dealer.ItemSlots.GetEnumerator(); while (enumerator2.MoveNext()) { ItemSlot slot = enumerator2.Current; Add(slot); } } }); Collect(delegate { if (dealer.overflowSlots != null) { foreach (ItemSlot item in (Il2CppArrayBase<ItemSlot>)(object)dealer.overflowSlots) { Add(item); } } }); long num = 0L; double num2 = FiniteCash(dealer.Cash); foreach (ItemSlot item2 in slots) { try { ItemInstance itemInstance = item2.ItemInstance; if (itemInstance == null) { continue; } int num3 = Math.Max(0, ((BaseItemInstance)itemInstance).Quantity); CashInstance val2 = ((Il2CppObjectBase)itemInstance).TryCast<CashInstance>(); double num4 = ((val2 == null) ? 0.0 : FiniteCash(val2.Balance)); bool isRemovalLocked = item2.IsRemovalLocked; try { item2.SetIsRemovalLocked(false); item2.ClearStoredInstance(false); if (item2.ItemInstance != null) { throw new InvalidOperationException("A dealer slot remained occupied after native removal."); } num += num3; num2 += num4; } finally { item2.SetIsRemovalLocked(isRemovalLocked); } } catch (Exception ex) { failures++; MelonLogger.Warning("Gang dealer stock removal: " + ex.Message); } } try { dealer.RpcLogic___SetCash_431000436(0f); if (dealer.Cash != 0f) { throw new InvalidOperationException("Dealer sale proceeds remained after native removal."); } } catch (Exception ex2) { failures++; MelonLogger.Warning("Gang dealer cash removal: " + ex2.Message); } ((NPC)dealer).HasChanged = true; bool flag = slots.All((ItemSlot slot) => slot.ItemInstance == null); bool flag2 = dealer.Cash == 0f; LastResult = new DealerRobberyResult(((NPC)dealer).ID, ((NPC)dealer).FullName, encounter.Faction, num, num2, flag, flag2, failures); EventCount++; MelonLogger.Msg($"Gang robbed {((NPC)dealer).FullName}: faction={encounter.Faction}, units={num}, cash={num2:0.##}, inventoryEmpty={flag}, cashEmpty={flag2}, failures={failures}."); try { encounter.Notify(encounter.Faction + " defeated " + ((NPC)dealer).FullName + " and stole " + ((failures == 0 && flag && flag2) ? "all their stock and cash." : "their stock and cash.")); } catch (Exception ex3) { MelonLogger.Warning("Gang robbery notification: " + ex3.Message); } void Add(ItemSlot? slot) { if (slot != null && seen.Add(((Il2CppObjectBase)slot).Pointer)) { slots.Add(slot); } } void Collect(Action collect) { try { collect(); } catch (Exception ex4) { failures++; MelonLogger.Warning("Gang dealer slot enumeration: " + ex4.Message); } } } private static double FiniteCash(float amount) { if (!float.IsFinite(amount)) { return 0.0; } return Math.Max(0.0, amount); } } internal enum CommandKind { Help, Status, Raid, Gang, Police, Cancel, Error } internal sealed record DifficultyCommand(CommandKind Kind, string Argument = "", string Target = "player", bool Immediate = false, int Count = 8); internal static class DifficultyCommands { internal static bool TryParse(string input, out DifficultyCommand command) { command = new DifficultyCommand(CommandKind.Error); string text = (input ?? "").Trim(); int num = text.IndexOfAny(new char[4] { ' ', '\t', '\r', '\n' }); string text2 = ((num < 0) ? text : text.Substring(0, num)); if (!text2.Equals("do", StringComparison.OrdinalIgnoreCase) && !text2.Equals("difficulty", StringComparison.OrdinalIgnoreCase)) { return false; } if (text.Length > 1024) { command = Error("Difficulty command is too long."); return true; } if (!Tokenize(text, out List<string> tokens)) { command = Error("Close the quoted property or dealer name."); return true; } string[] array = tokens.Skip(1).ToArray(); if (array.Length == 0) { command = new DifficultyCommand(CommandKind.Help); return true; } string text3 = array[0].ToLowerInvariant(); switch (text3) { case "help": case "status": case "cancel": command = ((array.Length == 1) ? new DifficultyCommand((!(text3 == "help")) ? ((text3 == "status") ? CommandKind.Status : CommandKind.Cancel) : CommandKind.Help) : Error("Use do " + text3 + " without extra arguments.")); break; case "raid": { bool flag = array.Length > 1 && array[^1].Equals("now", StringComparison.OrdinalIgnoreCase); string argument = string.Join(" ", array.Skip(1).Take(array.Length - 1 - (flag ? 1 : 0))); command = new DifficultyCommand(CommandKind.Raid, argument, "player", flag); break; } case "gang": { string text4 = ((array.Length > 1) ? array[1].ToLowerInvariant() : "random"); string text5 = ((array.Length > 2) ? string.Join(" ", array.Skip(2)) : "player"); if (text5.StartsWith("dealer ", StringComparison.OrdinalIgnoreCase)) { text5 = "dealer:" + text5.Substring(7).Trim(); } bool flag2; switch (text4) { case "benzies": case "mexican": case "colombian": case "random": flag2 = true; break; default: flag2 = false; break; } command = ((!flag2) ? Error("Use do gang benzies, mexican, colombian or random.") : ((!text5.Equals("player", StringComparison.OrdinalIgnoreCase) && !text5.Equals("dealer", StringComparison.OrdinalIgnoreCase) && !text5.StartsWith("dealer:", StringComparison.OrdinalIgnoreCase)) ? Error("Gang target must be player, dealer, or dealer:Name.") : new DifficultyCommand(CommandKind.Gang, text4, text5))); break; } case "police": { int result = 8; command = ((array.Length > 2 || (array.Length == 2 && (!int.TryParse(array[1], NumberStyles.None, CultureInfo.InvariantCulture, out result) || result < 1 || result > 40))) ? Error("Use do police [count], with count from 1 to 40. Available officers limit the actual squad.") : new DifficultyCommand(CommandKind.Police, "", "player", Immediate: false, result)); break; } default: command = Error("Unknown difficulty command. Use do help."); break; } return true; } private static DifficultyCommand Error(string message) { return new DifficultyCommand(CommandKind.Error, message); } private static bool Tokenize(string input, out List<string> tokens) { tokens = new List<string>(); StringBuilder stringBuilder = new StringBuilder(); char c = '\0'; foreach (char c2 in input) { if (c != 0) { if (c2 == c) { c = '\0'; } else { stringBuilder.Append(c2); } } else if (c2 == '"' || (c2 == '\'' && stringBuilder.Length == 0)) { c = c2; } else if (char.IsWhiteSpace(c2)) { if (stringBuilder.Length > 0) { tokens.Add(stringBuilder.ToString()); stringBuilder.Clear(); } } else { stringBuilder.Append(c2); } } if (c != 0) { return false; } if (stringBuilder.Length > 0) { tokens.Add(stringBuilder.ToString()); } return true; } } public sealed class DifficultyMod : MelonMod { private Settings? settings; private PolicePressure? police; private PropertyRaids? raids; private RivalGangs? gangs; private Harmony? patches; private NativeSmokeTest? smokeTest; private readonly EventClock clock = new EventClock(); private readonly EventGate gate = new EventGate(); private readonly ThreatHud hud = new ThreatHud(); private float heat; private float nextTick; private int failures; private string? session; private bool warnedCoop; public override void OnInitializeMelon() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown settings = new Settings(); patches = new Harmony("nacho.schedulei.difficultyoverhaul"); NpcArsenal.Initialize(patches); RivalGangs.Initialize(patches); police = new PolicePressure(settings, GameWorld.Notify, new PolicePopulation(patches)); raids = new PropertyRaids(RaidSettings, (Vector3 center, int count) => police.SpawnRaidSquad(center, count, GameWorld.Pressure), delegate(IReadOnlyList<NPC> squad) { police.CleanupSquad(squad); }, GameWorld.Notify, () => police.LastRaidDeploymentDiagnostic); gangs = new RivalGangs(GangSettings, GameWorld.Notify); ConsoleCommands.Initialize(patches, HandleCommand); smokeTest = new NativeSmokeTest(patches); ((MelonBase)this).LoggerInstance.Msg("Difficulty Overhaul 0.1.4: paired town patrols, 40 officers, property raids, rival attacks and dealer losses. Enter 'do help' in the game console. Single-player only."); } public override void OnUpdate() { //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Expected I4, but got Unknown //IL_01ed: Unknown result type (might be due to invalid IL or missing references) if (settings == null || police == null || raids == null || gangs == null) { return; } NativeSmokeTest? nativeSmokeTest = smokeTest; if (nativeSmokeTest != null && nativeSmokeTest.Update(police, raids, gangs)) { return; } try { if (!GameWorld.IsReady || !GameWorld.IsAuthority || !settings.Enabled.Value) { if (session != null) { ResetSession(); } return; } if (Player.PlayerList != null && Player.PlayerList.Count > 1) { if (session != null) { ResetSession(); } if (!warnedCoop) { ((MelonBase)this).LoggerInstance.Warning("Difficulty Overhaul suspended: this local test build supports single-player. No mod confiscations or attacks run in co-op."); warnedCoop = true; } return; } warnedCoop = false; bool isPlaying = GameWorld.IsPlaying; clock.Advance(Time.deltaTime, isPlaying); if (!isPlaying) { hud.Update(clock.Now, GameWorld.Pressure, "", "", visible: false); } else { if (clock.Now < nextTick) { return; } SaveInfo activeSaveInfo = Singleton<LoadManager>.Instance.ActiveSaveInfo; string text = ((activeSaveInfo != null) ? activeSaveInfo.SavePath : null); if (!string.IsNullOrEmpty(text)) { if (session != text) { ResetSession(); session = text; GameWorld.Notify("Difficulty Overhaul active. Raids and rivals grow stronger as your empire grows."); } float seconds = Math.Max(0f, clock.Now - (nextTick - 0.25f)); nextTick = clock.Now + 0.25f; heat = DifficultyPolicy.Heat(heat, seconds, (int)Player.Local.CrimeData.CurrentPursuitLevel); LevelManager instance = NetworkSingleton<LevelManager>.Instance; int properties = Property.OwnedProperties?.Count ?? 0; GameWorld.Pressure = DifficultyPolicy.Pressure((!((Object)(object)instance == (Object)null)) ? ((int)instance.Rank) : 0, properties, heat, settings.ScaleWithProgress.Value); float value = settings.RecoverySeconds.Value; bool active = raids.HasActiveEvent || gangs.HasActiveEvent; gate.Observe(clock.Now, active, value); bool allowStart = gate.CanStart(clock.Now, active, settings.StartupGraceSeconds.Value); raids.Update(clock.Now, allowStart); gate.Observe(clock.Now, raids.HasActiveEvent || gangs.HasActiveEvent, value); bool allowStart2 = gate.CanStart(clock.Now, raids.HasActiveEvent || gangs.HasActiveEvent, settings.StartupGraceSeconds.Value); gangs.Update(clock.Now, GameWorld.Pressure, allowStart2); gate.Observe(clock.Now, raids.HasActiveEvent || gangs.HasActiveEvent, value); police.Update(clock.Now, GameWorld.Pressure); hud.Update(clock.Now, GameWorld.Pressure, raids.StatusText, gangs.StatusText, settings.ShowStatus.Value); failures = 0; } } } catch (Exception ex) { nextTick = clock.Now + 5f; if (failures++ < 3) { ((MelonBase)this).LoggerInstance.Warning("Difficulty update paused for 5s: " + ex.GetType().Name + ": " + ex.Message); } } } private void HandleCommand(DifficultyCommand command) { if (command.Kind == CommandKind.Error) { Reply(command.Argument); return; } if (command.Kind == CommandKind.Help) { ((MelonBase)this).LoggerInstance.Msg("Test commands: do status | do police [count] | do raid [property/current/nearest/random] [now] | do gang [benzies/mexican/colombian/random] [player/dealer/dealer:Name] | do cancel"); Reply("Commands: do status; do police 40; do raid Bungalow now; do gang mexican player; do cancel. See README for property/dealer targeting."); return; } if (!GameWorld.IsReady || !GameWorld.IsAuthority || session == null || police == null || raids == null || gangs == null) { Reply("Load a single-player save before using difficulty test commands."); return; } if (Player.PlayerList != null && Player.PlayerList.Count > 1) { Reply("Difficulty tests are unavailable in co-op; this build supports single-player."); return; } if (command.Kind == CommandKind.Status) { Reply(police.StatusSummary + (raids.HasActiveEvent ? (" | " + raids.StatusText) : "") + (gangs.HasActiveEvent ? (" | " + gangs.StatusText) : "")); return; } if (command.Kind == CommandKind.Cancel) { raids.Reset(); gangs.Reset(); police.CancelResponses(); gate.Observe(clock.Now, active: false, settings.RecoverySeconds.Value); Reply("Mod raids, gang attacks and backup cancelled. No raid losses applied."); return; } if (settings == null || !settings.Enabled.Value) { Reply("Enable Difficulty Overhaul before starting a test."); return; } string message; switch (command.Kind) { default: return; case CommandKind.Raid: if (gangs.HasActiveEvent) { Reply("A gang attack is active. Use do cancel before testing a raid."); return; } raids.TryForceRaid(command.Argument, clock.Now, command.Immediate, out message); break; case CommandKind.Gang: if (raids.HasActiveEvent) { Reply("A property raid is active. Use do cancel before testing a gang attack."); return; } gangs.TryForceAttack(command.Argument, command.Target, clock.Now, GameWorld.Pressure, out message); break; case CommandKind.Police: { if (!settings.PoliceEnabled.Value) { Reply("Enable More police in the difficulty settings first."); return; } int num = police.ForceResponse(command.Count, clock.Now, GameWorld.Pressure); message = ((num > 0) ? ($"Police backup: {num}/{command.Count} requested officers dispatched. " + police.StatusSummary) : ("No backup could deploy here. Stand outdoors and move away from defeated officers, then retry. " + police.StatusSummary)); break; } } gate.Observe(clock.Now, raids.HasActiveEvent || gangs.HasActiveEvent, settings.RecoverySeconds.Value); Reply(message); } private void Reply(string message) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown GameWorld.Notify(message); Console.Log((Object)new String(IL2CPP.ManagedStringToIl2Cpp(message)), (Object)null); if (GameWorld.IsReady) { hud.Update(clock.Now, GameWorld.Pressure, raids?.StatusText ?? "", gangs?.StatusText ?? "", settings?.ShowStatus.Value ?? false); } } private RaidOptions RaidSettings() { Settings settings = this.settings; float num = DifficultyPolicy.IntervalScale(GameWorld.Pressure); float num2 = DifficultyPolicy.Finite(settings.RaidMinIntervalSeconds.Value, 900f, 60f, 14400f); float num3 = DifficultyPolicy.Finite(settings.RaidMaxIntervalSeconds.Value, 1500f, num2, 14400f); return new RaidOptions(settings.RaidsEnabled.Value, num2 * num, num3 * num, DifficultyPolicy.Finite(settings.RaidWarningSeconds.Value, 45f, 5f, 600f), DifficultyPolicy.Finite(settings.RaidDefenseSeconds.Value, 180f, 30f, 1200f), Math.Clamp(settings.RaidSquadSize.Value + (int)(GameWorld.Pressure * 2f), 1, 8), DifficultyPolicy.Finite(settings.StartupGraceSeconds.Value, 120f, 0f, 1800f)); } private GangOptions GangSettings() { Settings settings = this.settings; float num = DifficultyPolicy.IntervalScale(GameWorld.Pressure); float num2 = DifficultyPolicy.Finite(settings.GangMinIntervalSeconds.Value, 600f, 60f, 14400f); float num3 = DifficultyPolicy.Finite(settings.GangMaxIntervalSeconds.Value, 1200f, num2, 14400f); return new GangOptions(settings.BenziesEnabled.Value, settings.MexicanEnabled.Value, settings.ColombianEnabled.Value, num2 * num, num3 * num, Math.Clamp(settings.GangSquadSize.Value + (int)(GameWorld.Pressure * 2f), 1, 5), DifficultyPolicy.Finite(settings.GangDurationSeconds.Value, 240f, 30f, 900f)); } private void ResetSession() { gangs?.Reset(); raids?.Reset(); police?.Reset(); NpcArsenal.Reset(); hud.Dispose(); gate.Reset(); clock.Reset(); heat = (nextTick = (GameWorld.Pressure = 0f)); failures = 0; session = null; } public override void OnDeinitializeMelon() { ResetSession(); ConsoleCommands.Reset(); GameWorld.NotificationSink = null; Harmony? obj = patches; if (obj != null) { obj.UnpatchSelf(); } } } internal static class DifficultyPolicy { internal static float Finite(float value, float fallback, float min, float max) { return Math.Clamp(float.IsFinite(value) ? value : fallback, min, max); } internal static float Pressure(int rank, int properties, float heat, bool scale) { if (!scale) { return 0f; } return Math.Clamp((float)Math.Clamp(rank, 0, 10) / 10f * 0.55f + (float)Math.Clamp(properties - 1, 0, 6) / 6f * 0.2f + Finite(heat, 0f, 0f, 1f) * 0.25f, 0f, 1f); } internal static float IntervalScale(float pressure) { return 1f - Finite(pressure, 0f, 0f, 1f) * 0.35f; } internal static float Heat(float previous, float seconds, int pursuit) { return Math.Clamp(Finite(previous, 0f, 0f, 1f) + Finite(seconds, 0f, 0f, 2f) * ((pursuit >= 2) ? (0.0015f * (float)Math.Clamp(pursuit, 2, 4)) : (-0.0005f)), 0f, 1f); } } internal sealed class EventClock { internal float Now { get; private set; } internal void Advance(float seconds, bool playing) { if (playing) { Now += DifficultyPolicy.Finite(seconds, 0f, 0f, 2f); } } internal void Reset() { Now = 0f; } } internal sealed class EventGate { private bool previouslyActive; private float recoverUntil; internal void Observe(float now, bool active, float recoverySeconds) { if (previouslyActive && !active) { recoverUntil = now + DifficultyPolicy.Finite(recoverySeconds, 120f, 0f, 1800f); } previouslyActive = active; } internal bool CanStart(float now, bool active, float startupGrace) { if (!active) { return now >= Math.Max(recoverUntil, DifficultyPolicy.Finite(startupGrace, 120f, 0f, 1800f)); } return false; } internal void Reset() { previouslyActive = false; recoverUntil = 0f; } } internal static class ExteriorApproach { private sealed record Point(string Label, Vector3 Position, float Radius); private static List<Point>? deploymentAnchors; private static Vector3 deploymentCenter; internal static void ClearDeploymentCache() { deploymentAnchors = null; } private static void CacheAnchors(Vector3 center, List<Point> anchors) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) anchors.Sort((Point a, Point b) => DistanceSquared(a.Position, center).CompareTo(DistanceSquared(b.Position, center))); deploymentCenter = center; deploymentAnchors = anchors; } internal static Vector3 PreferredPosition(Property property) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) Transform val = (((Object)(object)property.NPCSpawnPoint != (Object)null) ? property.NPCSpawnPoint : property.SpawnPoint); if (!((Object)(object)val != (Object)null)) { return ((Component)property).transform.position; } return val.position; } internal static bool TryResolve(Property property, out Vector3 center, out string detail) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Expected O, but got Unknown //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_03c9: Unknown result type (might be due to invalid IL or missing references) //IL_0414: Unknown result type (might be due to invalid IL or missing references) //IL_0419: Unknown result type (might be due to invalid IL or missing references) //IL_0459: Unknown result type (might be due to invalid IL or missing references) //IL_045f: Unknown result type (might be due to invalid IL or missing references) //IL_0488: Unknown result type (might be due to invalid IL or missing references) //IL_04a5: Unknown result type (might be due to invalid IL or missing references) ClearDeploymentCache(); center = Vector3.zero; List<string> list = new List<string>(); try { List<Point> list2 = new List<Point>(); Add(list2, "NPCSpawnPoint", property.NPCSpawnPoint, 4f); Add(list2, "SpawnPoint", property.SpawnPoint, 6f); Add(list2, "property origin", ((Component)property).transform, 12f); List<Point> list3 = CollectExteriorAnchors(PreferredPosition(property)); MelonLogger.Msg($"Raid approach inputs for {property.PropertyName} ({property.PropertyCode}): NPCSpawnPoint={TransformText(property.NPCSpawnPoint)}; SpawnPoint={TransformText(property.SpawnPoint)}; InteriorSpawnPoint={TransformText(property.InteriorSpawnPoint)}; origin={PositionText(((Component)property).transform.position)}; exterior anchors={list3.Count}; door discovery deferred until needed."); if (list3.Count == 0) { detail = "No loaded native foot-patrol waypoint or police-station exterior access point could establish exterior connectivity."; return false; } NavMeshPath path = new NavMeshPath(); if (list2.Count > 0 && list2[0].Label == "NPCSpawnPoint") { Point candidate = list2[0]; list2.RemoveAt(0); if (TryCandidate(candidate, list3, path, out center, out string result)) { CacheAnchors(center, list3); detail = result; return true; } list.Add(result); } List<Point> list4 = new List<Point>(); foreach (PropertyDoorController item in Object.FindObjectsOfType<PropertyDoorController>()) { if ((Object)(object)item == (Object)null || (Object)(object)item.Property == (Object)null || ((Il2CppObjectBase)item.Property).Pointer != ((Il2CppObjectBase)property).Pointer) { continue; } Il2CppReferenceArray<InteractableObject> exteriorIntObjs = ((DoorController)item).ExteriorIntObjs; if (exteriorIntObjs != null) { for (int i = 0; i < ((Il2CppArrayBase<InteractableObject>)(object)exteriorIntObjs).Length; i++) { if ((Object)(object)((Il2CppArrayBase<InteractableObject>)(object)exteriorIntObjs)[i] != (Object)null) { Add(list4, $"{((Object)item).name} exterior handle {i}", ((Component)((Il2CppArrayBase<InteractableObject>)(object)exteriorIntObjs)[i]).transform, 4f); } } } Add(list4, ((Object)item).name + " threshold", ((Component)item).transform, 4f); } list2.InsertRange(0, list4); MelonLogger.Msg($"Raid fallback doors for {property.PropertyName}: linked exterior doors={list4.Count}. " + string.Join("; ", list4.Select((Point p) => p.Label + "=" + PositionText(p.Position)))); foreach (Point item2 in list2) { if (TryCandidate(item2, list3, path, out center, out string result2)) { CacheAnchors(center, list3); list.Add(result2); detail = string.Join("; ", list); return true; } list.Add(result2); } Vector3 preferred = PreferredPosition(property); foreach (Point item3 in list3.OrderBy((Point p) => HorizontalDistanceSquared(p.Position, preferred))) { if (!(HorizontalDistanceSquared(item3.Position, preferred) > 4900f) && TryCandidate(new Point("nearby exterior route (" + item3.Label + ")", item3.Position, 2f), list3, path, out center, out string result3)) { CacheAnchors(center, list3); list.Add(result3); detail = string.Join("; ", list); return true; } } list.Add("No connected native exterior route was within 70m of the property's arrival point."); } catch (Exception ex) { list.Add("Exterior approach query failed: " + ex.GetType().Name + ": " + ex.Message); } detail = string.Join("; ", list); return false; } internal static bool TryFindArrival(Vector3 center, int slot, out Vector3 point, out string detail) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: 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_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_02fd: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_030e: Unknown result type (might be due to invalid IL or missing references) //IL_0318: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0330: Unknown result type (might be due to invalid IL or missing references) //IL_0335: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_0343: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: 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_014a: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_015d: 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_0350: Unknown result type (might be due to invalid IL or missing references) //IL_0355: Unknown result type (might be due to invalid IL or missing references) //IL_03a2: Unknown result type (might be due to invalid IL or missing references) //IL_03cb: Unknown result type (might be due to invalid IL or missing references) //IL_01e5: Unknown result type (might be due to invalid IL or missing references) //IL_01f2: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_024f: Unknown result type (might be due to invalid IL or missing references) //IL_026a: Unknown result type (might be due to invalid IL or missing references) //IL_02af: Unknown result type (might be due to invalid IL or missing references) point = Vector3.zero; List<string> list = new List<string>(); try { if (!Finite(center)) { detail = "Connected-path arrival rejected invalid approach coordinates."; return false; } List<Point> list2 = deploymentAnchors; if (list2 == null || DistanceSquared(center, deploymentCenter) > 0.0625f) { list2 = CollectExteriorAnchors(center); CacheAnchors(center, list2); } NavMeshPath val = new NavMeshPath(); NavMeshPath val2 = new NavMeshPath(); float num = 4f + 2f * (float)((slot % 8 + 8) % 8); NavMeshHit val3 = default(NavMeshHit); NavMeshHit val7 = default(NavMeshHit); for (int i = 0; i < Math.Min(12, list2.Count); i++) { Point point2 = list2[i]; if (!NavMesh.SamplePosition(point2.Position, ref val3, point2.Radius, -1) || Math.Abs(((NavMeshHit)(ref val3)).position.y - point2.Position.y) > 4f || !NavMesh.CalculatePath(((NavMeshHit)(ref val3)).position, center, -1, val) || (int)val.status != 0) { continue; } Il2CppStructArray<Vector3> corners = val.corners; Vector3 val4 = ((NavMeshHit)(ref val3)).position; float num2 = num; if (corners != null) { for (int num3 = ((Il2CppArrayBase<Vector3>)(object)corners).Length - 1; num3 > 0; num3--) { Vector3 val5 = ((Il2CppArrayBase<Vector3>)(object)corners)[num3]; Vector3 val6 = ((Il2CppArrayBase<Vector3>)(object)corners)[num3 - 1]; float num4 = Vector3.Distance(val6, val5); if (!(num4 <= 0.001f)) { if (!(num2 > num4)) { val4 = val5 + (val6 - val5) * (num2 / num4); break; } num2 -= num4; } } } if (NavMesh.SamplePosition(val4, ref val7, 1f, -1) && Finite(((NavMeshHit)(ref val7)).position) && Vector3.Distance(((NavMeshHit)(ref val7)).position, val4) <= 1f && Math.Abs(((NavMeshHit)(ref val7)).position.y - center.y) <= 4f && NavMesh.CalculatePath(((NavMeshHit)(ref val7)).position, center, -1, val2) && (int)val2.status == 0) { point = ((NavMeshHit)(ref val7)).position; detail = $"Connected-path arrival slot={slot}, {num:0}m behind approach, raw={PositionText(val4)}, NavMesh={PositionText(point)}; route from {point2.Label} {PositionText(((NavMeshHit)(ref val3)).position)}, corners={((Il2CppArrayBase<Vector3>)(object)corners)?.Length ?? 0}."; return true; } if (Finite(((NavMeshHit)(ref val3)).position) && Math.Abs(((NavMeshHit)(ref val3)).position.y - center.y) <= 4f && NavMesh.CalculatePath(((NavMeshHit)(ref val3)).position, center, -1, val2) && (int)val2.status == 0) { point = ((NavMeshHit)(ref val3)).position; detail = $"Verified exterior anchor arrival slot={slot}: {point2.Label} {PositionText(point)}; complete path to approach {PositionText(center)} after close route sample was unavailable."; return true; } list.Add(point2.Label + ": connected route found, but both interpolated and anchor arrivals failed height/path validation"); } list.Add("No usable arrival on the twelve nearest native exterior routes."); } catch (Exception ex) { list.Add("Connected-path arrival query failed: " + ex.GetType().Name + ": " + ex.Message); } detail = string.Join("; ", list); return false; } private static List<Point> CollectExteriorAnchors(Vector3 preferred) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) List<Point> list = new List<Point>(); Scene scene; foreach (FootPatrolRoute item in Object.FindObjectsOfType<FootPatrolRoute>()) { if ((Object)(object)item == (Object)null) { continue; } scene = ((Component)item).gameObject.scene; if (!((Scene)(ref scene)).IsValid() || item.Waypoints == null) { continue; } foreach (Transform item2 in (Il2CppArrayBase<Transform>)(object)item.Waypoints) { if ((Object)(object)item2 != (Object)null) { Add(list, "patrol " + item.RouteName + "/" + ((Object)item2).name, item2, 3f); } } } List<PoliceStation> policeStations = PoliceStation.PoliceStations; if (policeStations != null) { for (int i = 0; i < policeStations.Count; i++) { PoliceStation val = policeStations[i]; if ((Object)(object)val == (Object)null) { continue; } scene = ((Component)val).gameObject.scene; if (!((Scene)(ref scene)).IsValid() || ((NPCEnterableBuilding)val).Doors == null) { continue; } for (int j = 0; j < ((Il2CppArrayBase<StaticDoor>)(object)((NPCEnterableBuilding)val).Doors).Length; j++) { if ((Object)(object)((Il2CppArrayBase<StaticDoor>)(object)((NPCEnterableBuilding)val).Doors)[j] != (Object)null) { Add(list, $"station {((NPCEnterableBuilding)val).BuildingName} exterior door {j}", ((Il2CppArrayBase<StaticDoor>)(object)((NPCEnterableBuilding)val).Doors)[j].AccessPoint, 4f); } } } } list.Sort((Point a, Point b) => DistanceSquared(a.Position, preferred).CompareTo(DistanceSquared(b.Position, preferred))); return list; } private static bool TryCandidate(Point candidate, IReadOnlyList<Point> anchors, NavMeshPath path, out Vector3 center, out string result) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: 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_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_02f3: Unknown result type (might be due to invalid IL or missing references) //IL_0316: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_0203: Unknown result type (might be due to invalid IL or missing references) //IL_0230: 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_0293: Unknown result type (might be due to invalid IL or missing references) center = Vector3.zero; try { NavMeshHit hit = default(NavMeshHit); if (!NavMesh.SamplePosition(candidate.Position, ref hit, candidate.Radius, -1)) { result = $"{candidate.Label} {PositionText(candidate.Position)}: no NavMesh within {candidate.Radius:0}m"; return false; } if (!Finite(((NavMeshHit)(ref hit)).position) || Math.Abs(((NavMeshHit)(ref hit)).position.y - candidate.Position.y) > 4f) { result = $"{candidate.Label} {PositionText(candidate.Position)}: rejected NavMesh {PositionText(((NavMeshHit)(ref hit)).position)} (height mismatch)"; return false; } NavMeshHit val = default(NavMeshHit); foreach (Point item in anchors.OrderBy((Point p) => DistanceSquared(p.Position, ((NavMeshHit)(ref hit)).position)).Take(12)) { if (NavMesh.SamplePosition(item.Position, ref val, item.Radius, -1) && !(Math.Abs(((NavMeshHit)(ref val)).position.y - item.Position.y) > 4f) && NavMesh.CalculatePath(((NavMeshHit)(ref val)).position, ((NavMeshHit)(ref hit)).position, -1, path) && (int)path.status == 0) { center = ((NavMeshHit)(ref hit)).position; result = $"{candidate.Label} raw={PositionText(candidate.Position)}, NavMesh={PositionText(center)}, complete exterior path from {item.Label} {PositionText(((NavMeshHit)(ref val)).position)}"; return true; } } result = $"{candidate.Label} raw={PositionText(candidate.Position)}, NavMesh={PositionText(((NavMeshHit)(ref hit)).position)}: disconnected from nearby exterior anchors"; return false; } catch (Exception ex) { result = $"{candidate.Label}: {ex.GetType().Name}: {ex.Message}"; return false; } } private static void Add(List<Point> points, string label, Transform? transform, float radius) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)transform == (Object)null)) { Vector3 position = transform.position; if (Finite(position)) { points.Add(new Point(label, position, radius)); } } } private static float HorizontalDistanceSquared(Vector3 a, Vector3 b) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) return (a.x - b.x) * (a.x - b.x) + (a.z - b.z) * (a.z - b.z); } private static float DistanceSquared(Vector3 a, Vector3 b) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) float num = a.x - b.x; float num2 = a.y - b.y; float num3 = a.z - b.z; return num * num + num2 * num2 + num3 * num3; } private static bool Finite(Vector3 value) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (float.IsFinite(value.x) && float.IsFinite(value.y)) { return float.IsFinite(value.z); } return false; } private static string TransformText(Transform? value) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)value != (Object)null)) { return "missing"; } return PositionText(value.position); } private static string PositionText(Vector3 value) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) return $"({value.x:0.0}, {value.y:0.0}, {value.z:0.0})"; } } internal static class GameWorld { internal static float Pressure { get; set; } internal static Action<string>? NotificationSink { get; set; } internal static bool IsAuthority { get { if ((Object)(object)Player.Local != (Object)null) { return ((NetworkBehaviour)Player.Local).IsServerInitialized; } return false; } } internal static bool IsReady { get { LoadManager instance = Singleton<LoadManager>.Instance; if ((Object)(object)instance != (Object)null && instance.IsGameLoaded && !instance.IsLoading) { return (Object)(object)Player.Local != (Object)null; } return false; } } internal static bool IsPlaying { get { PauseMenu instance = Singleton<PauseMenu>.Instance; SleepController instance2 = NetworkSingleton<SleepController>.Instance; if (Time.timeScale > 0f && (!((Object)(object)instance != (Object)null) || !instance.IsPaused) && (!((Object)(object)instance2 != (Object)null) || !instance2.IsSleepInProgress)) { return !Player.Local.IsSleeping; } return false; } } internal static void Notify(string message) { MelonLogger.Msg("[Difficulty] " + message); NotificationSink?.Invoke(message); } } internal readonly record struct DealerDefeatEvidence(bool Host, bool SinglePlayer, bool Recruited, bool EncounterActive, bool TargetMatches, bool OwnedAttacker, bool WasConscious, bool CanBeDefeated, bool AlreadyApplied); internal static class GangPolicy { internal static bool CanRobDealer(DealerDefeatEvidence evidence) { if (evidence.Host && evidence.SinglePlayer && evidence.Recruited && evidence.EncounterActive && evidence.TargetMatches && evidence.OwnedAttacker && evidence.WasConscious && evidence.CanBeDefeated) { return !evidence.AlreadyApplied; } return false; } internal static bool BenziesAvailable(bool cartelLoaded, bool defeated, bool questCompleted) { if (cartelLoaded && !defeated) { return !questCompleted; } return false; } } internal static class GangSmokeChecks { internal static void Run(RivalGangs gangs, float now, Action<object> record) { //IL_0603: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_0349: Unknown result type (might be due to invalid IL or missing references) //IL_034e: Unknown result type (might be due to invalid IL or missing references) //IL_0294: Unknown result type (might be due to invalid IL or missing references) ValidateFixture(); gangs.Reset(); List<Dealer> list = new List<Dealer>(); Enumerator<Dealer> enumerator = Dealer.AllPlayerDealers.GetEnumerator(); while (enumerator.MoveNext()) { Dealer current = enumerator.Current; if ((Object)(object)current != (Object)null && current.IsRecruited && ((NetworkBehaviour)current).IsServerInitialized && (Object)(object)((NPC)current).Health != (Object)null) { list.Add(current); if (list.Count == 4) { break; } } } Dealer[] array = list.ToArray(); if (array.Length != 4) { throw new InvalidOperationException("Gang fixture requires four native hired dealers in its copied save."); } PoliceOfficer val = null; Enumerator<PoliceOfficer> enumerator2 = PoliceOfficer.Officers.GetEnumerator(); while (enumerator2.MoveNext()) { PoliceOfficer current2 = enumerator2.Current; if ((Object)(object)current2 != (Object)null && ((NPC)current2).IsConscious && ((NPC)current2).IsActive && (Object)(object)((NPC)current2).CurrentBuilding == (Object)null && (Object)(object)((NetworkBehaviour)current2).NetworkObject != (Object)null) { val = current2; break; } } NPC val2 = null; Enumerator<NPC> enumerator3 = NPCManager.NPCRegistry.GetEnumerator(); while (enumerator3.MoveNext()) { NPC current3 = enumerator3.Current; if ((Object)(object)current3 != (Object)null && ((NetworkBehaviour)current3).IsServerInitialized && (Object)(object)((NetworkBehaviour)current3).NetworkObject != (Object)null && (Object)(object)((Il2CppObjectBase)current3).TryCast<Dealer>() == (Object)null && (Object)(object)((Il2CppObjectBase)current3).TryCast<PoliceOfficer>() == (Object)null && (Object)(object)((Il2CppObjectBase)current3).TryCast<CartelGoon>() == (Object)null) { val2 = current3; break; } } if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null) { throw new InvalidOperationException("Native police/civilian impact sources were unavailable."); } NavMeshHit val3 = default(NavMeshHit); if (!NavMesh.SamplePosition(((Component)val).transform.position, ref val3, 5f, -1)) { throw new InvalidOperationException("No outdoor navigation point was available for gang fixture dealers."); } (Dealer, bool, bool)[] array2 = array.Select((Dealer d) => (Dealer: d, Invincible: ((NPC)d).Health._invincible, IgnoreCombat: ((NPC)d).NPCData.Behaviour.IgnoreCombatImpacts)).ToArray(); Cartel instance = NetworkSingleton<Cartel>.Instance; if ((Object)(object)instance == (Object)null) { throw new InvalidOperationException("The fixture cartel was unavailable."); } ECartelStatus status = instance.Status; try { Dealer[] array3 = array; foreach (Dealer val4 in array3) { ((NPC)val4).Health._invincible = false; ((NPC)val4).NPCData.Behaviour.IgnoreCombatImpacts = false; if (((NPC)val4).IsInVehicle) { ((NPC)val4).ExitVehicle(); } if ((Object)(object)((NPC)val4).CurrentBuilding != (Object)null) { ((NPC)val4).ExitBuilding(((NPC)val4).CurrentBuilding); } ((NPC)val4).SetActive(true); if (((NPC)val4).Health.IsDead || ((NPC)val4).Health.IsKnockedOut) { ((NPC)val4).Health.Revive(); } else { ((NPC)val4).Health.RestoreHealth(); } ((NPC)val4).Movement.Warp(((NavMeshHit)(ref val3)).position); Assert(((NPC)val4).IsActive && ((NPC)val4).IsConscious && ((NPC)val4).IsCurrentlyTargetable && !((NPC)val4).isInBuilding, "dealer prepared outdoors", new { dealer = ((NPC)val4).ID }, record); } instance.SetStatus((NetworkConnection)null, (ECartelStatus)3, false); string message; bool flag = gangs.TryForceAttack("benzies", "dealer:" + ((NPC)array[0]).FullName, now, 1f, out message); Assert(!RivalGangs.BenziesAllowedByStory && !flag && !gangs.HasActiveEvent && message.Contains("defeated", StringComparison.OrdinalIgnoreCase), "defeated Benzies rejected", new { nativeStatus = ((object)instance.Status/*cast due to .constrained prefix*/).ToString(), benziesMessage = message }, record); Dealer val5 = array[0]; PrepareStock(val5); Start(gangs, val5, now, "random"); Assert(!gangs.StatusText.StartsWith("Benzies Family", StringComparison.Ordinal), "defeated Benzies excluded from random attack", new { gang = gangs.StatusText }, record); NetworkObject source = gangs.ActiveAttackSource ?? throw new InvalidOperationException("Owned native gang source unavailable."); int eventCount = DealerRobbery.EventCount; Hit(val5, source); DealerRobberyResult lastResult = DealerRobbery.LastResult; NPCInventory inventory = ((NPC)val5).Inventory; List<ItemSlot> val6 = ((inventory != null) ? inventory.ItemSlots : null) ?? throw new InvalidOperationException("Dealer inventory disappeared."); Il2CppReferenceArray<ItemSlot> val7 = val5.overflowSlots ?? throw new InvalidOperationException("Dealer overflow disappeared."); Assert(!((NPC)val5).IsConscious && DealerRobbery.EventCount == eventCount + 1 && lastResult != null && lastResult.DealerId == ((NPC)val5).ID && lastResult.ItemUnits >= 10 && lastResult.Cash >= 190.0 && lastResult.InventoryEmpty && lastResult.CashEmpty && lastResult.Failures == 0 && Empty(val5) && val5.Cash == 0f && val6[0].IsRemovalLocked && ((Il2CppArrayBase<ItemSlot>)(object)val7)[0].IsRemovalLocked, "gang dealer defeat removes all stock and cash", ((object)lastResult) ?? ((object)"No robbery receipt."), record); val5.RpcLogic___SetCash_431000436(17f); Hit(val5, source); Assert(DealerRobbery.EventCount == eventCount + 1 && val5.Cash == 17f, "dealer loss applied once", new { receipts = DealerRobbery.EventCount - eventCount, remainingCash = val5.Cash }, record); gangs.Reset(); TestForeignSource(gangs, array[1], ((NetworkBehaviour)val).NetworkObject, now, "police", record); TestForeignSource(gangs, array[2], ((NetworkBehaviour)val2).NetworkObject, now, "civilian", record); val5 = array[3]; PrepareStock(val5); Start(gangs, val5, now, "mexican"); source = gangs.ActiveAttackSource ?? throw new InvalidOperationException("Cancellation source unavailable."); string text = StockSignature(val5); eventCount = DealerRobbery.EventCount; gangs.Reset(); Hit(val5, source); Assert(!((NPC)val5).IsConscious && !gangs.HasActiveEvent && DealerRobbery.EventCount == eventCount && text == StockSignature(val5), "cancelled encounter preserves dealer stock and cash", new { dealer = ((NPC)val5).ID, receipts = DealerRobbery.EventCount - eventCount }, record); } finally { gangs.Reset(); instance.SetStatus((NetworkConnection)null, status, false); (Dealer, bool, bool)[] array4 = array2; for (int num = 0; num < array4.Length; num++) { (Dealer, bool, bool) tuple = array4[num]; if (((NPC)tuple.Item1).Health.IsDead || ((NPC)tuple.Item1).Health.IsKnockedOut) { ((NPC)tuple.Item1).Health.Revive(); } ((NPC)tuple.Item1).Health._invincible = tuple.Item2; ((NPC)tuple.Item1).NPCData.Behaviour.IgnoreCombatImpacts = tuple.Item3; } } } private static void TestForeignSource(RivalGangs gangs, Dealer dealer, NetworkObject source, float now, string name, Action<object> record) { PrepareStock(dealer); Start(gangs, dealer, now, "mexican"); int eventCount = DealerRobbery.EventCount; string text = StockSignature(dealer); Hit(dealer, source); Assert(!((NPC)dealer).IsConscious && DealerRobbery.EventCount == eventCount && text == StockSignature(dealer), name + " defeat preserves dealer stock and cash", new { dealer = ((NPC)dealer).ID, receipts = DealerRobbery.EventCount - eventCount }, record); gangs.Reset(); } private static void Start(RivalGangs gangs, Dealer dealer, float now, string faction) { if (!gangs.TryForceAttack(faction, "dealer:" + ((NPC)dealer).FullName, now, 1f, out string message) || !gangs.HasActiveEvent) { throw new InvalidOperationException("Native gang fixture deployment failed: " + message); } } private static void Hit(Dealer dealer, NetworkObject source) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown ((NPC)dealer).ReceiveImpact(new Impact(((Component)dealer).transform.position + Vector3.up, Vector3.zero, 0f, Math.Max(((NPC)dealer).Health.Health, ((NPC)dealer).Health.MaxHealth) + 50f, (EImpactType)0, source)); } private static void PrepareStock(Dealer dealer) { string text = ((IEnumerable<string>)new string[3] { "ogkushseed", "sourdieselseed", "greencrackseed" }).FirstOrDefault((Func<string, bool>)Registry.ItemExists); if (text == null) { throw new InvalidOperationException("No native seed definition was available."); } ItemDefinition item = Registry.GetItem(text); ItemDefinition item2 = Registry.GetItem("cuke"); ItemDefinition item3 = Registry.GetItem("cash"); CashInstance val = ((item3 != null) ? ((Il2CppObjectBase)item3.GetDefaultInstance(1)).TryCast<CashInstance>() : null); if ((Object)(object)item == (Object)null || (Object)(object)item2 == (Object)null || val == null || ((NPC)dealer).Inventory.ItemSlots.Count < 2 || dealer.overflowSlots == null || ((Il2CppArrayBase<ItemSlot>)(object)dealer.overflowSlots).Length < 2) { throw new InvalidOperationException("Dealer sentinel definitions or native inventory slots were unavailable."); } val.SetBalance(67f, false); ItemSlot val2 = ((NPC)dealer).Inventory.ItemSlots[0]; ItemSlot val3 = ((NPC)dealer).Inventory.ItemSlots[1]; ItemSlot val4 = ((Il2CppArrayBase<ItemSlot>)(object)dealer.overflowSlots)[0]; val2.SetIsRemovalLocked(false); val3.SetIsRemovalLocked(false); val4.SetIsRemovalLocked(false); val2.SetStoredItem(item.GetDefaultInstance(3), false); val3.SetStoredItem((ItemInstance)(object)val, false); val4.SetStoredItem(item2.GetDefaultInstance(2), false); ((Il2CppArrayBase<ItemSlot>)(object)dealer.overflowSlots)[1].SetStoredItem(item.GetDefaultInstance(4), false); val2.SetIsRemovalLocked(true); val4.SetIsRemovalLocked(true); dealer.RpcLogic___SetCash_431000436(123f); if (val2.ItemInstance != null && !(((BaseItemInstance)val2.ItemInstance).ID != text) && ((BaseItemInstance)val2.ItemInstance).Quantity == 3) { ItemInstance itemInstance = val3.ItemInstance; if (itemInstance != null) { CashInstance obj = ((Il2CppObjectBase)itemInstance).TryCast<CashInstance>(); if (((obj != null) ? new float?(obj.Balance) : ((float?)null)).GetValueOrDefault() == 67f && val4.ItemInstance != null && !(((BaseItemInstance)val4.ItemInstance).ID != "cuke") && ((BaseItemInstance)val4.ItemInstance).Quantity == 2 && ((Il2CppArrayBase<ItemSlot>)(object)dealer.overflowSlots)[1].ItemInstance != null && !(((BaseItemInstance)((Il2CppArrayBase<ItemSlot>)(object)dealer.overflowSlots)[1].ItemInstance).ID != text) && ((BaseItemInstance)((Il2CppArrayBase<ItemSlot>)(object)dealer.overflowSlots)[1].ItemInstance).Quantity == 4 && dealer.Cash == 123f) { return; } } } throw new InvalidOperationException($"Native dealer sentinel insertion failed: regularSeed={val2.ItemInstance != null}, physicalCash={val3.ItemInstance != null}, overflowLegal={val4.ItemInstance != null}, overflowSeed={((Il2CppArrayBase<ItemSlot>)(object)dealer.overflowSlots)[1].ItemInstance != null}, saleCash={dealer.Cash}."); } private static bool Empty(Dealer dealer) { Enumerator<ItemSlot> enumerator = dealer.GetAllSlots().GetEnumerator(); while (enumerator.MoveNext()) { if (enumerator.Current.ItemInstance != null) { return false; } } return true; } private static string StockSignature(Dealer dealer) { List<string> list = new List<string>(); Enumerator<ItemSlot> enumerator = dealer.GetAllSlots().GetEnumerator(); while (enumerator.MoveNext()) { ItemInstance itemInstance = enumerator.Current.ItemInstance; if (itemInstance == null) { list.Add("empty"); continue; } CashInstance val = ((Il2CppObjectBase)itemInstance).TryCast<CashInstance>(); list.Add(((BaseItemInstance)itemInstance).ID + ":" + ((BaseItemInstance)itemInstance).Quantity + ":" + ((val == null) ? "" : val.Balance.ToString("R", CultureInfo.InvariantCulture))); } return dealer.Cash.ToString("R", CultureInfo.InvariantCulture) + ":" + string.Join("|", list); } private static void Assert(bool passed, string label, object detail, Action<object> record) { record(new { label, passed, detail }); if (!passed) { throw new InvalidOperationException("Gang fixture assertion failed: " + label); } } private static void ValidateFixture() { LoadManager instance = Singleton<LoadManager>.Instance; string text = ((instance != null) ? instance.LoadedGameFolderPath : null); if (!Environment.GetCommandLineArgs().Any((string a) => a.StartsWith("--difficulty-smoke-save=", StringComparison.OrdinalIgnoreCase)) || !GameWorld.IsReady || !GameWorld.IsAuthority || Player.PlayerList == null || Player.PlayerList.Count != 1 || string.IsNullOrWhiteSpace(text) || !Path.IsPathFullyQualified(text)) { throw new InvalidOperationException("Gang checks require the explicitly launched single-player copied-save fixture."); } string path = Path.Combine(text, "difficulty-smoke.fixture"); if (!File.Exists(path) || !File.ReadAllText(path).Contains("no-live-save", StringComparison.Ordinal)) { throw new InvalidOperationException("The gang fixture's no-live-save marker was missing."); } string path2 = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "AppData", "LocalLow", "TVGS", "Schedule I", "Saves"); if (Path.GetFullPath(text).StartsWith(Path.GetFullPath(path2), StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException("Gang fixture checks cannot mutate a live save tree."); } } } internal sealed class NativeSmokeTest { private const string ArgumentPrefix = "--difficulty-smoke-save="; private static bool suppressSaves; private readonly Stopwatch elapsed = Stopwatch.StartNew(); private readonly List<string> errors = new List<string>(); private readonly List<object> snapshots = new List<object>(); private readonly string fixturePath = ""; private readonly string originalSavePath = ""; private string stage = "menu"; private string startupError = ""; private bool startedLoad; private bool preparedWorld; private bool completed; private bool timeFrozen; private float previousTimeSpeed; private float nextProgress; private double nextLoadingProgress; private string lastNativeLoadState = "No native loading update received."; private float cleanupAt; private int baselineCount; private int largestCount; private int largestOutside; private int lowPressureOutside; private int cleanupCount; private bool bungalowStarted; private bool businessRejected; private bool replacementPassed; private PoliceOfficer? replacementOfficer; private float testClockOffset; private string bungalowMessage = ""; private string businessMessage = ""; private string raidDiagnostic = ""; private readonly ThreatHud fixtureHud = new ThreatHud(); private readonly List<double> directorMilliseconds = new List<double>(); private long directorAllocatedBytes; private int routeQueriesBeforeMeasurement; private int profileWritesBeforeMeasurement; private float performanceStarted; private float nextPerformanceTick; private bool idleHudPassed; private bool activeHudPassed; private bool hiddenHudPassed; private bool partialRaidCancelPassed; private bool automaticRaidPassed; private int automaticRaidCount; private float nextArrivalTick; private bool pairedPatrolsPassed; private bool restoredPatrolsPassed; private bool patrolCleanupPassed; private bool patrolMovementPassed; private object? patrolMovementState; private float patrolMovementStarted; internal bool Active { get; private set; } internal NativeSmokeTest(Harmony patches) { //IL_028e: Unknown result type (might be due to invalid IL or missing references) //IL_0295: Expected O, but got Unknown string text = Environment.GetCommandLineArgs().FirstOrDefault((string a) => a.StartsWith("--difficulty-smoke-save=", StringComparison.OrdinalIgnoreCase)); if (text == null) { return; } try { string path = text.Substring("--difficulty-smoke-save=".Length).Trim().Trim('"'); if (!Path.IsPathFullyQualified(path)) { throw new InvalidOperationException("The fixture path must be absolute."); } fixturePath = NormalizePath(path); string path2 = Path.Combine(fixturePath, "difficulty-smoke.fixture"); if (!File.Exists(path2)) { throw new InvalidOperationException("The copied-save fixture marker is missing."); } string text2 = File.ReadAllText(path2); if (!text2.Contains("no-live-save", StringComparison.Ordinal)) { throw new InvalidOperationException("The fixture marker must contain no-live-save."); } string text3 = text2.Split(new char[2] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault((string line) => line.StartsWith("original-save=", StringComparison.OrdinalIgnoreCase)); if (text3 == null || !Path.IsPathFullyQualified(text3.Substring("original-save=".Length))) { throw new InvalidOperationException("The fixture marker must identify the absolute original-save path."); } originalSavePath = NormalizePath(text3.Substring("original-save=".Length).Trim().Trim('"')); if (SamePath(fixturePath, originalSavePath) || IsInside(fixturePath, originalSavePath) || IsInside(originalSavePath, fixturePath)) { throw new InvalidOperationException("The copied fixture must be separate from its original save."); } string text4 = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "AppData", "LocalLow", "TVGS", "Schedule I", "Saves"); if (IsInside(fixturePath, text4) || SamePath(fixturePath, text4)) { throw new InvalidOperationException("The fixture cannot reside in the live save tree."); } Active = true; suppressSaves = true; HarmonyMethod val = new HarmonyMethod(typeof(NativeSmokeTest), "BeforeSave", (Type[])null); patches.Patch((MethodBase)AccessTools.Method(typeof(SaveManager), "Save", Type.EmptyTypes, (Type[])null), val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); patches.Patch((MethodBase)AccessTools.Method(typeof(SaveManager), "Save", new Type[1] { typeof(string) }, (Type[])null), val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MelonLogger.Msg("[Difficulty smoke] Active copied-save fixture: " + fixturePath + "; saves blocked."); } catch (Exception ex) { startupError = ex.Message; MelonLogger.Warning("[Difficulty smoke] Fixture setup rejected: " + ex.Message); } } private static bool BeforeSave() { return !suppressSaves; } internal bool Update(PolicePressure police, PropertyRaids raids, RivalGangs gangs) { if (!Active) { return false; } if (completed) { return true; } try { if (startupError.Length > 0) { throw new InvalidOperationException(startupError); } LoadManager instance = Singleton<LoadManager>.Instance; if ((!preparedWorld || !GameWorld.IsReady || !GameWorld.IsAuthority) && elapsed.Elapsed.TotalSeconds >= nextLoadingProgress) { nextLoadingProgress = elapsed.Elapsed.TotalSeconds + 10.0; lastNativeLoadState = DescribeNativeLoad(instance); snapshots.Add(new { label = "loading state", elapsedSeconds = elapsed.Elapsed.TotalSeconds, nativeState = lastNativeLoadState }); MelonLogger.Msg("[Difficulty smoke] Waiting during " + stage + ": " + lastNativeLoadState); } if (elapsed.Elapsed.TotalSeconds >= 180.0) { lastNativeLoadState = DescribeNativeLoad(instance); throw new TimeoutException($"Native fixture timed out during {stage}: {lastNativeLoadState}; {police.StatusSummary}; raid={raids.LastDeploymentDiagnostic}"); } if (!startedLoad) { if (elapsed.Elapsed.TotalSeconds < 10.0 || (Object)(object)instance == (Object)null || instance.IsLoading) { return true; } if (instance.IsGameLoaded || instance.IsInGameScene) { throw new InvalidOperationException("A game was already loaded before the fixture could select its copy."); } ValidateOriginalSeparation(instance); SaveInfo val = default(SaveInfo); if (!LoadManager.TryLoadSaveInfo(fixturePath, 4, ref val, true) || val == null) { throw new InvalidOperationException("The native loader rejected the copied fixture save."); } if (!SamePath(val.SavePath, fixturePath)) { throw new InvalidOperationException("The native save info resolved outside the copied fixture."); } startedLoad = true; stage = "loading"; MelonLogger.Msg("[Difficulty smoke] Loading the isolated copy through native StartGame."); instance.StartGame(val, false, false); return true; } if (!GameWorld.IsReady || !GameWorld.IsAuthority || (Object)(object)instance == (Object)null || !Player.Local.PlayerLoaded) { return true; } ValidateLoadedFixture(instance); if (Player.PlayerList == null || Player.PlayerList.Count != 1) { throw new InvalidOperationException("The fixture requires exactly one loaded player."); } float num = (float)elapsed.Elapsed.TotalSeconds + testClockOffset; if (!preparedWorld) { lastNativeLoadState = DescribeNativeLoad(instance); baselineCount = CountPolice().Total; if (baselineCount != 10) { throw new InvalidOperationException($"Expected 10 original officers before expansion, found {baselineCount}."); } PatrolSmokeChecks.CaptureBaseline(delegate(object snapshot) { snapshots.Add(snapshot); }); TimeManager instance2 = NetworkSingleton<TimeManager>.Instance; if ((Object)(object)instance2 == (Object)null) { throw new InvalidOperationException("The native time manager was unavailable."); } previousTimeSpeed = instance2.TimeSpeedMultiplier; instance2.SetTimeSpeedMultiplier(0f); timeFrozen = true; GameWorld.Pressure = 1f; preparedWorld = true; fixtureHud.Notify("Fixture startup message", num); fixtureHud.Reply("Fixture command reply", num); fixtureHud.Update(num, 1f, "", "", visible: true); idleHudPassed = (Object)(object)GameObject.Find("Difficulty Overhaul Threats") == (Object)null; if (!idleHudPassed) { errors.Add("Idle notification or command created a threat overlay."); } stage = "expanding police"; RecordSnapshot("before expansion", police); } if (stage == "expanding police") { GameWorld.Pressure = 1f; int num2 = ((largestCount == 0) ? baselineCount : largestCount); long timestamp = Stopwatch.GetTimestamp(); police.Update(num, 1f); double synchronousMilliseconds = (double)(Stopwatch.GetTimestamp() - timestamp) * 1000.0 / (double)Stopwatch.Frequency; (int, int) tuple = CountPolice(); if (tuple.Item1 > num2) { snapshots.Add(new { label = "population creation batch", created = tuple.Item1 - num2, synchronousMilliseconds = synchronousMilliseconds }); } largestCount = Math.Max(largestCount, tuple.Item1); largestOutside = Math.Max(largestOutside, tuple.Item2); if (num >= nextProgress) { nextProgress = num + 5f; RecordSnapshot("expanding", police); } if (tuple.Item1 < 40 || tuple.Item2 < 32) { return true; } RecordSnapshot("population ready", police); PatrolSmokeChecks.CheckAmbient(police, "high-pressure patrol distribution", 100f, delegate(object snapshot) { snapshots.Add(snapshot); }); pairedPatrolsPassed = true; patrolMovementState = PatrolSmokeChecks.CaptureMovement(police); patrolMovementStarted = num; stage = "walking patrol check"; return true; } if (stage == "walking patrol check") { GameWorld.Pressure = 1f; police.Update(num, 1f); if (num - patrolMovementStarted < 10f) { return true; } if (patrolMovementState == null) { throw new InvalidOperationException("Patrol movement baseline was unavailable."); } PatrolSmokeChecks.CheckMovement(police, patrolMovementState, delegate(object snapshot) { snapshots.Add(snapshot); }); patrolMovementPassed = true; stage = "lowering pressure"; return true; } if (stage == "lowering pressure") { GameWorld.Pressure = 0f; police.Update(num, 0f); (int, int) tuple2 = CountPolice(); if (tuple2.Item1 != 40 || tuple2.Item2 != 12) { return true; } lowPressureOutside = tuple2.Item2; RecordSnapshot("low pressure ready", police); stage = "raising pressure"; return true; } if (stage == "raising pressure") { GameWorld.Pressure = 1f; police.Update(num, 1f); (int, int) tuple3 = CountPolice(); if (tuple3.Item1 != 40 || tuple3.Item2 < 32) { return true; } RecordSnapshot("high pressure restored", police); PatrolSmokeChecks.CheckAmbient(police, "restored patrol distribution", 100f, delegate(object snapshot) { snapshots.Add(snapshot); }); restoredPatrolsPassed = true; stage = "checking replacement"; return true; } if (stage == "checking replacement") { Enumerator<PoliceOfficer> enumerator = PoliceOfficer.Officers.GetEnumerator(); while (enumerator.MoveNext()) { PoliceOfficer current = enumerator.Current; if ((Object)(object)current != (Object)null && ((NPC)current).ID.StartsWith("difficulty_officer_", StringComparison.Ordinal) && ((NPC)current).IsConscious && (Object)(object)((NPC)current).CurrentBuilding != (Object)null) { replacementOfficer = current; break; } } if ((Object)(object)replacementOfficer == (Object)null) { throw new InvalidOperationException("No temporary reserve officer was available for the replacement check."); } ((NPC)replacementOfficer).Health.Die(); if (((NPC)replacementOfficer).IsConscious) { throw new InvalidOperationException("The fixture officer did not enter the native defeated state."); } testClockOffset += 2f; police.Update(num + 2f, 1f); stage = "replacement delay"; return true; } if (stage == "replacement delay") { testClockOffset += 31f; police.Update(num + 31f, 1f); replacementPassed = (Object)(object)replacementOfficer != (Object)null && ((NPC)replacementOfficer).IsConscious && (Object)(object)((NPC)replacementOfficer).CurrentBuilding != (Object)null && (Object)(object)((NPC)replacementOfficer).Avatar != (Object)null && !((Component)((NPC)replacementOfficer).Avatar).gameObject.activeSelf; if (!replacementPassed) { errors.Add("Defeated reserve officer was not revived while hidden inside its station."); } RecordSnapshot("replacement checked", police); stage = "raid checks"; return true; } if (stage == "raid checks") { long timestamp2 = Stopwatch.GetTimestamp(); bungalowStarted = raids.TryForceRaid("Bungalow", num, immediate: true, out bungalowMessage); snapshots.Add(new { label = "immediate raid deployment cost", synchronousMilliseconds = (double)(Stopwatch.GetTimestamp() - timestamp2) * 1000.0 / (double)Stopwatch.Frequency }); raidDiagnostic = raids.LastDeploymentDiagnostic + " " + police.LastRaidDeploymentDiagnostic; if (!bungalowStarted || !raids.HasActiveEvent) { errors.Add("Bungalow immediate raid failed: " + bungalowMessage + " " + raidDiagnostic); } fixtureHud.Update(num, 1f, raids.StatusText, "", visible: true); GameObject val2 = GameObject.Find("Difficulty Overhaul Threats"); activeHudPassed = (Object)(object)val2 != (Object)null && val2.activeSelf && val2.GetComponentsInChildren<Text>(true).Length == 2 && val2.GetComponentsInChildren<Image>(true).Length == 0; if (!activeHudPassed) { errors.Add("Active raid HUD did not show exactly two text lines without a background panel."); } raids.Reset(); fixtureHud.Update(num, 1f, "", "", visible: true); hiddenHudPassed = (Object)(object)val2 != (Object)null && !val2.activeSelf; if ((Object)(object)val2 != (Object)null) { foreach (Text componentsInChild in val2.GetComponentsInChildren<Text>(true)) { hiddenHudPassed &= string.IsNullOrEmpty(componentsInChild.text); } } if (!hiddenHudPassed) { errors.Add("Completed/cancelled event left threat text or its overlay visible."); } if (raids.HasActiveEvent) { errors.Add("Bungalow raid remained active after cancellation."); } bool flag = raids.TryForceRaid("Laundromat", num, immediate: true, out businessMessage); businessRejected = !flag && !raids.HasActiveEvent && businessMessage.IndexOf("business", StringComparison.OrdinalIgnoreCase) >= 0; if (!businessRejected) { errors.Add("Laundromat was not rejected as a business: " + businessMessage); } raids.Reset(); gangs.Reset(); if (!raids.TryForceRaid("Bungalow", num, immediate: false, out string message)) { throw new InvalidOperationException("Staged raid warning failed: " + message); } testClockOffset += 601f; float now = num + 601f; raids.Update(now, allowStart: false); if (!raids.HasActiveEvent || !raids.IsDeploying || raids.DeployedCount != 1) { throw new InvalidOperationException("Overdue warning did not deploy exactly one staged officer."); } raids.Reset(); partialRaidCancelPassed = !raids.HasActiveEvent && !raids.IsDeploying && raids.DeployedCount == 0 && string.IsNullOrEmpty(raids.StatusText); if (!partialRaidCancelPassed) { errors.Add("Cancelling a partial raid left its event or officer lease active."); } snapshots.Add(new { label = "partial staged raid cancelled", passed = partialRaidCancelPassed }); if (!raids.TryForceRaid("Bungalow", now, immediate: false, out message)) { throw new InvalidOperationException("Second staged raid warning failed: " + message); } testClockOffset += 601f; nextArrivalTick = 0f; stage = "automatic raid staging"; return true; } if (stage == "automatic raid staging") { if (num < nextArrivalTick) { return true; } nextArrivalTick = num + 0.25f; int deployedCount = raids.DeployedCount; long timestamp3 = Stopwatch.GetTimestamp(); raids.Update(num, allowStart: false); double synchronousMilliseconds2 = (double)(Stopwatch.GetTimestamp() - timestamp3) * 1000.0 / (double)Stopwatch.Frequency; automaticRaidCount = raids.DeployedCount; snapshots.Add(new { label = "automatic raid arrival", count = automaticRaidCount, deploying = raids.IsDeploying, status = raids.StatusText, synchronousMilliseconds = synchronousMilliseconds2 }); if (!raids.HasActiveEvent || automaticRaidCount != deployedCount + 1 || automaticRaidCount > 8) { throw new InvalidOperationException("Automatic raid failed to deploy exactly one officer per director update: " + raids.LastDeploymentDiagnostic); } if (raids.IsDeploying) { if (automaticRaidCount >= 8 || !raids.StatusText.Contains("arriving", StringComparison.Ordinal)) { throw new InvalidOperationException("Partial raid did not remain in its arrival phase."); } if (automaticRaidCount == 1) { testClockOffset += 601f; } return true; } automaticRaidPassed = automaticRaidCount == 8 && raids.StatusText.Contains("DEFEND", StringComparison.Ordinal) && raids.StatusText.Contains("180s", StringComparison.Ordinal); if (!automaticRaidPassed) { errors.Add("Full staged squad did not start a fresh 180-second defense timer."); } raids.Reset(); fixtureHud.Update(num, 1f, "", "", visible: true); stage = "measuring steady director"; performanceStarted = num; nextPerformanceTick = num; routeQueriesBeforeMeasurement = police.RouteDiscoveryCount; profileWritesBeforeMeasurement = NpcArsenal.ProfileApplicationCount; return true; } if (stage == "measuring steady director") { if (num < nextPerformanceTick) { return true; } nextPerformanceTick = num + 0.25f; long allocatedBytesForCurrentThread = GC.GetAllocatedBytesForCurrentThread(); long timestamp4 = Stopwatch.GetTimestamp(); raids.Update(num, allowStart: false); gangs.Update(num, 1f, allowStart: false); police.Update(num, 1f); fixtureHud.Update(num, 1f, raids.StatusText, gangs.StatusText, visible: true); directorMilliseconds.Add((double)(Stopwatch.GetTimestamp() - timestamp4) * 1000.0 / (double)Stopwatch.Frequency); directorAllocatedBytes += GC.GetAllocatedBytesForCurrentThread() - allocatedBytesForCurrentThread; if (num - performanceStarted < 15f) { return true; } snapshots.Add(new { label = "steady director cost", samples = directorMilliseconds.Count, averageMilliseconds = directorMilliseconds.Average(), maximumMilliseconds = directorMilliseconds.Max(), p95Milliseconds = directorMilliseconds.OrderBy((double value) => value).ElementAt(Math.Min(directorMilliseconds.Count - 1, (int)Math.Ceiling((double)directorMilliseconds.Count * 0.95) - 1)), managedAllocatedBytes = directorAllocatedBytes, routeSceneDiscoveries = police.RouteDiscoveryCount - routeQueriesBeforeMeasurement, weaponProfileApplications = NpcArsenal.ProfileApplicationCount - profileWritesBeforeMeasurement, scope = "Managed module updates and their synchronous native calls, 40 registered officers, high pressure. Excludes asynchronous Unity NPC AI/rendering; batch fixture is not a gameplay FPS benchmark." }); PatrolSmokeChecks.ExportRouteMap(police, delegate(object snapshot) { snapshots.Add(snapshot); }); stage = "gang checks"; return true; } if (stage == "gang checks") { GangSmokeChecks.Run(gangs, num, delegate(object snapshot) { snapshots.Add(snapshot); }); gangs.Reset(); police.Reset(); fixtureHud.Dispose(); stage = "cleanup"; cleanupAt = num; return true; } if (stage == "cleanup") { if (num - cleanupAt < 3f) { return true; } cleanupCount = CountPolice().Total; RecordSnapshot("after reset", police); if (cleanupCount != baselineCount || cleanupCount != 10) { errors.Add($"Police cleanup left {cleanupCount} actors; expected the original 10."); } PatrolSmokeChecks.CheckCleanup(police, baselineCount, delegate(object snapshot) { snapshots.Add(snapshot); }); patrolCleanupPassed = true; Finish(errors.Count == 0, police, raids, gangs); } } catch (Exception ex) { errors.Add(ex.GetType().Name + ": " + ex.Message); Finish(passed: false, police, raids, gangs); } return true; } private void ValidateOriginalSeparation(LoadManager loader) { if (SamePath(fixturePath, originalSavePath)) { throw new InvalidOperationException("Original and copied save paths are identical."); } Il2CppReferenceArray<SaveInfo> saveGames = LoadManager.SaveGames; if (saveGames != null) { foreach (SaveInfo item in (Il2CppArrayBase<SaveInfo>)(object)saveGames) { if (item != null && !string.IsNullOrWhiteSpace(item.SavePath) && SamePath(item.SavePath, fixturePath)) { throw new InvalidOperationException("The fixture is registered as a live save slot."); } } } if (loader.IsGameLoaded) { throw new InvalidOperationException("A live save is already loaded."); } } private static string DescribeNativeLoad(LoadManager? loader) { //IL_008a: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)loader == (Object)null) { return "LoadManager unavailable; waiting for the menu loader."; } Player local = Player.Local; return $"loading={loader.IsLoading}; loaded={loader.IsGameLoaded}; inGameScene={loader.IsInGameScene}; status={loader.LoadStatus}; detail='{loader.GetLoadStatusText()}'; loadedPath='{loader.LoadedGameFolderPath}'; playerExists={(Object)(object)local != (Object)null}; playerLoaded={(Object)(object)local != (Object)null && local.PlayerLoaded}; authority={(Object)(object)local != (Object)null && ((NetworkBehaviour)local).IsServerInitialized}"; } catch (Exception ex) { return "Native loading diagnostics unavailable: " + ex.GetType().Name + ": " + ex.Message; } } private void ValidateLoadedFixture(LoadManager loader) { if (!SamePath(loader.LoadedGameFolderPath, fixturePath) || loader.ActiveSaveInfo == null || !SamePath(loader.ActiveSaveInfo.SavePath, fixturePath)) { throw new InvalidOperationException("Loaded save path does not match the isolated fixture; no tests were run."); } if (SamePath(loader.LoadedGameFolderPath, originalSavePath)) { throw new InvalidOperationException("The fixture unexpectedly loaded its live source save."); } } private static (int Total, int Outside) CountPolice() { HashSet<IntPtr> hashSet = new HashSet<IntPtr>(); int num = 0; List<PoliceOfficer> officers = PoliceOfficer.Officers; if (officers != null) { Enumerator<PoliceOfficer> enumerator = officers.GetEnumerator(); while (enumerator.MoveNext()) { PoliceOfficer current = enumerator.Current; if ((Object)(object)current != (Object)null && hashSet.Add(((Il2CppObjectBase)current).Pointer) && ((NPC)current).IsConscious && ((NPC)current).IsActive && (Object)(object)((NPC)current).CurrentBuilding == (Object)null) { num++; } } } return (Total: hashSet.Count, Outside: num); } private void RecordSnapshot(string label, PolicePressure police) { (int, int) tuple = CountPolice(); snapshots.Add(new { label = label, elapsedSeconds = elapsed.Elapsed.TotalSeconds, total = tuple.Item1, outside = tuple.Item2, status = police.StatusSummary, actors = DescribePolice() }); MelonLogger.Msg($"[Difficulty smoke] {label}: {tuple.Item1} total, {tuple.Item2} outside; {police.StatusSummary}"); } private static string[] DescribePolice() { List<string> list = new List<string>(); if (PoliceOfficer.Officers == null) { return list.ToArray(); } Enumerator<PoliceOfficer> enumerator = PoliceOfficer.Officers.GetEnumerator(); while (enumerator.MoveNext()) { PoliceOfficer current = enumerator.Current; if ((Object)(object)current != (Object)null) { list.Add($"{((NPC)current).ID}: active={((NPC)current).IsActive}, inside={(Object)(object)((NPC)current).CurrentBuilding != (Object)null}, server={((NetworkBehaviour)current).IsServerInitialized}, client={((NetworkBehaviour)current).IsClientInitialized}, avatarActive={(Object)(object)((NPC)current).Avatar != (Object)null && ((Component)((NPC)current).Avatar).gameObject.activeInHierarchy}"); } } return list.ToArray(); } private void Finish(bool passed, PolicePressure police, PropertyRaids raids, RivalGangs gangs) { if (completed) { return; } completed = true; Cleanup(delegate { raids.Reset(); }); Cleanup(delegate { gangs.Reset(); }); Cleanup(delegate { police.Reset(); }); Cleanup(delegate { fixtureHud.Dispose(); }); if (timeFrozen) { Cleanup(delegate { TimeManager instance = NetworkSingleton<TimeManager>.Instance; if (instance != null) { instance.SetTimeSpeedMultiplier(previousTimeSpeed); } }); } try { var value = new { passed = (passed && errors.Count == 0), fixturePath = fixturePath, originalSavePath = originalSavePath, stage = stage, elapsedSeconds = elapsed.Elapsed.TotalSeconds, baselineCount = baselineCount, largestCount = largestCount, largestOutside = largestOutside, lowPressureOutside = lowPressureOutside, cleanupCount = cleanupCount, replacementPassed = replacementPassed, bungalowStarted = bungalowStarted, businessRejected = businessRejected, idleHudPassed = idleHudPassed, activeHudPassed = activeHudPassed, hiddenHudPassed = hiddenHudPassed, partialRaidCancelPassed = partialRaidCancelPassed, automaticRaidPassed = automaticRaidPassed, automaticRaidCount = automaticRaidCount, pairedPatrolsPassed = pairedPatrolsPassed, restoredPatrolsPassed = restoredPatrolsPassed, patrolCleanupPassed = patrolCleanupPassed, patrolMovementPassed = patrolMovementPassed, bungalowMessage = bungalowMessage, businessMessage = businessMessage, raidDiagnostic = raidDiagnostic, lastNativeLoadState = lastNativeLoadState, savesBlocked = suppressSaves, errors = errors, snapshots = snapshots }; File.WriteAllText(Path.Combine(fixturePath, "smoke-result.json"), JsonSerializer.Serialize(value, new JsonSerializerOptions { WriteIndented = true })); MelonLogger.Msg("[Difficulty smoke] " + ((passed && errors.Count == 0) ? "PASS" : "FAIL") + "; result saved in fixture folder."); } catch (Exception ex) { MelonLogger.Error("[Difficulty smoke] Result write failed: " + ex.Message); } finally { try {