Decompiled source of EasyCampaignTweaks v1.0.0

EasyCampaignTweaks.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("EasyCampaignTweaks")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("EasyCampaignTweaks")]
[assembly: AssemblyTitle("EasyCampaignTweaks")]
[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 EasyCampaignTweaks
{
	[BepInPlugin("twistedscarlett.ultrakill.easycampaigntweaks", "Easy Campaign Tweaks", "1.0.2")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class EasyCampaignTweaksPlugin : BaseUnityPlugin
	{
		[CompilerGenerated]
		private sealed class <DestroyOriginalAfterReplacementValid>d__108 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public GameObject source;

			public EasyCampaignTweaksPlugin <>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__108(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;
				EasyCampaignTweaksPlugin easyCampaignTweaksPlugin = <>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 (easyCampaignTweaksPlugin.IsSpawnedCloneValid(clone, key))
					{
						Object.Destroy((Object)(object)source);
					}
					else if ((Object)(object)clone != (Object)null)
					{
						Object.Destroy((Object)(object)clone);
					}
					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 = "twistedscarlett.ultrakill.easycampaigntweaks";

		public const string PluginName = "Easy Campaign Tweaks";

		public const string PluginVersion = "1.0.2";

		internal static EasyCampaignTweaksPlugin Instance;

		internal static ManualLogSource Log;

		private Harmony _harmony;

		private Type _enemyIdentifierType;

		private Type _gunControlType;

		private Type _dualWieldType;

		private Type _newMovementType;

		private Component _cachedMovement;

		private Rigidbody _cachedMovementRigidbody;

		private readonly Dictionary<string, FieldInfo> _fieldCache = new Dictionary<string, FieldInfo>();

		private readonly Dictionary<string, PropertyInfo> _propertyCache = new Dictionary<string, PropertyInfo>();

		private readonly Dictionary<string, MethodInfo> _methodCache = new Dictionary<string, MethodInfo>();

		private ConfigEntry<bool> _debugLogging;

		private ConfigEntry<float> _spawnDistanceMin;

		private ConfigEntry<float> _spawnDistanceMax;

		private ConfigEntry<bool> _mitosisEnabled;

		private ConfigEntry<float> _mitosisMultiplier;

		private ConfigEntry<bool> _enrageEnabled;

		private ConfigEntry<float> _enrageChancePercent;

		private ConfigEntry<bool> _radianceEnabled;

		private ConfigEntry<float> _radianceChancePercent;

		private ConfigEntry<float> _radianceTier;

		private ConfigEntry<bool> _radianceUseCustomMultipliers;

		private ConfigEntry<float> _radianceSpeedMultiplier;

		private ConfigEntry<float> _radianceHealthMultiplier;

		private ConfigEntry<float> _radianceDamageMultiplier;

		private ConfigEntry<bool> _radianceAffectsDamage;

		private ConfigEntry<bool> _sandifyEnabled;

		private ConfigEntry<float> _sandifyChancePercent;

		private ConfigEntry<bool> _tankifyEnabled;

		private ConfigEntry<float> _tankifyChancePercent;

		private ConfigEntry<float> _tankifyHealthMultiplier;

		private ConfigEntry<bool> _strongerEnabled;

		private ConfigEntry<float> _strongerChancePercent;

		private ConfigEntry<int> _strongerMinimumTierIncrease;

		private ConfigEntry<int> _strongerMaximumTierIncrease;

		private ConfigEntry<int> _strongerMaxReplacementsPerWave;

		private ConfigEntry<int> _strongerAntiSoftlockFilthCount;

		private ConfigEntry<bool> _randomizerEnabled;

		private ConfigEntry<float> _randomizerChancePercent;

		private ConfigEntry<int> _maxRandomizerReplacementsPerWave;

		private ConfigEntry<int> _randomizerAntiSoftlockFilthCount;

		private ConfigEntry<bool> _ultrahotEnabled;

		private ConfigEntry<float> _ultrahotVelocityForNormalTime;

		private ConfigEntry<float> _ultrahotMinimumTimeScale;

		private ConfigEntry<float> _ultrahotMaximumTimeScale;

		private ConfigEntry<float> _ultrahotSmoothing;

		private ConfigEntry<bool> _dualWieldEnabled;

		private ConfigEntry<int> _dualWieldStacks;

		private ConfigEntry<float> _dualWieldDuration;

		private ConfigEntry<float> _dualWieldRefreshSeconds;

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

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

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

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

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

		private float _pendingWaveDeadline = -1f;

		private float _sceneLoadedTime;

		private string _currentSceneName = string.Empty;

		private bool _ultrahotWasActive;

		private bool _globalPoolScanned;

		private float _nextDualWieldRefresh;

		private const int DefaultMaxLivingModdedEnemies = 96;

		private const bool AffectCyberGrind = false;

		private const float SceneStartGraceSeconds = 1f;

		private const float SpawnWaveReadSeconds = 1.25f;

		private const float LowTierSpawnWaveReadSeconds = 0.45f;

		private const int ProcessWaveImmediatelyAtSize = 5;

		private const int LargeEnemyMinimumNonFilthWaveSize = 5;

		private const float LargeEnemyRarityMultiplier = 0.5f;

		private const float LargeEnemyGroundCheckDistance = 10f;

		private void Awake()
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			CacheCommonTypes();
			BindConfig();
			_sceneLoadedTime = Time.time;
			Scene activeScene = SceneManager.GetActiveScene();
			_currentSceneName = ((Scene)(ref activeScene)).name ?? string.Empty;
			SceneManager.sceneLoaded += OnSceneLoaded;
			_harmony = new Harmony("twistedscarlett.ultrakill.easycampaigntweaks");
			PatchSafely();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Easy Campaign Tweaks loaded. Gameplay modifiers are disabled by default.");
		}

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

		private void CacheCommonTypes()
		{
			_enemyIdentifierType = AccessTools.TypeByName("EnemyIdentifier");
			_gunControlType = AccessTools.TypeByName("GunControl");
			_dualWieldType = AccessTools.TypeByName("DualWield");
			_newMovementType = AccessTools.TypeByName("NewMovement");
		}

		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()
		{
			_spawnDistanceMin = Bind("Mitosis", "SpawnDistanceMin", 2f, "Minimum offset used for Mitosis duplicates.");
			_spawnDistanceMax = Bind("Mitosis", "SpawnDistanceMax", 5.5f, "Maximum offset used for Mitosis duplicates.");
			_mitosisEnabled = Bind("Mitosis", "Enabled", defaultValue: false, "Duplicate eligible enemy spawns.");
			_mitosisMultiplier = Bind("Mitosis", "EnemySpawnMultiplier", 2f, "Total enemy spawn multiplier. 2.0 means one extra copy per natural enemy on average.");
			_enrageEnabled = Bind("Enraged", "Enabled", defaultValue: false, "Enable Enraged module.");
			_enrageChancePercent = Bind("Enraged", "EnrageChancePercent", 100f, "Chance from 0 to 100 for eligible enemies to become enraged.");
			_radianceEnabled = Bind("Radiant", "Enabled", defaultValue: false, "Enable Radiant module.");
			_radianceChancePercent = Bind("Radiant", "RadiantChancePercent", 100f, "Chance from 0 to 100 for eligible enemies to receive radiance.");
			_radianceTier = Bind("Radiant", "RadianceTier", 1f, "Radiance tier. Default 1 is tier-1 radiance. Values above 1 increase the game's radiance tier scaling.");
			_radianceUseCustomMultipliers = Bind("Radiant", "UseCustomMultipliers", defaultValue: false, "If false, use vanilla-style tier-1 per-enemy radiance presets. If true, use the custom Speed/Health/Damage multipliers below.");
			_radianceSpeedMultiplier = Bind("Radiant", "CustomSpeedMultiplier", 1.5f, "Only used when UseCustomMultipliers is true.");
			_radianceHealthMultiplier = Bind("Radiant", "CustomHealthMultiplier", 1.5f, "Only used when UseCustomMultipliers is true.");
			_radianceDamageMultiplier = Bind("Radiant", "CustomDamageMultiplier", 1.5f, "Only used when UseCustomMultipliers is true and RadianceAffectsDamage is enabled.");
			_radianceAffectsDamage = Bind("Radiant", "RadianceAffectsDamage", defaultValue: true, "If true, radiant enemies receive the damage buff. Disable for Cyber Grind-style no-damage radiance.");
			_sandifyEnabled = Bind("Sandify", "Enabled", defaultValue: false, "Enable Sandify module.");
			_sandifyChancePercent = Bind("Sandify", "SandifyChancePercent", 100f, "Chance from 0 to 100 for eligible enemies to spawn sanded.");
			_tankifyEnabled = Bind("Tankify", "Enabled", defaultValue: false, "Enable Tankify module.");
			_tankifyChancePercent = Bind("Tankify", "TankifyChancePercent", 100f, "Chance from 0 to 100 for eligible enemies to have altered health.");
			_tankifyHealthMultiplier = Bind("Tankify", "HealthMultiplier", 2f, "Enemy health multiplier. Values below 1 make enemies frailer; values above 1 make them tankier.");
			_strongerEnabled = Bind("Stronger Enemies", "Enabled", defaultValue: false, "Replace eligible enemies with stronger enemies from the protected global pool.");
			_strongerChancePercent = Bind("Stronger Enemies", "ReplacementChancePercent", 100f, "Chance from 0 to 100 for each eligible enemy in a wave to be considered for a stronger replacement.");
			_strongerMinimumTierIncrease = Bind("Stronger Enemies", "MinimumTierIncrease", 1, "Minimum tier increase. Default 1 means at least the next stronger tier.");
			_strongerMaximumTierIncrease = Bind("Stronger Enemies", "MaximumTierIncrease", 1, "Preferred maximum tier increase. If this is higher than the minimum, the target tier jump is randomized.");
			_strongerMaxReplacementsPerWave = Bind("Stronger Enemies", "MaxReplacementsPerWave", 25, "Hard cap for Stronger Enemies replacements in a single spawn wave.");
			_strongerAntiSoftlockFilthCount = Bind("Stronger Enemies", "AntiSoftlockFilth", 2, "After this module replaces enemies in a wave of 3+ natural enemies, spawn this many extra Filth as softlock insurance. Set to 0 to disable.");
			_randomizerEnabled = Bind("Safe Randomizer", "Enabled", defaultValue: false, "Randomize natural enemy spawns using protected templates. Malicious Faces are always protected.");
			_randomizerChancePercent = Bind("Safe Randomizer", "ReplacementChancePercent", 100f, "Chance from 0 to 100 for each eligible natural enemy in a wave to be replaced.");
			_maxRandomizerReplacementsPerWave = Bind("Safe Randomizer", "MaxReplacementsPerWave", 25, "Hard cap for Safe Randomizer replacements in a single wave.");
			_randomizerAntiSoftlockFilthCount = Bind("Safe Randomizer", "AntiSoftlockFilth", 2, "After this module replaces enemies in a wave of 3+ natural enemies, spawn this many extra Filth as softlock insurance. Set to 0 to disable.");
			_ultrahotEnabled = Bind("ULTRAHOT", "Enabled", defaultValue: false, "Enable ULTRAHOT: time moves based on player movement speed.");
			_ultrahotVelocityForNormalTime = Bind("ULTRAHOT", "VelocityForNormalTime", 20f, "Player velocity magnitude that maps to normal time scale.");
			_ultrahotMinimumTimeScale = Bind("ULTRAHOT", "MinimumTimeScale", 0.01f, "Slowest time scale while nearly still.");
			_ultrahotMaximumTimeScale = Bind("ULTRAHOT", "MaximumTimeScale", 1.25f, "Fastest time scale while moving quickly.");
			_ultrahotSmoothing = Bind("ULTRAHOT", "Smoothing", 15f, "How quickly time scale follows player movement.");
			_dualWieldEnabled = Bind("Dual Wield", "Enabled", defaultValue: false, "Always keep at least the configured number of Dual Wield powerups active.");
			_dualWieldStacks = Bind("Dual Wield", "Stacks", 1, "Minimum number of Dual Wield stacks to keep active. Stacks can be very strong.");
			_dualWieldDuration = Bind("Dual Wield", "PowerupDurationSeconds", 999999f, "Duration used when creating a maintained Dual Wield stack.");
			_dualWieldRefreshSeconds = Bind("Dual Wield", "RefreshCheckSeconds", 0.75f, "How often the mod checks whether it needs to add missing Dual Wield stacks.");
			_debugLogging = Bind("Debug", "DebugLogging", defaultValue: false, "Verbose logs for spawn-wave decisions and skipped enemies.");
		}

		private void PatchSafely()
		{
			PatchFirstMethod(GetEnemyIdentifierType(), "Start", "EnemyStartPostfix", required: true);
		}

		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(EasyCampaignTweaksPlugin).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 static void EnemyStartPostfix(object __instance)
		{
			try
			{
				Instance?.OnEnemyStarted(__instance);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogWarning((object)("Enemy Start patch failed safely: " + ex.GetType().Name + ": " + ex.Message));
				}
			}
		}

		private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			_sceneLoadedTime = Time.time;
			_currentSceneName = ((Scene)(ref scene)).name ?? string.Empty;
			_processedEnemies.Clear();
			_pendingWave.Clear();
			_pendingWaveDeadline = -1f;
			_scenePools.Clear();
			_globalPools.Clear();
			_globalPoolScanned = false;
			_cachedMovement = null;
			_cachedMovementRigidbody = null;
			_nextDualWieldRefresh = Time.time + 1f;
			if (_ultrahotWasActive)
			{
				Time.timeScale = 1f;
			}
			if (_debugLogging != null && _debugLogging.Value)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Scene loaded: " + _currentSceneName));
			}
		}

		private void Update()
		{
			if (NeedsWaveDirector())
			{
				ProcessPendingWaveIfReady();
			}
			else if (_pendingWave.Count > 0)
			{
				_pendingWave.Clear();
				_pendingWaveDeadline = -1f;
			}
			UpdateUltraHot();
			UpdateDualWieldMaintenance();
		}

		private bool CanModifyCurrentScene()
		{
			string text = (_currentSceneName ?? string.Empty).ToLowerInvariant();
			if (text.Contains("endless") || text.Contains("cyber") || text.Contains("grid"))
			{
				return false;
			}
			return true;
		}

		private bool NeedsWaveDirector()
		{
			if (_strongerEnabled == null || !_strongerEnabled.Value)
			{
				if (_randomizerEnabled != null)
				{
					return _randomizerEnabled.Value;
				}
				return false;
			}
			return true;
		}

		private bool NeedsEnemyEffectModifiers()
		{
			if ((_sandifyEnabled == null || !_sandifyEnabled.Value) && (_tankifyEnabled == null || !_tankifyEnabled.Value) && (_radianceEnabled == null || !_radianceEnabled.Value))
			{
				if (_enrageEnabled != null)
				{
					return _enrageEnabled.Value;
				}
				return false;
			}
			return true;
		}

		private void OnEnemyStarted(object enemyObject)
		{
			if (!CanModifyCurrentScene() || 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<EcmSpawnedMarker>() != (Object)null || ((Object)gameObject).name.IndexOf("[ECM", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				ApplyConfiguredEnemyEffects(gameObject, isModdedEnemy: true);
				return;
			}
			if (IsHardBlockedTemplateName(((Object)gameObject).name))
			{
				LogBlocked(string.Empty, ((Object)gameObject).name, "hard-blocked name");
				return;
			}
			string text = ReadEnemyTypeName(enemyObject, gameObject);
			EnemyProfile profile = EnemyCatalog.GetProfile(text, ((Object)gameObject).name, allowUnknown: false, allowExperimentalLarge: true);
			if (!profile.Allowed)
			{
				LogBlocked(text, ((Object)gameObject).name, profile.BlockReason);
				return;
			}
			if (NeedsWaveDirector())
			{
				RegisterTemplate(_scenePools, profile, gameObject, allowInactiveTemplates: false);
				RegisterTemplate(_globalPools, profile, gameObject, allowInactiveTemplates: false);
			}
			ApplyConfiguredEnemyEffects(gameObject, isModdedEnemy: false);
			TryApplyImmediateMitosis(gameObject, profile);
			if (NeedsWaveDirector())
			{
				QueueNaturalEnemyForWave(gameObject, profile);
			}
		}

		private void QueueNaturalEnemyForWave(GameObject source, EnemyProfile profile)
		{
			if (!((Object)(object)source == (Object)null) && 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 || !IsFastFodder(pendingWaveEnemy.Profile.Key))
				{
					flag = false;
					break;
				}
			}
			float num = Mathf.Clamp(1.25f, 0.05f, 6f);
			float num2 = Mathf.Clamp(0.45f, 0.05f, num);
			float num3 = (flag ? num2 : num);
			int num4 = Mathf.Max(2, 5);
			if (_pendingWave.Count >= num4)
			{
				num3 = Mathf.Min(num3, 0.12f);
			}
			_pendingWaveDeadline = Time.time + num3;
		}

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

		private void ProcessPendingWaveIfReady()
		{
			if (!NeedsWaveDirector())
			{
				if (_pendingWave.Count > 0)
				{
					_pendingWave.Clear();
					_pendingWaveDeadline = -1f;
				}
			}
			else
			{
				if (_pendingWave.Count == 0 || Time.time < _pendingWaveDeadline)
				{
					return;
				}
				float num = Mathf.Max(0f, 1f);
				if (Time.time - _sceneLoadedTime < num)
				{
					_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<EcmSpawnedMarker>() != (Object)null)))
					{
						list.Add(pendingWaveEnemy);
					}
				}
				_pendingWave.Clear();
				_pendingWaveDeadline = -1f;
				if (list.Count != 0)
				{
					EnsureGlobalPoolScanned();
					HashSet<int> replacedIds = new HashSet<int>();
					int nonFilthCount = CountNonFilthEnemies(list);
					int num2 = TryApplyStrongerEnemies(list, replacedIds, nonFilthCount);
					int num3 = TryApplySafeRandomizer(list, replacedIds, nonFilthCount);
					if (_debugLogging.Value && (num2 > 0 || num3 > 0))
					{
						((BaseUnityPlugin)this).Logger.LogInfo((object)("ECM wave: natural=" + list.Count + ", stronger=" + num2 + ", random=" + num3 + "."));
					}
				}
			}
		}

		private Dictionary<string, EnemyTemplatePool> GetPreferredReplacementPools()
		{
			EnsureGlobalPoolScanned();
			if (_globalPools.Count > 1)
			{
				return _globalPools;
			}
			return _scenePools;
		}

		private void EnsureGlobalPoolScanned()
		{
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			if (_globalPoolScanned)
			{
				return;
			}
			_globalPoolScanned = 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 || (Object)(object)val.gameObject == (Object)null)
					{
						continue;
					}
					GameObject gameObject = val.gameObject;
					Scene scene = gameObject.scene;
					if ((((Scene)(ref scene)).IsValid() && (Object)(object)gameObject.GetComponent<EcmSpawnedMarker>() != (Object)null) || IsHardBlockedTemplateName(((Object)gameObject).name))
					{
						continue;
					}
					EnemyProfile profile = EnemyCatalog.GetProfile(ReadEnemyTypeName(val, gameObject), ((Object)gameObject).name, allowUnknown: false, allowExperimentalLarge: true);
					if (profile.Allowed && !(profile.Key == "maurice"))
					{
						int count = _globalPools.Count;
						RegisterTemplate(_globalPools, profile, gameObject, allowInactiveTemplates: true);
						if (_globalPools.Count > count)
						{
							num++;
						}
					}
				}
			}
			catch (Exception ex)
			{
				if (_debugLogging != null && _debugLogging.Value)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("ECM global enemy template scan skipped safely: " + ex.Message));
				}
			}
			if (_debugLogging != null && _debugLogging.Value)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("ECM protected global pool contains " + _globalPools.Count + " enemy types after scan."));
			}
		}

		private int TryApplyStrongerEnemies(List<PendingWaveEnemy> wave, HashSet<int> replacedIds, int nonFilthCount)
		{
			if (!_strongerEnabled.Value)
			{
				return 0;
			}
			if (EcmSpawnedMarker.ActiveCount >= 96)
			{
				return 0;
			}
			float num = Mathf.Clamp01(_strongerChancePercent.Value / 100f);
			int num2 = Mathf.Max(0, _strongerMaxReplacementsPerWave.Value);
			if (num <= 0f || num2 <= 0)
			{
				return 0;
			}
			int num3 = Mathf.Max(1, _strongerMinimumTierIncrease.Value);
			int maxIncrease = Mathf.Max(num3, _strongerMaximumTierIncrease.Value);
			Dictionary<string, EnemyTemplatePool> preferredReplacementPools = GetPreferredReplacementPools();
			if (preferredReplacementPools.Count <= 1)
			{
				return 0;
			}
			List<PendingWaveEnemy> list = CopyWave(wave);
			Shuffle(list);
			int num4 = 0;
			for (int i = 0; i < list.Count; i++)
			{
				if (num4 >= num2)
				{
					break;
				}
				PendingWaveEnemy pendingWaveEnemy = list[i];
				if (pendingWaveEnemy != null && !replacedIds.Contains(pendingWaveEnemy.SourceId) && !(Random.value > num) && IsEntrySourceAlive(pendingWaveEnemy) && !IsMaurice(pendingWaveEnemy.Profile) && !ShouldAvoidCloningOrReplacingSource(pendingWaveEnemy.Source))
				{
					EnemySpawnCandidate enemySpawnCandidate = ChooseStrongerCandidate(pendingWaveEnemy.Profile, pendingWaveEnemy.Source, preferredReplacementPools, nonFilthCount, num3, maxIncrease);
					if (enemySpawnCandidate != null && SpawnReplacement(pendingWaveEnemy, enemySpawnCandidate, keepOriginalIfFails: true, "Stronger"))
					{
						replacedIds.Add(pendingWaveEnemy.SourceId);
						num4++;
					}
				}
			}
			if (num4 > 0)
			{
				SpawnAntiSoftlockFilth(wave, _strongerAntiSoftlockFilthCount.Value, "Stronger");
			}
			return num4;
		}

		private EnemySpawnCandidate ChooseStrongerCandidate(EnemyProfile sourceProfile, GameObject source, Dictionary<string, EnemyTemplatePool> pools, int nonFilthCount, int minIncrease, int maxIncrease)
		{
			int tier = sourceProfile.Tier;
			if (tier <= 0)
			{
				return null;
			}
			int num = Random.Range(minIncrease, maxIncrease + 1);
			int preferredTier = tier + num;
			int minTier = tier + minIncrease;
			int maxTier = tier + maxIncrease;
			List<WeightedCandidate> list = new List<WeightedCandidate>();
			BuildStrongerCandidateList(list, sourceProfile, source, pools, nonFilthCount, preferredTier, minTier, maxTier, strictPreferredBand: true);
			if (list.Count == 0)
			{
				BuildStrongerCandidateList(list, sourceProfile, source, pools, nonFilthCount, preferredTier, minTier, int.MaxValue, strictPreferredBand: false);
			}
			return PickWeightedCandidate(list);
		}

		private void BuildStrongerCandidateList(List<WeightedCandidate> weighted, EnemyProfile sourceProfile, GameObject source, Dictionary<string, EnemyTemplatePool> pools, int nonFilthCount, int preferredTier, int minTier, int maxTier, bool strictPreferredBand)
		{
			foreach (EnemyTemplatePool value in pools.Values)
			{
				if (value == null || !value.HasUsableTemplate || value.Key == sourceProfile.Key || value.Key == "maurice")
				{
					continue;
				}
				EnemyProfile profileByKey = EnemyCatalog.GetProfileByKey(value.Key);
				if (profileByKey.Allowed && profileByKey.Tier > sourceProfile.Tier && profileByKey.Tier >= minTier && profileByKey.Tier <= maxTier && (!value.IsLarge || CanUseLargeEnemyCandidate(value, sourceProfile, source, nonFilthCount)))
				{
					float num = Mathf.Abs(profileByKey.Tier - preferredTier);
					float num2 = 1f / (1f + num * 2f);
					if (!strictPreferredBand && profileByKey.Tier > maxTier)
					{
						num2 *= 0.35f;
					}
					if (value.IsLarge)
					{
						num2 *= 0.5f;
					}
					weighted.Add(new WeightedCandidate(new EnemySpawnCandidate(value), num2));
				}
			}
		}

		private int TryApplySafeRandomizer(List<PendingWaveEnemy> wave, HashSet<int> replacedIds, int nonFilthCount)
		{
			if (!_randomizerEnabled.Value)
			{
				return 0;
			}
			if (EcmSpawnedMarker.ActiveCount >= 96)
			{
				return 0;
			}
			float num = Mathf.Clamp01(_randomizerChancePercent.Value / 100f);
			int num2 = Mathf.Max(0, _maxRandomizerReplacementsPerWave.Value);
			if (num <= 0f || num2 <= 0)
			{
				return 0;
			}
			List<PendingWaveEnemy> list = CopyWave(wave);
			Shuffle(list);
			int num3 = 0;
			for (int i = 0; i < list.Count; i++)
			{
				if (num3 >= num2)
				{
					break;
				}
				PendingWaveEnemy pendingWaveEnemy = list[i];
				if (pendingWaveEnemy != null && !replacedIds.Contains(pendingWaveEnemy.SourceId) && !(Random.value > num) && IsEntrySourceAlive(pendingWaveEnemy) && !IsMaurice(pendingWaveEnemy.Profile) && !ShouldAvoidCloningOrReplacingSource(pendingWaveEnemy.Source))
				{
					EnemySpawnCandidate enemySpawnCandidate = ChooseRandomizerCandidate(pendingWaveEnemy.Profile, pendingWaveEnemy.Source, nonFilthCount);
					if (enemySpawnCandidate != null && SpawnReplacement(pendingWaveEnemy, enemySpawnCandidate, keepOriginalIfFails: true, "Randomizer"))
					{
						replacedIds.Add(pendingWaveEnemy.SourceId);
						num3++;
					}
				}
			}
			if (num3 > 0)
			{
				SpawnAntiSoftlockFilth(wave, _randomizerAntiSoftlockFilthCount.Value, "Randomizer");
			}
			return num3;
		}

		private EnemySpawnCandidate ChooseRandomizerCandidate(EnemyProfile sourceProfile, GameObject source, int nonFilthCount)
		{
			Dictionary<string, EnemyTemplatePool> preferredReplacementPools = GetPreferredReplacementPools();
			if (preferredReplacementPools.Count <= 1)
			{
				return null;
			}
			List<WeightedCandidate> list = new List<WeightedCandidate>();
			foreach (EnemyTemplatePool value in preferredReplacementPools.Values)
			{
				if (value != null && value.HasUsableTemplate && !(value.Key == sourceProfile.Key) && !(value.Key == "maurice") && (!value.IsLarge || CanUseLargeEnemyCandidate(value, sourceProfile, source, nonFilthCount)))
				{
					float num = 1f;
					if (value.Tier <= sourceProfile.Tier)
					{
						num *= 0.75f;
					}
					if (value.Tier >= sourceProfile.Tier + 2)
					{
						num *= 1.15f;
					}
					if (value.IsLarge)
					{
						num *= 0.5f;
					}
					list.Add(new WeightedCandidate(new EnemySpawnCandidate(value), num));
				}
			}
			return PickWeightedCandidate(list);
		}

		private int SpawnAntiSoftlockFilth(List<PendingWaveEnemy> wave, int requestedCount, string sourceName)
		{
			if (requestedCount <= 0 || wave == null || wave.Count < 3)
			{
				return 0;
			}
			if (EcmSpawnedMarker.ActiveCount >= 96)
			{
				return 0;
			}
			GameObject val = FindAntiSoftlockAnchor(wave);
			if ((Object)(object)val == (Object)null)
			{
				return 0;
			}
			GameObject val2 = FindFilthTemplate(wave);
			if ((Object)(object)val2 == (Object)null || !IsTemplateSafeToSpawn(val2, "filth"))
			{
				return 0;
			}
			EnemyProfile profileByKey = EnemyCatalog.GetProfileByKey("filth");
			int num = 0;
			for (int i = 0; i < requestedCount; i++)
			{
				if (EcmSpawnedMarker.ActiveCount >= 96)
				{
					break;
				}
				if (SpawnAdditionalTemplate(val2, val, profileByKey, sourceName + " Anti-Softlock Filth", i))
				{
					num++;
				}
			}
			if (_debugLogging.Value && num > 0)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("ECM " + sourceName + " spawned anti-softlock Filth x" + num + "."));
			}
			return num;
		}

		private GameObject FindAntiSoftlockAnchor(List<PendingWaveEnemy> wave)
		{
			for (int i = 0; i < wave.Count; i++)
			{
				PendingWaveEnemy pendingWaveEnemy = wave[i];
				if (pendingWaveEnemy != null && (Object)(object)pendingWaveEnemy.Source != (Object)null && pendingWaveEnemy.Source.activeInHierarchy)
				{
					return pendingWaveEnemy.Source;
				}
			}
			for (int j = 0; j < wave.Count; j++)
			{
				PendingWaveEnemy pendingWaveEnemy2 = wave[j];
				if (pendingWaveEnemy2 != null && (Object)(object)pendingWaveEnemy2.Source != (Object)null)
				{
					return pendingWaveEnemy2.Source;
				}
			}
			return null;
		}

		private GameObject FindFilthTemplate(List<PendingWaveEnemy> wave)
		{
			for (int i = 0; i < wave.Count; i++)
			{
				PendingWaveEnemy pendingWaveEnemy = wave[i];
				if (pendingWaveEnemy != null && pendingWaveEnemy.Profile.Key == "filth" && (Object)(object)pendingWaveEnemy.Source != (Object)null && pendingWaveEnemy.Source.activeInHierarchy && IsTemplateSafeToSpawn(pendingWaveEnemy.Source, "filth"))
				{
					return pendingWaveEnemy.Source;
				}
			}
			if (_scenePools.TryGetValue("filth", out var value) && value != null && value.HasUsableTemplate)
			{
				return value.GetRandomLiveTemplate();
			}
			if (_globalPools.TryGetValue("filth", out value) && value != null && value.HasUsableTemplate)
			{
				return value.GetRandomLiveTemplate();
			}
			return null;
		}

		private bool SpawnAdditionalTemplate(GameObject template, GameObject anchor, EnemyProfile profile, string sourceName, int index)
		{
			//IL_001c: 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_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)template == (Object)null || (Object)(object)anchor == (Object)null)
			{
				return false;
			}
			try
			{
				Vector3 val = FindNearbySpawnPosition(anchor.transform.position, index + 100);
				GameObject val2 = Object.Instantiate<GameObject>(template, val, anchor.transform.rotation, anchor.transform.parent);
				((Object)val2).name = ((Object)template).name + " [ECM " + sourceName + "]";
				val2.AddComponent<EcmSpawnedMarker>();
				SanitizeClonedEnemyRuntimeState(val2);
				if (!val2.activeSelf)
				{
					val2.SetActive(true);
				}
				if (!IsSpawnedCloneValid(val2, profile.Key))
				{
					Object.Destroy((Object)(object)val2);
					return false;
				}
				ApplyConfiguredEnemyEffects(val2, isModdedEnemy: true);
				return true;
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("ECM skipped anti-softlock Filth safely: " + ex.GetType().Name + ": " + ex.Message));
				return false;
			}
		}

		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 += Mathf.Max(0f, weighted[i].Weight);
			}
			if (num <= 0f)
			{
				return null;
			}
			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 CanUseLargeEnemyCandidate(EnemyTemplatePool pool, EnemyProfile sourceProfile, GameObject source, int nonFilthCount)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			if (pool == null || !pool.IsLarge)
			{
				return true;
			}
			if (sourceProfile.IsFlying)
			{
				return false;
			}
			if (nonFilthCount < 5)
			{
				return false;
			}
			if ((Object)(object)source == (Object)null)
			{
				return false;
			}
			if (!HasGroundBelow(source.transform.position))
			{
				return false;
			}
			return true;
		}

		private bool HasGroundBelow(Vector3 position)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			float num = 10f;
			RaycastHit val = default(RaycastHit);
			return Physics.Raycast(position + Vector3.up * 1.25f, Vector3.down, ref val, num + 1.25f, -1, (QueryTriggerInteraction)1);
		}

		private int TryApplyImmediateMitosis(GameObject source, EnemyProfile profile)
		{
			if (!_mitosisEnabled.Value)
			{
				return 0;
			}
			if ((Object)(object)source == (Object)null || !source.activeInHierarchy)
			{
				return 0;
			}
			if (EcmSpawnedMarker.ActiveCount >= 96)
			{
				return 0;
			}
			if (ShouldAvoidCloningOrReplacingSource(source))
			{
				return 0;
			}
			float num = Mathf.Max(1f, _mitosisMultiplier.Value) - 1f;
			int num2 = Mathf.FloorToInt(num);
			if (Random.value < num - (float)num2)
			{
				num2++;
			}
			num2 = Mathf.Max(0, num2);
			if (num2 <= 0)
			{
				return 0;
			}
			int num3 = 0;
			for (int i = 0; i < num2; i++)
			{
				if (EcmSpawnedMarker.ActiveCount >= 96)
				{
					break;
				}
				if (SpawnDuplicate(source, profile, i))
				{
					num3++;
				}
			}
			if (_debugLogging.Value && num3 > 0)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("ECM Mitosis duplicated " + profile.Key + " x" + num3 + "."));
			}
			return num3;
		}

		private bool SpawnReplacement(PendingWaveEnemy entry, EnemySpawnCandidate candidate, bool keepOriginalIfFails, string sourceName)
		{
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			if (entry == null || candidate == null || candidate.Pool == null)
			{
				return false;
			}
			GameObject source = entry.Source;
			GameObject randomLiveTemplate = candidate.Pool.GetRandomLiveTemplate();
			if ((Object)(object)source == (Object)null || (Object)(object)randomLiveTemplate == (Object)null)
			{
				return false;
			}
			if (!IsTemplateSafeToSpawn(randomLiveTemplate, candidate.Pool.Key))
			{
				return false;
			}
			try
			{
				GameObject val = Object.Instantiate<GameObject>(randomLiveTemplate, source.transform.position, source.transform.rotation, source.transform.parent);
				((Object)val).name = ((Object)randomLiveTemplate).name + " [ECM " + sourceName + "]";
				val.AddComponent<EcmSpawnedMarker>();
				SanitizeClonedEnemyRuntimeState(val);
				if (!val.activeSelf)
				{
					val.SetActive(true);
				}
				if (!IsSpawnedCloneValid(val, candidate.Pool.Key))
				{
					Object.Destroy((Object)(object)val);
					return false;
				}
				ApplyConfiguredEnemyEffects(val, isModdedEnemy: true);
				if (keepOriginalIfFails)
				{
					((MonoBehaviour)this).StartCoroutine(DestroyOriginalAfterReplacementValid(source, val, candidate.Pool.Key));
				}
				else
				{
					Object.Destroy((Object)(object)source);
				}
				if (_debugLogging.Value)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)("ECM " + sourceName + " replaced " + entry.Profile.Key + " with " + candidate.Pool.Key + "."));
				}
				return true;
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("ECM skipped replacement safely: " + ex.GetType().Name + ": " + ex.Message));
				return false;
			}
		}

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

		private bool SpawnDuplicate(GameObject source, EnemyProfile profile, int index)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)source == (Object)null)
			{
				return false;
			}
			try
			{
				Vector3 val = FindNearbySpawnPosition(source.transform.position, index);
				GameObject val2 = Object.Instantiate<GameObject>(source, val, source.transform.rotation, source.transform.parent);
				((Object)val2).name = ((Object)source).name + " [ECM Mitosis]";
				val2.AddComponent<EcmSpawnedMarker>();
				SanitizeClonedEnemyRuntimeState(val2);
				if (!val2.activeSelf)
				{
					val2.SetActive(true);
				}
				if (!IsSpawnedCloneValid(val2, profile.Key))
				{
					Object.Destroy((Object)(object)val2);
					return false;
				}
				ApplyConfiguredEnemyEffects(val2, isModdedEnemy: true);
				return true;
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("ECM skipped duplicate safely: " + ex.GetType().Name + ": " + ex.Message));
				return false;
			}
		}

		private Vector3 FindNearbySpawnPosition(Vector3 origin, int index)
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: 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_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: 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_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			float num = Mathf.Max(0.25f, _spawnDistanceMin.Value);
			float num2 = Mathf.Max(num, _spawnDistanceMax.Value);
			float num3 = Random.Range(0f, MathF.PI * 2f) + (float)index * 2.399963f;
			float num4 = Random.Range(num, num2);
			Vector3 val = default(Vector3);
			((Vector3)(ref val))..ctor(Mathf.Cos(num3) * num4, 0f, Mathf.Sin(num3) * num4);
			Vector3 val2 = origin + val;
			RaycastHit val3 = default(RaycastHit);
			if (Physics.Raycast(val2 + Vector3.up * 4f, Vector3.down, ref val3, 12f, -1, (QueryTriggerInteraction)1))
			{
				val2 = ((RaycastHit)(ref val3)).point + Vector3.up * 0.1f;
			}
			return val2;
		}

		private bool ApplyConfiguredEnemyEffects(GameObject enemy, bool isModdedEnemy)
		{
			if ((Object)(object)enemy == (Object)null || !NeedsEnemyEffectModifiers())
			{
				return false;
			}
			bool result = false;
			if (_sandifyEnabled.Value && Random.value < Mathf.Clamp01(_sandifyChancePercent.Value / 100f))
			{
				ApplySandify(enemy);
				result = true;
			}
			if (_radianceEnabled.Value && Random.value < Mathf.Clamp01(_radianceChancePercent.Value / 100f))
			{
				ApplyRadiant(enemy);
				result = true;
			}
			if (_tankifyEnabled.Value && Random.value < Mathf.Clamp01(_tankifyChancePercent.Value / 100f))
			{
				ApplyTankify(enemy);
				result = true;
			}
			if (_enrageEnabled.Value && Random.value < Mathf.Clamp01(_enrageChancePercent.Value / 100f))
			{
				ApplyEnrage(enemy);
				result = true;
			}
			return result;
		}

		private void ApplySandify(GameObject enemy)
		{
			Component val = FindEnemyIdentifierComponent(enemy);
			if (!((Object)(object)val == (Object)null))
			{
				Type type = ((object)val).GetType();
				SetBoolMember(type, val, "sandified", value: true, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				InvokeMethodIfExists(type, val, "UpdateBuffs", null);
				InvokeMethodIfExists(type, val, "UpdateModifiers", null);
			}
		}

		private void ApplyTankify(GameObject enemy)
		{
			Component val = FindEnemyIdentifierComponent(enemy);
			if (!((Object)(object)val == (Object)null))
			{
				Type type = ((object)val).GetType();
				float num = Mathf.Clamp(_tankifyHealthMultiplier.Value, 0.05f, 20f);
				if (!ReadBoolMember(type, val, "healthBuff", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
				{
					InvokeMethodIfExists(type, val, "HealthBuff", new object[1] { num });
				}
				else
				{
					float num2 = ReadFloatMember(type, val, "healthBuffModifier", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, 1f);
					SetFloatMember(type, val, "healthBuffModifier", Mathf.Max(0.05f, num2 * num), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				}
				InvokeMethodIfExists(type, val, "ForceGetHealth", null);
				InvokeMethodIfExists(type, val, "UpdateBuffs", null);
				InvokeMethodIfExists(type, val, "UpdateModifiers", null);
			}
		}

		private void ApplyRadiant(GameObject enemy)
		{
			Component val = FindEnemyIdentifierComponent(enemy);
			if (!((Object)(object)val == (Object)null))
			{
				Type type = ((object)val).GetType();
				float value = Mathf.Clamp(_radianceTier.Value, 0.05f, 10f);
				bool value2 = _radianceAffectsDamage.Value;
				RadiancePreset radiancePreset = GetRadiancePreset(enemy, val);
				float value3 = radiancePreset.Speed;
				float value4 = radiancePreset.Health;
				float num = radiancePreset.Damage;
				if (_radianceUseCustomMultipliers.Value)
				{
					value3 = Mathf.Clamp(_radianceSpeedMultiplier.Value, 0.05f, 20f);
					value4 = Mathf.Clamp(_radianceHealthMultiplier.Value, 0.05f, 20f);
					num = Mathf.Clamp(_radianceDamageMultiplier.Value, 0.05f, 20f);
				}
				EnableCheatsIfPossible();
				SetFloatMember(type, val, "radianceTier", value, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				SetFloatMember(type, val, "speedBuffModifier", value3, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				SetFloatMember(type, val, "healthBuffModifier", value4, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				SetFloatMember(type, val, "damageBuffModifier", value2 ? num : 1f, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				SetBoolMember(type, val, "speedBuff", value: true, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				SetBoolMember(type, val, "healthBuff", value: true, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				SetBoolMember(type, val, "damageBuff", value2, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				InvokeMethodIfExists(type, val, "BuffAll", null);
				SetFloatMember(type, val, "radianceTier", value, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				SetFloatMember(type, val, "speedBuffModifier", value3, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				SetFloatMember(type, val, "healthBuffModifier", value4, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				SetFloatMember(type, val, "damageBuffModifier", value2 ? num : 1f, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				SetBoolMember(type, val, "speedBuff", value: true, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				SetBoolMember(type, val, "healthBuff", value: true, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				SetBoolMember(type, val, "damageBuff", value2, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				InvokeMethodIfExists(type, val, "UpdateBuffs", null);
				InvokeMethodIfExists(type, val, "UpdateModifiers", null);
				InvokeMethodIfExists(type, val, "ForceGetHealth", null);
			}
		}

		private RadiancePreset GetRadiancePreset(GameObject enemy, Component enemyIdentifier)
		{
			return RadiancePreset.ForEnemyKey(EnemyCatalog.GetProfile(ReadEnemyTypeName(enemyIdentifier, enemy), ((Object)(object)enemy != (Object)null) ? ((Object)enemy).name : string.Empty, allowUnknown: true, allowExperimentalLarge: true).Key);
		}

		private void EnableCheatsIfPossible()
		{
			try
			{
				Type type = AccessTools.TypeByName("AssistController");
				Component val = FindSingleComponent(type);
				if ((Object)(object)val != (Object)null)
				{
					SetBoolMember(type, val, "cheatsEnabled", value: true, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				}
			}
			catch
			{
			}
		}

		private void ApplyEnrage(GameObject enemy)
		{
			Component[] componentsInChildren = enemy.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 || GetCachedMethod(type, "Enrage") == null)
			{
				return false;
			}
			if (type.Name.IndexOf("enrage", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return true;
			}
			Type[] interfaces = type.GetInterfaces();
			for (int i = 0; i < interfaces.Length; i++)
			{
				string name = interfaces[i].Name;
				if (name.IndexOf("IEnrage", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("Enrage", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					return true;
				}
			}
			return false;
		}

		private void UpdateUltraHot()
		{
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			if (!CanModifyCurrentScene() || _ultrahotEnabled == null || !_ultrahotEnabled.Value)
			{
				if (_ultrahotWasActive)
				{
					Time.timeScale = 1f;
					_ultrahotWasActive = false;
				}
				return;
			}
			try
			{
				if ((Object)(object)_cachedMovement == (Object)null || (Object)(object)_cachedMovementRigidbody == (Object)null)
				{
					_cachedMovement = FindSingleComponent(GetNewMovementType());
					if ((Object)(object)_cachedMovement == (Object)null)
					{
						return;
					}
					_cachedMovementRigidbody = ReadRigidbodyMember(((object)_cachedMovement).GetType(), _cachedMovement, "rb");
				}
				Rigidbody cachedMovementRigidbody = _cachedMovementRigidbody;
				if ((Object)(object)cachedMovementRigidbody == (Object)null)
				{
					_cachedMovement = null;
					_cachedMovementRigidbody = null;
					return;
				}
				float num = Mathf.Max(0.1f, _ultrahotVelocityForNormalTime.Value);
				Vector3 velocity = cachedMovementRigidbody.velocity;
				float num2 = ((Vector3)(ref velocity)).magnitude / num;
				num2 = Mathf.Clamp(num2, Mathf.Max(0.001f, _ultrahotMinimumTimeScale.Value), Mathf.Max(_ultrahotMinimumTimeScale.Value, _ultrahotMaximumTimeScale.Value));
				Time.timeScale = Mathf.Lerp(Time.timeScale, num2, Time.unscaledDeltaTime * Mathf.Max(0.1f, _ultrahotSmoothing.Value));
				_ultrahotWasActive = true;
			}
			catch (Exception ex)
			{
				if (_debugLogging.Value)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("ULTRAHOT update skipped safely: " + ex.Message));
				}
			}
		}

		private void UpdateDualWieldMaintenance()
		{
			if (!CanModifyCurrentScene() || _dualWieldEnabled == null || !_dualWieldEnabled.Value || Time.time < _nextDualWieldRefresh)
			{
				return;
			}
			_nextDualWieldRefresh = Time.time + Mathf.Clamp(_dualWieldRefreshSeconds.Value, 0.05f, 10f);
			try
			{
				Type gunControlType = GetGunControlType();
				Type dualWieldType = GetDualWieldType();
				if (gunControlType == null || dualWieldType == null)
				{
					return;
				}
				Component val = FindSingleComponent(gunControlType);
				if (!((Object)(object)val == (Object)null))
				{
					Component[] componentsInChildren = val.GetComponentsInChildren(dualWieldType, true);
					int num = Mathf.Clamp(_dualWieldStacks.Value, 0, 16);
					for (int i = ((componentsInChildren != null) ? componentsInChildren.Length : 0); i < num; i++)
					{
						AddDualWieldStack(val, dualWieldType, i);
					}
				}
			}
			catch (Exception ex)
			{
				if (_debugLogging.Value)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Dual Wield maintenance skipped safely: " + ex.Message));
				}
			}
		}

		private void AddDualWieldStack(Component gunControl, Type dualType, int existingCount)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: 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_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: 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)
			GameObject val = new GameObject("ECM Dual Wield");
			val.transform.SetParent(gunControl.transform, true);
			val.transform.localRotation = Quaternion.identity;
			val.transform.localScale = (Vector3)((existingCount % 2 == 0) ? new Vector3(-1f, 1f, 1f) : Vector3.one);
			if (existingCount == 0)
			{
				val.transform.localPosition = Vector3.zero;
			}
			else if (existingCount % 2 == 0)
			{
				val.transform.localPosition = new Vector3((float)(existingCount / 2) * -1.5f, 0f, 0f);
			}
			else
			{
				val.transform.localPosition = new Vector3((float)((existingCount + 1) / 2) * 1.5f, 0f, 0f);
			}
			Component instance = val.AddComponent(dualType);
			SetFloatMember(dualType, instance, "delay", 0.05f + (float)existingCount / 20f, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			SetFloatMember(dualType, instance, "juiceAmount", Mathf.Max(1f, _dualWieldDuration.Value), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		}

		private void RegisterTemplate(Dictionary<string, EnemyTemplatePool> pools, EnemyProfile profile, GameObject source, bool allowInactiveTemplates)
		{
			if (pools != null && !((Object)(object)source == (Object)null) && profile.Allowed && IsTemplateSafeToSpawn(source, profile.Key))
			{
				if (!pools.TryGetValue(profile.Key, out var value))
				{
					value = new EnemyTemplatePool(profile.Key, profile.Tier, profile.IsLarge, profile.IsFlying, allowInactiveTemplates);
					pools[profile.Key] = value;
				}
				value.AddTemplate(source);
			}
		}

		private bool IsTemplateSafeToSpawn(GameObject template, string key)
		{
			if ((Object)(object)template == (Object)null)
			{
				return false;
			}
			if ((Object)(object)template.GetComponent<EcmSpawnedMarker>() != (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 || !clone.activeInHierarchy)
			{
				return false;
			}
			if (IsHardBlockedTemplateName((key ?? string.Empty) + " " + ((Object)clone).name))
			{
				return false;
			}
			if (!HasEnemyIdentifierComponent(clone))
			{
				return false;
			}
			return true;
		}

		private bool ShouldAvoidCloningOrReplacingSource(GameObject source)
		{
			if ((Object)(object)source == (Object)null)
			{
				return true;
			}
			try
			{
				List<Component> list = FindEnemyIdentifierComponents(source);
				for (int i = 0; i < list.Count; i++)
				{
					Component val = list[i];
					if ((Object)(object)val == (Object)null)
					{
						continue;
					}
					Type type = ((object)val).GetType();
					if (ReadBoolMember(type, val, "blessed", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
					{
						if (_debugLogging.Value)
						{
							((BaseUnityPlugin)this).Logger.LogInfo((object)("ECM skipped Idol-blessed source: " + ((Object)source).name));
						}
						return true;
					}
				}
			}
			catch
			{
			}
			return false;
		}

		private void SanitizeClonedEnemyRuntimeState(GameObject clone)
		{
			if ((Object)(object)clone == (Object)null)
			{
				return;
			}
			List<Component> list = FindEnemyIdentifierComponents(clone);
			for (int i = 0; i < list.Count; i++)
			{
				Component val = list[i];
				if (!((Object)(object)val == (Object)null))
				{
					Type type = ((object)val).GetType();
					SetBoolMember(type, val, "blessed", value: false, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					InvokeMethodIfExists(type, val, "UpdateBuffs", null);
					InvokeMethodIfExists(type, val, "UpdateModifiers", null);
				}
			}
		}

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

		private int CountNonFilthEnemies(List<PendingWaveEnemy> wave)
		{
			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 List<PendingWaveEnemy> CopyWave(List<PendingWaveEnemy> wave)
		{
			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]);
				}
			}
			return list;
		}

		private void Shuffle<T>(List<T> list)
		{
			for (int i = 0; i < list.Count; i++)
			{
				int index = Random.Range(i, list.Count);
				T value = list[i];
				list[i] = list[index];
				list[index] = value;
			}
		}

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

		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 Type GetGunControlType()
		{
			if (_gunControlType == null)
			{
				_gunControlType = AccessTools.TypeByName("GunControl");
			}
			return _gunControlType;
		}

		private Type GetDualWieldType()
		{
			if (_dualWieldType == null)
			{
				_dualWieldType = AccessTools.TypeByName("DualWield");
			}
			return _dualWieldType;
		}

		private Type GetNewMovementType()
		{
			if (_newMovementType == null)
			{
				_newMovementType = AccessTools.TypeByName("NewMovement");
			}
			return _newMovementType;
		}

		private Component FindSingleComponent(Type type)
		{
			if (type == null)
			{
				return null;
			}
			Object obj = Object.FindObjectOfType(type);
			return (Component)(object)((obj is Component) ? obj : null);
		}

		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 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 List<Component> FindEnemyIdentifierComponents(GameObject gameObject)
		{
			List<Component> list = new List<Component>();
			if ((Object)(object)gameObject == (Object)null)
			{
				return list;
			}
			Type enemyIdentifierType = GetEnemyIdentifierType();
			if (enemyIdentifierType == null)
			{
				return list;
			}
			Component component = gameObject.GetComponent(enemyIdentifierType);
			if ((Object)(object)component != (Object)null)
			{
				list.Add(component);
			}
			Component[] componentsInChildren = gameObject.GetComponentsInChildren(enemyIdentifierType, true);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				if ((Object)(object)componentsInChildren[i] != (Object)null && !list.Contains(componentsInChildren[i]))
				{
					list.Add(componentsInChildren[i]);
				}
			}
			return list;
		}

		private string ReadEnemyTypeName(object enemyObject, GameObject source)
		{
			if ((Object)(object)source != (Object)null)
			{
				string text = NormalizeObjectName(((Object)source).name);
				if (!string.IsNullOrEmpty(text))
				{
					return text;
				}
			}
			if (enemyObject != null)
			{
				Type type = enemyObject.GetType();
				string[] array = new string[6] { "enemyType", "EnemyType", "type", "enemyName", "FullName", "fullName" };
				for (int i = 0; i < array.Length; i++)
				{
					string text2 = ReadStringMember(enemyObject, type, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, array[i]);
					if (!string.IsNullOrEmpty(text2))
					{
						return text2;
					}
				}
			}
			if (!((Object)(object)source != (Object)null))
			{
				return string.Empty;
			}
			return ((Object)source).name;
		}

		private string NormalizeObjectName(string name)
		{
			string text = Normalize(name);
			if (text.Contains("maliciousface") || text.Contains("maurice"))
			{
				return "maurice";
			}
			if (text.Contains("hideousmass"))
			{
				return "hideousmass";
			}
			if (text.Contains("sisyph") || text.Contains("insurrection"))
			{
				return "insurrectionist";
			}
			if (text.Contains("streetclean"))
			{
				return "streetcleaner";
			}
			if (text.Contains("guttertank"))
			{
				return "guttertank";
			}
			if (text.Contains("gutterman"))
			{
				return "gutterman";
			}
			if (text.Contains("mindflayer"))
			{
				return "mindflayer";
			}
			if (text.Contains("swordsmachine"))
			{
				return "swordsmachine";
			}
			if (text.Contains("cerberus"))
			{
				return "cerberus";
			}
			if (text.Contains("ferryman"))
			{
				return "ferryman";
			}
			if (text.Contains("providence"))
			{
				return "providence";
			}
			if (text.Contains("sentry"))
			{
				return "sentry";
			}
			if (text.Contains("virtue"))
			{
				return "virtue";
			}
			if (text.Contains("drone"))
			{
				return "drone";
			}
			if (text.Contains("soldier"))
			{
				return "soldier";
			}
			if (text.Contains("schism"))
			{
				return "schism";
			}
			if (text.Contains("stalker"))
			{
				return "stalker";
			}
			if (text.Contains("mannequin"))
			{
				return "mannequin";
			}
			if (text.Contains("stray"))
			{
				return "stray";
			}
			if (text.Contains("filth") || text.Contains("zombie"))
			{
				return "filth";
			}
			if (text.Contains("minotaur"))
			{
				return "minotaur";
			}
			if (text.Contains("mirrorreaper") || text.Contains("mirror"))
			{
				return "mirrorreaper";
			}
			if (text == "power" || text.Contains("enemytypepower"))
			{
				return "power";
			}
			return string.Empty;
		}

		private static string ReflectionCacheKey(Type type, BindingFlags flags, string memberName)
		{
			string[] obj = new string[5]
			{
				(type != null) ? type.FullName : string.Empty,
				"|",
				null,
				null,
				null
			};
			int num = (int)flags;
			obj[2] = num.ToString();
			obj[3] = "|";
			obj[4] = memberName;
			return string.Concat(obj);
		}

		private FieldInfo GetCachedField(Type type, string memberName, BindingFlags flags)
		{
			if (type == null || string.IsNullOrEmpty(memberName))
			{
				return null;
			}
			string key = ReflectionCacheKey(type, flags, memberName);
			if (_fieldCache.TryGetValue(key, out var value))
			{
				return value;
			}
			value = type.GetField(memberName, flags);
			_fieldCache[key] = value;
			return value;
		}

		private PropertyInfo GetCachedProperty(Type type, string memberName, BindingFlags flags)
		{
			if (type == null || string.IsNullOrEmpty(memberName))
			{
				return null;
			}
			string key = ReflectionCacheKey(type, flags, memberName);
			if (_propertyCache.TryGetValue(key, out var value))
			{
				return value;
			}
			value = type.GetProperty(memberName, flags);
			_propertyCache[key] = value;
			return value;
		}

		private MethodInfo GetCachedMethod(Type type, string methodName)
		{
			if (type == null || string.IsNullOrEmpty(methodName))
			{
				return null;
			}
			string key = (type.FullName ?? type.Name) + "|method|" + methodName;
			if (_methodCache.TryGetValue(key, out var value))
			{
				return value;
			}
			value = AccessTools.Method(type, methodName, (Type[])null, (Type[])null);
			_methodCache[key] = value;
			return value;
		}

		private string ReadStringMember(object instance, Type type, BindingFlags flags, string memberName)
		{
			try
			{
				FieldInfo cachedField = GetCachedField(type, memberName, flags);
				if (cachedField != null)
				{
					object value = cachedField.GetValue(instance);
					if (value != null)
					{
						return value.ToString();
					}
				}
				PropertyInfo cachedProperty = GetCachedProperty(type, memberName, flags);
				if (cachedProperty != null && cachedProperty.CanRead && cachedProperty.GetIndexParameters().Length == 0)
				{
					object value2 = cachedProperty.GetValue(instance, null);
					if (value2 != null)
					{
						return value2.ToString();
					}
				}
			}
			catch
			{
			}
			return null;
		}

		private bool ReadBoolMember(Type type, object instance, string memberName, BindingFlags flags)
		{
			try
			{
				FieldInfo cachedField = GetCachedField(type, memberName, flags);
				if (cachedField != null && cachedField.FieldType == typeof(bool))
				{
					return (bool)cachedField.GetValue(instance);
				}
				PropertyInfo cachedProperty = GetCachedProperty(type, memberName, flags);
				if (cachedProperty != null && cachedProperty.PropertyType == typeof(bool) && cachedProperty.CanRead)
				{
					return (bool)cachedProperty.GetValue(instance, null);
				}
			}
			catch
			{
			}
			return false;
		}

		private float ReadFloatMember(Type type, object instance, string memberName, BindingFlags flags, float fallback)
		{
			try
			{
				FieldInfo cachedField = GetCachedField(type, memberName, flags);
				if (cachedField != null && cachedField.FieldType == typeof(float))
				{
					return (float)cachedField.GetValue(instance);
				}
				PropertyInfo cachedProperty = GetCachedProperty(type, memberName, flags);
				if (cachedProperty != null && cachedProperty.PropertyType == typeof(float) && cachedProperty.CanRead)
				{
					return (float)cachedProperty.GetValue(instance, null);
				}
			}
			catch
			{
			}
			return fallback;
		}

		private Rigidbody ReadRigidbodyMember(Type type, object instance, string memberName)
		{
			try
			{
				FieldInfo cachedField = GetCachedField(type, memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (cachedField != null)
				{
					object? value = cachedField.GetValue(instance);
					return (Rigidbody)((value is Rigidbody) ? value : null);
				}
				PropertyInfo cachedProperty = GetCachedProperty(type, memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (cachedProperty != null && cachedProperty.CanRead)
				{
					object? value2 = cachedProperty.GetValue(instance, null);
					return (Rigidbody)((value2 is Rigidbody) ? value2 : null);
				}
			}
			catch
			{
			}
			return null;
		}

		private void SetBoolMember(Type type, object instance, string memberName, bool value, BindingFlags flags)
		{
			try
			{
				FieldInfo cachedField = GetCachedField(type, memberName, flags);
				if (cachedField != null && cachedField.FieldType == typeof(bool))
				{
					cachedField.SetValue(instance, value);
					return;
				}
				PropertyInfo cachedProperty = GetCachedProperty(type, memberName, flags);
				if (cachedProperty != null && cachedProperty.PropertyType == typeof(bool) && cachedProperty.CanWrite)
				{
					cachedProperty.SetValue(instance, value, null);
				}
			}
			catch
			{
			}
		}

		private void SetFloatMember(Type type, object instance, string memberName, float value, BindingFlags flags)
		{
			try
			{
				FieldInfo cachedField = GetCachedField(type, memberName, flags);
				if (cachedField != null && cachedField.FieldType == typeof(float))
				{
					cachedField.SetValue(instance, value);
					return;
				}
				PropertyInfo cachedProperty = GetCachedProperty(type, memberName, flags);
				if (cachedProperty != null && cachedProperty.PropertyType == typeof(float) && cachedProperty.CanWrite)
				{
					cachedProperty.SetValue(instance, value, null);
				}
			}
			catch
			{
			}
		}

		private void InvokeMethodIfExists(Type type, object instance, string methodName, object[] args)
		{
			try
			{
				MethodInfo cachedMethod = GetCachedMethod(type, methodName);
				if (cachedMethod != null)
				{
					cachedMethod.Invoke(instance, args);
				}
			}
			catch
			{
			}
		}

		private void LogBlocked(string enemyType, string objectName, string reason)
		{
			if (_debugLogging.Value)
			{
				string item = Normalize(enemyType + " " + objectName + " " + reason);
				if (_loggedBlocked.Add(item))
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)("ECM skipped enemy. type='" + enemyType + "' object='" + objectName + "' reason='" + reason + "'."));
				}
			}
		}

		internal static string Normalize(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return string.Empty;
			}
			char[] array = new char[value.Length];
			int length = 0;
			for (int i = 0; i < value.Length; i++)
			{
				char c = char.ToLowerInvariant(value[i]);
				if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
				{
					array[length++] = c;
				}
			}
			return new string(array, 0, length);
		}
	}
	internal struct RadiancePreset
	{
		public readonly float Health;

		public readonly float Speed;

		public readonly float Damage;

		public RadiancePreset(float health, float speed, float damage)
		{
			Health = health;
			Speed = speed;
			Damage = damage;
		}

		public static RadiancePreset ForEnemyKey(string key)
		{
			key = EasyCampaignTweaksPlugin.Normalize(key);
			return key switch
			{
				"filth" => new RadiancePreset(5f, 2f, 1.5f), 
				"stray" => new RadiancePreset(2.5f, 2f, 1.5f), 
				"schism" => new RadiancePreset(2.5f, 2f, 1.5f), 
				"soldier" => new RadiancePreset(2f, 2f, 1.5f), 
				"stalker" => new RadiancePreset(2f, 2f, 1.5f), 
				"insurrectionist" => new RadiancePreset(1.5f, 1.5f, 1.5f), 
				"ferryman" => new RadiancePreset(1.5f, 1.5f, 1.5f), 
				"swordsmachine" => new RadiancePreset(1.5f, 1f, 1.5f), 
				"drone" => new RadiancePreset(2.5f, 2f, 1.5f), 
				"streetcleaner" => new RadiancePreset(1.5f, 2f, 1.5f), 
				"mindflayer" => new RadiancePreset(2f, 1.5f, 1.5f), 
				"sentry" => new RadiancePreset(1.5f, 1.5f, 1.5f), 
				"gutterman" => new RadiancePreset(1.5f, 1.5f, 1.5f), 
				"guttertank" => new RadiancePreset(1.5f, 1.5f, 1.5f), 
				"maurice" => new RadiancePreset(2f, 1.5f, 1.5f), 
				"cerberus" => new RadiancePreset(1.5f, 1.35f, 1.5f), 
				"hideousmass" => new RadiancePreset(2f, 2f, 1.5f), 
				"mannequin" => new RadiancePreset(1.5f, 1f, 1.5f), 
				"virtue" => new RadiancePreset(1.5f, 1.5f, 1.5f), 
				_ => new RadiancePreset(1.5f, 1.5f, 1.5f), 
			};
		}
	}
	internal sealed class PendingWaveEnemy
	{
		public readonly GameObject Source;

		public readonly EnemyProfile Profile;

		public readonly float TimeStarted;

		public readonly int SourceId;

		public PendingWaveEnemy(GameObject source, EnemyProfile profile, float timeStarted)
		{
			Source = source;
			Profile = profile;
			TimeStarted = timeStarted;
			SourceId = (((Object)(object)source != (Object)null) ? ((Object)source).GetInstanceID() : 0);
		}
	}
	internal sealed class EnemyTemplatePool
	{
		public readonly string Key;

		public readonly int Tier;

		public readonly bool IsLarge;

		public readonly bool IsFlying;

		private readonly bool _allowInactiveTemplates;

		private readonly List<GameObject> _templates = new List<GameObject>();

		public bool HasUsableTemplate
		{
			get
			{
				CleanupDeadTemplates();
				return _templates.Count > 0;
			}
		}

		public EnemyTemplatePool(string key, int tier, bool isLarge, bool isFlying, bool allowInactiveTemplates)
		{
			Key = key;
			Tier = tier;
			IsLarge = isLarge;
			IsFlying = isFlying;
			_allowInactiveTemplates = allowInactiveTemplates;
		}

		public void AddTemplate(GameObject template)
		{
			if (!((Object)(object)template == (Object)null) && !_templates.Contains(template))
			{
				_templates.Add(template);
				CleanupDeadTemplates();
			}
		}

		public GameObject GetRandomLiveTemplate()
		{
			CleanupDeadTemplates();
			if (_templates.Count == 0)
			{
				return null;
			}
			return _templates[Random.Range(0, _templates.Count)];
		}

		private void CleanupDeadTemplates()
		{
			for (int num = _templates.Count - 1; num >= 0; num--)
			{
				GameObject val = _templates[num];
				if ((Object)(object)val == (Object)null || (!_allowInactiveTemplates && !val.activeInHierarchy))
				{
					_templates.RemoveAt(num);
				}
			}
		}
	}
	internal sealed class EnemySpawnCandidate
	{
		public readonly EnemyTemplatePool Pool;

		public EnemySpawnCandidate(EnemyTemplatePool pool)
		{
			Pool = pool;
		}
	}
	internal sealed class WeightedCandidate
	{
		public readonly EnemySpawnCandidate Candidate;

		public readonly float Weight;

		public WeightedCandidate(EnemySpawnCandidate candidate, float weight)
		{
			Candidate = candidate;
			Weight = weight;
		}
	}
	internal struct EnemyProfile
	{
		public readonly string Key;

		public readonly int Tier;

		public readonly bool Allowed;

		public readonly string BlockReason;

		public readonly bool IsLarge;

		public readonly bool IsFlying;

		public EnemyProfile(string key, int tier, bool allowed, string blockReason, bool isLarge, bool isFlying)
		{
			Key = key;
			Tier = tier;
			Allowed = allowed;
			BlockReason = blockReason;
			IsLarge = isLarge;
			IsFlying = isFlying;
		}

		public static EnemyProfile AllowedProfile(string key, int tier, bool isLarge = false, bool isFlying = false)
		{
			return new EnemyProfile(key, tier, allowed: true, string.Empty, isLarge, isFlying);
		}

		public static EnemyProfile Blocked(string reason)
		{
			return new EnemyProfile(string.Empty, 0, allowed: false, reason, isLarge: false, isFlying: false);
		}
	}
	internal static class EnemyCatalog
	{
		public static readonly string[] TierOrder = new string[22]
		{
			"filth", "stray", "schism", "soldier", "drone", "streetcleaner", "stalker", "mannequin", "virtue", "sentry",
			"swordsmachine", "cerberus", "gutterman", "providence", "guttertank", "mindflayer", "power", "ferryman", "hideousmass", "insurrectionist",
			"minotaur", "mirrorreaper"
		};

		public static EnemyProfile GetProfileByKey(string key)
		{
			return GetProfile(key, key, allowUnknown: false, allowExperimentalLarge: true);
		}

		public static EnemyProfile GetProfile(string enemyTypeName, string objectName, bool allowUnknown, bool allowExperimentalLarge)
		{
			string text = EasyCampaignTweaksPlugin.Normalize((enemyTypeName ?? string.Empty) + " " + (objectName ?? string.Empty));
			if (text.Contains("verycancerous") || text.Contains("cancerousrodent") || text.Contains("rodent"))
			{
				return EnemyProfile.Blocked("rodent/special");
			}
			if (text.Contains("idol"))
			{
				return EnemyProfile.Blocked("idol/special");
			}
			if (text.Contains("deathcatcher"))
			{
				return EnemyProfile.Blocked("deathcatcher/special");
			}
			if (text.Contains("bigjohn") || text.Contains("jakito") || text.Contains("somethingwicked"))
			{
				return EnemyProfile.Blocked("special");
			}
			if (text.Contains("prime") || text.Contains("gabriel") || text.Contains("v2") || text.Contains("fleshprison") || text.Contains("fleshpanopticon") || text.Contains("leviathan") || text.Contains("earthmover"))
			{
				return EnemyProfile.Blocked("boss/special");
			}
			if (text.Contains("puppet"))
			{
				return EnemyProfile.Blocked("puppet/unstable");
			}
			if (text == "20" || text.Contains("sentry"))
			{
				return EnemyProfile.AllowedProfile("sentry", TierOf("sentry"));
			}
			if (text == "19" || text.Contains("sisyph") || text.Contains("insurrection"))
			{
				return EnemyProfile.AllowedProfile("insurrectionist", TierOf("insurrectionist"), isLarge: true);
			}
			if (text == "2" || text.Contains("hideousmass"))
			{
				return EnemyProfile.AllowedProfile("hideousmass", TierOf("hideousmass"), isLarge: true);
			}
			if (text == "4" || text.Contains("maliciousface") || text.Contains("maurice"))
			{
				return EnemyProfile.AllowedProfile("maurice", 11);
			}
			if (text.Contains("guttertank"))
			{
				return EnemyProfile.AllowedProfile("guttertank", TierOf("guttertank"));
			}
			if (text.Contains("gutterman"))
			{
				return EnemyProfile.AllowedProfile("gutterman", TierOf("gutterman"));
			}
			if (text.Contains("mindflayer"))
			{
				return EnemyProfile.AllowedProfile("mindflayer", TierOf("mindflayer"), isLarge: false, isFlying: true);
			}
			if (text.Contains("power"))
			{
				return EnemyProfile.AllowedProfile("power", TierOf("power"), isLarge: false, isFlying: true);
			}
			if (text.Contains("providence"))
			{
				return EnemyProfile.AllowedProfile("providence", TierOf("providence"), isLarge: false, isFlying: true);
			}
			if (text.Contains("ferryman"))
			{
				return EnemyProfile.AllowedProfile("ferryman", TierOf("ferryman"));
			}
			if (text.Contains("cerberus"))
			{
				return EnemyProfile.AllowedProfile("cerberus", TierOf("cerberus"));
			}
			if (text.Contains("swordsmachine"))
			{
				return EnemyProfile.AllowedProfile("swordsmachine", TierOf("swordsmachine"));
			}
			if (text.Contains("virtue"))
			{
				return EnemyProfile.AllowedProfile("virtue", TierOf("virtue"), isLarge: false, isFlying: true);
			}
			if (text.Contains("streetclean"))
			{
				return EnemyProfile.AllowedProfile("streetcleaner", TierOf("streetcleaner"));
			}
			if (text.Contains("stalker"))
			{
				return EnemyProfile.AllowedProfile("stalker", TierOf("stalker"));
			}
			if (text.Contains("mannequin"))
			{
				return EnemyProfile.AllowedProfile("mannequin", TierOf("mannequin"));
			}
			if (text.Contains("drone"))
			{
				return EnemyProfile.AllowedProfile("drone", TierOf("drone"), isLarge: false, isFlying: true);
			}
			if (text.Contains("soldier"))
			{
				return EnemyProfile.AllowedProfile("soldier", TierOf("soldier"));
			}
			if (text.Contains("schism"))
			{
				return EnemyProfile.AllowedProfile("schism", TierOf("schism"));
			}
			if (text.Contains("stray"))
			{
				return EnemyProfile.AllowedProfile("stray", TierOf("stray"));
			}
			if (text.Contains("filth") || text.Contains("zombie"))
			{
				return EnemyProfile.AllowedProfile("filth", TierOf("filth"));
			}
			if (allowExperimentalLarge)
			{
				if (text.Contains("minotaur"))
				{
					return EnemyProfile.AllowedProfile("minotaur", TierOf("minotaur"), isLarge: true);
				}
				if (text.Contains("mirrorreaper") || text.Contains("mirror"))
				{
					return EnemyProfile.AllowedProfile("mirrorreaper", TierOf("mirrorreaper"), isLarge: true);
				}
			}
			else if (text.Contains("minotaur") || text.Contains("mirrorreaper") || text.Contains("mirror"))
			{
				return EnemyProfile.Blocked("experimental large enemy disabled");
			}
			if (allowUnknown)
			{
				return EnemyProfile.AllowedProfile(text, 8);
			}
			return EnemyProfile.Blocked("unknown enemy type");
		}

		public static int TierOf(string key)
		{
			key = EasyCampaignTweaksPlugin.Normalize(key);
			for (int i = 0; i < TierOrder.Length; i++)
			{
				if (TierOrder[i] == key)
				{
					return i + 1;
				}
			}
			return 0;
		}
	}
	internal sealed class EcmSpawnedMarker : MonoBehaviour
	{
		public const string CloneNameTag = "[ECM";

		public static int ActiveCount;

		private void OnEnable()
		{
			ActiveCount++;
		}

		private void OnDisable()
		{
			ActiveCount = Mathf.Max(0, ActiveCount - 1);
		}
	}
}