Decompiled source of EasyCampaignTweaks v1.1.0

EasyCampaignTweaks.dll

Decompiled 2 weeks 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 TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[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.1.3")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class EasyCampaignTweaksPlugin : BaseUnityPlugin
	{
		private sealed class HealthBarManager : MonoBehaviour
		{
			private EasyCampaignTweaksPlugin _owner;

			private Canvas _canvas;

			private RectTransform _canvasRect;

			private TMP_FontAsset _font;

			private Sprite _whiteSprite;

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

			private readonly List<int> _removeBuffer = new List<int>();

			private float _nextScanTime;

			private static readonly Color OuterBorder = new Color(0.02f, 0.02f, 0.025f, 0.96f);

			private static readonly Color InnerBorder = new Color(0.78f, 0.79f, 0.84f, 0.92f);

			private static readonly Color EmptyGrey = new Color(0.36f, 0.36f, 0.38f, 0.92f);

			private static readonly Color DamageRed = new Color(0.86f, 0.05f, 0.04f, 0.96f);

			private static readonly Color NormalOrange = new Color(1f, 0.78f, 0.2f, 1f);

			private static readonly Color BossRed = new Color(1f, 0.24f, 0.18f, 1f);

			private static readonly Color BlessedBlue = new Color(0.3f, 0.85f, 1f, 1f);

			private const float HealthbarLengthExponent = 0.55f;

			private const float HealthbarWidthBase = 12.5f;

			public void Initialize(EasyCampaignTweaksPlugin owner)
			{
				_owner = owner;
				_nextScanTime = Time.time + 0.5f;
				CreateCanvas();
			}

			private void OnDestroy()
			{
				ClearBars();
				if ((Object)(object)_canvas != (Object)null)
				{
					Object.Destroy((Object)(object)((Component)_canvas).gameObject);
				}
				if ((Object)(object)_whiteSprite != (Object)null)
				{
					Object.Destroy((Object)(object)_whiteSprite.texture);
				}
			}

			private void Update()
			{
				if ((Object)(object)_owner == (Object)null || _owner._healthBarsEnabled == null || !_owner._healthBarsEnabled.Value)
				{
					ClearBars();
					if ((Object)(object)_canvas != (Object)null)
					{
						((Component)_canvas).gameObject.SetActive(false);
					}
					return;
				}
				if ((Object)(object)_canvas == (Object)null)
				{
					CreateCanvas();
				}
				if (!((Object)(object)_canvas == (Object)null))
				{
					if (!((Component)_canvas).gameObject.activeSelf)
					{
						((Component)_canvas).gameObject.SetActive(true);
					}
					if (Time.time >= _nextScanTime)
					{
						_nextScanTime = Time.time + 1.25f;
						ScanExistingEnemies();
					}
					UpdateBars();
				}
			}

			private void CreateCanvas()
			{
				//IL_0041: Unknown result type (might be due to invalid IL or missing references)
				//IL_0047: Expected O, but got Unknown
				if (!((Object)(object)_canvas != (Object)null))
				{
					GameObject val = new GameObject("Easy Campaign Tweaks Health Bars", new Type[3]
					{
						typeof(Canvas),
						typeof(CanvasScaler),
						typeof(GraphicRaycaster)
					});
					Object.DontDestroyOnLoad((Object)(object)val);
					((Object)val).hideFlags = (HideFlags)61;
					_canvas = val.GetComponent<Canvas>();
					_canvas.renderMode = (RenderMode)0;
					_canvas.sortingOrder = 32000;
					CanvasScaler component = val.GetComponent<CanvasScaler>();
					component.uiScaleMode = (ScaleMode)0;
					component.scaleFactor = 1f;
					ref RectTransform canvasRect = ref _canvasRect;
					Transform transform = val.transform;
					canvasRect = (RectTransform)(object)((transform is RectTransform) ? transform : null);
					_font = FindUltrakillFont();
					_whiteSprite = CreateWhiteSprite();
				}
			}

			private Sprite CreateWhiteSprite()
			{
				//IL_0004: Unknown result type (might be due to invalid IL or missing references)
				//IL_0009: Unknown result type (might be due to invalid IL or missing references)
				//IL_000c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0016: 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_003a: 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_0058: Expected O, but got Unknown
				Texture2D val = new Texture2D(1, 1, (TextureFormat)4, false);
				val.SetPixel(0, 0, Color.white);
				val.Apply(false, true);
				((Object)val).hideFlags = (HideFlags)61;
				Sprite obj = Sprite.Create(val, new Rect(0f, 0f, 1f, 1f), new Vector2(0.5f, 0.5f), 1f);
				((Object)obj).hideFlags = (HideFlags)61;
				return obj;
			}

			private TMP_FontAsset FindUltrakillFont()
			{
				try
				{
					TMP_FontAsset val = null;
					TMP_FontAsset defaultFontAsset = TMP_Settings.defaultFontAsset;
					TMP_FontAsset[] array = Resources.FindObjectsOfTypeAll<TMP_FontAsset>();
					foreach (TMP_FontAsset val2 in array)
					{
						if (!((Object)(object)val2 == (Object)null) && !string.IsNullOrEmpty(((Object)val2).name))
						{
							string text = Normalize(((Object)val2).name);
							if (text.Contains("vcr") || text.Contains("terminal") || text.Contains("hud") || text.Contains("ultrakill") || text.Contains("fontmain"))
							{
								return val2;
							}
							if ((Object)(object)val == (Object)null && (text.Contains("digital") || text.Contains("mono") || text.Contains("pixel")))
							{
								val = val2;
							}
						}
					}
					return ((Object)(object)val != (Object)null) ? val : defaultFontAsset;
				}
				catch
				{
					return TMP_Settings.defaultFontAsset;
				}
			}

			private void ScanExistingEnemies()
			{
				//IL_0055: Unknown result type (might be due to invalid IL or missing references)
				//IL_005a: Unknown result type (might be due to invalid IL or missing references)
				Type enemyIdentifierType = _owner.GetEnemyIdentifierType();
				if (enemyIdentifierType == null)
				{
					return;
				}
				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) && val.gameObject.activeInHierarchy)
						{
							Scene scene = val.gameObject.scene;
							if (((Scene)(ref scene)).IsValid())
							{
								Track(val, val.gameObject);
							}
						}
					}
				}
				catch
				{
				}
			}

			public void Track(Component enemyIdentifier, GameObject source)
			{
				if ((Object)(object)_owner == (Object)null || (Object)(object)enemyIdentifier == (Object)null || (Object)(object)source == (Object)null || _owner._healthBarsEnabled == null || !_owner._healthBarsEnabled.Value || !source.activeInHierarchy)
				{
					return;
				}
				int instanceID = ((Object)source).GetInstanceID();
				if (_bars.ContainsKey(instanceID))
				{
					return;
				}
				Type type = ((object)enemyIdentifier).GetType();
				bool flag = _owner.ReadBoolMember(type, enemyIdentifier, "isBoss", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (flag && !_owner._healthBarsShowBosses.Value)
				{
					return;
				}
				EnemyProfile healthBarProfile = _owner.GetHealthBarProfile(enemyIdentifier, source);
				float num = ReadDisplayHealth(type, enemyIdentifier, source, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, healthBarProfile);
				if (num <= 0f)
				{
					return;
				}
				float num2 = ResolveDisplayMaxHealth(type, enemyIdentifier, source, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, healthBarProfile, num);
				if (!(num2 < Mathf.Max(0f, _owner._healthBarsMinHealth.Value)))
				{
					if ((Object)(object)_canvas == (Object)null)
					{
						CreateCanvas();
					}
					float healthModifier = Mathf.Max(0.01f, _owner.ReadFloatMember(type, enemyIdentifier, "totalHealthModifier", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, 1f));
					string healthBarDisplayNameFromProfile = _owner.GetHealthBarDisplayNameFromProfile(healthBarProfile, enemyIdentifier, source);
					HealthBarEntry healthBarEntry = new HealthBarEntry(source, enemyIdentifier, num2, healthModifier, healthBarDisplayNameFromProfile, flag, healthBarProfile.Key);
					CreateEntryUi(healthBarEntry);
					_bars[instanceID] = healthBarEntry;
				}
			}

			private void CreateEntryUi(HealthBarEntry entry)
			{
				//IL_0034: Unknown result type (might be due to invalid IL or missing references)
				//IL_003a: Expected O, but got Unknown
				//IL_0072: 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_009c: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
				//IL_0103: Unknown result type (might be due to invalid IL or missing references)
				//IL_011f: Unknown result type (might be due to invalid IL or missing references)
				//IL_013b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0157: Unknown result type (might be due to invalid IL or missing references)
				//IL_0190: Unknown result type (might be due to invalid IL or missing references)
				//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
				//IL_01d2: Unknown result type (might be due to invalid IL or missing references)
				if (!((Object)(object)_canvas == (Object)null))
				{
					GameObject val = new GameObject("ECT Health Bar", new Type[2]
					{
						typeof(RectTransform),
						typeof(CanvasGroup)
					});
					((Object)val).hideFlags = (HideFlags)61;
					val.transform.SetParent(((Component)_canvas).transform, false);
					entry.Root = val;
					entry.RootRect = val.GetComponent<RectTransform>();
					entry.RootRect.anchorMin = Vector2.zero;
					entry.RootRect.anchorMax = Vector2.zero;
					entry.RootRect.pivot = new Vector2(0.5f, 0.5f);
					entry.CanvasGroup = val.GetComponent<CanvasGroup>();
					entry.CanvasGroup.alpha = 1f;
					entry.CanvasGroup.blocksRaycasts = false;
					entry.CanvasGroup.interactable = false;
					entry.Outer = CreateImage(val.transform, "Outer Border", OuterBorder);
					entry.Inner = CreateImage(val.transform, "Inner Border", InnerBorder);
					entry.Background = CreateImage(val.transform, "Missing Health", EmptyGrey);
					entry.DamageBar = CreateImage(val.transform, "Recent Damage", DamageRed);
					entry.CurrentBar = CreateImage(val.transform, "Current Health", NormalOrange);
					ConfigureFill(entry.DamageBar);
					ConfigureFill(entry.CurrentBar);
					entry.Label = CreateText(val.transform, "Health Text", (TextAlignmentOptions)513, Color.white);
					entry.DamageLabel = CreateText(val.transform, "Damage Text", (TextAlignmentOptions)513, DamageRed);
					entry.NameLabel = CreateText(val.transform, "Name Text", (TextAlignmentOptions)514, Color.white);
				}
			}

			private Image CreateImage(Transform parent, string name, Color color)
			{
				//IL_0021: 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_002e: Unknown result type (might be due to invalid IL or missing references)
				//IL_004d: Unknown result type (might be due to invalid IL or missing references)
				GameObject val = new GameObject(name, new Type[2]
				{
					typeof(RectTransform),
					typeof(Image)
				})
				{
					hideFlags = (HideFlags)61
				};
				val.transform.SetParent(parent, false);
				Image component = val.GetComponent<Image>();
				component.sprite = _whiteSprite;
				((Graphic)component).color = color;
				((Graphic)component).raycastTarget = false;
				return component;
			}

			private TextMeshProUGUI CreateText(Transform parent, string name, TextAlignmentOptions alignment, Color color)
			{
				//IL_0021: 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_002e: 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_0057: Unknown result type (might be due to invalid IL or missing references)
				GameObject val = new GameObject(name, new Type[2]
				{
					typeof(RectTransform),
					typeof(TextMeshProUGUI)
				})
				{
					hideFlags = (HideFlags)61
				};
				val.transform.SetParent(parent, false);
				TextMeshProUGUI component = val.GetComponent<TextMeshProUGUI>();
				((Graphic)component).raycastTarget = false;
				((TMP_Text)component).richText = true;
				((TMP_Text)component).alignment = alignment;
				((Graphic)component).color = color;
				((TMP_Text)component).fontStyle = (FontStyles)1;
				if ((Object)(object)_font != (Object)null)
				{
					((TMP_Text)component).font = _font;
				}
				((TMP_Text)component).enableWordWrapping = false;
				return component;
			}

			private void ConfigureFill(Image image)
			{
				image.type = (Type)3;
				image.fillMethod = (FillMethod)0;
				image.fillOrigin = 0;
				image.fillAmount = 1f;
			}

			private void UpdateBars()
			{
				//IL_02f0: Unknown result type (might be due to invalid IL or missing references)
				//IL_02f5: Unknown result type (might be due to invalid IL or missing references)
				//IL_0319: Unknown result type (might be due to invalid IL or missing references)
				//IL_031e: Unknown result type (might be due to invalid IL or missing references)
				//IL_033f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0341: Unknown result type (might be due to invalid IL or missing references)
				//IL_0346: Unknown result type (might be due to invalid IL or missing references)
				//IL_0348: Unknown result type (might be due to invalid IL or missing references)
				//IL_03f0: Unknown result type (might be due to invalid IL or missing references)
				//IL_0404: Unknown result type (might be due to invalid IL or missing references)
				//IL_0409: Unknown result type (might be due to invalid IL or missing references)
				//IL_042f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0444: Unknown result type (might be due to invalid IL or missing references)
				//IL_045f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0474: Unknown result type (might be due to invalid IL or missing references)
				//IL_048f: Unknown result type (might be due to invalid IL or missing references)
				//IL_04a4: Unknown result type (might be due to invalid IL or missing references)
				//IL_04ad: Unknown result type (might be due to invalid IL or missing references)
				//IL_04c2: Unknown result type (might be due to invalid IL or missing references)
				//IL_04cb: Unknown result type (might be due to invalid IL or missing references)
				//IL_04e0: Unknown result type (might be due to invalid IL or missing references)
				//IL_04e9: Unknown result type (might be due to invalid IL or missing references)
				//IL_0522: Unknown result type (might be due to invalid IL or missing references)
				//IL_0581: Unknown result type (might be due to invalid IL or missing references)
				//IL_0597: Unknown result type (might be due to invalid IL or missing references)
				//IL_059c: Unknown result type (might be due to invalid IL or missing references)
				//IL_059d: Unknown result type (might be due to invalid IL or missing references)
				//IL_05a4: Unknown result type (might be due to invalid IL or missing references)
				//IL_05c6: Unknown result type (might be due to invalid IL or missing references)
				//IL_05e0: Unknown result type (might be due to invalid IL or missing references)
				//IL_067c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0692: Unknown result type (might be due to invalid IL or missing references)
				//IL_0697: Unknown result type (might be due to invalid IL or missing references)
				//IL_0698: Unknown result type (might be due to invalid IL or missing references)
				//IL_069f: Unknown result type (might be due to invalid IL or missing references)
				//IL_06bd: Unknown result type (might be due to invalid IL or missing references)
				//IL_06d7: Unknown result type (might be due to invalid IL or missing references)
				//IL_0757: Unknown result type (might be due to invalid IL or missing references)
				//IL_076d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0772: Unknown result type (might be due to invalid IL or missing references)
				//IL_0773: Unknown result type (might be due to invalid IL or missing references)
				//IL_077a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0798: Unknown result type (might be due to invalid IL or missing references)
				//IL_07b2: Unknown result type (might be due to invalid IL or missing references)
				Camera main = Camera.main;
				if ((Object)(object)main == (Object)null)
				{
					return;
				}
				_removeBuffer.Clear();
				foreach (KeyValuePair<int, HealthBarEntry> bar in _bars)
				{
					HealthBarEntry value = bar.Value;
					if (value == null || (Object)(object)value.Source == (Object)null || (Object)(object)value.EnemyIdentifier == (Object)null || (Object)(object)value.Root == (Object)null)
					{
						_removeBuffer.Add(bar.Key);
						continue;
					}
					Type type = ((object)value.EnemyIdentifier).GetType();
					bool num = _owner.ReadBoolMember(type, value.EnemyIdentifier, "dead", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					EnemyProfile profile = (string.IsNullOrEmpty(value.ProfileKey) ? _owner.GetHealthBarProfile(value.EnemyIdentifier, value.Source) : EnemyCatalog.GetProfileByKey(value.ProfileKey));
					float num2 = ReadDisplayHealth(type, value.EnemyIdentifier, value.Source, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, profile);
					if (num || num2 <= 0f || !value.Source.activeInHierarchy)
					{
						_removeBuffer.Add(bar.Key);
						continue;
					}
					float num3 = (value.HealthModifier = Mathf.Max(0.01f, _owner.ReadFloatMember(type, value.EnemyIdentifier, "totalHealthModifier", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, value.HealthModifier)));
					if (!IsLikelySentinelHealth(num2))
					{
						value.MaxHealth = Mathf.Max(value.MaxHealth, num2);
					}
					else
					{
						value.MaxHealth = Mathf.Max(value.MaxHealth, ResolveDisplayMaxHealth(type, value.EnemyIdentifier, value.Source, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, profile, num2));
					}
					float num4 = Mathf.Max(0f, Mathf.Min(num2, value.MaxHealth) * num3);
					float num5 = Mathf.Max(0.01f, value.MaxHealth * num3);
					float num6 = Mathf.Clamp01(num4 / num5);
					if (num4 < value.LastShownHealth - 0.01f)
					{
						float num7 = Mathf.Min(value.LastShownHealth - num4, Mathf.Max(value.LastShownHealth, 0f));
						if (value.TimeSinceDamaged <= 4f)
						{
							value.RecentDamage += num7;
						}
						else
						{
							value.RecentDamage = num7;
						}
						value.TimeSinceDamaged = 0f;
						value.DamageFill = Mathf.Max(value.DamageFill, Mathf.Clamp01(value.LastShownHealth / num5));
					}
					else if (num4 > value.LastShownHealth + 0.01f)
					{
						value.RecentDamage = 0f;
						value.TimeSinceDamaged = 0f;
						value.DamageFill = num6;
					}
					value.LastShownHealth = num4;
					value.TimeSinceDamaged += Time.deltaTime;
					value.DamageFill = Mathf.Max(num6, Mathf.MoveTowards(value.DamageFill, num6, Time.deltaTime * Mathf.Lerp(1f, 10f, Mathf.Max(0f, value.DamageFill - num6))));
					Vector3 barWorldPosition = GetBarWorldPosition(value.Source);
					float num8 = Mathf.Max(1f, _owner._healthBarsMaxDrawDistance.Value);
					if (Vector3.Distance(((Component)main).transform.position, barWorldPosition) > num8)
					{
						value.CanvasGroup.alpha = 0f;
						continue;
					}
					Vector3 val = main.WorldToScreenPoint(barWorldPosition);
					if (val.z <= 0f)
					{
						value.CanvasGroup.alpha = 0f;
						continue;
					}
					value.CanvasGroup.alpha = 1f;
					float num9 = Mathf.Clamp(_owner._healthBarsScale.Value, 0.25f, 4f);
					float num10 = Mathf.Ceil(Mathf.Pow(num5, 0.55f) * 12.5f * num9);
					num10 = Mathf.Clamp(num10, 42f * num9, 620f * num9);
					float num11 = Mathf.Clamp(7f * num9, 4f, 22f);
					((Transform)value.RootRect).position = val + new Vector3(0f, 12f * num9, 0f);
					value.RootRect.sizeDelta = new Vector2(num10 + 4f * num9, num11 + 28f * num9);
					SetRect(((Graphic)value.Outer).rectTransform, Vector2.zero, new Vector2(num10 + 4f * num9, num11 + 4f * num9));
					SetRect(((Graphic)value.Inner).rectTransform, Vector2.zero, new Vector2(num10 + 2f * num9, num11 + 2f * num9));
					SetRect(((Graphic)value.Background).rectTransform, Vector2.zero, new Vector2(num10, num11));
					SetRect(((Graphic)value.DamageBar).rectTransform, Vector2.zero, new Vector2(num10, num11));
					SetRect(((Graphic)value.CurrentBar).rectTransform, Vector2.zero, new Vector2(num10, num11));
					value.DamageBar.fillAmount = Mathf.Clamp01(value.DamageFill);
					value.CurrentBar.fillAmount = num6;
					((Graphic)value.CurrentBar).color = GetBarColor(value, type, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					if (_owner._healthBarsShowNumbers.Value)
					{
						((Component)value.Label).gameObject.SetActive(true);
						((TMP_Text)value.Label).fontSize = Mathf.RoundToInt(12f * num9);
						RectTransform rectTransform = ((TMP_Text)value.Label).rectTransform;
						rectTransform.pivot = new Vector2(0f, 0f);
						Vector2 anchorMin = (rectTransform.anchorMax = new Vector2(0.5f, 0.5f));
						rectTransform.anchorMin = anchorMin;
						rectTransform.anchoredPosition = new Vector2((0f - num10) * 0.5f, num11 * 0.5f + 2f * num9);
						rectTransform.sizeDelta = new Vector2(num10 * 1.4f, 18f * num9);
						((TMP_Text)value.Label).text = FormatHealthRich(num4, num5);
					}
					else
					{
						((Component)value.Label).gameObject.SetActive(false);
					}
					bool flag = value.RecentDamage > 0.01f && value.TimeSinceDamaged <= 4f;
					((Component)value.DamageLabel).gameObject.SetActive(flag);
					if (flag)
					{
						((TMP_Text)value.DamageLabel).fontSize = Mathf.RoundToInt(12f * num9);
						RectTransform rectTransform2 = ((TMP_Text)value.DamageLabel).rectTransform;
						rectTransform2.pivot = new Vector2(0f, 0.5f);
						Vector2 anchorMin = (rectTransform2.anchorMax = new Vector2(0.5f, 0.5f));
						rectTransform2.anchorMin = anchorMin;
						rectTransform2.anchoredPosition = new Vector2(num10 * 0.5f + 6f * num9, 0f);
						rectTransform2.sizeDelta = new Vector2(110f * num9, 22f * num9);
						((TMP_Text)value.DamageLabel).text = "-" + FormatDamage(value.RecentDamage);
					}
					if (_owner._healthBarsShowNames.Value)
					{
						((Component)value.NameLabel).gameObject.SetActive(true);
						((TMP_Text)value.NameLabel).fontSize = Mathf.RoundToInt(11f * num9);
						RectTransform rectTransform3 = ((TMP_Text)value.NameLabel).rectTransform;
						rectTransform3.pivot = new Vector2(0.5f, 0f);
						Vector2 anchorMin = (rectTransform3.anchorMax = new Vector2(0.5f, 0.5f));
						rectTransform3.anchorMin = anchorMin;
						rectTransform3.anchoredPosition = new Vector2(0f, num11 * 0.5f + 18f * num9);
						rectTransform3.sizeDelta = new Vector2(num10 * 1.5f, 18f * num9);
						((TMP_Text)value.NameLabel).text = value.DisplayName;
					}
					else
					{
						((Component)value.NameLabel).gameObject.SetActive(false);
					}
				}
				for (int i = 0; i < _removeBuffer.Count; i++)
				{
					if (_bars.TryGetValue(_removeBuffer[i], out var value2) && value2 != null && (Object)(object)value2.Root != (Object)null)
					{
						Object.Destroy((Object)(object)value2.Root);
					}
					_bars.Remove(_removeBuffer[i]);
				}
			}

			private static void SetRect(RectTransform rect, Vector2 anchoredPosition, Vector2 size)
			{
				//IL_000b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0028: Unknown result type (might be due to invalid IL or missing references)
				//IL_002e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0035: 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)
				rect.pivot = new Vector2(0.5f, 0.5f);
				Vector2 val = default(Vector2);
				((Vector2)(ref val))..ctor(0.5f, 0.5f);
				rect.anchorMax = val;
				rect.anchorMin = val;
				rect.anchoredPosition = anchoredPosition;
				rect.sizeDelta = size;
			}

			private Vector3 GetBarWorldPosition(GameObject source)
			{
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				//IL_000b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0060: 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_007c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0081: Unknown result type (might be due to invalid IL or missing references)
				//IL_0086: Unknown result type (might be due to invalid IL or missing references)
				//IL_001d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0022: 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_002a: 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_0038: 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_0058: Unknown result type (might be due to invalid IL or missing references)
				//IL_005d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0087: Unknown result type (might be due to invalid IL or missing references)
				Vector3 position = source.transform.position;
				Collider componentInChildren = source.GetComponentInChildren<Collider>();
				if ((Object)(object)componentInChildren != (Object)null)
				{
					Bounds bounds = componentInChildren.bounds;
					Vector3 center = ((Bounds)(ref bounds)).center;
					Vector3 up = Vector3.up;
					bounds = componentInChildren.bounds;
					return center + up * (((Bounds)(ref bounds)).extents.y + _owner._healthBarsHeightOffset.Value);
				}
				return position + Vector3.up * (2f + _owner._healthBarsHeightOffset.Value);
			}

			private Color GetBarColor(HealthBarEntry bar, Type type, BindingFlags flags)
			{
				//IL_001a: 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_0028: Unknown result type (might be due to invalid IL or missing references)
				if (_owner.ReadBoolMember(type, bar.EnemyIdentifier, "blessed", flags))
				{
					return BlessedBlue;
				}
				if (bar.IsBoss)
				{
					return BossRed;
				}
				return NormalOrange;
			}

			private string FormatHealthRich(float health, float max)
			{
				int num = Mathf.CeilToInt(Mathf.Max(health * 100f, 0f));
				int num2 = Mathf.CeilToInt(Mathf.Max(max * 100f, 0f));
				return num + "<space=0.25em><size=75%><color=#FFFA>" + num2 + "</color></size>";
			}

			private string FormatDamage(float damage)
			{
				return Mathf.FloorToInt(Mathf.Max(damage * 100f, 0f)).ToString();
			}

			private static bool IsLikelySentinelHealth(float health)
			{
				return health >= 900f;
			}

			private float ReadDisplayHealth(Type type, Component enemyIdentifier, GameObject source, BindingFlags flags, EnemyProfile profile)
			{
				float num = Mathf.Max(0f, _owner.ReadFloatMember(type, enemyIdentifier, "health", flags, 0f));
				if (!IsLikelySentinelHealth(num))
				{
					return num;
				}
				if (TryReadPlausibleHealthFromComponents(source, type, out var value))
				{
					return value;
				}
				return ResolveDisplayMaxHealth(type, enemyIdentifier, source, flags, profile, num);
			}

			private float ResolveDisplayMaxHealth(Type type, Component enemyIdentifier, GameObject source, BindingFlags flags, EnemyProfile profile, float currentHealth)
			{
				float num = Mathf.Max(0f, _owner.ReadFloatMember(type, enemyIdentifier, "maxHealth", flags, 0f));
				num = Mathf.Max(num, _owner.ReadFloatMember(type, enemyIdentifier, "totalHealth", flags, 0f));
				num = Mathf.Max(num, _owner.ReadFloatMember(type, enemyIdentifier, "fullHealth", flags, 0f));
				if (num > 0.01f && !IsLikelySentinelHealth(num))
				{
					return num;
				}
				if (!IsLikelySentinelHealth(currentHealth))
				{
					return Mathf.Max(0.01f, currentHealth);
				}
				float knownBaseHealth = GetKnownBaseHealth(profile.Key);
				if (knownBaseHealth > 0f)
				{
					return knownBaseHealth;
				}
				if (TryReadPlausibleHealthFromComponents(source, type, out var value))
				{
					return value;
				}
				return 1f;
			}

			private bool TryReadPlausibleHealthFromComponents(GameObject source, Type enemyIdentifierType, out float value)
			{
				value = 0f;
				if ((Object)(object)source == (Object)null)
				{
					return false;
				}
				Component[] componentsInChildren = source.GetComponentsInChildren<Component>(true);
				foreach (Component val in componentsInChildren)
				{
					if ((Object)(object)val == (Object)null)
					{
						continue;
					}
					Type type = ((object)val).GetType();
					if (enemyIdentifierType != null && type == enemyIdentifierType)
					{
						continue;
					}
					FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					foreach (FieldInfo fieldInfo in fields)
					{
						if (LooksLikeHealthMember(fieldInfo.Name))
						{
							object value2;
							try
							{
								value2 = fieldInfo.GetValue(val);
							}
							catch
							{
								continue;
							}
							if (TryCoercePlausibleHealth(value2, out value))
							{
								return true;
							}
						}
					}
					PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					foreach (PropertyInfo propertyInfo in properties)
					{
						if (propertyInfo.CanRead && LooksLikeHealthMember(propertyInfo.Name))
						{
							object value3;
							try
							{
								value3 = propertyInfo.GetValue(val, null);
							}
							catch
							{
								continue;
							}
							if (TryCoercePlausibleHealth(value3, out value))
							{
								return true;
							}
						}
					}
				}
				return false;
			}

			private static bool LooksLikeHealthMember(string name)
			{
				string text = Normalize(name);
				if (!(text == "health") && !(text == "hp") && !text.Contains("currenthealth"))
				{
					return text.Contains("healthpoints");
				}
				return true;
			}

			private static bool TryCoercePlausibleHealth(object raw, out float value)
			{
				value = 0f;
				if (raw == null)
				{
					return false;
				}
				if (raw is float)
				{
					value = (float)raw;
				}
				else if (raw is int)
				{
					value = (int)raw;
				}
				else
				{
					if (!(raw is double))
					{
						return false;
					}
					value = (float)(double)raw;
				}
				if (value > 0.01f)
				{
					return value < 900f;
				}
				return false;
			}

			private static float GetKnownBaseHealth(string profileKey)
			{
				return Normalize(profileKey) switch
				{
					"filth" => 0.5f, 
					"stray" => 1.5f, 
					"drone" => 2f, 
					"schism" => 5f, 
					"soldier" => 2f, 
					"streetcleaner" => 4.5f, 
					"stalker" => 3f, 
					"mannequin" => 3f, 
					"virtue" => 10f, 
					"sentry" => 8f, 
					"maurice" => 10f, 
					"swordsmachine" => 15f, 
					"cerberus" => 15f, 
					"gutterman" => 18f, 
					"providence" => 8f, 
					"guttertank" => 18f, 
					"mindflayer" => 20f, 
					"power" => 10f, 
					"ferryman" => 30f, 
					"hideousmass" => 30f, 
					"insurrectionist" => 50f, 
					"minotaur" => 50f, 
					"mirrorreaper" => 30f, 
					_ => 0f, 
				};
			}

			private void ClearBars()
			{
				foreach (KeyValuePair<int, HealthBarEntry> bar in _bars)
				{
					if (bar.Value != null && (Object)(object)bar.Value.Root != (Object)null)
					{
						Object.Destroy((Object)(object)bar.Value.Root);
					}
				}
				_bars.Clear();
				_removeBuffer.Clear();
			}
		}

		private sealed class HealthBarEntry
		{
			public readonly GameObject Source;

			public readonly Component EnemyIdentifier;

			public readonly string DisplayName;

			public readonly bool IsBoss;

			public readonly string ProfileKey;

			public GameObject Root;

			public RectTransform RootRect;

			public CanvasGroup CanvasGroup;

			public Image Outer;

			public Image Inner;

			public Image Background;

			public Image DamageBar;

			public Image CurrentBar;

			public TextMeshProUGUI Label;

			public TextMeshProUGUI DamageLabel;

			public TextMeshProUGUI NameLabel;

			public float MaxHealth;

			public float HealthModifier;

			public float DamageFill = 1f;

			public float LastShownHealth;

			public float RecentDamage;

			public float TimeSinceDamaged;

			public HealthBarEntry(GameObject source, Component enemyIdentifier, float maxHealth, float healthModifier, string displayName, bool isBoss, string profileKey)
			{
				Source = source;
				EnemyIdentifier = enemyIdentifier;
				MaxHealth = Mathf.Max(0.01f, maxHealth);
				HealthModifier = Mathf.Max(0.01f, healthModifier);
				DisplayName = displayName;
				IsBoss = isBoss;
				ProfileKey = profileKey ?? string.Empty;
				LastShownHealth = MaxHealth * HealthModifier;
				TimeSinceDamaged = 0f;
			}
		}

		[CompilerGenerated]
		private sealed class <DestroyOriginalAfterReplacementValid>d__120 : 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__120(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.1.3";

		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 HealthBarManager _healthBarManager;

		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> _healthBarsEnabled;

		private ConfigEntry<float> _healthBarsMinHealth;

		private ConfigEntry<bool> _healthBarsShowBosses;

		private ConfigEntry<bool> _healthBarsShowNames;

		private ConfigEntry<bool> _healthBarsShowNumbers;

		private ConfigEntry<float> _healthBarsScale;

		private ConfigEntry<float> _healthBarsHeightOffset;

		private ConfigEntry<float> _healthBarsMaxDrawDistance;

		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_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: 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_0082: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			CacheCommonTypes();
			BindConfig();
			_healthBarManager = ((Component)this).gameObject.AddComponent<HealthBarManager>();
			_healthBarManager.Initialize(this);
			_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;
			}
			if ((Object)(object)_healthBarManager != (Object)null)
			{
				Object.Destroy((Object)(object)_healthBarManager);
			}
			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.");
			_healthBarsEnabled = Bind("Health Bars", "Enabled", defaultValue: false, "Show compact health bars above enemies.");
			_healthBarsMinHealth = Bind("Health Bars", "MinimumBaseHealth", 0f, "Only show bars for enemies with at least this much base health. 0 shows all eligible enemies.");
			_healthBarsShowBosses = Bind("Health Bars", "ShowBosses", defaultValue: false, "Show Easy Campaign Tweaks health bars for boss enemies. Bosses already have vanilla bars, so this is disabled by default.");
			_healthBarsShowNames = Bind("Health Bars", "ShowEnemyNames", defaultValue: false, "Show the enemy name above each health bar.");
			_healthBarsShowNumbers = Bind("Health Bars", "ShowHealthNumbers", defaultValue: true, "Show current and maximum health numbers on the bar.");
			_healthBarsScale = Bind("Health Bars", "Scale", 1.6f, "Global health bar size multiplier.");
			_healthBarsHeightOffset = Bind("Health Bars", "HeightOffset", 0.1f, "Extra vertical offset above the enemy.");
			_healthBarsMaxDrawDistance = Bind("Health Bars", "MaxDrawDistance", 160f, "Hide health bars farther than this distance from the camera. Set very high to effectively disable distance hiding.");
			_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);
				TryTrackHealthBar(val, gameObject);
				return;
			}
			TryTrackHealthBar(val, gameObject);
			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 TryTrackHealthBar(Component enemyIdentifier, GameObject source)
		{
			if (!((Object)(object)_healthBarManager == (Object)null) && _healthBarsEnabled != null && _healthBarsEnabled.Value)
			{
				_healthBarManager.Track(enemyIdentifier, source);
			}
		}

		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)
				{
					return;
				}
				if (list.Count < 2)
				{
					if (_debugLogging.Value)
					{
						((BaseUnityPlugin)this).Logger.LogInfo((object)"ECM wave ignored by replacement modules because it only contained one enemy.");
					}
					return;
				}
				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 (wave == null || wave.Count < 2)
			{
				return 0;
			}
			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 || pools == null || pools.Count == 0)
			{
				return null;
			}
			minIncrease = Mathf.Max(1, minIncrease);
			maxIncrease = Mathf.Max(minIncrease, maxIncrease);
			List<int> list = BuildTierIncreaseOrder(minIncrease, maxIncrease);
			for (int i = 0; i < list.Count; i++)
			{
				int targetTier = tier + list[i];
				List<WeightedCandidate> weighted = new List<WeightedCandidate>();
				BuildStrongerCandidateList(weighted, sourceProfile, source, pools, nonFilthCount, targetTier);
				EnemySpawnCandidate enemySpawnCandidate = PickWeightedCandidate(weighted);
				if (enemySpawnCandidate != null)
				{
					return enemySpawnCandidate;
				}
			}
			if (_debugLogging.Value)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("ECM Stronger found no usable tier-up candidate for " + sourceProfile.Key + " in the configured range +" + minIncrease + " to +" + maxIncrease + "."));
			}
			return null;
		}

		private List<int> BuildTierIncreaseOrder(int minIncrease, int maxIncrease)
		{
			List<int> list = new List<int>();
			for (int i = minIncrease; i <= maxIncrease; i++)
			{
				list.Add(i);
			}
			if (list.Count > 1)
			{
				ShuffleInts(list);
			}
			return list;
		}

		private void ShuffleInts(List<int> list)
		{
			if (list != null)
			{
				for (int num = list.Count - 1; num > 0; num--)
				{
					int index = Random.Range(0, num + 1);
					int value = list[num];
					list[num] = list[index];
					list[index] = value;
				}
			}
		}

		private void BuildStrongerCandidateList(List<WeightedCandidate> weighted, EnemyProfile sourceProfile, GameObject source, Dictionary<string, EnemyTemplatePool> pools, int nonFilthCount, int targetTier)
		{
			foreach (EnemyTemplatePool value in pools.Values)
			{
				if (value != null && value.HasUsableTemplate && !(value.Key == sourceProfile.Key) && !(value.Key == "maurice"))
				{
					EnemyProfile profileByKey = EnemyCatalog.GetProfileByKey(value.Key);
					if (profileByKey.Allowed && profileByKey.Tier == targetTier && profileByKey.Tier > sourceProfile.Tier && (!value.IsLarge || CanUseLargeEnemyCandidate(value, sourceProfile, source, nonFilthCount)))
					{
						float weight = (value.IsLarge ? 0.5f : 1f);
						weighted.Add(new WeightedCandidate(new EnemySpawnCandidate(value), weight));
					}
				}
			}
		}

		private int TryApplySafeRandomizer(List<PendingWaveEnemy> wave, HashSet<int> replacedIds, int nonFilthCount)
		{
			if (wave == null || wave.Count < 2)
			{
				return 0;
			}
			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__120))]
		private IEnumerator DestroyOriginalAfterReplacementValid(GameObject source, GameObject clone, string key)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <DestroyOriginalAfterReplacementValid>d__120(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);
				bool num2 = ReadBoolMember(type, val, "healthBuff", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				float num3 = Mathf.Max(0.01f, ReadFloatMember(type, val, "healthBuffModifier", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, 1f));
				float num4 = (num2 ? (num3 * num) : num);
				num4 = Mathf.Clamp(num4, 0.01f, 100f);
				SetBoolMember(type, val, "healthBuff", value: true, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				SetFloatMember(type, val, "healthBuffModifier", num4, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				InvokeMethodIfExists(type, val, "UpdateBuffs", null);
				InvokeMethodIfExists(type, val, "UpdateModifiers", null);
				float num5 = ReadFloatMember(type, val, "totalHealthModifier", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, 1f);
				if (Mathf.Abs(num5 - num4) > 0.01f && num5 <= 1.01f && num4 > 1.01f)
				{
					SetFloatMember(type, val, "totalHealthModifier", num4, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				}
				InvokeMethodIfExists(type, val, "ForceGetHealth", 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"))
			{