Decompiled source of DynamicPressure v0.1.9

Dynamic Pressure.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Runtime.Versioning;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using I2.Loc;
using PerfectRandom.Sulfur.Core;
using PerfectRandom.Sulfur.Core.LevelGeneration;
using PerfectRandom.Sulfur.Core.Units;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Dynamic Pressure")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Dynamic Pressure")]
[assembly: AssemblyCopyright("Copyright ©  2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("bf243145-c378-4bad-8447-4747dbf03372")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace Ryuka.Sulfur.DynamicPressure;

[BepInPlugin("ryuka.sulfur.dynamicpressure", "Dynamic Pressure", "0.1.9")]
public sealed class DynamicPressurePlugin : BaseUnityPlugin
{
	private struct SpawnPointChoice
	{
		public Vector3 position;

		public Room room;

		public float distanceToPlayer;

		public string source;
	}

	private sealed class Snapshot
	{
		public bool hasGameManager;

		public bool hasPlayer;

		public bool playerAlive;

		public bool inSafeZone;

		public string gameState = "Unknown";

		public string currentEnvironment = "Unknown";

		public float playerHp;

		public float playerTimeSinceDamage;

		public string playerRoomName = "null";

		public bool playerRoomIsEndRoom;

		public int aliveNpcs;

		public int originalHostileAlive;

		public int originalEngagedHostiles;

		public int nearbyOriginalHostiles;

		public int originalTargetingPlayer;

		public int modHostileAlive;

		public int nearbyModHostiles;

		public int modTargetingPlayer;

		public int originalPressure;

		public int modPressure;

		public int currentPressure;

		public int targetPressure;

		public int deficit;

		public int candidateCount;

		public string candidateSource = "None";

		public int spawnedThisLevel;

		public int spawnedSinceLastOriginalKill;

		public float timeSinceLastOriginalKill;

		public float timeSinceLastModKill;

		public float nextSpawnIn;

		public bool wouldDelayOnAllEnemiesDead;
	}

	public const string PluginGuid = "ryuka.sulfur.dynamicpressure";

	public const string PluginName = "Dynamic Pressure";

	public const string PluginVersion = "0.1.9";

	private static DynamicPressurePlugin _instance;

	private Harmony _harmony;

	private ConfigEntry<bool> _enableMod;

	private ConfigEntry<bool> _enableAutoSpawn;

	private ConfigEntry<int> _pressureStyle;

	private ConfigEntry<bool> _enableOverlay;

	private ConfigEntry<Key> _overlayToggleKey;

	private ConfigEntry<Key> _manualSpawnKey;

	private ConfigEntry<bool> _logSpawnDecisions;

	private ConfigEntry<float> _scanInterval;

	private ConfigEntry<float> _enemyScanRadius;

	private ConfigEntry<float> _closeEnemyDistance;

	private ConfigEntry<float> _lowHealthStopThreshold;

	private ConfigEntry<float> _recentDamageStopWindow;

	private ConfigEntry<int> _minOriginalEngagedHostilesForSpawn;

	private ConfigEntry<float> _cooldownAfterModKillOnly;

	private ConfigEntry<float> _maxSecondsWithoutOriginalKill;

	private ConfigEntry<int> _maxModSpawnsPerOriginalEngaged;

	private ConfigEntry<bool> _allowEnvironmentFallbackCandidates;

	private ConfigEntry<float> _spawnDistanceMin;

	private ConfigEntry<float> _spawnDistanceMax;

	private ConfigEntry<float> _style1Cooldown;

	private ConfigEntry<float> _style2Cooldown;

	private ConfigEntry<float> _style3Cooldown;

	private ConfigEntry<int> _style1TargetPressure;

	private ConfigEntry<int> _style2TargetPressure;

	private ConfigEntry<int> _style3TargetPressure;

	private ConfigEntry<int> _style1MaxSpawnPerWave;

	private ConfigEntry<int> _style2MaxSpawnPerWave;

	private ConfigEntry<int> _style3MaxSpawnPerWave;

	private ConfigEntry<int> _style1MaxModAlive;

	private ConfigEntry<int> _style2MaxModAlive;

	private ConfigEntry<int> _style3MaxModAlive;

	private ConfigEntry<int> _style1MaxModPerRoom;

	private ConfigEntry<int> _style2MaxModPerRoom;

	private ConfigEntry<int> _style3MaxModPerRoom;

	private ConfigEntry<int> _style1MaxModPerLevel;

	private ConfigEntry<int> _style2MaxModPerLevel;

	private ConfigEntry<int> _style3MaxModPerLevel;

	private const float FailedWaveRetrySeconds = 1f;

	private const float DiagLogIntervalSeconds = 5f;

	private readonly List<UnitSO> _candidatePool = new List<UnitSO>();

	private readonly List<string> _recentEvents = new List<string>();

	private readonly Dictionary<int, int> _modSpawnedPerRoom = new Dictionary<int, int>();

	private readonly HashSet<int> _deadNpcIds = new HashSet<int>();

	private object _lastGraphContext;

	private float _nextScanTime;

	private float _nextSpawnTime;

	private float _nextDiagLogTime;

	private int _diagWindowWaves;

	private int _diagWindowSpawned;

	private string _pluginDirectory;

	private float _nextLanguageRetryTime;

	private GUIStyle _overlayStyle;

	private Vector2 _overlayScroll;

	private OnLanguageChange _reloadOverlayLanguage;

	private bool _spawnInProgress;

	private bool _inLevelTransition;

	private int _waveId;

	private int _spawnedThisLevel;

	private int _spawnedSinceLastOriginalKill;

	private float _lastOriginalKillTime = -9999f;

	private float _lastModKillTime = -9999f;

	private const float NeverDamagedTime = -99999f;

	private const float NeverDamagedSeconds = 9999f;

	private static float _playerLastDamageTime = -99999f;

	private static bool _playerDamageTimerActive;

	private string _lastUpdateFailure;

	private int _updateFailureCount;

	private Snapshot _snapshot = new Snapshot();

	private SpawnBlockReason _lastBlockReason;

	private string _lastDecision = "None";

	private string _lastSpawnSummary = "None";

	private string _lastAggroReport = "None";

	private static float PlayerTimeSinceDamage
	{
		get
		{
			if (_playerLastDamageTime <= -99999f)
			{
				return 9999f;
			}
			return Mathf.Clamp(Time.time - _playerLastDamageTime, 0f, 9999f);
		}
	}

	private void Awake()
	{
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_003b: Expected O, but got Unknown
		_instance = this;
		BindConfig();
		_pluginDirectory = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location) ?? string.Empty;
		_harmony = new Harmony("ryuka.sulfur.dynamicpressure");
		TryPatchTransitions();
		TryPatchNpcDie();
		TryPatchAllDeadTrigger();
		TryPatchPlayerDamageTimer();
		Log("Loaded.");
	}

	private void OnDestroy()
	{
		StopFollowingLanguage();
		try
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
		catch
		{
		}
		if ((Object)(object)_instance == (Object)(object)this)
		{
			_instance = null;
		}
	}

	private void BindConfig()
	{
		_enableMod = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableMod", true, "Enable Dynamic Pressure.");
		_enableAutoSpawn = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableAutoSpawn", true, "Enable automatic dynamic pressure spawning. Keep false for first debug testing.");
		_pressureStyle = ((BaseUnityPlugin)this).Config.Bind<int>("General", "PressureStyle", 1, "1 = Light, 2 = Heavy, 3 = Nightmare.");
		_enableOverlay = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug Overlay", "EnableOverlay", false, "Show debug overlay.");
		_overlayToggleKey = ((BaseUnityPlugin)this).Config.Bind<Key>("Debug Overlay", "OverlayToggleKey", (Key)102, "Toggle overlay key.");
		_manualSpawnKey = ((BaseUnityPlugin)this).Config.Bind<Key>("Debug Overlay", "ManualSpawnKey", (Key)101, "Manual debug spawn key.");
		_logSpawnDecisions = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug Overlay", "LogSpawnDecisions", false, "Write a periodic summary of spawn wave outcomes to the BepInEx log. Diagnostics only; does not affect spawning.");
		_scanInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Pressure", "ScanInterval", 1f, "Seconds between pressure scans.");
		_enemyScanRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Pressure", "EnemyScanRadius", 25f, "Nearby enemy scan radius.");
		_closeEnemyDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Pressure", "CloseEnemyDistance", 8f, "Enemies closer than this add extra pressure.");
		_lowHealthStopThreshold = ((BaseUnityPlugin)this).Config.Bind<float>("Safety", "LowHealthStopThreshold", 0.35f, "Do not spawn if player health is below this normalized value.");
		_recentDamageStopWindow = ((BaseUnityPlugin)this).Config.Bind<float>("Safety", "RecentDamageStopWindow", 4f, "Do not spawn if player was damaged within this many seconds.");
		_minOriginalEngagedHostilesForSpawn = ((BaseUnityPlugin)this).Config.Bind<int>("Anti Loop", "MinOriginalEngagedHostilesForSpawn", 1, "Require this many engaged original hostiles before spawning mod enemies.");
		_cooldownAfterModKillOnly = ((BaseUnityPlugin)this).Config.Bind<float>("Anti Loop", "CooldownAfterModKillOnly", 8f, "If only mod enemies are dying, pause spawning for this many seconds.");
		_maxSecondsWithoutOriginalKill = ((BaseUnityPlugin)this).Config.Bind<float>("Anti Loop", "MaxSecondsWithoutOriginalKill", 30f, "Stop spawning if no original enemy has died for this long after mod spawns.");
		_maxModSpawnsPerOriginalEngaged = ((BaseUnityPlugin)this).Config.Bind<int>("Anti Loop", "MaxModSpawnsPerOriginalEngaged", 2, "Soft budget: each engaged original enemy can support this many mod spawns.");
		_allowEnvironmentFallbackCandidates = ((BaseUnityPlugin)this).Config.Bind<bool>("Candidates", "AllowEnvironmentFallbackCandidates", false, "Use current environment enemy metadata if current level alive NPC candidate pool is empty.");
		_spawnDistanceMin = ((BaseUnityPlugin)this).Config.Bind<float>("Spawn", "SpawnDistanceMin", 8f, "Minimum spawn distance from player.");
		_spawnDistanceMax = ((BaseUnityPlugin)this).Config.Bind<float>("Spawn", "SpawnDistanceMax", 60f, "Maximum spawn distance from player.");
		_style1TargetPressure = ((BaseUnityPlugin)this).Config.Bind<int>("Style 1 - Light", "TargetPressure", 6, "Target pressure.");
		_style1Cooldown = ((BaseUnityPlugin)this).Config.Bind<float>("Style 1 - Light", "SpawnCooldown", 16f, "Spawn cooldown.");
		_style1MaxSpawnPerWave = ((BaseUnityPlugin)this).Config.Bind<int>("Style 1 - Light", "MaxSpawnPerWave", 1, "Max spawns per wave.");
		_style1MaxModAlive = ((BaseUnityPlugin)this).Config.Bind<int>("Style 1 - Light", "MaxModSpawnedAlive", 2, "Max alive mod-spawned NPCs.");
		_style1MaxModPerRoom = ((BaseUnityPlugin)this).Config.Bind<int>("Style 1 - Light", "MaxModSpawnedPerRoom", 3, "Max mod spawns per room.");
		_style1MaxModPerLevel = ((BaseUnityPlugin)this).Config.Bind<int>("Style 1 - Light", "MaxModSpawnedPerLevel", 8, "Max mod spawns per level.");
		_style2TargetPressure = ((BaseUnityPlugin)this).Config.Bind<int>("Style 2 - Heavy", "TargetPressure", 9, "Target pressure.");
		_style2Cooldown = ((BaseUnityPlugin)this).Config.Bind<float>("Style 2 - Heavy", "SpawnCooldown", 10f, "Spawn cooldown.");
		_style2MaxSpawnPerWave = ((BaseUnityPlugin)this).Config.Bind<int>("Style 2 - Heavy", "MaxSpawnPerWave", 1, "Max spawns per wave.");
		_style2MaxModAlive = ((BaseUnityPlugin)this).Config.Bind<int>("Style 2 - Heavy", "MaxModSpawnedAlive", 4, "Max alive mod-spawned NPCs.");
		_style2MaxModPerRoom = ((BaseUnityPlugin)this).Config.Bind<int>("Style 2 - Heavy", "MaxModSpawnedPerRoom", 5, "Max mod spawns per room.");
		_style2MaxModPerLevel = ((BaseUnityPlugin)this).Config.Bind<int>("Style 2 - Heavy", "MaxModSpawnedPerLevel", 16, "Max mod spawns per level.");
		_style3TargetPressure = ((BaseUnityPlugin)this).Config.Bind<int>("Style 3 - Nightmare", "TargetPressure", 13, "Target pressure.");
		_style3Cooldown = ((BaseUnityPlugin)this).Config.Bind<float>("Style 3 - Nightmare", "SpawnCooldown", 7f, "Spawn cooldown.");
		_style3MaxSpawnPerWave = ((BaseUnityPlugin)this).Config.Bind<int>("Style 3 - Nightmare", "MaxSpawnPerWave", 2, "Max spawns per wave.");
		_style3MaxModAlive = ((BaseUnityPlugin)this).Config.Bind<int>("Style 3 - Nightmare", "MaxModSpawnedAlive", 6, "Max alive mod-spawned NPCs.");
		_style3MaxModPerRoom = ((BaseUnityPlugin)this).Config.Bind<int>("Style 3 - Nightmare", "MaxModSpawnedPerRoom", 8, "Max mod spawns per room.");
		_style3MaxModPerLevel = ((BaseUnityPlugin)this).Config.Bind<int>("Style 3 - Nightmare", "MaxModSpawnedPerLevel", 28, "Max mod spawns per level.");
	}

	private void Update()
	{
		HandleKeys();
		EnsureLanguageSubscription();
		if (!_enableMod.Value)
		{
			_lastDecision = "BLOCKED";
			_lastBlockReason = SpawnBlockReason.Disabled;
			return;
		}
		try
		{
			UpdatePressure();
		}
		catch (Exception e)
		{
			ReportUpdateFailure(e);
		}
		LogSpawnDiagnostics();
	}

	private void UpdatePressure()
	{
		GameManager instance = StaticInstance<GameManager>.Instance;
		if ((Object)(object)instance != (Object)null && (Object)(object)instance.graphContext != (Object)null && _lastGraphContext != instance.graphContext)
		{
			_lastGraphContext = instance.graphContext;
			ResetRuntimeState("New graphContext");
		}
		if (Time.time >= _nextScanTime)
		{
			_nextScanTime = Time.time + Mathf.Max(0.2f, _scanInterval.Value);
			ScanPressure();
		}
		if (_enableAutoSpawn.Value && !_spawnInProgress && Time.time >= _nextSpawnTime)
		{
			TryAutoSpawn();
		}
	}

	private void ReportUpdateFailure(Exception e)
	{
		string text = e.GetType().Name + ": " + e.Message;
		if (text != _lastUpdateFailure)
		{
			_lastUpdateFailure = text;
			_updateFailureCount = 0;
			AddEvent("Pressure update failed: " + e.GetType().Name);
			((BaseUnityPlugin)this).Logger.LogError((object)"Pressure update failed. Repeats of this same failure are counted, not logged.");
			((BaseUnityPlugin)this).Logger.LogError((object)e);
		}
		_updateFailureCount++;
		_lastDecision = "FAILED";
		_lastBlockReason = SpawnBlockReason.UpdateFailed;
	}

	private void LogSpawnDiagnostics()
	{
		if (!_logSpawnDecisions.Value)
		{
			_nextDiagLogTime = Time.time + 5f;
			_diagWindowWaves = 0;
			_diagWindowSpawned = 0;
		}
		else if (!(Time.time < _nextDiagLogTime))
		{
			_nextDiagLogTime = Time.time + 5f;
			if (_diagWindowWaves > 0)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Spawn waves in last " + 5f.ToString("0") + "s: " + _diagWindowWaves + " | placed: " + _diagWindowSpawned + " | last decision: " + _lastDecision + " / " + _lastBlockReason.ToString() + " | room: " + _snapshot.playerRoomName + " | mod alive: " + _snapshot.modHostileAlive + " | this level: " + _spawnedThisLevel + "/" + GetMaxModPerLevel()));
			}
			_diagWindowWaves = 0;
			_diagWindowSpawned = 0;
		}
	}

	private void HandleKeys()
	{
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		Keyboard current = Keyboard.current;
		if (current != null)
		{
			if (((ButtonControl)current[_overlayToggleKey.Value]).wasPressedThisFrame)
			{
				_enableOverlay.Value = !_enableOverlay.Value;
			}
			if (((ButtonControl)current[_manualSpawnKey.Value]).wasPressedThisFrame && !_spawnInProgress)
			{
				SpawnWaveAsync(1, "Manual debug spawn");
			}
		}
	}

	private void ScanPressure()
	{
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_003d: Unknown result type (might be due to invalid IL or missing references)
		//IL_018d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0193: Unknown result type (might be due to invalid IL or missing references)
		Snapshot snapshot = new Snapshot();
		_candidatePool.Clear();
		GameManager instance = StaticInstance<GameManager>.Instance;
		if ((Object)(object)instance == (Object)null)
		{
			_snapshot = snapshot;
			_lastBlockReason = SpawnBlockReason.NoGameManager;
			return;
		}
		snapshot.hasGameManager = true;
		snapshot.gameState = ((object)instance.gameState/*cast due to .constrained prefix*/).ToString();
		snapshot.inSafeZone = instance.InSafeZone;
		snapshot.currentEnvironment = (((Object)(object)instance.currentEnvironment != (Object)null) ? ((object)Unsafe.As<WorldEnvironmentIds, WorldEnvironmentIds>(ref instance.currentEnvironment.id)/*cast due to .constrained prefix*/).ToString() : "null");
		if ((Object)(object)instance.PlayerUnit == (Object)null || (Object)(object)instance.PlayerObject == (Object)null)
		{
			_snapshot = snapshot;
			_lastBlockReason = SpawnBlockReason.NoPlayer;
			return;
		}
		Unit playerUnit = instance.PlayerUnit;
		Room unitRoom = GetUnitRoom(playerUnit);
		snapshot.hasPlayer = true;
		snapshot.playerAlive = playerUnit.IsAlive;
		snapshot.playerHp = SafeGetPlayerHp(playerUnit);
		snapshot.playerTimeSinceDamage = PlayerTimeSinceDamage;
		snapshot.playerRoomName = (((Object)(object)unitRoom != (Object)null) ? ((Object)unitRoom).name : "null");
		snapshot.playerRoomIsEndRoom = (Object)(object)unitRoom != (Object)null && unitRoom.IsEndRoom;
		List<Npc> aliveNpcs = instance.aliveNpcs;
		snapshot.aliveNpcs = aliveNpcs?.Count ?? 0;
		if (aliveNpcs != null)
		{
			for (int i = 0; i < aliveNpcs.Count; i++)
			{
				Npc val = aliveNpcs[i];
				if (!IsValidAliveNpc(val))
				{
					continue;
				}
				bool flag = (Object)(object)((Component)val).GetComponent<DynamicPressureSpawnMarker>() != (Object)null;
				if (!IsHostileToPlayer(val))
				{
					continue;
				}
				float num = Vector3.Distance(((Component)val).transform.position, instance.PlayerPosition);
				bool flag2 = num <= _enemyScanRadius.Value;
				bool flag3 = SafeTargetIsPlayer(val);
				bool flag4 = SafeHasKnownPlayerPosition(val, instance.PlayerUnit);
				bool flag5 = IsSameOrConnectedRoom(GetUnitRoom((Unit)(object)val), unitRoom);
				bool flag6 = flag2 || flag3 || flag4 || flag5;
				int num2 = CalculateNpcPressure(val, num, flag3, flag4);
				if (flag)
				{
					snapshot.modHostileAlive++;
					if (flag2)
					{
						snapshot.nearbyModHostiles++;
						snapshot.modPressure += num2;
					}
					if (flag3)
					{
						snapshot.modTargetingPlayer++;
					}
					continue;
				}
				snapshot.originalHostileAlive++;
				if (flag6)
				{
					snapshot.originalEngagedHostiles++;
				}
				if (flag2)
				{
					snapshot.nearbyOriginalHostiles++;
					snapshot.originalPressure += num2;
				}
				if (flag3)
				{
					snapshot.originalTargetingPlayer++;
				}
				TryAddCandidateFromNpc(val);
			}
		}
		if (_candidatePool.Count == 0 && _allowEnvironmentFallbackCandidates.Value)
		{
			AddEnvironmentFallbackCandidates(instance);
			snapshot.candidateSource = ((_candidatePool.Count > 0) ? "EnvironmentMetadata" : "None");
		}
		else
		{
			snapshot.candidateSource = ((_candidatePool.Count > 0) ? "CurrentLevelAliveNpcs" : "None");
		}
		snapshot.candidateCount = _candidatePool.Count;
		snapshot.targetPressure = GetTargetPressure();
		snapshot.currentPressure = snapshot.originalPressure + snapshot.modPressure;
		snapshot.deficit = snapshot.targetPressure - snapshot.currentPressure;
		snapshot.spawnedThisLevel = _spawnedThisLevel;
		snapshot.spawnedSinceLastOriginalKill = _spawnedSinceLastOriginalKill;
		snapshot.timeSinceLastOriginalKill = Time.time - _lastOriginalKillTime;
		snapshot.timeSinceLastModKill = Time.time - _lastModKillTime;
		snapshot.nextSpawnIn = Mathf.Max(0f, _nextSpawnTime - Time.time);
		snapshot.wouldDelayOnAllEnemiesDead = snapshot.originalHostileAlive == 0 && snapshot.modHostileAlive > 0;
		_snapshot = snapshot;
	}

	private void TryAutoSpawn()
	{
		if (!CanSpawn(out var blockReason, out var spawnCount))
		{
			_lastDecision = "BLOCKED";
			_lastBlockReason = blockReason;
		}
		else
		{
			SpawnWaveAsync(spawnCount, "Auto pressure deficit");
		}
	}

	private bool CanSpawn(out SpawnBlockReason blockReason, out int spawnCount)
	{
		//IL_0047: Unknown result type (might be due to invalid IL or missing references)
		//IL_004c: Unknown result type (might be due to invalid IL or missing references)
		spawnCount = 0;
		GameManager instance = StaticInstance<GameManager>.Instance;
		if ((Object)(object)instance == (Object)null)
		{
			blockReason = SpawnBlockReason.NoGameManager;
			return false;
		}
		if (_inLevelTransition)
		{
			blockReason = SpawnBlockReason.LevelTransition;
			return false;
		}
		if ((Object)(object)instance.PlayerUnit == (Object)null || (Object)(object)instance.PlayerObject == (Object)null)
		{
			blockReason = SpawnBlockReason.NoPlayer;
			return false;
		}
		if (((object)instance.gameState/*cast due to .constrained prefix*/).ToString() != "Running")
		{
			blockReason = SpawnBlockReason.GameStateNotRunning;
			return false;
		}
		if (instance.InSafeZone)
		{
			blockReason = SpawnBlockReason.SafeZone;
			return false;
		}
		Unit playerUnit = instance.PlayerUnit;
		if (!playerUnit.IsAlive)
		{
			blockReason = SpawnBlockReason.PlayerDead;
			return false;
		}
		if (SafeGetPlayerHp(playerUnit) <= _lowHealthStopThreshold.Value)
		{
			blockReason = SpawnBlockReason.LowHealth;
			return false;
		}
		if (PlayerTimeSinceDamage <= _recentDamageStopWindow.Value)
		{
			blockReason = SpawnBlockReason.RecentDamage;
			return false;
		}
		Room unitRoom = GetUnitRoom(playerUnit);
		if ((Object)(object)unitRoom != (Object)null && unitRoom.IsEndRoom)
		{
			blockReason = SpawnBlockReason.EndRoomBlocked;
			return false;
		}
		if (_snapshot.originalEngagedHostiles < _minOriginalEngagedHostilesForSpawn.Value)
		{
			blockReason = SpawnBlockReason.NoEngagedOriginalHostiles;
			return false;
		}
		if (_snapshot.currentPressure >= _snapshot.targetPressure)
		{
			blockReason = SpawnBlockReason.PressureAlreadyHigh;
			return false;
		}
		if (_snapshot.modHostileAlive >= GetMaxModAlive())
		{
			blockReason = SpawnBlockReason.MaxModSpawnedAlive;
			return false;
		}
		if (_spawnedThisLevel >= GetMaxModPerLevel())
		{
			blockReason = SpawnBlockReason.LevelSpawnBudgetExceeded;
			return false;
		}
		int num = Mathf.Max(1, _snapshot.originalEngagedHostiles) * Mathf.Max(1, _maxModSpawnsPerOriginalEngaged.Value);
		if (_spawnedSinceLastOriginalKill >= num)
		{
			blockReason = SpawnBlockReason.SpawnBudgetPerOriginalExceeded;
			return false;
		}
		if (_spawnedSinceLastOriginalKill > 0 && Time.time - _lastOriginalKillTime > _maxSecondsWithoutOriginalKill.Value)
		{
			blockReason = SpawnBlockReason.NoOriginalKillProgress;
			return false;
		}
		if (_lastModKillTime > _lastOriginalKillTime && Time.time - _lastModKillTime < _cooldownAfterModKillOnly.Value)
		{
			blockReason = SpawnBlockReason.RecentModKillOnly;
			return false;
		}
		if (_candidatePool.Count == 0)
		{
			blockReason = SpawnBlockReason.NoCandidateUnits;
			return false;
		}
		spawnCount = Mathf.Min(GetMaxSpawnPerWave(), Mathf.Max(1, _snapshot.deficit));
		spawnCount = Mathf.Min(spawnCount, GetMaxModAlive() - _snapshot.modHostileAlive);
		spawnCount = Mathf.Min(spawnCount, GetMaxModPerLevel() - _spawnedThisLevel);
		if (spawnCount <= 0)
		{
			blockReason = SpawnBlockReason.MaxModSpawnedAlive;
			return false;
		}
		blockReason = SpawnBlockReason.None;
		return true;
	}

	private async Task SpawnWaveAsync(int count, string reason)
	{
		if (_spawnInProgress)
		{
			return;
		}
		_spawnInProgress = true;
		_waveId++;
		int spawned = 0;
		try
		{
			ScanPressure();
			for (int i = 0; i < count; i++)
			{
				UnitSO val = PickCandidate();
				if ((Object)(object)val == (Object)null)
				{
					_lastDecision = "BLOCKED";
					_lastBlockReason = SpawnBlockReason.NoCandidateUnits;
					AddEvent("Spawn blocked: no candidate.");
					break;
				}
				if (!TryFindSpawnPoint(val, out var result))
				{
					_lastDecision = "BLOCKED";
					_lastBlockReason = SpawnBlockReason.NoValidNpcSpawnPoint;
					AddEvent("Spawn blocked: no valid NPCSpawn point.");
					break;
				}
				int roomId = GetRoomId(result.room);
				_modSpawnedPerRoom.TryGetValue(roomId, out var value);
				if (value >= GetMaxModPerRoom())
				{
					_lastDecision = "BLOCKED";
					_lastBlockReason = SpawnBlockReason.RoomSpawnBudgetExceeded;
					AddEvent("Spawn blocked: room budget exceeded.");
					break;
				}
				if (!(await SpawnOneAsync(val, result, reason)))
				{
					_lastDecision = "FAILED";
					_lastBlockReason = SpawnBlockReason.SpawnAsyncFailed;
					break;
				}
				spawned++;
			}
			if (spawned > 0)
			{
				_lastDecision = "SPAWNED";
				_lastBlockReason = SpawnBlockReason.None;
				_nextSpawnTime = Time.time + GetSpawnCooldown();
			}
		}
		catch (Exception ex)
		{
			_lastDecision = "FAILED";
			_lastBlockReason = SpawnBlockReason.SpawnAsyncFailed;
			AddEvent("Spawn exception: " + ex.GetType().Name);
			((BaseUnityPlugin)this).Logger.LogError((object)ex);
		}
		finally
		{
			if (spawned == 0)
			{
				_nextSpawnTime = Time.time + 1f;
			}
			_diagWindowWaves++;
			_diagWindowSpawned += spawned;
			_spawnInProgress = false;
			ScanPressure();
		}
	}

	private async Task<bool> SpawnOneAsync(UnitSO unitSo, SpawnPointChoice spawnPoint, string reason)
	{
		if ((Object)(object)unitSo == (Object)null)
		{
			return false;
		}
		Unit val = await unitSo.SpawnUnitAsync((MonoBehaviour)(object)this, spawnPoint.position, Quaternion.identity);
		if ((Object)(object)val == (Object)null)
		{
			return false;
		}
		Npc val2 = (Npc)(object)((val is Npc) ? val : null);
		if ((Object)(object)val2 == (Object)null)
		{
			val2 = ((Component)val).GetComponent<Npc>();
		}
		if ((Object)(object)val2 == (Object)null)
		{
			return false;
		}
		if ((Object)(object)spawnPoint.room != (Object)null)
		{
			((Unit)val2).currentRoom = spawnPoint.room;
			((Unit)val2).lastValidCurrentRoom = spawnPoint.room;
			((Unit)val2).lastRoomCalcPosition = ((Component)val2).transform.position;
		}
		DynamicPressureSpawnMarker dynamicPressureSpawnMarker = ((Component)val2).gameObject.AddComponent<DynamicPressureSpawnMarker>();
		dynamicPressureSpawnMarker.sourceUnitSo = unitSo;
		dynamicPressureSpawnMarker.spawnTime = Time.time;
		dynamicPressureSpawnMarker.spawnPosition = spawnPoint.position;
		dynamicPressureSpawnMarker.spawnRoom = spawnPoint.room;
		dynamicPressureSpawnMarker.spawnReason = reason;
		dynamicPressureSpawnMarker.waveId = _waveId;
		int roomId = GetRoomId(spawnPoint.room);
		_modSpawnedPerRoom.TryGetValue(roomId, out var value);
		_modSpawnedPerRoom[roomId] = value + 1;
		_spawnedThisLevel++;
		_spawnedSinceLastOriginalKill++;
		_lastSpawnSummary = string.Format("{0} at {1}, room={2}, source={3}", ((Object)unitSo).name, spawnPoint.position, ((Object)(object)spawnPoint.room != (Object)null) ? ((Object)spawnPoint.room).name : "null", spawnPoint.source);
		AddEvent("Spawned: " + ((Object)unitSo).name);
		((MonoBehaviour)this).StartCoroutine(ReportPlayerPositionLater(val2, dynamicPressureSpawnMarker));
		return true;
	}

	private IEnumerator ReportPlayerPositionLater(Npc npc, DynamicPressureSpawnMarker marker)
	{
		yield return (object)new WaitForSeconds(0.5f);
		GameManager instance = StaticInstance<GameManager>.Instance;
		if ((Object)(object)instance == (Object)null || (Object)(object)instance.PlayerObject == (Object)null || (Object)(object)instance.PlayerUnit == (Object)null)
		{
			_lastAggroReport = "Failed: no player";
			yield break;
		}
		if ((Object)(object)npc == (Object)null || !((Unit)npc).IsAlive || (Object)(object)npc.AiAgent == (Object)null)
		{
			_lastAggroReport = "Failed: invalid npc";
			yield break;
		}
		try
		{
			npc.AiAgent.ReportLastSeen(instance.PlayerUnit, ((Component)instance.PlayerUnit).transform.position, ((Component)npc.AiAgent).transform.position, npc.AiAgent.propagateToGroup);
			marker.aggroReported = true;
			marker.lastAggroReportTime = Time.time;
			marker.targetingPlayerAfterReport = SafeTargetIsPlayer(npc);
			marker.hasKnownPlayerPositionAfterReport = SafeHasKnownPlayerPosition(npc, instance.PlayerUnit);
			_lastAggroReport = ((marker.targetingPlayerAfterReport || marker.hasKnownPlayerPositionAfterReport) ? "Success" : "Reported but not targeting yet");
			AddEvent("ReportLastSeen: " + _lastAggroReport);
		}
		catch (Exception ex)
		{
			_lastAggroReport = "Exception: " + ex.GetType().Name;
			((BaseUnityPlugin)this).Logger.LogWarning((object)ex);
		}
	}

	private UnitSO PickCandidate()
	{
		if (_candidatePool.Count == 0)
		{
			return null;
		}
		return _candidatePool[Random.Range(0, _candidatePool.Count)];
	}

	private void TryAddCandidateFromNpc(Npc npc)
	{
		if (!((Object)(object)npc == (Object)null) && !((Object)(object)((Unit)npc).unitSO == (Object)null))
		{
			UnitSO unitSO = ((Unit)npc).unitSO;
			if (IsValidCandidateUnitSo(unitSO) && !_candidatePool.Contains(unitSO))
			{
				_candidatePool.Add(unitSO);
			}
		}
	}

	private void AddEnvironmentFallbackCandidates(GameManager gm)
	{
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		//IL_003b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0040: Unknown result type (might be due to invalid IL or missing references)
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		//IL_0092: Unknown result type (might be due to invalid IL or missing references)
		//IL_004f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0056: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			if ((Object)(object)gm == (Object)null || (Object)(object)gm.environmentsInOrder == (Object)null || (Object)(object)gm.currentEnvironment == (Object)null)
			{
				return;
			}
			Metadata metadata = gm.environmentsInOrder.GetMetadata(gm.currentEnvironment.id);
			if (metadata.availableEnemiesReadOnly == null)
			{
				return;
			}
			for (int i = 0; i < metadata.availableEnemiesReadOnly.Length; i++)
			{
				UnitSO asset = AssetAccess.GetAsset(metadata.availableEnemiesReadOnly[i]);
				if ((Object)(object)asset != (Object)null && IsValidCandidateUnitSo(asset) && !_candidatePool.Contains(asset))
				{
					_candidatePool.Add(asset);
				}
			}
		}
		catch (Exception ex)
		{
			AddEvent("Candidate fallback failed: " + ex.GetType().Name);
		}
	}

	private bool IsValidCandidateUnitSo(UnitSO so)
	{
		//IL_002b: 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)
		if ((Object)(object)so == (Object)null)
		{
			return false;
		}
		if (so.isCivilian)
		{
			return false;
		}
		if (so.isProtectedNpc)
		{
			return false;
		}
		if (so.ExperienceOnKill <= 0)
		{
			return false;
		}
		if ((so.unitType & 8) != 0)
		{
			return false;
		}
		return true;
	}

	private bool TryFindSpawnPoint(UnitSO unitSo, out SpawnPointChoice result)
	{
		//IL_003e: 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_019e: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b7: 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_00c3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00da: Unknown result type (might be due to invalid IL or missing references)
		//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
		//IL_00de: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
		result = default(SpawnPointChoice);
		GameManager instance = StaticInstance<GameManager>.Instance;
		if ((Object)(object)instance == (Object)null || (Object)(object)instance.PlayerUnit == (Object)null || (Object)(object)instance.PlayerObject == (Object)null || (Object)(object)unitSo == (Object)null)
		{
			return false;
		}
		Vector3 playerPosition = instance.PlayerPosition;
		List<SpawnPointChoice> list = new List<SpawnPointChoice>();
		List<SpawnPointChoice> list2 = new List<SpawnPointChoice>();
		List<Room> orderedRooms = instance.orderedRooms;
		if (orderedRooms == null || orderedRooms.Count == 0)
		{
			return false;
		}
		for (int i = 0; i < orderedRooms.Count; i++)
		{
			Room val = orderedRooms[i];
			if (!IsValidSpawnRoom(val))
			{
				continue;
			}
			NPCSpawn[] nPCSpawns = val.GetNPCSpawns();
			if (nPCSpawns == null || nPCSpawns.Length == 0)
			{
				continue;
			}
			foreach (NPCSpawn val2 in nPCSpawns)
			{
				if (!((Object)(object)val2 == (Object)null) && (val2.usableByTypes & unitSo.unitType) != 0)
				{
					Vector3 position = ((Component)val2).transform.position;
					float num = Vector3.Distance(position, playerPosition);
					SpawnPointChoice item = new SpawnPointChoice
					{
						position = position,
						room = val,
						distanceToPlayer = num,
						source = "GameManager.orderedRooms NPCSpawn"
					};
					if (num >= _spawnDistanceMin.Value && num <= _spawnDistanceMax.Value)
					{
						list.Add(item);
					}
					else if (num > _spawnDistanceMax.Value && num <= _spawnDistanceMax.Value * 2f)
					{
						list2.Add(item);
					}
				}
			}
		}
		if (list.Count > 0)
		{
			result = PickNearestRandomized(list, playerPosition);
			return true;
		}
		if (list2.Count > 0)
		{
			result = PickNearestRandomized(list2, playerPosition);
			result.source += " / loose distance";
			return true;
		}
		return false;
	}

	private bool IsValidSpawnRoom(Room room)
	{
		if ((Object)(object)room == (Object)null)
		{
			return false;
		}
		if (room.IsStartRoom)
		{
			return false;
		}
		if (room.IsEndRoom)
		{
			return false;
		}
		if (room.disallowEnemySpawn)
		{
			return false;
		}
		_modSpawnedPerRoom.TryGetValue(GetRoomId(room), out var value);
		if (value >= GetMaxModPerRoom())
		{
			return false;
		}
		return true;
	}

	private SpawnPointChoice PickNearestRandomized(List<SpawnPointChoice> choices, Vector3 playerPos)
	{
		choices.Sort((SpawnPointChoice a, SpawnPointChoice b) => a.distanceToPlayer.CompareTo(b.distanceToPlayer));
		int num = Mathf.Min(choices.Count, 5);
		return choices[Random.Range(0, num)];
	}

	private int CalculateNpcPressure(Npc npc, float distance, bool targeting, bool knowsPlayer)
	{
		int num = 1;
		if ((Object)(object)npc != (Object)null && (Object)(object)((Unit)npc).unitSO != (Object)null)
		{
			num = Mathf.Clamp(((Unit)npc).unitSO.SpawnCost, 1, 4);
		}
		if (distance <= _closeEnemyDistance.Value)
		{
			num++;
		}
		if (targeting)
		{
			num += 2;
		}
		else if (knowsPlayer)
		{
			num++;
		}
		return num;
	}

	private bool IsValidAliveNpc(Npc npc)
	{
		if ((Object)(object)npc != (Object)null && (Object)(object)((Component)npc).gameObject != (Object)null)
		{
			return ((Unit)npc).IsAlive;
		}
		return false;
	}

	private bool IsHostileToPlayer(Npc npc)
	{
		try
		{
			return (Object)(object)npc != (Object)null && ((Unit)npc).IsHostileTo((FactionIds)16);
		}
		catch
		{
			return false;
		}
	}

	private bool SafeTargetIsPlayer(Npc npc)
	{
		try
		{
			return (Object)(object)npc != (Object)null && npc.targetIsPlayer;
		}
		catch
		{
			return false;
		}
	}

	private bool SafeHasKnownPlayerPosition(Npc npc, Unit playerUnit)
	{
		try
		{
			return (Object)(object)npc != (Object)null && (Object)(object)npc.AiAgent != (Object)null && (Object)(object)playerUnit != (Object)null && npc.AiAgent.HasKnownPosition(playerUnit);
		}
		catch
		{
			return false;
		}
	}

	private float SafeGetPlayerHp(Unit playerUnit)
	{
		try
		{
			return ((Object)(object)playerUnit != (Object)null) ? playerUnit.GetNormalizedHealth() : 0f;
		}
		catch
		{
			return 0f;
		}
	}

	private Room GetUnitRoom(Unit unit)
	{
		if ((Object)(object)unit == (Object)null)
		{
			return null;
		}
		if ((Object)(object)unit.currentRoom != (Object)null)
		{
			return unit.currentRoom;
		}
		return unit.lastValidCurrentRoom;
	}

	private bool IsSameOrConnectedRoom(Room npcRoom, Room playerRoom)
	{
		if ((Object)(object)npcRoom == (Object)null || (Object)(object)playerRoom == (Object)null)
		{
			return false;
		}
		if ((Object)(object)npcRoom == (Object)(object)playerRoom)
		{
			return true;
		}
		if (playerRoom.connectedRooms == null)
		{
			return false;
		}
		for (int i = 0; i < playerRoom.connectedRooms.Length; i++)
		{
			if ((Object)(object)playerRoom.connectedRooms[i] == (Object)(object)npcRoom)
			{
				return true;
			}
		}
		return false;
	}

	private int GetRoomId(Room room)
	{
		if (!((Object)(object)room != (Object)null))
		{
			return 0;
		}
		return ((Object)room).GetInstanceID();
	}

	private int GetStyle()
	{
		return Mathf.Clamp(_pressureStyle.Value, 1, 3);
	}

	private int GetTargetPressure()
	{
		return GetStyle() switch
		{
			1 => _style1TargetPressure.Value, 
			2 => _style2TargetPressure.Value, 
			_ => _style3TargetPressure.Value, 
		};
	}

	private float GetSpawnCooldown()
	{
		return GetStyle() switch
		{
			1 => _style1Cooldown.Value, 
			2 => _style2Cooldown.Value, 
			_ => _style3Cooldown.Value, 
		};
	}

	private int GetMaxSpawnPerWave()
	{
		return GetStyle() switch
		{
			1 => _style1MaxSpawnPerWave.Value, 
			2 => _style2MaxSpawnPerWave.Value, 
			_ => _style3MaxSpawnPerWave.Value, 
		};
	}

	private int GetMaxModAlive()
	{
		return GetStyle() switch
		{
			1 => _style1MaxModAlive.Value, 
			2 => _style2MaxModAlive.Value, 
			_ => _style3MaxModAlive.Value, 
		};
	}

	private int GetMaxModPerRoom()
	{
		return GetStyle() switch
		{
			1 => _style1MaxModPerRoom.Value, 
			2 => _style2MaxModPerRoom.Value, 
			_ => _style3MaxModPerRoom.Value, 
		};
	}

	private int GetMaxModPerLevel()
	{
		return GetStyle() switch
		{
			1 => _style1MaxModPerLevel.Value, 
			2 => _style2MaxModPerLevel.Value, 
			_ => _style3MaxModPerLevel.Value, 
		};
	}

	private void ResetRuntimeState(string reason)
	{
		_candidatePool.Clear();
		_recentEvents.Clear();
		_modSpawnedPerRoom.Clear();
		_deadNpcIds.Clear();
		_waveId = 0;
		_spawnedThisLevel = 0;
		_spawnedSinceLastOriginalKill = 0;
		_lastOriginalKillTime = Time.time;
		_lastModKillTime = -9999f;
		_lastDecision = "Reset";
		_lastBlockReason = SpawnBlockReason.None;
		_lastSpawnSummary = "None";
		_lastAggroReport = "None";
		_inLevelTransition = false;
		_nextSpawnTime = Time.time + 3f;
		AddEvent("Reset: " + reason);
	}

	private static void OnAllDeadTriggerCheckAllDeadPrefix(List<Npc> ___allAliveNpcs)
	{
		if (___allAliveNpcs != null && ___allAliveNpcs.Count != 0)
		{
			___allAliveNpcs.RemoveAll(IsDynamicPressureSpawnedNpc);
		}
	}

	private static bool OnAllDeadTriggerRegisterDeathPrefix(Unit __0)
	{
		return !IsDynamicPressureSpawnedNpc((Npc)(object)((__0 is Npc) ? __0 : null));
	}

	private static bool IsDynamicPressureSpawnedNpc(Npc npc)
	{
		if ((Object)(object)npc != (Object)null)
		{
			return (Object)(object)((Component)npc).GetComponent<DynamicPressureSpawnMarker>() != (Object)null;
		}
		return false;
	}

	private static void OnNpcDiePostfix(Npc __instance)
	{
		if (!((Object)(object)_instance == (Object)null) && !((Object)(object)__instance == (Object)null))
		{
			_instance.HandleNpcDeath(__instance);
		}
	}

	private void HandleNpcDeath(Npc npc)
	{
		int instanceID = ((Object)npc).GetInstanceID();
		if (_deadNpcIds.Contains(instanceID))
		{
			return;
		}
		_deadNpcIds.Add(instanceID);
		bool flag = (Object)(object)((Component)npc).GetComponent<DynamicPressureSpawnMarker>() != (Object)null;
		if (IsHostileToPlayer(npc))
		{
			if (flag)
			{
				_lastModKillTime = Time.time;
				AddEvent("Mod NPC died: " + ((Object)npc).name);
			}
			else
			{
				_lastOriginalKillTime = Time.time;
				_spawnedSinceLastOriginalKill = 0;
				AddEvent("Original NPC died: " + ((Object)npc).name);
			}
		}
	}

	private static void OnLevelTransitionPrefix()
	{
		if (!((Object)(object)_instance == (Object)null))
		{
			_instance._inLevelTransition = true;
			_instance.ResetRuntimeState("Level transition");
		}
	}

	private void TryPatchTransitions()
	{
		MethodInfo prefix = AccessTools.Method(typeof(DynamicPressurePlugin), "OnLevelTransitionPrefix", (Type[])null, (Type[])null);
		TryPatchMethod(typeof(NextLevelTrigger), "MakeTransition", prefix);
		TryPatchMethod(typeof(GameManager), "CompleteLevel", prefix);
		TryPatchAllNamedMethods(typeof(GameManager), "GoToLevel", prefix);
		TryPatchAllNamedMethods(typeof(GameManager), "GoToChurchHub", prefix);
		TryPatchAllNamedMethods(typeof(GameManager), "GoToCarHub", prefix);
	}

	private static void OnUnitReceiveDamagePostfix(Unit __instance, bool __result)
	{
		if (__result && (Object)(object)__instance != (Object)null && __instance.isPlayer)
		{
			_playerLastDamageTime = Time.time;
		}
	}

	private void TryPatchPlayerDamageTimer()
	{
		//IL_0094: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a1: Expected O, but got Unknown
		try
		{
			MethodInfo methodInfo = AccessTools.Method(typeof(Unit), "ReceiveDamage", new Type[4]
			{
				typeof(float),
				typeof(DamageSourceData),
				typeof(Data),
				typeof(Vector3?)
			}, (Type[])null);
			MethodInfo methodInfo2 = AccessTools.Method(typeof(DynamicPressurePlugin), "OnUnitReceiveDamagePostfix", (Type[])null, (Type[])null);
			if (methodInfo == null || methodInfo2 == null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Failed to patch Unit.ReceiveDamage: method not found. The RecentDamage safety hold is inactive - waves will not wait out incoming player damage.");
				return;
			}
			_harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			_playerDamageTimerActive = true;
			Log("Patched Unit.ReceiveDamage (player damage timer).");
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Failed to patch Unit.ReceiveDamage: " + ex.Message + " | The RecentDamage safety hold is inactive."));
		}
	}

	private void TryPatchNpcDie()
	{
		//IL_0049: Unknown result type (might be due to invalid IL or missing references)
		//IL_0056: Expected O, but got Unknown
		try
		{
			MethodInfo methodInfo = AccessTools.Method(typeof(Npc), "Die", (Type[])null, (Type[])null);
			MethodInfo methodInfo2 = AccessTools.Method(typeof(DynamicPressurePlugin), "OnNpcDiePostfix", (Type[])null, (Type[])null);
			if (methodInfo != null && methodInfo2 != null)
			{
				_harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Log("Patched Npc.Die.");
			}
			else
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Failed to patch Npc.Die: method not found.");
			}
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Failed to patch Npc.Die: " + ex.Message));
		}
	}

	private void TryPatchAllDeadTrigger()
	{
		//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00be: Expected O, but got Unknown
		//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d5: Expected O, but got Unknown
		try
		{
			MethodInfo methodInfo = AccessTools.Method(typeof(AllDeadTrigger), "CheckAllDead", Type.EmptyTypes, (Type[])null);
			MethodInfo methodInfo2 = AccessTools.Method(typeof(AllDeadTrigger), "RegisterDeath", new Type[1] { typeof(Unit) }, (Type[])null);
			MethodInfo methodInfo3 = AccessTools.Method(typeof(DynamicPressurePlugin), "OnAllDeadTriggerCheckAllDeadPrefix", (Type[])null, (Type[])null);
			MethodInfo methodInfo4 = AccessTools.Method(typeof(DynamicPressurePlugin), "OnAllDeadTriggerRegisterDeathPrefix", (Type[])null, (Type[])null);
			if (methodInfo == null || methodInfo2 == null || methodInfo3 == null || methodInfo4 == null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Failed to patch AllDeadTrigger: expected methods were not found.");
				return;
			}
			_harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo3), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			_harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(methodInfo4), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			Log("Patched AllDeadTrigger progression filtering.");
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Failed to patch AllDeadTrigger: " + ex.Message));
		}
	}

	private void TryPatchMethod(Type type, string methodName, MethodInfo prefix)
	{
		//IL_003e: Unknown result type (might be due to invalid IL or missing references)
		//IL_004c: Expected O, but got Unknown
		try
		{
			MethodInfo methodInfo = AccessTools.Method(type, methodName, (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Patch skipped: " + type.Name + "." + methodName));
				return;
			}
			_harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(prefix), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			Log("Patched " + type.Name + "." + methodName);
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Patch failed: " + type.Name + "." + methodName + " / " + ex.Message));
		}
	}

	private void TryPatchAllNamedMethods(Type type, string methodName, MethodInfo prefix)
	{
		//IL_0067: Unknown result type (might be due to invalid IL or missing references)
		//IL_0075: Expected O, but got Unknown
		try
		{
			MethodInfo[] array = (from m in AccessTools.GetDeclaredMethods(type)
				where m.Name == methodName
				select m).ToArray();
			if (array.Length == 0)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Patch skipped: " + type.Name + "." + methodName));
				return;
			}
			for (int num = 0; num < array.Length; num++)
			{
				_harmony.Patch((MethodBase)array[num], new HarmonyMethod(prefix), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			Log("Patched " + type.Name + "." + methodName + " x" + array.Length);
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Patch failed: " + type.Name + "." + methodName + " / " + ex.Message));
		}
	}

	private void AddEvent(string text)
	{
		_recentEvents.Add(DateTime.Now.ToString("HH:mm:ss") + " " + text);
		while (_recentEvents.Count > 8)
		{
			_recentEvents.RemoveAt(0);
		}
	}

	private void Log(string text)
	{
		((BaseUnityPlugin)this).Logger.LogInfo((object)text);
	}

	private static string Label(string key, string english)
	{
		return OverlayPalette.Tint("#B9B9B9", OverlayText.Get(key, english) + ": ");
	}

	private static string Header(string key, string english)
	{
		return OverlayPalette.Tint("#7EC8FF", OverlayText.Get(key, english));
	}

	private static string Separator()
	{
		return OverlayPalette.Tint("#8A8A8A", "  |  ");
	}

	private static string TintDecision(string decision)
	{
		return decision switch
		{
			"SPAWNED" => OverlayPalette.Tint("#7BE07B", decision), 
			"FAILED" => OverlayPalette.Tint("#FF6B6B", decision), 
			"BLOCKED" => OverlayPalette.Tint("#FFC451", decision), 
			_ => OverlayPalette.Tint("#8A8A8A", decision), 
		};
	}

	private static string TintReason(SpawnBlockReason reason)
	{
		switch (reason)
		{
		case SpawnBlockReason.None:
			return OverlayPalette.Tint("#7BE07B", reason);
		case SpawnBlockReason.NoCandidateUnits:
		case SpawnBlockReason.NoValidNpcSpawnPoint:
		case SpawnBlockReason.RoomSpawnBudgetExceeded:
		case SpawnBlockReason.SpawnAsyncFailed:
		case SpawnBlockReason.UpdateFailed:
			return OverlayPalette.Tint("#FF6B6B", reason);
		default:
			return OverlayPalette.Tint("#FFC451", reason);
		}
	}

	private void OnGUI()
	{
		//IL_007e: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d3: 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_00e2: 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_002b: Expected O, but got Unknown
		if (_enableOverlay.Value)
		{
			if (_overlayStyle == null)
			{
				_overlayStyle = new GUIStyle(GUI.skin.label);
				_overlayStyle.richText = true;
			}
			float num = Mathf.Min(680f, (float)Screen.width - 40f);
			float num2 = Mathf.Max(240f, (float)Screen.height - 120f - 20f);
			Rect val = default(Rect);
			((Rect)(ref val))..ctor(20f, 120f, num, num2);
			GUI.Box(val, OverlayText.Get("overlay.title", "Dynamic Pressure Debug"));
			GUILayout.BeginArea(new Rect(((Rect)(ref val)).x + 10f, ((Rect)(ref val)).y + 25f, ((Rect)(ref val)).width - 20f, ((Rect)(ref val)).height - 35f));
			_overlayScroll = GUILayout.BeginScrollView(_overlayScroll, Array.Empty<GUILayoutOption>());
			Row(Label("overlay.enabled", "Enabled") + OverlayPalette.Flag(_enableMod.Value, nominal: true) + Separator() + Label("overlay.autoSpawn", "AutoSpawn") + OverlayPalette.Flag(_enableAutoSpawn.Value, nominal: true) + Separator() + Label("overlay.style", "Style") + OverlayPalette.Tint("#D89CFF", GetStyle()));
			Row(Label("overlay.gameState", "GameState") + OverlayPalette.Tint((_snapshot.gameState == "Running") ? "#7BE07B" : "#FFC451", _snapshot.gameState) + Separator() + Label("overlay.safeZone", "SafeZone") + OverlayPalette.Flag(_snapshot.inSafeZone, nominal: false) + Separator() + Label("overlay.env", "Env") + OverlayPalette.Tint("#FFFFFF", _snapshot.currentEnvironment));
			string hex = "#7BE07B";
			if (_snapshot.playerHp <= _lowHealthStopThreshold.Value)
			{
				hex = "#FF6B6B";
			}
			else if (_snapshot.playerHp < 0.5f)
			{
				hex = "#FFC451";
			}
			Row(Label("overlay.player", "Player") + (_snapshot.hasPlayer ? OverlayPalette.Tint("#7BE07B", OverlayText.Get("overlay.ok", "OK")) : OverlayPalette.Tint("#FF6B6B", OverlayText.Get("overlay.missing", "Missing"))) + Separator() + Label("overlay.alive", "Alive") + OverlayPalette.Flag(_snapshot.playerAlive, nominal: true) + Separator() + Label("overlay.hp", "HP") + OverlayPalette.Tint(hex, Mathf.RoundToInt(_snapshot.playerHp * 100f) + "%") + Separator() + Label("overlay.damageAgo", "DamageAgo") + (_playerDamageTimerActive ? OverlayPalette.Tint((_snapshot.playerTimeSinceDamage <= _recentDamageStopWindow.Value) ? "#FFC451" : "#FFFFFF", (_snapshot.playerTimeSinceDamage >= 9999f) ? OverlayText.Get("overlay.never", "never") : (_snapshot.playerTimeSinceDamage.ToString("0.0") + "s")) : OverlayPalette.Tint("#FF6B6B", OverlayText.Get("overlay.unavailable", "n/a"))));
			if (_lastUpdateFailure != null)
			{
				Row(Label("overlay.updateFailure", "Update Error") + OverlayPalette.Tint("#FF6B6B", _lastUpdateFailure) + OverlayPalette.Tint("#8A8A8A", " x" + _updateFailureCount));
			}
			Row(Label("overlay.room", "Room") + OverlayPalette.Tint("#FFFFFF", _snapshot.playerRoomName) + Separator() + Label("overlay.endRoom", "EndRoom") + OverlayPalette.Flag(_snapshot.playerRoomIsEndRoom, nominal: false));
			GUILayout.Space(8f);
			Row(Header("overlay.section.pressure", "Pressure"));
			Row(Label("overlay.originalHostileAlive", "Original Hostile Alive") + OverlayPalette.Count(_snapshot.originalHostileAlive) + Separator() + Label("overlay.engaged", "Engaged") + OverlayPalette.Count(_snapshot.originalEngagedHostiles) + Separator() + Label("overlay.targeting", "Targeting") + OverlayPalette.Count(_snapshot.originalTargetingPlayer));
			Row(Label("overlay.modHostileAlive", "Mod Hostile Alive") + OverlayPalette.Budget(_snapshot.modHostileAlive, GetMaxModAlive()) + Separator() + Label("overlay.targeting", "Targeting") + OverlayPalette.Count(_snapshot.modTargetingPlayer));
			Row(Label("overlay.originalPressure", "Original Pressure") + OverlayPalette.Count(_snapshot.originalPressure) + Separator() + Label("overlay.modPressure", "Mod Pressure") + OverlayPalette.Count(_snapshot.modPressure) + Separator() + Label("overlay.current", "Current") + OverlayPalette.Tint("#FFFFFF", _snapshot.currentPressure) + OverlayPalette.Tint("#8A8A8A", " / " + _snapshot.targetPressure) + Separator() + Label("overlay.deficit", "Deficit") + OverlayPalette.Tint((_snapshot.deficit > 0) ? "#FFC451" : "#7BE07B", _snapshot.deficit));
			GUILayout.Space(8f);
			Row(Header("overlay.section.spawnDecision", "Spawn Decision"));
			Row(Label("overlay.lastDecision", "Last Decision") + TintDecision(_lastDecision) + Separator() + Label("overlay.reason", "Reason") + TintReason(_lastBlockReason));
			Row(Label("overlay.nextSpawnIn", "Next Spawn In") + OverlayPalette.Tint((_snapshot.nextSpawnIn > 0f) ? "#FFFFFF" : "#8A8A8A", _snapshot.nextSpawnIn.ToString("0.0") + "s") + Separator() + Label("overlay.candidateSource", "Candidate Source") + OverlayPalette.Tint("#FFFFFF", _snapshot.candidateSource) + Separator() + Label("overlay.candidates", "Candidates") + OverlayPalette.Tint((_snapshot.candidateCount > 0) ? "#FFFFFF" : "#FF6B6B", _snapshot.candidateCount));
			GUILayout.Space(8f);
			Row(Header("overlay.section.modImpact", "Mod Impact"));
			Row(Label("overlay.spawnedThisLevel", "Spawned This Level") + OverlayPalette.Budget(_spawnedThisLevel, GetMaxModPerLevel()));
			Row(Label("overlay.spawnedSinceOriginalKill", "Spawned Since Last Original Kill") + OverlayPalette.Count(_spawnedSinceLastOriginalKill));
			Row(Label("overlay.timeSinceOriginalKill", "Time Since Original Kill") + OverlayPalette.Tint("#FFFFFF", _snapshot.timeSinceLastOriginalKill.ToString("0.0") + "s") + Separator() + Label("overlay.timeSinceModKill", "Time Since Mod Kill") + OverlayPalette.Tint("#FFFFFF", _snapshot.timeSinceLastModKill.ToString("0.0") + "s"));
			Row(Label("overlay.wouldDelayAllDead", "Would Delay OnAllEnemiesDead") + OverlayPalette.Flag(_snapshot.wouldDelayOnAllEnemiesDead, nominal: false));
			Row(Label("overlay.lastSpawn", "Last Spawn") + OverlayPalette.Tint("#FFFFFF", _lastSpawnSummary));
			Row(Label("overlay.lastAggroReport", "Last Aggro Report") + OverlayPalette.Tint("#FFFFFF", _lastAggroReport));
			GUILayout.Space(8f);
			Row(OverlayPalette.Tint("#8A8A8A", OverlayText.Get("overlay.keys", "Keys: F8 Manual Spawn | F9 Toggle Overlay")));
			GUILayout.Space(8f);
			Row(Header("overlay.section.recentEvents", "Recent Events"));
			for (int i = 0; i < _recentEvents.Count; i++)
			{
				Row(OverlayPalette.Tint("#8A8A8A", _recentEvents[i]));
			}
			GUILayout.EndScrollView();
			GUILayout.EndArea();
		}
	}

	private void Row(string text)
	{
		GUILayout.Label(text, _overlayStyle, Array.Empty<GUILayoutOption>());
	}

	private void EnsureLanguageSubscription()
	{
		//IL_004e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0058: Expected O, but got Unknown
		//IL_006a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0074: Expected O, but got Unknown
		if (_reloadOverlayLanguage != null)
		{
			if (OverlayText.GameCode == null && Time.unscaledTime >= _nextLanguageRetryTime)
			{
				_nextLanguageRetryTime = Time.unscaledTime + 1f;
				LoadOverlayLanguage();
			}
			return;
		}
		try
		{
			AsyncAssetLoading instance = StaticInstance<AsyncAssetLoading>.Instance;
			if (!((Object)(object)instance == (Object)null))
			{
				_reloadOverlayLanguage = new OnLanguageChange(LoadOverlayLanguage);
				instance.onLanguageChange = (OnLanguageChange)Delegate.Combine((Delegate?)(object)instance.onLanguageChange, (Delegate?)(object)_reloadOverlayLanguage);
				LoadOverlayLanguage();
			}
		}
		catch (Exception ex)
		{
			_reloadOverlayLanguage = null;
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Overlay language will not follow the game language: " + ex.Message));
		}
	}

	private void StopFollowingLanguage()
	{
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Expected O, but got Unknown
		OnLanguageChange reloadOverlayLanguage = _reloadOverlayLanguage;
		_reloadOverlayLanguage = null;
		if (reloadOverlayLanguage == null)
		{
			return;
		}
		try
		{
			AsyncAssetLoading instance = StaticInstance<AsyncAssetLoading>.Instance;
			if ((Object)(object)instance != (Object)null)
			{
				instance.onLanguageChange = (OnLanguageChange)Delegate.Remove((Delegate?)(object)instance.onLanguageChange, (Delegate?)(object)reloadOverlayLanguage);
			}
		}
		catch (Exception)
		{
		}
	}

	private void LoadOverlayLanguage()
	{
		if (OverlayText.Reload(_pluginDirectory, out var resolvedCode))
		{
			string text = "Overlay language: " + resolvedCode + " (game language code: " + (OverlayText.GameCode ?? "not loaded yet") + ") - " + OverlayText.LoadedCount + " strings from " + (OverlayText.LoadedFrom ?? _pluginDirectory);
			if (OverlayText.LoadedCount == 0)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)(text + " | " + (OverlayText.LastLoadNote ?? "no reason recorded")));
			}
			else
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)text);
			}
		}
	}
}
public sealed class DynamicPressureSpawnMarker : MonoBehaviour
{
	public UnitSO sourceUnitSo;

