Decompiled source of Difficulty Overhaul v0.1.4

Mods/ScheduleIDifficultyOverhaul.dll

Decompiled a day ago
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
				{