Decompiled source of LevelDeath v0.8.1

LevelDeath.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("LevelDeath")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("LevelDeath")]
[assembly: AssemblyTitle("LevelDeath")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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 LevelDeath
{
	[BepInPlugin("oai.ultrakill.leveldeath", "Level Death", "0.8.4")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class LevelDeathPlugin : BaseUnityPlugin
	{
		private sealed class PendingWaveEnemy
		{
			public readonly GameObject Source;

			public readonly EnemyProfile Profile;

			public readonly float TimeStarted;

			public readonly Vector3 Position;

			public readonly Quaternion Rotation;

			public readonly Transform Parent;

			private readonly int _sourceId;

			public int SourceId => _sourceId;

			public PendingWaveEnemy(GameObject source, EnemyProfile profile, float timeStarted)
			{
				//IL_0032: 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)
				//IL_0037: 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)
				//IL_0046: Unknown result type (might be due to invalid IL or missing references)
				//IL_0058: Unknown result type (might be due to invalid IL or missing references)
				Source = source;
				Profile = profile;
				TimeStarted = timeStarted;
				Position = (((Object)(object)source != (Object)null) ? source.transform.position : Vector3.zero);
				Rotation = (((Object)(object)source != (Object)null) ? source.transform.rotation : Quaternion.identity);
				Parent = (((Object)(object)source != (Object)null) ? source.transform.parent : null);
				_sourceId = (((Object)(object)source != (Object)null) ? ((Object)source).GetInstanceID() : 0);
			}
		}

		private sealed class WeightedWaveEnemy
		{
			public readonly PendingWaveEnemy Enemy;

			public readonly float Weight;

			public WeightedWaveEnemy(PendingWaveEnemy enemy, float weight)
			{
				Enemy = enemy;
				Weight = Mathf.Max(0.001f, weight);
			}
		}

		private sealed class EnemySpawnCandidate
		{
			public readonly EnemyTemplatePool Pool;

			public readonly int EffectiveCost;

			public readonly bool IsGlobalOnly;

			public bool IsLarge
			{
				get
				{
					if (Pool != null)
					{
						return Pool.IsLarge;
					}
					return false;
				}
			}

			public int BaseCost
			{
				get
				{
					if (Pool == null)
					{
						return EffectiveCost;
					}
					return Pool.Cost;
				}
			}

			public EnemySpawnCandidate(EnemyTemplatePool pool, int effectiveCost, bool isGlobalOnly)
			{
				Pool = pool;
				EffectiveCost = Mathf.Max(1, effectiveCost);
				IsGlobalOnly = isGlobalOnly;
			}
		}

		private sealed class EnemyCooldownState
		{
			public float Heat;

			public float LastUpdatedTime;
		}

		private sealed class WeightedCandidate
		{
			public readonly EnemySpawnCandidate Candidate;

			public readonly float Weight;

			public WeightedCandidate(EnemySpawnCandidate candidate, float weight)
			{
				Candidate = candidate;
				Weight = Mathf.Max(0.001f, weight);
			}
		}

		[CompilerGenerated]
		private sealed class <DestroyOriginalAfterReplacementValid>d__131 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public GameObject source;

			public LevelDeathPlugin <>4__this;

			public GameObject clone;

			public string key;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <DestroyOriginalAfterReplacementValid>d__131(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0024: Unknown result type (might be due to invalid IL or missing references)
				//IL_002e: Expected O, but got Unknown
				int num = <>1__state;
				LevelDeathPlugin levelDeathPlugin = <>4__this;
				switch (num)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>2__current = (object)new WaitForSeconds(0.2f);
					<>1__state = 1;
					return true;
				case 1:
					<>1__state = -1;
					if ((Object)(object)source == (Object)null)
					{
						return false;
					}
					if (levelDeathPlugin.IsSpawnedCloneValid(clone, key))
					{
						Object.Destroy((Object)(object)source);
					}
					else
					{
						levelDeathPlugin.DestroyCloneIfPresent(clone);
						if (levelDeathPlugin._debugLogging != null && levelDeathPlugin._debugLogging.Value)
						{
							((BaseUnityPlugin)levelDeathPlugin).Logger.LogInfo((object)("Level Death kept original enemy because its replacement failed validation after spawn: " + ((Object)source).name));
						}
					}
					return false;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[CompilerGenerated]
		private sealed class <FindEnemyIdentifierComponents>d__180 : IEnumerable<Component>, IEnumerable, IEnumerator<Component>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private Component <>2__current;

			private int <>l__initialThreadId;

			private GameObject gameObject;

			public GameObject <>3__gameObject;

			public LevelDeathPlugin <>4__this;

			private Type <enemyIdentifierType>5__2;

			private Component <self>5__3;

			private Component[] <>7__wrap3;

			private int <>7__wrap4;

			Component IEnumerator<Component>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <FindEnemyIdentifierComponents>d__180(int <>1__state)
			{
				this.<>1__state = <>1__state;
				<>l__initialThreadId = Environment.CurrentManagedThreadId;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<enemyIdentifierType>5__2 = null;
				<self>5__3 = null;
				<>7__wrap3 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				int num = <>1__state;
				LevelDeathPlugin levelDeathPlugin = <>4__this;
				Component[] componentsInChildren;
				switch (num)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					if ((Object)(object)gameObject == (Object)null)
					{
						return false;
					}
					<enemyIdentifierType>5__2 = levelDeathPlugin.GetEnemyIdentifierType();
					if (<enemyIdentifierType>5__2 == null)
					{
						return false;
					}
					<self>5__3 = gameObject.GetComponent(<enemyIdentifierType>5__2);
					if ((Object)(object)<self>5__3 != (Object)null)
					{
						<>2__current = <self>5__3;
						<>1__state = 1;
						return true;
					}
					goto IL_0096;
				case 1:
					<>1__state = -1;
					goto IL_0096;
				case 2:
					{
						<>1__state = -1;
						goto IL_00fa;
					}
					IL_0108:
					if (<>7__wrap4 < <>7__wrap3.Length)
					{
						Component val = <>7__wrap3[<>7__wrap4];
						if ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)<self>5__3)
						{
							<>2__current = val;
							<>1__state = 2;
							return true;
						}
						goto IL_00fa;
					}
					<>7__wrap3 = null;
					return false;
					IL_0096:
					componentsInChildren = gameObject.GetComponentsInChildren(<enemyIdentifierType>5__2, true);
					if (componentsInChildren == null)
					{
						return false;
					}
					<>7__wrap3 = componentsInChildren;
					<>7__wrap4 = 0;
					goto IL_0108;
					IL_00fa:
					<>7__wrap4++;
					goto IL_0108;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}

			[DebuggerHidden]
			IEnumerator<Component> IEnumerable<Component>.GetEnumerator()
			{
				<FindEnemyIdentifierComponents>d__180 <FindEnemyIdentifierComponents>d__;
				if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
				{
					<>1__state = 0;
					<FindEnemyIdentifierComponents>d__ = this;
				}
				else
				{
					<FindEnemyIdentifierComponents>d__ = new <FindEnemyIdentifierComponents>d__180(0)
					{
						<>4__this = <>4__this
					};
				}
				<FindEnemyIdentifierComponents>d__.gameObject = <>3__gameObject;
				return <FindEnemyIdentifierComponents>d__;
			}

			[DebuggerHidden]
			IEnumerator IEnumerable.GetEnumerator()
			{
				return ((IEnumerable<Component>)this).GetEnumerator();
			}
		}

		[CompilerGenerated]
		private sealed class <PrimeGlobalEnemyPoolSoon>d__159 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public LevelDeathPlugin <>4__this;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <PrimeGlobalEnemyPoolSoon>d__159(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				int num = <>1__state;
				LevelDeathPlugin levelDeathPlugin = <>4__this;
				switch (num)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>2__current = null;
					<>1__state = 1;
					return true;
				case 1:
					<>1__state = -1;
					<>2__current = null;
					<>1__state = 2;
					return true;
				case 2:
					<>1__state = -1;
					if (levelDeathPlugin._enabled == null || !levelDeathPlugin._enabled.Value)
					{
						return false;
					}
					if (levelDeathPlugin._enemyPoolMode == null || levelDeathPlugin._enemyPoolMode.Value != EnemyPoolMode.GlobalEnemies)
					{
						return false;
					}
					if (levelDeathPlugin._cyberGrindOnly != null && levelDeathPlugin._cyberGrindOnly.Value)
					{
						string text = Normalize(levelDeathPlugin._currentSceneName);
						if (!text.Contains("cyber") && !text.Contains("endless") && !text.Contains("grid"))
						{
							return false;
						}
					}
					levelDeathPlugin.ScanGlobalEnemyPoolOncePerScene();
					return false;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		public const string PluginGuid = "oai.ultrakill.leveldeath";

		public const string PluginName = "Level Death";

		public const string PluginVersion = "0.8.4";

		internal static LevelDeathPlugin Instance;

		internal static ManualLogSource Log;

		private Harmony _harmony;

		private Type _enemyIdentifierType;

		private ConfigEntry<bool> _enabled;

		private ConfigEntry<float> _maxMeterCarriedBetweenScenes;

		private ConfigEntry<bool> _showHud;

		private ConfigEntry<bool> _cyberGrindOnly;

		private ConfigEntry<StartingLevelMode> _startingLevelMode;

		private ConfigEntry<float> _startingMeterPercent;

		private ConfigEntry<EnemyPoolMode> _enemyPoolMode;

		private ConfigEntry<DirectorActionMode> _directorActionMode;

		private ConfigEntry<bool> _allowUnknownEnemyTypes;

		private ConfigEntry<bool> _debugLogging;

		private ConfigEntry<float> _styleGainScale;

		private ConfigEntry<float> _softCapGainMultiplier;

		private ConfigEntry<float> _levelCompletionReward;

		private ConfigEntry<float> _deathMeterRetainedMultiplier;

		private ConfigEntry<float> _damagePenaltyFlat;

		private ConfigEntry<float> _damagePenaltyScale;

		private ConfigEntry<float> _damagePenaltyMultiplier;

		private ConfigEntry<float> _isolatedHitPenaltyMultiplier;

		private ConfigEntry<float> _consecutiveHitWindowSeconds;

		private ConfigEntry<float> _consecutiveHitPenaltyStep;

		private ConfigEntry<float> _maxConsecutiveHitPenaltyMultiplier;

		private ConfigEntry<float> _minimumMeterForBonusSpawns;

		private ConfigEntry<float> _toughPoolStartsAtMeter;

		private ConfigEntry<float> _globalEnemyMinimumMeter;

		private ConfigEntry<float> _spawnChanceMultiplier;

		private ConfigEntry<int> _maxBonusBudget;

		private ConfigEntry<int> _maxOvercapBonusBudget;

		private ConfigEntry<float> _deathMarkBudgetMultiplier;

		private ConfigEntry<int> _maxActiveBonusEnemies;

		private ConfigEntry<float> _globalEnemyCostMultiplier;

		private ConfigEntry<int> _globalEnemyFlatSurcharge;

		private ConfigEntry<float> _minimumSecondsBetweenBonusSpawns;

		private ConfigEntry<float> _minimumSceneAgeForSpawns;

		private ConfigEntry<float> _spawnRadiusMin;

		private ConfigEntry<float> _spawnRadiusMax;

		private ConfigEntry<bool> _allowTypeReplacements;

		private ConfigEntry<float> _replacementChanceLevel2;

		private ConfigEntry<float> _replacementChanceLevel3;

		private ConfigEntry<float> _replacementChanceLevel4;

		private ConfigEntry<float> _waveBatchWindowSeconds;

		private ConfigEntry<float> _lowTierWaveReadSeconds;

		private ConfigEntry<int> _processWaveImmediatelyAtSize;

		private ConfigEntry<bool> _allowSingleFodderReplacement;

		private ConfigEntry<int> _fullDirectorStartsAtWaveSize;

		private ConfigEntry<int> _mediumWaveMaxDuplicates;

		private ConfigEntry<int> _mediumWaveMaxReplacements;

		private ConfigEntry<int> _maxReplacementsLevel1;

		private ConfigEntry<int> _maxReplacementsLevel2;

		private ConfigEntry<int> _maxReplacementsLevel3;

		private ConfigEntry<int> _maxReplacementsLevel4;

		private ConfigEntry<int> _maxDuplicatesLevel1;

		private ConfigEntry<int> _maxDuplicatesLevel2;

		private ConfigEntry<int> _maxDuplicatesLevel3;

		private ConfigEntry<int> _maxDuplicatesLevel4;

		private ConfigEntry<float> _maxReplacementFractionOfWave;

		private ConfigEntry<bool> _guaranteeSafeDuplicateAtLevel2;

		private ConfigEntry<bool> _allowNaturalSpawnBuffs;

		private ConfigEntry<float> _naturalEnrageChanceLevel2;

		private ConfigEntry<float> _naturalRadiantChanceLevel3;

		private ConfigEntry<bool> _allowHighMeterEnemyBuffs;

		private ConfigEntry<float> _enrageChanceAt300;

		private ConfigEntry<float> _enrageChanceAt1000;

		private ConfigEntry<float> _radiantChanceAt400;

		private ConfigEntry<float> _radiantChanceAt1000;

		private ConfigEntry<float> _radianceTier;

		private ConfigEntry<bool> _sanitizeInheritedIdolState;

		private ConfigEntry<float> _cerberusPairChance;

		private ConfigEntry<bool> _enableLargeEnemyPool;

		private ConfigEntry<float> _largeEnemyRarityMultiplier;

		private ConfigEntry<float> _largeEnemyGroundCheckDistance;

		private ConfigEntry<float> _largeEnemyActionPenalty;

		private ConfigEntry<int> _largeEnemyMinNonFilthWaveSize;

		private ConfigEntry<bool> _enableEnemyVarietyCooldown;

		private ConfigEntry<float> _enemyVarietyCooldownDecaySeconds;

		private ConfigEntry<float> _enemyVarietyCooldownStrength;

		private ConfigEntry<float> _largeEnemyCooldownBonus;

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

		private readonly List<PendingWaveEnemy> _pendingWave = new List<PendingWaveEnemy>();

		private readonly Dictionary<string, EnemyTemplatePool> _limitedEnemyPools = new Dictionary<string, EnemyTemplatePool>();

		private readonly Dictionary<string, EnemyTemplatePool> _globalEnemyPools = new Dictionary<string, EnemyTemplatePool>();

		private readonly HashSet<string> _loggedUnknownTypes = new HashSet<string>();

		private readonly Dictionary<string, EnemyCooldownState> _enemyCooldowns = new Dictionary<string, EnemyCooldownState>();

		private GUIStyle _hudStyle;

		private float _meter;

		private float _nextAllowedBonusSpawnTime;

		private float _sceneLoadedTime;

		private bool _globalPoolScannedThisScene;

		private string _currentSceneName = string.Empty;

		private bool _levelCompletionRewardGiven;

		private float _pendingWaveDeadline = -1f;

		private float _lastDamageTime = -999f;

		private float _lastDeathPenaltyTime = -999f;

		private int _consecutiveDamageHits;

		private bool _activeWaveLargeEnemySpawned;

		private int _activeWaveNonFilthEnemyCount;

		private void Awake()
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			BindConfig();
			_enemyIdentifierType = AccessTools.TypeByName("EnemyIdentifier");
			_sceneLoadedTime = Time.time;
			Scene activeScene = SceneManager.GetActiveScene();
			_currentSceneName = ((Scene)(ref activeScene)).name ?? string.Empty;
			ApplyStartingMeterFloor();
			SceneManager.sceneLoaded += OnSceneLoaded;
			_harmony = new Harmony("oai.ultrakill.leveldeath");
			PatchSafely();
			((MonoBehaviour)this).StartCoroutine(PrimeGlobalEnemyPoolSoon());
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Level Death loaded. Wave director enabled. Levels: Normal, Enraged, Radiant, Level Death.");
		}

		private void OnDestroy()
		{
			SceneManager.sceneLoaded -= OnSceneLoaded;
			try
			{
				Harmony harmony = _harmony;
				if (harmony != null)
				{
					harmony.UnpatchSelf();
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Failed to unpatch Level Death cleanly: " + ex.Message));
			}
		}

		private ConfigEntry<T> Bind<T>(string section, string key, T defaultValue, string description)
		{
			return ((BaseUnityPlugin)this).Config.Bind<T>(section, key, defaultValue, description);
		}

		private void BindConfig()
		{
			_enabled = Bind("General", "Enabled", defaultValue: true, "Master switch for Level Death. Turning this off makes the mod inert and hides the HUD.");
			_enabled.SettingChanged += OnEnabledSettingChanged;
			_showHud = Bind("General", "ShowMeter", defaultValue: true, "Show the Level Death meter on screen.");
			_cyberGrindOnly = Bind("General", "CyberGrindOnly", defaultValue: false, "If true, Level Death only changes enemy spawns in Cyber Grind / Endless-style scenes.");
			_startingLevelMode = Bind("General", "StartingDifficulty", StartingLevelMode.Normal, "Starting meter floor for new scenes. Normal=0%, Enraged=100%, Radiant=200%, LevelDeath=300%, Custom=CustomStartingMeterPercent.");
			_startingMeterPercent = Bind("General", "CustomStartingMeterPercent", 0f, "Used only when StartingDifficulty is Custom. Clamped from 0% to 400%.");
			_enemyPoolMode = Bind("General", "EnemyPool", EnemyPoolMode.GlobalEnemies, "GlobalEnemies uses a curated global enemy pool. LimitedEnemyPool only clones enemies naturally present in the current scene/session.");
			_directorActionMode = Bind("General", "DirectorStyle", DirectorActionMode.MixedUpgradesAndReinforcements, "How Level Death spends pressure: AddReinforcementsOnly, MixedUpgradesAndReinforcements, or PreferUpgrades.");
			_debugLogging = Bind("General", "DebugLogging", defaultValue: false, "Verbose logs for wave decisions, replacement choices, damage penalties, and unknown enemy types.");
			_styleGainScale = Bind("Meter", "StyleGainScale", 0.02f, "Meter gained per style point before action bonuses are applied.");
			_softCapGainMultiplier = Bind("Meter", "GainMultiplierAbove100Percent", 0.82f, "Meter gain multiplier after Level 2 begins at 100%.");
			_levelCompletionReward = Bind("Meter", "LevelCompletionBonus", 25f, "Meter awarded once when the normal level-end rank screen appears.");
			_deathMeterRetainedMultiplier = Bind("Meter", "DeathMeterRetainedMultiplier", 0.5f, "Meter retained after death/respawn. Default 0.5 means dying halves the current Level Death percentage instead of resetting it to zero.");
			_damagePenaltyFlat = Bind("Meter", "BaseDamagePenalty", 6f, "Base meter removed when the player takes damage before isolated/consecutive hit scaling.");
			_damagePenaltyScale = Bind("Meter", "DamagePenaltyPerDamagePoint", 0.06f, "Extra meter removed per point of damage, when the damage value can be read.");
			_damagePenaltyMultiplier = Bind("Meter", "DamagePenaltyMultiplier", 1.3f, "Overall multiplier for damage meter loss. Default 1.3 makes damage remove about 30% more meter than previous versions.");
			_isolatedHitPenaltyMultiplier = Bind("Meter", "OccasionalHitPenaltyMultiplier", 0.25f, "Multiplier for isolated damage hits before streak scaling.");
			_consecutiveHitWindowSeconds = Bind("Meter", "ConsecutiveHitWindowSeconds", 4f, "Hits inside this window count as a damage streak and become increasingly punishing.");
			_consecutiveHitPenaltyStep = Bind("Meter", "ConsecutiveHitPenaltyStep", 0.35f, "Additional penalty multiplier per consecutive hit after the first.");
			_maxConsecutiveHitPenaltyMultiplier = Bind("Meter", "MaxConsecutiveHitPenaltyMultiplier", 1.8f, "Maximum multiplier for repeated hits in a short window.");
			_maxMeterCarriedBetweenScenes = Bind("Meter", "MaxMeterCarriedBetweenScenes", 400f, "Maximum meter preserved when loading a new scene. Level Death runs from 0% to 400%.");
			Bind("Meter", "OutOfCombatDecayPerSecond", 0f, "Deprecated/no-op. Time decay was removed so quiet levels do not punish the player.");
			Bind("Meter", "OutOfCombatDecayDelay", 0f, "Deprecated/no-op. Time decay was removed so quiet levels do not punish the player.");
			_minimumMeterForBonusSpawns = Bind("Director", "MinimumMeterForDirector", 18f, "Level Death will not add duplicate/replacement pressure below this meter value.");
			_toughPoolStartsAtMeter = Bind("Director", "ToughEnemiesOnlyAfterPercent", 9999f, "Deprecated/no-op. Low-tier enemies such as Filth, Strays, and Drones are no longer suppressed at high meter.");
			_globalEnemyMinimumMeter = Bind("Director", "GlobalEnemyAppearsAfterPercent", 70f, "Global-only enemy types cannot appear before this meter value, even when EnemyPool is GlobalEnemies.");
			_spawnChanceMultiplier = Bind("Director", "PressureChanceMultiplier", 1f, "Multiplies Level Death's duplicate/replacement chances.");
			_maxBonusBudget = Bind("Director", "BudgetAt100Percent", 12, "Base extra enemy-cost budget unlocked at 100% before the 100% budget jump.");
			_maxOvercapBonusBudget = Bind("Director", "BudgetAt400Percent", 38, "Maximum extra enemy-cost budget unlocked at 400%.");
			_deathMarkBudgetMultiplier = Bind("Director", "BudgetJumpAt100Percent", 2f, "When the meter reaches 100%, the director budget jumps by this multiplier before continuing toward the 400% cap.");
			_maxActiveBonusEnemies = Bind("Director", "MaxLivingBonusEnemies", 24, "Hard cap on living Level Death-created enemies.");
			_globalEnemyCostMultiplier = Bind("Director", "GlobalEnemyCostMultiplier", 1.75f, "Cost multiplier for enemies that are not already part of the current scene's limited pool.");
			_globalEnemyFlatSurcharge = Bind("Director", "GlobalEnemyCostBonus", 1, "Flat extra cost for global-only enemies. This keeps enemies like Ferryman expensive in tiny early rooms.");
			_minimumSecondsBetweenBonusSpawns = Bind("Director", "MinimumSecondsBetweenWaves", 0.45f, "Throttle after Level Death spawns duplicates/replacements.");
			_minimumSceneAgeForSpawns = Bind("Director", "SceneStartGraceSeconds", 3f, "Grace period after loading a scene before Level Death changes spawns. Natural enemies that appear during this grace period are now held and processed after the grace period instead of being discarded.");
			_waveBatchWindowSeconds = Bind("Director", "SpawnWaveReadSeconds", 1.45f, "How long Level Death keeps listening for drip-fed natural spawns before treating them as one wave. Higher values help rooms that drip-feed mixed enemies, but delay director actions slightly.");
			_lowTierWaveReadSeconds = Bind("Director", "LowTierSpawnWaveReadSeconds", 0.45f, "Shorter read window for waves made only of Filth/Strays/Drones. This prevents fast fodder from dying before Level Death can process it.");
			_processWaveImmediatelyAtSize = Bind("Director", "ProcessWaveImmediatelyAtSize", 4, "If a pending wave reaches this many enemies, process it almost immediately instead of waiting out the full read window.");
			Bind("Director", "WaveBatchWindowSeconds", 0.65f, "Deprecated/no-op. Use SpawnWaveReadSeconds instead.");
			_spawnRadiusMin = Bind("Director", "SpawnDistanceMin", 2.25f, "Minimum distance from the source spawn for bonus enemies.");
			_spawnRadiusMax = Bind("Director", "SpawnDistanceMax", 6.5f, "Maximum distance from the source spawn for bonus enemies.");
			_cerberusPairChance = Bind("Director", "CerberusPairChance", 0.55f, "When Level Death spawns a Cerberus, chance to spawn a second Cerberus nearby if safety caps allow it.");
			_enableEnemyVarietyCooldown = Bind("Director", "EnableHighCostEnemyCooldown", defaultValue: true, "Soft variety cooldown for enemies costing 7 or more. It never blocks a spawn, but heavily lowers odds after that enemy was recently selected.");
			_enemyVarietyCooldownDecaySeconds = Bind("Director", "HighCostEnemyCooldownDecaySeconds", 22f, "How quickly high-cost enemy cooldown pressure fades. Higher values keep rare enemies rare for longer.");
			_enemyVarietyCooldownStrength = Bind("Director", "HighCostEnemyCooldownStrength", 1f, "Overall strength of the high-cost enemy cooldown. 0 disables the effect even if enabled.");
			_largeEnemyCooldownBonus = Bind("Director", "LargeEnemyCooldownBonus", 3.5f, "Extra cooldown heat added to large enemies, making repeat large enemy picks substantially rarer.");
			_enableLargeEnemyPool = Bind("Large Enemies", "EnableLargeEnemyPool", defaultValue: true, "Allow very large enemies to appear in large-enough waves. They still require ground below and cannot replace flying enemies.");
			_largeEnemyRarityMultiplier = Bind("Large Enemies", "LargeEnemyRarityMultiplier", 0.5f, "Selection weight multiplier for large enemies. Default 0.5 makes them half as common as normal candidates.");
			_largeEnemyGroundCheckDistance = Bind("Large Enemies", "GroundCheckDistance", 10f, "Large enemies require ground below within this distance.");
			_largeEnemyActionPenalty = Bind("Large Enemies", "ActionPenaltyWhenLargeEnemySpawns", 1f, "When a large enemy spawns in a wave, reduce remaining duplicate/replacement caps by this amount.");
			_largeEnemyMinNonFilthWaveSize = Bind("Large Enemies", "MinimumNonFilthWaveSize", 5, "Large enemies may only appear in waves with this many non-Filth natural enemies. Filth are ignored because they are often spawned as swarms in small spaces.");
			_fullDirectorStartsAtWaveSize = Bind("Wave Rules", "FullDirectorStartsAtWaveSize", 4, "Waves this large use the normal per-level duplicate/replacement caps. Smaller waves are protected from obvious cloning/randomizer behavior.");
			_mediumWaveMaxDuplicates = Bind("Wave Rules", "MediumWaveMaxDuplicates", 1, "Maximum duplicates for small waves below FullDirectorStartsAtWaveSize but larger than one enemy. Default means 2-3 enemy waves can only get one duplicate.");
			_mediumWaveMaxReplacements = Bind("Wave Rules", "MediumWaveMaxReplacements", 1, "Maximum type replacements for small waves below FullDirectorStartsAtWaveSize but larger than one enemy. Default means 2-3 enemy waves can only get one replacement.");
			_guaranteeSafeDuplicateAtLevel2 = Bind("Wave Rules", "GuaranteeDuplicateAt100Percent", defaultValue: true, "At 100% and above, try to add one safe local duplicate, but only when the natural wave has at least two enemies.");
			_allowSingleFodderReplacement = Bind("Wave Rules", "AllowSingleFodderReplacement", defaultValue: true, "Allow lone Filth/Stray/Drone spawns to be type-replaced at 100%+. They still cannot be duplicated. This keeps drip-fed fodder waves active without cloning single scripted enemies.");
			_maxReplacementFractionOfWave = Bind("Wave Rules", "MaxReplacementFractionOfWave", 0.35f, "Personality-preservation cap. By default, no more than about 35% of a natural wave is type-replaced.");
			_maxDuplicatesLevel1 = Bind("Duplicate Caps", "Level1Normal", 1, "Maximum safe local duplicates per eligible spawn wave in Level 1: Normal. Single-enemy waves still receive zero duplicates.");
			_maxDuplicatesLevel2 = Bind("Duplicate Caps", "Level2Enraged", 2, "Maximum safe local duplicates per eligible spawn wave in Level 2: Enraged.");
			_maxDuplicatesLevel3 = Bind("Duplicate Caps", "Level3Radiant", 3, "Maximum safe local duplicates per eligible spawn wave in Level 3: Radiant.");
			_maxDuplicatesLevel4 = Bind("Duplicate Caps", "LevelDeath", 3, "Maximum safe local duplicates per eligible spawn wave in Level Death. Kept at 3 by default so Level Death favors quality over spam.");
			_allowTypeReplacements = Bind("Replacements", "AllowTypeReplacements", defaultValue: true, "Allow Level Death to replace some natural enemy spawns with more expensive global-pool enemies. Turn this off if a level behaves strangely.");
			_replacementChanceLevel2 = Bind("Replacements", "Level2EnragedChance", 0.45f, "Per-wave chance for each eligible natural spawn to be replaced by a tougher enemy in Level 2: Enraged, up to the cap.");
			_replacementChanceLevel3 = Bind("Replacements", "Level3RadiantChance", 0.62f, "Per-wave chance for each eligible natural spawn to be replaced by a tougher enemy in Level 3: Radiant, up to the cap.");
			_replacementChanceLevel4 = Bind("Replacements", "LevelDeathChance", 0.74f, "Per-wave chance for each eligible natural spawn to be replaced by a tougher enemy in Level Death, up to the cap.");
			_maxReplacementsLevel1 = Bind("Replacement Caps", "Level1Normal", 0, "Maximum type replacements per eligible spawn wave in Level 1: Normal.");
			_maxReplacementsLevel2 = Bind("Replacement Caps", "Level2Enraged", 1, "Maximum type replacements per eligible spawn wave in Level 2: Enraged.");
			_maxReplacementsLevel3 = Bind("Replacement Caps", "Level3Radiant", 3, "Maximum type replacements per eligible spawn wave in Level 3: Radiant.");
			_maxReplacementsLevel4 = Bind("Replacement Caps", "LevelDeath", 4, "Maximum type replacements per eligible spawn wave in Level Death.");
			_allowNaturalSpawnBuffs = Bind("Enemy Upgrades", "AllowNaturalEnemyUpgrades", defaultValue: true, "Allow natural, non-Level-Death spawns to be upgraded in place. This reduces enemy spam by making existing enemies more dangerous.");
			_naturalEnrageChanceLevel2 = Bind("Enemy Upgrades", "NaturalEnrageChance", 0.16f, "Chance for a natural spawn to become enraged in Level 2: Enraged and above.");
			_naturalRadiantChanceLevel3 = Bind("Enemy Upgrades", "NaturalRadiantChance", 0.14f, "Chance for a natural spawn to receive tier-1 radiant buffs in Level 3: Radiant and above.");
			_allowHighMeterEnemyBuffs = Bind("Enemy Upgrades", "AllowBonusEnemyUpgrades", defaultValue: true, "Allow Level Death-created enemies to occasionally become enraged or radiant.");
			_enrageChanceAt300 = Bind("Enemy Upgrades", "BonusEnemyEnrageChanceAt100Percent", 0.1f, "Chance for a Level Death-created reinforcement to be enraged at 100%, the start of Level 2.");
			_enrageChanceAt1000 = Bind("Enemy Upgrades", "BonusEnemyEnrageChanceAt400Percent", 0.3f, "Chance for a Level Death-created reinforcement to be enraged at 400%, the Level Death cap.");
			_radiantChanceAt400 = Bind("Enemy Upgrades", "BonusEnemyRadiantChanceAt200Percent", 0.08f, "Chance for a Level Death-created reinforcement to receive tier-1 radiant buffs at 200%, the start of Level 3.");
			_radiantChanceAt1000 = Bind("Enemy Upgrades", "BonusEnemyRadiantChanceAt400Percent", 0.22f, "Chance for a Level Death-created reinforcement to receive tier-1 radiant buffs at 400%.");
			_radianceTier = Bind("Enemy Upgrades", "RadianceTier", 1f, "Radiance tier assigned to radiant Level Death enemies. Default is true tier 1 radiance.");
			_allowUnknownEnemyTypes = Bind("Advanced Safety", "AllowUnknownEnemyTypes", defaultValue: false, "Unknown enemy types are ignored by default because custom/scripted enemies are more likely to break when cloned.");
			_sanitizeInheritedIdolState = Bind("Advanced Safety", "RemoveInheritedIdolBlessingFromClones", defaultValue: true, "When Level Death clones an enemy, clear inherited Idol/blessed runtime state so duplicates do not become invulnerable just because the source was protected by an Idol.");
		}

		private void OnEnabledSettingChanged(object sender, EventArgs e)
		{
			if (_enabled != null && !_enabled.Value)
			{
				_meter = 0f;
				_pendingWave.Clear();
				_pendingWaveDeadline = -1f;
				_consecutiveDamageHits = 0;
				_lastDamageTime = -999f;
				_nextAllowedBonusSpawnTime = Time.time + Mathf.Max(0.5f, (_minimumSecondsBetweenBonusSpawns != null) ? _minimumSecondsBetweenBonusSpawns.Value : 1f);
				if (_debugLogging != null && _debugLogging.Value)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)"Level Death disabled from config; meter reset and future spawns halted.");
				}
			}
			else if (_enabled != null && _enabled.Value)
			{
				_sceneLoadedTime = Time.time;
				_nextAllowedBonusSpawnTime = Time.time + ((_minimumSceneAgeForSpawns != null) ? Mathf.Max(0f, _minimumSceneAgeForSpawns.Value) : 3f);
				if (_debugLogging != null && _debugLogging.Value)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)"Level Death enabled from config; spawns will resume after the scene grace period.");
				}
			}
		}

		private void PatchSafely()
		{
			PatchFirstMethod(GetEnemyIdentifierType(), "Start", "EnemyStartPostfix", required: true);
			PatchFirstMethod(AccessTools.TypeByName("StyleHUD"), "AddPoints", "StyleAddPointsPostfix", required: false);
			PatchAllNamedMethods(AccessTools.TypeByName("NewMovement"), "GetHurt", "PlayerHurtPostfix", required: false);
			PatchAllNamedMethods(AccessTools.TypeByName("NewMovement"), "Respawn", "PlayerDeathOrRespawnPostfix", required: false);
			PatchAllNamedMethods(AccessTools.TypeByName("NewMovement"), "Die", "PlayerDeathOrRespawnPostfix", required: false);
			PatchAllNamedMethods(AccessTools.TypeByName("FinalRank"), "SetInfo", "LevelCompletePostfix", required: false);
		}

		private void PatchFirstMethod(Type type, string methodName, string patchMethodName, bool required)
		{
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Expected O, but got Unknown
			if (type == null)
			{
				if (required)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not find type for required patch method " + methodName + "."));
				}
				return;
			}
			MethodInfo methodInfo = AccessTools.Method(type, methodName, (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				if (required)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not find required method " + type.Name + "." + methodName + "."));
				}
			}
			else
			{
				HarmonyMethod val = new HarmonyMethod(typeof(LevelDeathPlugin).GetMethod(patchMethodName, BindingFlags.Static | BindingFlags.NonPublic));
				_harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Patched " + type.Name + "." + methodName + "."));
			}
		}

		private void PatchAllNamedMethods(Type type, string methodName, string patchMethodName, bool required)
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Expected O, but got Unknown
			if (type == null)
			{
				if (required)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not find type for required patch method " + methodName + "."));
				}
				return;
			}
			HarmonyMethod val = new HarmonyMethod(typeof(LevelDeathPlugin).GetMethod(patchMethodName, BindingFlags.Static | BindingFlags.NonPublic));
			int num = 0;
			foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(type))
			{
				if (!(declaredMethod == null) && !(declaredMethod.Name != methodName))
				{
					try
					{
						_harmony.Patch((MethodBase)declaredMethod, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
						num++;
					}
					catch (Exception ex)
					{
						((BaseUnityPlugin)this).Logger.LogDebug((object)("Skipped patch " + type.Name + "." + methodName + ": " + ex.Message));
					}
				}
			}
			if (num > 0)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Patched " + num + " overload(s) of " + type.Name + "." + methodName + "."));
			}
			else if (required)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not find required method " + type.Name + "." + methodName + "."));
			}
			else
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Optional patch not found: " + type.Name + "." + methodName + "."));
			}
		}

		private void Update()
		{
			ProcessPendingWaveIfReady();
		}

		private void OnGUI()
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: 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)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Expected O, but got Unknown
			//IL_005f: Expected O, but got Unknown
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			if (_showHud.Value && _enabled.Value)
			{
				if (_hudStyle == null)
				{
					_hudStyle = new GUIStyle(GUI.skin.box)
					{
						fontSize = 18,
						alignment = (TextAnchor)3,
						richText = true,
						padding = new RectOffset(10, 10, 8, 8)
					};
				}
				string rankName = GetRankName();
				string text = (CanSpawnInCurrentScene() ? string.Empty : "\n<size=12>spawns gated</size>");
				string text2 = ((_enemyPoolMode.Value == EnemyPoolMode.GlobalEnemies) ? "Global" : "Limited");
				GUI.Box(new Rect(20f, 20f, 340f, 70f), "<b>LEVEL DEATH</b>  " + rankName + "  <size=12>" + text2 + "</size>\n" + Mathf.RoundToInt(_meter) + "% " + MakeMeterBar(_meter) + text, _hudStyle);
			}
		}

		private static string MakeMeterBar(float value)
		{
			int num = Mathf.Clamp(Mathf.RoundToInt(((value >= 400f) ? 100f : Mathf.Repeat(Mathf.Max(0f, value), 100f)) / 10f), 0, 10);
			return new string('|', num) + new string('.', 10 - num);
		}

		private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			_processedEnemies.Clear();
			_pendingWave.Clear();
			_pendingWaveDeadline = -1f;
			_enemyCooldowns.Clear();
			_limitedEnemyPools.Clear();
			_loggedUnknownTypes.Clear();
			LevelDeathSpawnedMarker.ActiveCount = 0;
			_meter = Mathf.Min(_meter, Mathf.Clamp((_maxMeterCarriedBetweenScenes != null) ? _maxMeterCarriedBetweenScenes.Value : 400f, 0f, 400f));
			ApplyStartingMeterFloor();
			_sceneLoadedTime = Time.time;
			_globalPoolScannedThisScene = false;
			_currentSceneName = ((Scene)(ref scene)).name ?? string.Empty;
			_levelCompletionRewardGiven = false;
			_consecutiveDamageHits = 0;
			_lastDamageTime = -999f;
			_nextAllowedBonusSpawnTime = Time.time + _minimumSceneAgeForSpawns.Value;
			((MonoBehaviour)this).StartCoroutine(PrimeGlobalEnemyPoolSoon());
		}

		private static void EnemyStartPostfix(object __instance)
		{
			try
			{
				Instance?.OnEnemyStarted(__instance);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogError((object)("Level Death EnemyStartPostfix failed: " + ex));
				}
			}
		}

		private static void StyleAddPointsPostfix(object[] __args)
		{
			try
			{
				if (!((Object)(object)Instance == (Object)null) && __args != null && __args.Length != 0)
				{
					int points = ReadIntLike(__args[0]);
					string text = ((__args.Length > 1) ? Convert.ToString(__args[1]) : string.Empty);
					Instance.OnStylePoints(points, text ?? string.Empty);
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogError((object)("Level Death StyleAddPointsPostfix failed: " + ex));
				}
			}
		}

		private static void PlayerHurtPostfix(object[] __args)
		{
			try
			{
				if ((Object)(object)Instance == (Object)null)
				{
					return;
				}
				float num = 0f;
				if (__args != null)
				{
					foreach (object obj in __args)
					{
						if (obj is int num2)
						{
							num = Mathf.Max(num, (float)num2);
							break;
						}
						if (obj is float num3)
						{
							num = Mathf.Max(num, num3);
							break;
						}
					}
				}
				Instance.OnPlayerHurt(num);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogError((object)("Level Death PlayerHurtPostfix failed: " + ex));
				}
			}
		}

		private static void PlayerDeathOrRespawnPostfix()
		{
			try
			{
				Instance?.OnPlayerDeathOrRespawn();
			}
			catch (Exception ex)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogError((object)("Level Death PlayerDeathOrRespawnPostfix failed: " + ex));
				}
			}
		}

		private static void LevelCompletePostfix()
		{
			try
			{
				Instance?.OnLevelComplete();
			}
			catch (Exception ex)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogError((object)("Level Death LevelCompletePostfix failed: " + ex));
				}
			}
		}

		private static int ReadIntLike(object value)
		{
			if (value is int)
			{
				return (int)value;
			}
			if (value is float num)
			{
				return Mathf.RoundToInt(num);
			}
			if (value is double a)
			{
				return (int)Math.Round(a);
			}
			return 0;
		}

		private void OnStylePoints(int points, string styleKey)
		{
			if (_enabled.Value && points > 0)
			{
				string text = Normalize(styleKey);
				float num = Mathf.Clamp((float)points * _styleGainScale.Value, 0.05f, 3.5f);
				if (text.Contains("parry"))
				{
					num += 2.25f;
				}
				if (text.Contains("projectileboost"))
				{
					num += 0.75f;
				}
				if (text.Contains("kill"))
				{
					num += 0.8f;
				}
				if (text.Contains("doublekill") || text.Contains("triplekill") || text.Contains("multikill"))
				{
					num += 1.2f;
				}
				if (text.Contains("air") || text.Contains("arsenal") || text.Contains("ultra"))
				{
					num += 0.35f;
				}
				AddMeter(num);
			}
		}

		private void OnPlayerHurt(float damage)
		{
			if (_enabled.Value)
			{
				float time = Time.time;
				float num = Mathf.Max(0.25f, _consecutiveHitWindowSeconds.Value);
				if (time - _lastDamageTime > num)
				{
					_consecutiveDamageHits = 0;
				}
				_consecutiveDamageHits++;
				_lastDamageTime = time;
				float num2 = _damagePenaltyFlat.Value + Mathf.Max(0f, damage) * _damagePenaltyScale.Value;
				float num3 = Mathf.Clamp(_isolatedHitPenaltyMultiplier.Value, 0f, 1f);
				float num4 = Mathf.Max(0f, _consecutiveHitPenaltyStep.Value);
				float num5 = Mathf.Max(num3, _maxConsecutiveHitPenaltyMultiplier.Value);
				float num6 = Mathf.Clamp(num3 + (float)(_consecutiveDamageHits - 1) * num4, num3, num5);
				float num7 = num2 * num6 * Mathf.Max(0f, (_damagePenaltyMultiplier != null) ? _damagePenaltyMultiplier.Value : 1.3f);
				AddMeter(0f - num7);
				if (_debugLogging.Value)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Level Death damage penalty: damage=" + damage + ", streak=" + _consecutiveDamageHits + ", multiplier=" + num6.ToString("0.00") + ", penalty=" + num7.ToString("0.0") + ", meter=" + Mathf.RoundToInt(_meter) + "%."));
				}
			}
		}

		private void OnPlayerDeathOrRespawn()
		{
			if (!_enabled.Value)
			{
				return;
			}
			float time = Time.time;
			if (!(time - _lastDeathPenaltyTime < 1.25f))
			{
				_lastDeathPenaltyTime = time;
				float num = Mathf.Clamp01((_deathMeterRetainedMultiplier != null) ? _deathMeterRetainedMultiplier.Value : 0.5f);
				_meter = Mathf.Clamp(_meter * num, 0f, 400f);
				_pendingWave.Clear();
				_pendingWaveDeadline = -1f;
				_consecutiveDamageHits = 0;
				_lastDamageTime = -999f;
				_nextAllowedBonusSpawnTime = Time.time + 3f;
				if (_debugLogging.Value)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Level Death death/respawn penalty applied. Retained " + Mathf.RoundToInt(num * 100f) + "%; meter=" + Mathf.RoundToInt(_meter) + "%."));
				}
			}
		}

		private void OnLevelComplete()
		{
			if (_enabled.Value && !_levelCompletionRewardGiven)
			{
				_levelCompletionRewardGiven = true;
				AddMeter(Mathf.Max(0f, _levelCompletionReward.Value), applySoftCap: false);
				if (_debugLogging.Value)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Level Death awarded level completion reward. Meter=" + Mathf.RoundToInt(_meter) + "%."));
				}
			}
		}

		private void AddMeter(float delta, bool applySoftCap = true)
		{
			if (applySoftCap && delta > 0f && _meter >= 100f)
			{
				delta *= Mathf.Clamp(_softCapGainMultiplier.Value, 0.01f, 1f);
			}
			else if (applySoftCap && delta > 0f && _meter + delta > 100f)
			{
				float num = 100f - _meter;
				float num2 = (_meter + delta - 100f) * Mathf.Clamp(_softCapGainMultiplier.Value, 0.01f, 1f);
				delta = Mathf.Max(0f, num) + num2;
			}
			_meter = Mathf.Clamp(_meter + delta, 0f, 400f);
		}

		private bool IsHardBlockedTemplateName(string name)
		{
			string text = Normalize(name);
			if (!text.Contains("verycancerous") && !text.Contains("cancerousrodent") && !text.Contains("rodent") && !text.Contains("idol") && !text.Contains("deathcatcher") && !text.Contains("bigjohn") && !text.Contains("jakito") && !text.Contains("somethingwicked") && !text.Contains("prime") && !text.Contains("gabriel") && !text.Contains("fleshprison") && !text.Contains("fleshpanopticon") && !text.Contains("leviathan") && !text.Contains("earthmover"))
			{
				return text.Contains("v2");
			}
			return true;
		}

		private Type GetEnemyIdentifierType()
		{
			if (_enemyIdentifierType == null)
			{
				_enemyIdentifierType = AccessTools.TypeByName("EnemyIdentifier");
			}
			return _enemyIdentifierType;
		}

		private bool HasEnemyIdentifierComponent(GameObject gameObject)
		{
			if ((Object)(object)gameObject == (Object)null)
			{
				return false;
			}
			Type enemyIdentifierType = GetEnemyIdentifierType();
			if (enemyIdentifierType == null)
			{
				return false;
			}
			if (!((Object)(object)gameObject.GetComponent(enemyIdentifierType) != (Object)null))
			{
				return (Object)(object)gameObject.GetComponentInChildren(enemyIdentifierType, true) != (Object)null;
			}
			return true;
		}

		private bool IsTemplateSafeToSpawn(GameObject template, string key)
		{
			if ((Object)(object)template == (Object)null)
			{
				return false;
			}
			if ((Object)(object)template.GetComponent<LevelDeathSpawnedMarker>() != (Object)null)
			{
				return false;
			}
			if (IsHardBlockedTemplateName((key ?? string.Empty) + " " + ((Object)template).name))
			{
				return false;
			}
			if (!HasEnemyIdentifierComponent(template))
			{
				return false;
			}
			return true;
		}

		private bool IsSpawnedCloneValid(GameObject clone, string key)
		{
			if ((Object)(object)clone == (Object)null)
			{
				return false;
			}
			if (!clone.activeInHierarchy)
			{
				return false;
			}
			if (IsHardBlockedTemplateName((key ?? string.Empty) + " " + ((Object)clone).name))
			{
				return false;
			}
			if (!HasEnemyIdentifierComponent(clone))
			{
				return false;
			}
			return true;
		}

		private void DestroyCloneIfPresent(GameObject clone)
		{
			if ((Object)(object)clone != (Object)null)
			{
				Object.Destroy((Object)(object)clone);
			}
		}

		[IteratorStateMachine(typeof(<DestroyOriginalAfterReplacementValid>d__131))]
		private IEnumerator DestroyOriginalAfterReplacementValid(GameObject source, GameObject clone, string key)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <DestroyOriginalAfterReplacementValid>d__131(0)
			{
				<>4__this = this,
				source = source,
				clone = clone,
				key = key
			};
		}

		private void OnEnemyStarted(object enemyObject)
		{
			if (_enabled == null || !_enabled.Value || enemyObject == null)
			{
				return;
			}
			Component val = (Component)((enemyObject is Component) ? enemyObject : null);
			if ((Object)(object)val == (Object)null || (Object)(object)val.gameObject == (Object)null)
			{
				return;
			}
			GameObject gameObject = val.gameObject;
			if (!gameObject.activeInHierarchy)
			{
				return;
			}
			int instanceID = ((Object)gameObject).GetInstanceID();
			if (_processedEnemies.Contains(instanceID))
			{
				return;
			}
			_processedEnemies.Add(instanceID);
			if ((Object)(object)gameObject.GetComponent<LevelDeathSpawnedMarker>() != (Object)null || ((Object)gameObject).name.IndexOf(" [Level Death]", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return;
			}
			if (IsHardBlockedTemplateName(((Object)gameObject).name))
			{
				LogUnknownOrBlocked(string.Empty, ((Object)gameObject).name, "hard-blocked name");
				return;
			}
			string text = ReadEnemyTypeName(enemyObject, gameObject);
			EnemyProfile profile = EnemyCatalog.GetProfile(text, ((Object)gameObject).name, _allowUnknownEnemyTypes.Value);
			if (!profile.Allowed)
			{
				LogUnknownOrBlocked(text, ((Object)gameObject).name, profile.BlockReason);
				return;
			}
			RegisterLimitedTemplate(profile.Key, gameObject, profile.Cost);
			RegisterGlobalTemplate(profile.Key, gameObject, profile.Cost, allowInactiveTemplate: false);
			if (CanSpawnInCurrentScene() && _enemyPoolMode.Value == EnemyPoolMode.GlobalEnemies)
			{
				ScanGlobalEnemyPoolOncePerScene();
			}
			QueueNaturalEnemyForWave(gameObject, profile);
		}

		private void QueueNaturalEnemyForWave(GameObject source, EnemyProfile profile)
		{
			if (!((Object)(object)source == (Object)null) && source.activeInHierarchy && profile.Allowed)
			{
				_pendingWave.Add(new PendingWaveEnemy(source, profile, Time.time));
				RefreshPendingWaveDeadline();
			}
		}

		private void RefreshPendingWaveDeadline()
		{
			if (_pendingWave.Count == 0)
			{
				_pendingWaveDeadline = -1f;
				return;
			}
			bool flag = true;
			for (int i = 0; i < _pendingWave.Count; i++)
			{
				PendingWaveEnemy pendingWaveEnemy = _pendingWave[i];
				if (pendingWaveEnemy == null || !IsFastFodderKey(pendingWaveEnemy.Profile.Key))
				{
					flag = false;
					break;
				}
			}
			float num = Mathf.Clamp((_waveBatchWindowSeconds != null) ? _waveBatchWindowSeconds.Value : 1.45f, 0.1f, 4f);
			float num2 = Mathf.Clamp((_lowTierWaveReadSeconds != null) ? _lowTierWaveReadSeconds.Value : 0.45f, 0.05f, num);
			float num3 = (flag ? num2 : num);
			int num4 = Mathf.Max(2, (_processWaveImmediatelyAtSize != null) ? _processWaveImmediatelyAtSize.Value : 4);
			if (_pendingWave.Count >= num4)
			{
				num3 = Mathf.Min(num3, 0.12f);
			}
			_pendingWaveDeadline = Time.time + num3;
		}

		private bool ShouldFlushPendingWaveEarly()
		{
			if (_pendingWave.Count == 0)
			{
				return false;
			}
			float num = Time.time;
			bool flag = true;
			bool flag2 = false;
			for (int i = 0; i < _pendingWave.Count; i++)
			{
				PendingWaveEnemy pendingWaveEnemy = _pendingWave[i];
				if (pendingWaveEnemy != null)
				{
					num = Mathf.Min(num, pendingWaveEnemy.TimeStarted);
					if (!IsFastFodderKey(pendingWaveEnemy.Profile.Key))
					{
						flag = false;
					}
					if ((Object)(object)pendingWaveEnemy.Source == (Object)null || !pendingWaveEnemy.Source.activeInHierarchy)
					{
						flag2 = true;
					}
				}
			}
			float num2 = Time.time - num;
			int num3 = Mathf.Max(2, (_processWaveImmediatelyAtSize != null) ? _processWaveImmediatelyAtSize.Value : 4);
			if (_pendingWave.Count >= num3 && num2 >= 0.08f)
			{
				return true;
			}
			if (flag && flag2 && num2 >= 0.08f)
			{
				return true;
			}
			return false;
		}

		private bool IsFastFodderKey(string key)
		{
			key = Normalize(key);
			if (!(key == "filth") && !(key == "stray"))
			{
				return key == "drone";
			}
			return true;
		}

		private bool IsEntrySourceAlive(PendingWaveEnemy entry)
		{
			if (entry != null && (Object)(object)entry.Source != (Object)null)
			{
				return entry.Source.activeInHierarchy;
			}
			return false;
		}

		private void ProcessPendingWaveIfReady()
		{
			if (_pendingWave.Count == 0)
			{
				return;
			}
			if (!_enabled.Value)
			{
				_pendingWave.Clear();
				_pendingWaveDeadline = -1f;
			}
			else
			{
				if (Time.time < _pendingWaveDeadline && !ShouldFlushPendingWaveEarly())
				{
					return;
				}
				if (!CanSpawnInCurrentScene())
				{
					_pendingWaveDeadline = Time.time + 0.25f;
					return;
				}
				List<PendingWaveEnemy> list = new List<PendingWaveEnemy>();
				for (int i = 0; i < _pendingWave.Count; i++)
				{
					PendingWaveEnemy pendingWaveEnemy = _pendingWave[i];
					if (pendingWaveEnemy != null && pendingWaveEnemy.Profile.Allowed && (!((Object)(object)pendingWaveEnemy.Source != (Object)null) || (!((Object)(object)pendingWaveEnemy.Source.GetComponent<LevelDeathSpawnedMarker>() != (Object)null) && (((Object)pendingWaveEnemy.Source).name ?? string.Empty).IndexOf(" [Level Death]", StringComparison.OrdinalIgnoreCase) < 0)))
					{
						list.Add(pendingWaveEnemy);
					}
				}
				_pendingWave.Clear();
				_pendingWaveDeadline = -1f;
				if (list.Count != 0)
				{
					if (_enemyPoolMode.Value == EnemyPoolMode.GlobalEnemies)
					{
						ScanGlobalEnemyPoolOncePerScene();
					}
					int levelIndex = GetLevelIndex();
					HashSet<int> replacedIds = new HashSet<int>();
					_activeWaveNonFilthEnemyCount = CountNonFilthEnemies(list);
					_activeWaveLargeEnemySpawned = false;
					int num = TryApplyWaveReplacements(list, replacedIds, levelIndex);
					int num2 = TryApplyWaveNaturalBuffs(list, replacedIds, levelIndex);
					int num3 = TryApplyWaveDuplicates(list, replacedIds, levelIndex);
					if (_debugLogging.Value && (num > 0 || num2 > 0 || num3 > 0))
					{
						((BaseUnityPlugin)this).Logger.LogInfo((object)("Level Death processed wave: natural=" + list.Count + ", level=" + GetRankName() + ", replacements=" + num + ", naturalBuffs=" + num2 + ", duplicates=" + num3 + ", meter=" + Mathf.RoundToInt(_meter) + "%."));
					}
					_activeWaveLargeEnemySpawned = false;
					_activeWaveNonFilthEnemyCount = 0;
				}
			}
		}

		private List<PendingWaveEnemy> GetWaveOrderedByCost(List<PendingWaveEnemy> wave, bool ascending)
		{
			List<PendingWaveEnemy> list = new List<PendingWaveEnemy>();
			if (wave == null)
			{
				return list;
			}
			for (int i = 0; i < wave.Count; i++)
			{
				if (wave[i] != null)
				{
					list.Add(wave[i]);
				}
			}
			list.Sort((PendingWaveEnemy a, PendingWaveEnemy b) => (!ascending) ? b.Profile.Cost.CompareTo(a.Profile.Cost) : a.Profile.Cost.CompareTo(b.Profile.Cost));
			return list;
		}

		private bool IsMauriceProfile(EnemyProfile profile)
		{
			if (!(profile.Key == "maurice"))
			{
				return profile.Key == "maliciousface";
			}
			return true;
		}

		private int CountMaurices(List<PendingWaveEnemy> wave)
		{
			if (wave == null)
			{
				return 0;
			}
			int num = 0;
			for (int i = 0; i < wave.Count; i++)
			{
				PendingWaveEnemy pendingWaveEnemy = wave[i];
				if (pendingWaveEnemy != null && IsMauriceProfile(pendingWaveEnemy.Profile))
				{
					num++;
				}
			}
			return num;
		}

		private int CountNonFilthEnemies(List<PendingWaveEnemy> wave)
		{
			if (wave == null)
			{
				return 0;
			}
			int num = 0;
			for (int i = 0; i < wave.Count; i++)
			{
				PendingWaveEnemy pendingWaveEnemy = wave[i];
				if (pendingWaveEnemy != null && pendingWaveEnemy.Profile.Key != "filth")
				{
					num++;
				}
			}
			return num;
		}

		private int TryApplyWaveReplacements(List<PendingWaveEnemy> wave, HashSet<int> replacedIds, int level)
		{
			if (!_allowTypeReplacements.Value)
			{
				return 0;
			}
			if (_enemyPoolMode.Value != EnemyPoolMode.GlobalEnemies)
			{
				return 0;
			}
			if (_directorActionMode.Value == DirectorActionMode.AddReinforcementsOnly)
			{
				return 0;
			}
			if (_meter < 100f)
			{
				return 0;
			}
			if (LevelDeathSpawnedMarker.ActiveCount >= _maxActiveBonusEnemies.Value)
			{
				return 0;
			}
			int num = ApplyLargeEnemyActionPenalty(GetReplacementCapForLevel(level, wave.Count));
			if (num <= 0 && IsEligibleSingleFodderReplacementWave(wave, level))
			{
				num = 1;
			}
			if (num <= 0)
			{
				return 0;
			}
			float typeReplacementChance = GetTypeReplacementChance();
			bool flag = CountMaurices(wave) >= 2;
			int num2 = 0;
			List<PendingWaveEnemy> waveOrderedByCost = GetWaveOrderedByCost(wave, ascending: true);
			for (int i = 0; i < waveOrderedByCost.Count; i++)
			{
				PendingWaveEnemy pendingWaveEnemy = waveOrderedByCost[i];
				if (num2 >= num)
				{
					break;
				}
				bool flag2 = IsEntrySourceAlive(pendingWaveEnemy);
				if ((flag && IsMauriceProfile(pendingWaveEnemy.Profile)) || (flag2 && ShouldAvoidCloningOrReplacingSource(pendingWaveEnemy.Source)) || (pendingWaveEnemy.Profile.Cost >= 10 && _meter < 300f) || Random.value > typeReplacementChance)
				{
					continue;
				}
				EnemySpawnCandidate enemySpawnCandidate = ChooseReplacementCandidate(pendingWaveEnemy.Profile, flag2 ? pendingWaveEnemy.Source : null);
				if (enemySpawnCandidate == null || enemySpawnCandidate.BaseCost <= pendingWaveEnemy.Profile.Cost || !CanCandidateReplaceSource(enemySpawnCandidate, pendingWaveEnemy.Profile, flag2 ? pendingWaveEnemy.Source : null))
				{
					continue;
				}
				int sourceId = pendingWaveEnemy.SourceId;
				if (SpawnReplacement(pendingWaveEnemy, enemySpawnCandidate.Pool, pendingWaveEnemy.Profile, enemySpawnCandidate))
				{
					replacedIds.Add(sourceId);
					num2++;
					if (_activeWaveLargeEnemySpawned)
					{
						num = ApplyLargeEnemyActionPenalty(num);
					}
				}
			}
			return num2;
		}

		private bool IsEligibleSingleFodderReplacementWave(List<PendingWaveEnemy> wave, int level)
		{
			if (_allowSingleFodderReplacement == null || !_allowSingleFodderReplacement.Value)
			{
				return false;
			}
			if (level < 2 || _meter < 100f)
			{
				return false;
			}
			if (wave == null || wave.Count != 1 || wave[0] == null)
			{
				return false;
			}
			return IsFastFodderKey(wave[0].Profile.Key);
		}

		private int ApplyLargeEnemyActionPenalty(int cap)
		{
			if (!_activeWaveLargeEnemySpawned)
			{
				return cap;
			}
			int num = Mathf.Max(0, Mathf.RoundToInt((_largeEnemyActionPenalty != null) ? _largeEnemyActionPenalty.Value : 1f));
			return Mathf.Max(0, cap - num);
		}

		private int TryApplyWaveNaturalBuffs(List<PendingWaveEnemy> wave, HashSet<int> replacedIds, int level)
		{
			if (!_allowNaturalSpawnBuffs.Value)
			{
				return 0;
			}
			if (_directorActionMode.Value == DirectorActionMode.AddReinforcementsOnly)
			{
				return 0;
			}
			if (level < 2)
			{
				return 0;
			}
			int num = 0;
			int num2 = Mathf.Clamp(level, 1, 4);
			List<PendingWaveEnemy> waveOrderedByCost = GetWaveOrderedByCost(wave, ascending: false);
			for (int i = 0; i < waveOrderedByCost.Count; i++)
			{
				PendingWaveEnemy pendingWaveEnemy = waveOrderedByCost[i];
				if (num >= num2)
				{
					break;
				}
				if (!replacedIds.Contains(pendingWaveEnemy.SourceId) && !((Object)(object)pendingWaveEnemy.Source == (Object)null) && pendingWaveEnemy.Source.activeInHierarchy)
				{
					bool flag = false;
					if (level >= 3 && Random.value < GetNaturalRadiantChance())
					{
						ApplyRadiantLikeBuff(pendingWaveEnemy.Source);
						flag = true;
					}
					else if (Random.value < GetNaturalEnrageChance())
					{
						ApplyEnrage(pendingWaveEnemy.Source);
						flag = true;
					}
					if (flag)
					{
						num++;
					}
				}
			}
			return num;
		}

		private int TryApplyWaveDuplicates(List<PendingWaveEnemy> wave, HashSet<int> replacedIds, int level)
		{
			if (_meter < _minimumMeterForBonusSpawns.Value)
			{
				return 0;
			}
			if (Time.time < _nextAllowedBonusSpawnTime && (level < 2 || !_guaranteeSafeDuplicateAtLevel2.Value))
			{
				return 0;
			}
			if (LevelDeathSpawnedMarker.ActiveCount >= _maxActiveBonusEnemies.Value)
			{
				return 0;
			}
			int num = ApplyLargeEnemyActionPenalty(GetDuplicateCapForLevel(level, wave.Count));
			if (num <= 0)
			{
				return 0;
			}
			List<PendingWaveEnemy> list = new List<PendingWaveEnemy>();
			for (int i = 0; i < wave.Count; i++)
			{
				PendingWaveEnemy pendingWaveEnemy = wave[i];
				if (pendingWaveEnemy != null && (Object)(object)pendingWaveEnemy.Source != (Object)null && pendingWaveEnemy.Source.activeInHierarchy && !replacedIds.Contains(pendingWaveEnemy.SourceId) && !ShouldAvoidCloningOrReplacingSource(pendingWaveEnemy.Source))
				{
					list.Add(pendingWaveEnemy);
				}
			}
			if (list.Count == 0)
			{
				return 0;
			}
			int j = 0;
			bool flag = level >= 2 && _guaranteeSafeDuplicateAtLevel2.Value && wave.Count >= 2;
			if (flag)
			{
				j = 1;
			}
			else if (Random.value < GetWaveDuplicateChance(level))
			{
				j = 1;
			}
			for (; j < num && Random.value < GetAdditionalDuplicateChance(level, j); j++)
			{
			}
			int num2 = 0;
			int num3 = Mathf.Max(1, GetBonusBudgetForMeter());
			int num4 = 0;
			while (num2 < j && num4 < j * 4 + 4 && LevelDeathSpawnedMarker.ActiveCount < _maxActiveBonusEnemies.Value)
			{
				num4++;
				PendingWaveEnemy pendingWaveEnemy2 = ChooseLocalDuplicateSource(list, num3, num2 == 0 && flag);
				if (pendingWaveEnemy2 == null)
				{
					break;
				}
				if (SpawnDuplicateOfNatural(pendingWaveEnemy2.Source, num2))
				{
					num2++;
					RecordEnemySelectionCooldown(pendingWaveEnemy2.Profile.Key, pendingWaveEnemy2.Profile.Cost, pendingWaveEnemy2.Profile.IsLarge);
					num3 -= Mathf.Max(1, pendingWaveEnemy2.Profile.Cost);
					if (_activeWaveLargeEnemySpawned)
					{
						j = Mathf.Min(j, ApplyLargeEnemyActionPenalty(num));
					}
				}
			}
			if (num2 > 0)
			{
				_nextAllowedBonusSpawnTime = Time.time + Mathf.Max(0.05f, _minimumSecondsBetweenBonusSpawns.Value);
			}
			return num2;
		}

		private PendingWaveEnemy ChooseLocalDuplicateSource(List<PendingWaveEnemy> alive, int remainingBudget, bool safetyDuplicate)
		{
			if (alive == null || alive.Count == 0)
			{
				return null;
			}
			List<WeightedWaveEnemy> list = new List<WeightedWaveEnemy>();
			bool flag = !safetyDuplicate && _meter >= _toughPoolStartsAtMeter.Value;
			bool flag2 = false;
			if (flag)
			{
				for (int i = 0; i < alive.Count; i++)
				{
					PendingWaveEnemy pendingWaveEnemy = alive[i];
					if (pendingWaveEnemy != null && !ShouldSuppressLowTier(pendingWaveEnemy.Profile.Key))
					{
						flag2 = true;
						break;
					}
				}
			}
			for (int j = 0; j < alive.Count; j++)
			{
				PendingWaveEnemy pendingWaveEnemy2 = alive[j];
				if (pendingWaveEnemy2 != null && !((Object)(object)pendingWaveEnemy2.Source == (Object)null) && pendingWaveEnemy2.Source.activeInHierarchy && (safetyDuplicate || pendingWaveEnemy2.Profile.Cost <= remainingBudget) && (!(flag && flag2) || !ShouldSuppressLowTier(pendingWaveEnemy2.Profile.Key)))
				{
					float num = 1f;
					if (pendingWaveEnemy2.Profile.Cost >= 5)
					{
						num *= 1.25f;
					}
					if (pendingWaveEnemy2.Profile.Key == "cerberus")
					{
						num *= 1.35f;
					}
					num *= GetEnemyVarietyCooldownWeight(pendingWaveEnemy2.Profile.Key, pendingWaveEnemy2.Profile.Cost, pendingWaveEnemy2.Profile.IsLarge);
					list.Add(new WeightedWaveEnemy(pendingWaveEnemy2, num));
				}
			}
			if (list.Count == 0 && safetyDuplicate)
			{
				for (int k = 0; k < alive.Count; k++)
				{
					PendingWaveEnemy pendingWaveEnemy3 = alive[k];
					if (pendingWaveEnemy3 != null && (Object)(object)pendingWaveEnemy3.Source != (Object)null && pendingWaveEnemy3.Source.activeInHierarchy && !ShouldAvoidCloningOrReplacingSource(pendingWaveEnemy3.Source))
					{
						list.Add(new WeightedWaveEnemy(pendingWaveEnemy3, 1f));
					}
				}
			}
			if (list.Count == 0)
			{
				return null;
			}
			float num2 = 0f;
			for (int l = 0; l < list.Count; l++)
			{
				num2 += list[l].Weight;
			}
			float num3 = Random.value * num2;
			for (int m = 0; m < list.Count; m++)
			{
				num3 -= list[m].Weight;
				if (num3 <= 0f)
				{
					return list[m].Enemy;
				}
			}
			return list[list.Count - 1].Enemy;
		}

		private bool SpawnDuplicateOfNatural(GameObject source, int index)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: 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_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)source == (Object)null)
			{
				return false;
			}
			if (IsHardBlockedTemplateName(((Object)source).name) || !IsTemplateSafeToSpawn(source, string.Empty))
			{
				return false;
			}
			try
			{
				Vector3 val = PickSpawnPosition(source.transform.position, index);
				Quaternion rotation = source.transform.rotation;
				Transform parent = source.transform.parent;
				GameObject val2 = Object.Instantiate<GameObject>(source, val, rotation, parent);
				((Object)val2).name = ((Object)source).name + " [Level Death]";
				val2.AddComponent<LevelDeathSpawnedMarker>();
				SanitizeClonedEnemyRuntimeState(val2);
				if (!val2.activeSelf)
				{
					val2.SetActive(true);
				}
				if (!IsSpawnedCloneValid(val2, string.Empty))
				{
					DestroyCloneIfPresent(val2);
					if (_debugLogging != null && _debugLogging.Value)
					{
						((BaseUnityPlugin)this).Logger.LogInfo((object)("Level Death rejected a natural duplicate clone that did not validate: " + ((Object)source).name));
					}
					return false;
				}
				TryApplyHighMeterSpawnBuffs(val2);
				if (EnemyCatalog.IsLargeKey(EnemyCatalog.GetProfile(string.Empty, ((Object)val2).name, allowUnknown: false).Key))
				{
					_activeWaveLargeEnemySpawned = true;
				}
				TrySpawnCerberusPairBonus(val2, source.transform.position, parent, null);
				return true;
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Level Death refused a local duplicate because Instantiate failed: " + ex.Message));
				return false;
			}
		}

		private void TrySpawnCerberusPairBonus(GameObject spawned, Vector3 sourcePosition, Transform parent, EnemyTemplatePool preferredPool)
		{
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)spawned == (Object)null || LevelDeathSpawnedMarker.ActiveCount >= _maxActiveBonusEnemies.Value || Normalize(((Object)spawned).name).IndexOf("cerberus", StringComparison.OrdinalIgnoreCase) < 0 || Random.value > Mathf.Clamp01((_cerberusPairChance != null) ? _cerberusPairChance.Value : 0.55f))
			{
				return;
			}
			try
			{
				GameObject val = spawned;
				if (preferredPool != null && preferredPool.HasUsableTemplate)
				{
					GameObject randomLiveTemplate = preferredPool.GetRandomLiveTemplate();
					if ((Object)(object)randomLiveTemplate != (Object)null)
					{
						val = randomLiveTemplate;
					}
				}
				Vector3 val2 = PickSpawnPosition(sourcePosition, 2 + Random.Range(0, 3));
				GameObject val3 = Object.Instantiate<GameObject>(val, val2, spawned.transform.rotation, parent);
				((Object)val3).name = ((Object)val).name + " [Level Death Cerberus Pair]";
				val3.AddComponent<LevelDeathSpawnedMarker>();
				SanitizeClonedEnemyRuntimeState(val3);
				if (!val3.activeSelf)
				{
					val3.SetActive(true);
				}
				if (!IsSpawnedCloneValid(val3, "cerberus"))
				{
					DestroyCloneIfPresent(val3);
					if (_debugLogging != null && _debugLogging.Value)
					{
						((BaseUnityPlugin)this).Logger.LogInfo((object)"Level Death rejected a paired Cerberus clone that did not validate.");
					}
				}
				else
				{
					TryApplyHighMeterSpawnBuffs(val3);
					if (_debugLogging != null && _debugLogging.Value)
					{
						((BaseUnityPlugin)this).Logger.LogInfo((object)"Level Death spawned a paired Cerberus.");
					}
				}
			}
			catch (Exception ex)
			{
				if (_debugLogging != null && _debugLogging.Value)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Level Death skipped Cerberus pair spawn safely: " + ex.Message));
				}
			}
		}

		private int GetReplacementCapForLevel(int level, int waveCount)
		{
			if (waveCount <= 1)
			{
				return 0;
			}
			int num = ((level <= 1) ? _maxReplacementsLevel1.Value : (level switch
			{
				2 => _maxReplacementsLevel2.Value, 
				3 => _maxReplacementsLevel3.Value, 
				_ => _maxReplacementsLevel4.Value, 
			}));
			if (num <= 0)
			{
				return 0;
			}
			int num2 = Mathf.Max(2, (_fullDirectorStartsAtWaveSize != null) ? _fullDirectorStartsAtWaveSize.Value : 4);
			if (waveCount < num2)
			{
				num = Mathf.Min(num, Mathf.Max(0, (_mediumWaveMaxReplacements == null) ? 1 : _mediumWaveMaxReplacements.Value));
			}
			float num3 = Mathf.Clamp(_maxReplacementFractionOfWave.Value, 0.05f, 1f);
			int num4 = Mathf.Max(1, Mathf.FloorToInt((float)waveCount * num3));
			if (level >= 4 && waveCount >= num2)
			{
				num4 = Mathf.Max(num4, Mathf.Min(num, Mathf.CeilToInt((float)waveCount * 0.45f)));
			}
			return Mathf.Clamp(num, 0, num4);
		}

		private int GetDuplicateCapForLevel(int level, int waveCount)
		{
			if (waveCount <= 1)
			{
				return 0;
			}
			int num = ((level <= 1) ? Mathf.Max(0, _maxDuplicatesLevel1.Value) : (level switch
			{
				2 => Mathf.Max(0, _maxDuplicatesLevel2.Value), 
				3 => Mathf.Max(0, _maxDuplicatesLevel3.Value), 
				_ => Mathf.Max(0, _maxDuplicatesLevel4.Value), 
			}));
			if (num <= 0)
			{
				return 0;
			}
			int num2 = Mathf.Max(2, (_fullDirectorStartsAtWaveSize != null) ? _fullDirectorStartsAtWaveSize.Value : 4);
			if (waveCount < num2)
			{
				num = Mathf.Min(num, Mathf.Max(0, (_mediumWaveMaxDuplicates == null) ? 1 : _mediumWaveMaxDuplicates.Value));
			}
			return num;
		}

		private float GetWaveDuplicateChance(int level)
		{
			if (_meter < _minimumMeterForBonusSpawns.Value)
			{
				return 0f;
			}
			if (level <= 1)
			{
				return Mathf.Lerp(0.08f, 0.24f, Mathf.InverseLerp(_minimumMeterForBonusSpawns.Value, 100f, _meter)) * Mathf.Max(0f, _spawnChanceMultiplier.Value);
			}
			return level switch
			{
				2 => 0.55f * Mathf.Max(0f, _spawnChanceMultiplier.Value), 
				3 => 0.7f * Mathf.Max(0f, _spawnChanceMultiplier.Value), 
				_ => 0.8f * Mathf.Max(0f, _spawnChanceMultiplier.Value), 
			};
		}

		private float GetAdditionalDuplicateChance(int level, int alreadyPlanned)
		{
			if (alreadyPlanned <= 0)
			{
				return GetWaveDuplicateChance(level);
			}
			if (level <= 1)
			{
				return 0f;
			}
			return level switch
			{
				2 => 0.28f, 
				3 => 0.36f, 
				_ => 0.42f, 
			};
		}

		private void ApplyStartingMeterFloor()
		{
			if (_startingLevelMode != null)
			{
				float num = 0f;
				num = _startingLevelMode.Value switch
				{
					StartingLevelMode.Enraged => 100f, 
					StartingLevelMode.Radiant => 200f, 
					StartingLevelMode.LevelDeath => 300f, 
					StartingLevelMode.Custom => Mathf.Clamp((_startingMeterPercent != null) ? _startingMeterPercent.Value : 0f, 0f, 400f), 
					_ => 0f, 
				};
				if (num > _meter)
				{
					_meter = num;
				}
			}
		}

		private string ReadEnemyTypeName(object enemyObject, GameObject gameObject)
		{
			Type type = enemyObject.GetType();
			string text = ReadStringMember(enemyObject, type, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, "FullName") ?? ReadStringMember(enemyObject, type, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, "fullName") ?? ReadStringMember(enemyObject, type, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, "enemyName") ?? ReadStringMember(enemyObject, type, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, "EnemyName");
			if (!string.IsNullOrEmpty(text) && !string.Equals(text, "None", StringComparison.OrdinalIgnoreCase) && !string.Equals(text, "Unknown Entity", StringComparison.OrdinalIgnoreCase))
			{
				return text;
			}
			FieldInfo fieldInfo = type.GetField("enemyType", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetField("type", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (fieldInfo != null)
			{
				object value = fieldInfo.GetValue(enemyObject);
				if (value != null)
				{
					string knownEnemyTypeAlias = EnemyCatalog.GetKnownEnemyTypeAlias(value);
					if (!string.IsNullOrEmpty(knownEnemyTypeAlias))
					{
						return knownEnemyTypeAlias + " " + value;
					}
					return value.ToString();
				}
			}
			PropertyInfo propertyInfo = type.GetProperty("enemyType", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetProperty("type", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (propertyInfo != null && propertyInfo.GetIndexParameters().Length == 0)
			{
				object value2 = propertyInfo.GetValue(enemyObject, null);
				if (value2 != null)
				{
					string knownEnemyTypeAlias2 = EnemyCatalog.GetKnownEnemyTypeAlias(value2);
					if (!string.IsNullOrEmpty(knownEnemyTypeAlias2))
					{
						return knownEnemyTypeAlias2 + " " + value2;
					}
					return value2.ToString();
				}
			}
			return ((Object)gameObject).name ?? "unknown";
		}

		private void RegisterLimitedTemplate(string key, GameObject source, int cost)
		{
			if (!string.IsNullOrEmpty(key) && !((Object)(object)source == (Object)null) && !IsHardBlockedTemplateName(key + " " + ((Object)source).name))
			{
				if (!_limitedEnemyPools.TryGetValue(key, out var value))
				{
					value = new EnemyTemplatePool(key, cost, requireLiveTemplate: true);
					_limitedEnemyPools[key] = value;
				}
				value.Cost = cost;
				value.IsLarge = EnemyCatalog.IsLargeKey(key);
				value.IsFlying = EnemyCatalog.IsFlyingKey(key);
				value.Add(source);
			}
		}

		private void RegisterGlobalTemplate(string key, GameObject source, int cost, bool allowInactiveTemplate)
		{
			if (!string.IsNullOrEmpty(key) && !((Object)(object)source == (Object)null) && !IsHardBlockedTemplateName(key + " " + ((Object)source).name))
			{
				if (!_globalEnemyPools.TryGetValue(key, out var value))
				{
					value = new EnemyTemplatePool(key, cost, !allowInactiveTemplate);
					_globalEnemyPools[key] = value;
				}
				value.Cost = cost;
				value.IsLarge = EnemyCatalog.IsLargeKey(key);
				value.IsFlying = EnemyCatalog.IsFlyingKey(key);
				value.RequireLiveTemplate = value.RequireLiveTemplate && !allowInactiveTemplate;
				value.Add(source);
			}
		}

		[IteratorStateMachine(typeof(<PrimeGlobalEnemyPoolSoon>d__159))]
		private IEnumerator PrimeGlobalEnemyPoolSoon()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <PrimeGlobalEnemyPoolSoon>d__159(0)
			{
				<>4__this = this
			};
		}

		private void ScanGlobalEnemyPoolOncePerScene()
		{
			if (_globalPoolScannedThisScene)
			{
				return;
			}
			_globalPoolScannedThisScene = true;
			Type enemyIdentifierType = GetEnemyIdentifierType();
			if (enemyIdentifierType == null)
			{
				return;
			}
			int num = 0;
			try
			{
				Object[] array = Resources.FindObjectsOfTypeAll(enemyIdentifierType);
				foreach (Object obj in array)
				{
					Component val = (Component)(object)((obj is Component) ? obj : null);
					if ((Object)(object)val == (Object)null)
					{
						continue;
					}
					GameObject gameObject = val.gameObject;
					if ((Object)(object)gameObject == (Object)null || (Object)(object)gameObject.GetComponent<LevelDeathSpawnedMarker>() != (Object)null || IsHardBlockedTemplateName(((Object)gameObject).name) || (((Object)gameObject).name ?? string.Empty).IndexOf(" [Level Death]", StringComparison.OrdinalIgnoreCase) >= 0)
					{
						continue;
					}
					string text = ReadEnemyTypeName(val, gameObject);
					EnemyProfile profile = EnemyCatalog.GetProfile(text, ((Object)gameObject).name, _allowUnknownEnemyTypes.Value);
					if (!profile.Allowed)
					{
						LogUnknownOrBlocked(text, ((Object)gameObject).name, profile.BlockReason);
					}
					else if (IsTemplateSafeToSpawn(gameObject, profile.Key))
					{
						int num2 = (_globalEnemyPools.ContainsKey(profile.Key) ? _globalEnemyPools[profile.Key].Count : 0);
						bool allowInactiveTemplate = !gameObject.activeInHierarchy;
						RegisterGlobalTemplate(profile.Key, gameObject, profile.Cost, allowInactiveTemplate);
						if ((_globalEnemyPools.ContainsKey(profile.Key) ? _globalEnemyPools[profile.Key].Count : 0) > num2)
						{
							num++;
						}
					}
				}
				if (_debugLogging.Value)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Level Death global scan added " + num + " template(s). Global enemy types available: " + _globalEnemyPools.Count + "."));
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Level Death global enemy scan failed safely: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private bool CanSpawnInCurrentScene()
		{
			if (Time.time - _sceneLoadedTime < _minimumSceneAgeForSpawns.Value)
			{
				return false;
			}
			if (!_cyberGrindOnly.Value)
			{
				return true;
			}
			string text = Normalize(_currentSceneName);
			if (!text.Contains("cyber") && !text.Contains("endless"))
			{
				return text.Contains("grid");
			}
			return true;
		}

		private float GetNaturalEnrageChance()
		{
			if (_meter < 100f)
			{
				return 0f;
			}
			if (_meter < 200f)
			{
				float num = Mathf.InverseLerp(100f, 200f, _meter);
				return Mathf.Lerp(Mathf.Clamp01(_naturalEnrageChanceLevel2.Value), Mathf.Clamp01(_naturalEnrageChanceLevel2.Value) * 1.35f, num);
			}
			if (_meter < 300f)
			{
				return Mathf.Clamp01(_naturalEnrageChanceLevel2.Value * 0.85f);
			}
			return Mathf.Clamp01(_naturalEnrageChanceLevel2.Value * 1.1f);
		}

		private float GetNaturalRadiantChance()
		{
			if (_meter < 200f)
			{
				return 0f;
			}
			if (_meter < 300f)
			{
				float num = Mathf.InverseLerp(200f, 400f, _meter);
				return Mathf.Lerp(Mathf.Clamp01(_naturalRadiantChanceLevel3.Value), Mathf.Clamp01(_naturalRadiantChanceLevel3.Value) * 1.35f, num);
			}
			return Mathf.Clamp01(_naturalRadiantChanceLevel3.Value * 1.65f);
		}

		private float GetTypeReplacementChance()
		{
			if (_directorActionMode.Value == DirectorActionMode.AddReinforcementsOnly)
			{
				return 0f;
			}
			if (_meter < 100f)
			{
				return 0f;
			}
			float num = ((_meter < 200f) ? Mathf.Lerp(0.08f, Mathf.Clamp01(_replacementChanceLevel2.Value), Mathf.InverseLerp(100f, 200f, _meter)) : ((!(_meter < 300f)) ? Mathf.Lerp(Mathf.Clamp01(_replacementChanceLevel3.Value), Mathf.Clamp01(_replacementChanceLevel4.Value), Mathf.InverseLerp(300f, 400f, _meter)) : Mathf.Lerp(Mathf.Clamp01(_replacementChanceLevel2.Value), Mathf.Clamp01(_replacementChanceLevel3.Value), Mathf.InverseLerp(200f, 300f, _meter))));
			if (_directorActionMode.Value == DirectorActionMode.PreferUpgrades)
			{
				num *= 1.2f;
			}
			return Mathf.Clamp01(num);
		}

		private EnemySpawnCandidate ChooseReplacementCandidate(EnemyProfile sourceProfile)
		{
			return ChooseReplacementCandidate(sourceProfile, null);
		}

		private EnemySpawnCandidate ChooseReplacementCandidate(EnemyProfile sourceProfile, GameObject source)
		{
			CleanupPools();
			int pointCapForSource = GetPointCapForSource(sourceProfile.Cost);
			int num = Mathf.Max(sourceProfile.Cost + 1, 3);
			int num2 = Mathf.Max(num, pointCapForSource);
			List<EnemySpawnCandidate> list = new List<EnemySpawnCandidate>();
			HashSet<string> hashSet = new HashSet<string>(_limitedEnemyPools.Keys);
			foreach (EnemyTemplatePool value in _globalEnemyPools.Values)
			{
				if (!value.HasUsableTemplate || value.Cost < num || (hashSet.Contains(value.Key) && value.Cost <= sourceProfile.Cost) || ShouldSuppressLowTier(value.Key))
				{
					continue;
				}
				int num3 = (hashSet.Contains(value.Key) ? value.Cost : GetGlobalEffectiveCost(value.Cost));
				if (num3 <= num2)
				{
					EnemySpawnCandidate enemySpawnCandidate = new EnemySpawnCandidate(value, num3, !hashSet.Contains(value.Key));
					if (CanCandidateReplaceSource(enemySpawnCandidate, sourceProfile, source))
					{
						list.Add(enemySpawnCandidate);
					}
				}
			}
			if (list.Count == 0)
			{
				return null;
			}
			List<WeightedCandidate> list2 = new List<WeightedCandidate>();
			foreach (EnemySpawnCandidate item in list)
			{
				float num4 = Mathf.Max(1f, (float)(item.BaseCost - sourceProfile.Cost));
				float num5 = 0.75f + num4 * 0.22f;
				if (item.BaseCost >= 8 && _meter < 200f)
				{
					num5 *= 0.35f;
				}
				if (item.BaseCost >= 9 && _meter < 260f)
				{
					num5 *= 0.45f;
				}
				if (item.BaseCost >= 10 && _meter < 320f)
				{
					num5 *= 0.35f;
				}
				if (item.IsGlobalOnly)
				{
					num5 *= 0.9f;
				}
				if (item.IsLarge)
				{
					num5 *= Mathf.Clamp((_largeEnemyRarityMultiplier != null) ? _largeEnemyRarityMultiplier.Value : 0.5f, 0.05f, 1f);
				}
				num5 *= GetEnemyVarietyCooldownWeight(item.Pool.Key, item.BaseCost, item.IsLarge);
				list2.Add(new WeightedCandidate(item, num5));
			}
			return PickWeightedCandidate(list2);
		}

		private float GetEnemyVarietyCooldownWeight(string key, int cost, bool isLarge)
		{
			if (_enableEnemyVarietyCooldown == null || !_enableEnemyVarietyCooldown.Value)
			{
				return 1f;
			}
			if (((_enemyVarietyCooldownStrength != null) ? _enemyVarietyCooldownStrength.Value : 1f) <= 0f)
			{
				return 1f;
			}
			if (cost <= 6)
			{
				return 1f;
			}
			key = Normalize(key);
			if (!_enemyCooldowns.TryGetValue(key, out var value))
			{
				return 1f;
			}
			DecayEnemyCooldown(value);
			if (value.Heat <= 0.01f)
			{
				return 1f;
			}
			float num = Mathf.Max(0f, (_enemyVarietyCooldownStrength != null) ? _enemyVarietyCooldownStrength.Value : 1f);
			float num2 = 1f + (float)Mathf.Max(0, cost - 6) * 0.35f;
			if (isLarge)
			{
				num2 += Mathf.Max(0f, (_largeEnemyCooldownBonus != null) ? _largeEnemyCooldownBonus.Value : 3.5f) * 0.35f;
			}
			return Mathf.Clamp(1f / (1f + value.Heat * num2 * num), 0.04f, 1f);
		}

		private void RecordEnemySelectionCooldown(string key, int cost, bool isLarge)
		{
			if (_enableEnemyVarietyCooldown == null || !_enableEnemyVarietyCooldown.Value || cost <= 6)
			{
				return;
			}
			key = Normalize(key);
			if (!string.IsNullOrEmpty(key))
			{
				if (!_enemyCooldowns.TryGetValue(key, out var value))
				{
					value = new EnemyCooldownState();
					value.LastUpdatedTime = Time.time;
					_enemyCooldowns[key] = value;
				}
				DecayEnemyCooldown(value);
				float num = Mathf.Max(0.5f, (float)(cost - 6) * 0.85f);
				if (isLarge)
				{
					num += Mathf.Max(0f, (_largeEnemyCooldownBonus != null) ? _largeEnemyCooldownBonus.Value : 3.5f);
				}
				value.Heat = Mathf.Clamp(value.Heat + num, 0f, 20f);
				value.LastUpdatedTime = Time.time;
			}
		}

		private void DecayEnemyCooldown(EnemyCooldownState state)
		{
			if (state != null)
			{
				float time = Time.time;
				float num = Mathf.Max(0f, time - state.LastUpdatedTime);
				float num2 = Mathf.Max(1f, (_enemyVarietyCooldownDecaySeconds != null) ? _enemyVarietyCooldownDecaySeconds.Value : 22f);
				if (num > 0f)
				{
					state.Heat *= Mathf.Exp((0f - num) / num2);
					state.LastUpdatedTime = time;
				}
			}
		}

		private EnemySpawnCandidate PickWeightedCandidate(List<WeightedCandidate> weighted)
		{
			if (weighted == null || weighted.Count == 0)
			{
				return null;
			}
			float num = 0f;
			for (int i = 0; i < weighted.Count; i++)
			{
				num += weighted[i].Weight;
			}
			float num2 = Random.value * num;
			for (int j = 0; j < weighted.Count; j++)
			{
				num2 -= weighted[j].Weight;
				if (num2 <= 0f)
				{
					return weighted[j].Candidate;
				}
			}
			return weighted[weighted.Count - 1].Candidate;
		}

		private bool CanCandidateReplaceSource(EnemySpawnCandidate candidate, EnemyProfile sourceProfile, GameObject source)
		{
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			if (candidate == null || candidate.Pool == null)
			{
				return false;
			}
			if (!candidate.IsLarge)
			{
				return true;
			}
			if (_enableLargeEnemyPool == null || !_enableLargeEnemyPool.Value)
			{
				return false;
			}
			if (sourceProfile.IsFlying)
			{
				return false;
			}
			if ((Object)(object)source == (Object)null)
			{
				return false;
			}
			int num = Mathf.Max(1, (_largeEnemyMinNonFilthWaveSize != null) ? _largeEnemyMinNonFilthWaveSize.Value : 5);
			if (_activeWaveNonFilthEnemyCount < num)
			{
				return false;
			}
			if (!HasGroundBelow(source.transform.position))
			{
				return false;
			}
			return true;
		}

		private bool HasGroundBelow(Vector3 position)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: 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_003a: Unknown result type (might be due to invalid IL or missing references)
			float num = Mathf.Max(1f, (_largeEnemyGroundCheckDistance != null) ? _largeEnemyGroundCheckDistance.Value : 8f);
			RaycastHit val = default(RaycastHit);
			return Physics.Raycast(position + Vector3.up * 1.25f, Vector3.down, ref val, num + 1.25f, -1, (QueryTriggerInteraction)1);
		}

		private bool SpawnReplacement(PendingWaveEnemy entry, EnemyTemplatePool pool, EnemyProfile sourceProfile, EnemySpawnCandidate candidate)
		{
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: 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_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0176: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = entry?.Source;
			bool flag = IsEntrySourceAlive(entry);
			GameObject randomLiveTemplate = pool.GetRandomLiveTemplate();
			if ((Object)(object)randomLiveTemplate == (Object)null)
			{
				return false;
			}
			if (!IsTemplateSafeToSpawn(randomLiveTemplate, pool.Key))
			{
				return false;
			}
			try
			{
				Vector3 val2 = (flag ? val.transform.position : (entry?.Position ?? Vector3.zero));
				if (candidate != null && candidate.IsLarge && (!flag || !CanCandidateReplaceSource(candidate, sourceProfile, val)))
				{
					return false;
				}
				Quaternion val3 = (flag ? val.transform.rotation : (entry?.Rotation ?? Quaternion.identity));
				Transform val4 = (flag ? val.transform.parent : entry?.Parent);
				GameObject val5 = Object.Instantiate<GameObject>(randomLiveTemplate, val2, val3, val4);
				((Object)val5).name = ((Object)randomLiveTemplate).name + " [Level Death Replacement]";
				val5.AddComponent<LevelDeathSpawnedMarker>();
				SanitizeClonedEnemyRuntimeState(val5);
				if (!val5.activeSelf)
				{
					val5.SetActive(true);
				}
				if (!IsSpawnedCloneValid(val5, pool.Key))
				{
					DestroyCloneIfPresent(val5);
					if (_debugLogging != null && _debugLogging.Value)
					{
						((BaseUnityPlugin)this).Logger.LogInfo((object)("Level Death rejected a replacement clone that did not validate: " + pool.Key));
					}
					return false;
				}
				TryApplyHighMeterSpawnBuffs(val5);
				RecordEnemySelectionCooldown(pool.Key, pool.Cost, pool.IsLarge);
				TrySpawnCerberusPairBonus(val5, val2, val4, (candidate != null) ? candidate.Pool : pool);
				if (candidate != null && candidate.IsLarge)
				{
					_activeWaveLargeEnemySpawned = true;
				}
				if (flag)
				{
					((MonoBehaviour)this).StartCoroutine(DestroyOriginalAfterReplacementValid(val, val5, pool.Key));
				}
				_nextAllowedBonusSpawnTime = Time.time + Mathf.Max(0.1f, _minimumSecondsBetweenBonusSpawns.Value * 0.75f);
				if (_debugLogging.Value)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Level Death replaced natural " + sourceProfile.Key + " with " + pool.Key + " at " + Mathf.RoundToInt(_meter) + "%, effectiveCost=" + (candidate?.EffectiveCost ?? pool.Cost) + "."));
				}
				return true;
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Level Death skipped a type replacement safely: " + ex.GetType().Name + ": " + ex.Message));
				return false;
			}
		}

		private int GetPointCapForSource(int baseCost)
		{
			return Mathf.Max(baseCost, baseCost + GetBonusBudgetForMeter());
		}

		private int GetBonusBudgetForMeter()
		{
			float num = Mathf.Clamp(_meter, 0f, 400f);
			int num2 = Mathf.Max(1, _maxBonusBudget.Value);
			int num3 = Mathf.Max(num2, Mathf.RoundToInt((float)num2 * Mathf.Max(1f, _deathMarkBudgetMultiplier.Value)));
			int num4 = Mathf.Max(num3, _maxOvercapBonusBudget.Value);
			float num5 = Mathf.InverseLerp(_minimumMeterForBonusSpawns.Value, 100f, Mathf.Min(num, 100f));
			if (num < 100f)
			{
				return Mathf.RoundToInt(Mathf.Lerp(0f, (float)num2, num5));
			}
			float num6 = Mathf.InverseLerp(100f, 400f, num);
			return Mathf.RoundToInt(Mathf.Lerp((float)num3, (float)num4, Mathf.Pow(Mathf.Clamp01(num6), 0.82f)));
		}

		private bool ShouldSuppressLowTier(string key)
		{
			return false;
		}

		private int GetGlobalEffectiveCost(int baseCost)
		{
			float num = Mathf.Max(1f, _globalEnemyCostMultiplier.Value);
			int num2 = Mathf.Max(0, _globalEnemyFlatSurcharge.Value);
			return Mathf.Max(baseCost + 1, Mathf.CeilToInt((float)baseCost * num) + num2);
		}

		private bool ShouldAvoidCloningOrReplacingSource(GameObject source)
		{
			if ((Object)(object)source == (Object)null || _sanitizeInheritedIdolState == null || !_sanitizeInheritedIdolState.Value)
			{
				return false;
			}
			try
			{
				foreach (Component item in FindEnemyIdentifierComponents(source))
				{
					if ((Object)(object)item == (Object)null)
					{
						continue;
					}
					Type type = ((object)item).GetType();
					if (ReadBoolMember(type, item, "blessed", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
					{
						if (_debugLogging != null && _debugLogging.Value)
						{
							((BaseUnityPlugin)this).Logger.LogInfo((object)("Level Death skipped an Idol-blessed enemy as a clone/replacement source: " + ((Object)source).name));
						}
						return true;
					}
				}
			}
			catch (Exception ex)
			{
				if (_debugLogging != null && _debugLogging.Value)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Level Death could not inspect Idol/blessed state on " + ((Object)source).name + ": " + ex.Message));
				}
			}
			return false;
		}

		private void SanitizeClonedEnemyRuntimeState(GameObject clone)
		{
			if ((Object)(object)clone == (Object)null || _sanitizeInheritedIdolState == null || !_sanitizeInheritedIdolState.Value)
			{
				return;
			}
			try
			{
				bool flag = false;
				foreach (Component item in FindEnemyIdentifierComponents(clone))
				{
					if (!((Object)(object)item == (Object)null))
					{
						Type type = ((object)item).GetType();
						if (ReadBoolMember(type, item, "blessed", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
						{
							SetBoolMember(type, item, "blessed", value: false, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
							flag = true;
						}
						InvokeMethodIfExists(type, item, "UpdateBuffs", null);
						InvokeMethodIfExists(type, item, "UpdateModifiers", null);
					}
				}
				if (flag && _debugLogging != null && _debugLogging.Value)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Level Death cleared inherited Idol/blessed state from clone: " + ((Object)clone).name));
				}
			}
			catch (Exception ex)
			{
				if (_debugLogging != null && _debugLogging.Value)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Level Death could not sanitize clone runtime state on " + ((Object)clone).name + ": " + ex.Message));
				}
			}
		}

		[IteratorStateMachine(typeof(<FindEnemyIdentifierComponents>d__180))]
		private IEnumerable<Component> FindEnemyIdentifierComponents(GameObject gameObject)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <FindEnemyIdentifierComponents>d__180(-2)
			{
				<>4__this = this,
				<>3__gameObject = gameObject
			};
		}

		private void TryApplyHighMeterSpawnBuffs(GameObject clone)
		{
			if (!_allowHighMeterEnemyBuffs.Value || (Object)(object)clone == (Object)null || _meter < 100f)
			{
				return;
			}
			try
			{
				bool flag = RollRadiant();
				bool flag2 = RollEnrage();
				if (flag)
				{
					ApplyRadiantLikeBuff(clone);
				}
				if (flag2)
				{
					ApplyEnrage(clone);
				}
				if (_debugLogging.Value && (flag || flag2))
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Level Death buffed reinforcement " + ((Object)clone).name + " radiant=" + flag + " enraged=" + flag2 + " meter=" + Mathf.RoundToInt(_meter) + "%."));
				}
			}
			catch (Exception ex)
			{
				if (_debugLogging.Value)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Level Death skipped high-meter spawn buff safely: " + ex.GetType().Name + ": " + ex.Message));
				}
			}
		}

		private bool RollEnrage()
		{
			if (_meter < 100f)
			{
				return false;
			}
			float num = Mathf.InverseLerp(100f, 400f, _meter);
			float num2 = Mathf.Lerp(Mathf.Clamp01(_enrageChanceAt300.Value), Mathf.Clamp01(_enrageChanceAt1000.Value), Mathf.Sqrt(Mathf.Clamp01(num)));
			return Random.value < num2;
		}

		private bool RollRadiant()
		{
			if (_meter < 200f)
			{
				return false;
			}
			float num = Mathf.InverseLerp(200f, 400f, _meter);
			float num2 = Mathf.Lerp(Mathf.Clamp01(_radiantChanceAt400.Value), Mathf.Clamp01(_radiantChanceAt1000.Value), Mathf.Sqrt(Mathf.Clamp01(num)));
			return Random.value < num2;
		}

		private void ApplyRadiantLikeBuff(GameObject clone)
		{
			Component val = FindEnemyIdentifierComponent(clone);
			if (!((Object)(object)val == (Object)null))
			{
				Type type = ((object)val).GetType();
				float num = Mathf.Clamp(_radianceTier.Value, 1f, 3f);
				SetFloatMember(type, val, "radianceTier", num, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				InvokeMethodIfExists(type, val, "SpeedBuff", new object[1] { num });
				InvokeMethodIfExists(type, val, "HealthBuff", new object[1] { num });
				InvokeMethodIfExists(type, val, "DamageBuff", null);
				InvokeMethodIfExists(type, val, "UpdateBuffs", null);
				InvokeMethodIfExists(type, val, "UpdateModifiers", null);
				InvokeMethodIfExists(type, val, "ForceGetHealth", null);
			}
		}

		private void ApplyEnrage(GameObject clone)
		{
			Component[] componentsInChildren = clone.GetComponentsInChildren<Component>(true);
			foreach (Component val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null))
				{
					Type type = ((object)val).GetType();
					if (CanEnrage(type) && !ReadBoolMember(type, val, "isEnraged", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
					{
						InvokeMethodIfExists(type, val, "Enrage", null);
					}
				}
			}
		}

		private bool CanEnrage(Type type)
		{
			if (type == null)
			{
				return false;
			}
			if (AccessTools.Method(type, "Enrage", (Type[])null, (Type[])null) == null)
			{
				return false;
			}
			if (type.Name.IndexOf("enrage", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return true;
			}
			Type[] interfaces = type.GetInterfaces();
			foreach (Type type2 in interfaces)
			{
				if (type2.Name.IndexOf("IEnrage", StringComparison.OrdinalIgnoreCase) >= 0 || type2.Name.IndexOf("Enrage", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					return true;
				}
			}
			return false;
		}

		private Component FindEnemyIdentifierComponent(GameObject gameObject)
		{
			if ((Object)(object)gameObject == (Object)null)
			{
				return null;
			}
			Type enemyIdentifierType = GetEnemyIdentifierType();
			if (enemyIdentifierType == null)
			{
				return null;
			}
			return gameObject.GetComponent(enemyIdentifierType) ?? gameObject.GetComponentInChildren(enemyIdentifierType, true);
		}

		private void SetFloatMember(Type type, object instance, string memberName, float value, BindingFlags flags)
		{
			FieldInfo field = type.GetField(memberName, flags);
			if (field != null && field.FieldType == typeof(float))
			{
				field.SetValue(instance, value);
				return;
			}
			PropertyInfo property = type.GetProperty(memberName, flags);
			if (property != null && property.PropertyType == typeof(float) && property.CanWrite)
			{
				property.SetValue(instance, value, null);
			}
		}

		private string ReadStringMember(object instance, Type type, BindingFlags flags, string memberName)
		{
			if (instance == null || type == null || string.IsNullOrEmpty(memberName))
			{
				return null;
			}
			try
			{
				FieldInfo field = type.GetField(memberName, flags);
				if (field != null)
				{
					object value = field.GetValue(instance);
					if (value != null)
					{
						return value.ToString();
					}
				}
				PropertyInfo property = type.GetProperty(memberName, flags);
				if (property != null && property.GetIndexParameters().Length == 0 && property.CanRead)
				{
					object value2 = property.GetValue(instance, null);
					if (value2 != null)
					{
						return value2.ToString();
					}
				}
			}
			catch
			{
			}
			return null;
		}

		private bool ReadBoolMember(Type type, object instance, string memberName, BindingFlags flags)
		{
			FieldInfo field = type.GetField(memberName, flags);
			if (field != null && field.FieldType == typeof(bool))
			{
				return (bool)field.GetValue(instance);
			}
			PropertyInfo property = type.GetProperty(memberName, flags);
			if (property != null && property.PropertyType == typeof(bool) && property.CanRead)
			{
				return (bool)property.GetValue(instance, null);
			}
			return false;
		}

		private void SetBoolMember(Type type, object instance, string memberName, bool value, BindingFlags flags)
		{
			FieldInfo field = type.GetField(memberName, flags);
			if (field != null && field.FieldType == typeof(bool))
			{
				field.SetValue(instance, value);
				return;
			}
			PropertyInfo property = type.GetProperty(memberName, flags);
			if (property != null && property.PropertyType == typeof(bool) && property.CanWrite)
			{
				property.SetValue(instance, value, null);
			}
		}

		private void InvokeMethodIfExists(Type type, object instance, string methodName, object[] args)
		{
			MethodInfo methodInfo = AccessTools.Method(type, methodName, (Type[])null, (Type[])null);
			if (!(methodInfo == null))
			{
				ParameterInfo[] parameters = methodInfo.GetParameters();
				object[] parameters2 = args;
				if (parameters.Length == 0)
				{
					parameters2 = null;
				}
				else if (args == null || args.Length != parameters.Length)
				{
					return;
				}
				methodInfo.Invoke(instance, parameters2);
			}
		}

		private Vector3 PickSpawnPosition(Vector3 sourcePosition, int index)
		{
			//IL_0027: 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)
			//IL_0030: 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_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (mig