	public float spawnTime;

	public Vector3 spawnPosition;

	public Room spawnRoom;

	public string spawnReason;

	public int waveId;

	public bool aggroReported;

	public float lastAggroReportTime;

	public bool targetingPlayerAfterReport;

	public bool hasKnownPlayerPositionAfterReport;
}
public enum SpawnBlockReason
{
	None,
	Disabled,
	NoGameManager,
	NoPlayer,
	PlayerDead,
	SafeZone,
	GameStateNotRunning,
	LowHealth,
	RecentDamage,
	EndRoomBlocked,
	SpawnRoomIsEndRoom,
	NoOriginalHostiles,
	NoEngagedOriginalHostiles,
	NotEnoughOriginalHostiles,
	NoCandidateUnits,
	NoValidNpcSpawnPoint,
	Cooldown,
	MaxModSpawnedAlive,
	RoomSpawnBudgetExceeded,
	LevelSpawnBudgetExceeded,
	PressureAlreadyHigh,
	SpawnAsyncFailed,
	LevelTransition,
	RecentModKillOnly,
	NoOriginalKillProgress,
	SpawnBudgetPerOriginalExceeded,
	UpdateFailed
}
internal static class GameLanguage
{
	public const string FallbackCode = "en";

	private static readonly string[] ShippedCodes = new string[14]
	{
		"en", "sv", "fr", "it", "de", "es", "pt", "ru", "pl", "ja",
		"ko", "zh-CN", "tr", "ar"
	};

	public static string CurrentCode()
	{
		try
		{
			string currentLanguageCode = LocalizationManager.CurrentLanguageCode;
			return string.IsNullOrEmpty(currentLanguageCode) ? null : currentLanguageCode;
		}
		catch (Exception)
		{
			return null;
		}
	}

	public static bool IsRightToLeft()
	{
		try
		{
			return LocalizationManager.IsRight2Left;
		}
		catch (Exception)
		{
			return false;
		}
	}

	public static string FixRightToLeft(string text)
	{
		try
		{
			return LocalizationManager.FixRTL_IfNeeded(text, 0, false);
		}
		catch (Exception)
		{
			return text;
		}
	}

	public static string ResolveCode(string languageCode)
	{
		if (string.IsNullOrEmpty(languageCode))
		{
			return "en";
		}
		for (int i = 0; i < ShippedCodes.Length; i++)
		{
			if (string.Equals(ShippedCodes[i], languageCode, StringComparison.OrdinalIgnoreCase))
			{
				return ShippedCodes[i];
			}
		}
		string b = LanguageOf(languageCode);
		for (int j = 0; j < ShippedCodes.Length; j++)
		{
			if (string.Equals(LanguageOf(ShippedCodes[j]), b, StringComparison.OrdinalIgnoreCase))
			{
				return ShippedCodes[j];
			}
		}
		return "en";
	}

	private static string LanguageOf(string languageCode)
	{
		int num = languageCode.IndexOf('-');
		if (num <= 0)
		{
			return languageCode;
		}
		return languageCode.Substring(0, num);
	}
}
internal static class OverlayText
{
	[DataContract]
	private sealed class LangEntry
	{
		[DataMember(Name = "key")]
		public string Key;

		[DataMember(Name = "value")]
		public string Value;
	}

	[DataContract]
	private sealed class LangFile
	{
		[DataMember(Name = "entries")]
		public LangEntry[] Entries;
	}

	private static readonly Dictionary<string, string> Strings = new Dictionary<string, string>(StringComparer.Ordinal);

	public static string LoadedCode { get; private set; }

	public static string GameCode { get; private set; }

	public static int LoadedCount => Strings.Count;

	public static string LastLoadNote { get; private set; }

	public static string LoadedFrom { get; private set; }

	private static IEnumerable<string> CandidateDirectories(string pluginDirectory)
	{
		if (!string.IsNullOrEmpty(pluginDirectory))
		{
			yield return Path.Combine(pluginDirectory, "lang");
			yield return pluginDirectory;
			string text;
			try
			{
				text = Directory.GetParent(pluginDirectory)?.FullName;
			}
			catch (Exception)
			{
				text = null;
			}
			if (!string.IsNullOrEmpty(text))
			{
				yield return Path.Combine(text, "lang");
			}
		}
	}

	public static bool Reload(string pluginDirectory, out string resolvedCode)
	{
		string text = GameLanguage.CurrentCode();
		resolvedCode = GameLanguage.ResolveCode(text);
		if (LoadedCode == resolvedCode && GameCode == text)
		{
			return false;
		}
		Strings.Clear();
		LastLoadNote = null;
		LoadedFrom = null;
		if (resolvedCode != "en")
		{
			LoadFirstMatch(pluginDirectory, "en");
		}
		LoadFirstMatch(pluginDirectory, resolvedCode);
		LoadedCode = resolvedCode;
		GameCode = text;
		return true;
	}

	private static void LoadFirstMatch(string pluginDirectory, string code)
	{
		foreach (string item in CandidateDirectories(pluginDirectory))
		{
			if (LoadInto(item, code))
			{
				LoadedFrom = item;
				return;
			}
		}
		LastLoadNote = "no readable " + code + ".json under " + (pluginDirectory ?? "<unknown>");
	}

	private static bool LoadInto(string langDirectory, string code)
	{
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		string text = Path.Combine(langDirectory, code + ".json");
		if (!File.Exists(text))
		{
			return false;
		}
		try
		{
			LangFile langFile;
			using (MemoryStream memoryStream = new MemoryStream(File.ReadAllBytes(text)))
			{
				langFile = ((XmlObjectSerializer)new DataContractJsonSerializer(typeof(LangFile))).ReadObject((Stream)memoryStream) as LangFile;
			}
			if (langFile == null || langFile.Entries == null)
			{
				LastLoadNote = "no entries in " + text;
				return false;
			}
			bool flag = GameLanguage.IsRightToLeft();
			int num = 0;
			for (int i = 0; i < langFile.Entries.Length; i++)
			{
				LangEntry langEntry = langFile.Entries[i];
				if (langEntry != null && !string.IsNullOrEmpty(langEntry.Key) && langEntry.Value != null)
				{
					Strings[langEntry.Key] = (flag ? GameLanguage.FixRightToLeft(langEntry.Value) : langEntry.Value);
					num++;
				}
			}
			return num > 0;
		}
		catch (Exception ex)
		{
			LastLoadNote = ex.GetType().Name + " reading " + text;
			return false;
		}
	}

	public static string Get(string key, string englishFallback)
	{
		if (Strings.TryGetValue(key, out var value) && !string.IsNullOrEmpty(value))
		{
			return value;
		}
		return englishFallback;
	}
}
internal static class OverlayPalette
{
	public const string Header = "#7EC8FF";

	public const string Label = "#B9B9B9";

	public const string Value = "#FFFFFF";

	public const string Good = "#7BE07B";

	public const string Warn = "#FFC451";

	public const string Bad = "#FF6B6B";

	public const string Muted = "#8A8A8A";

	public const string Accent = "#D89CFF";

	public static string Tint(string hex, object value)
	{
		return "<color=" + hex + ">" + value?.ToString() + "</color>";
	}

	public static string Flag(bool value, bool nominal)
	{
		return Tint((value == nominal) ? "#7BE07B" : "#FFC451", value);
	}

	public static string Budget(int used, int limit)
	{
		string hex = "#7BE07B";
		if (limit > 0 && used >= limit)
		{
			hex = "#FF6B6B";
		}
		else if (limit > 0 && used >= limit - 1)
		{
			hex = "#FFC451";
		}
		return Tint(hex, used) + Tint("#8A8A8A", " / " + limit);
	}

	public static string Count(int value)
	{
		return Tint((value > 0) ? "#FFFFFF" : "#8A8A8A", value);
	}
}