Decompiled source of Joive BG EnemyControl v1.0.7

BG-EnemyControl-Joive.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
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 Unity.Netcode;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("Joive")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Enemy spawn and stat controls for Burglin Gnomes.")]
[assembly: AssemblyFileVersion("1.0.7.0")]
[assembly: AssemblyInformationalVersion("1.0.7")]
[assembly: AssemblyProduct("BG Enemy Control")]
[assembly: AssemblyTitle("BG-EnemyControl-Joive")]
[assembly: AssemblyVersion("1.0.7.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace BGEnemyControlJoive
{
	[BepInPlugin("joive.bg.enemycontrol", "BG Enemy Control", "1.0.7")]
	public sealed class EnemyControlPlugin : BaseUnityPlugin
	{
		private sealed class EnemyRule
		{
			public string Name;

			public ConfigEntry<bool> Enabled;

			public ConfigEntry<float> SpawnPercent;

			public ConfigEntry<int> MaxAlive;

			public ConfigEntry<float> HealthPercent;

			public ConfigEntry<float> DamagePercent;

			public ConfigEntry<float> SpeedPercent;

			public string SpawnText;

			public string MaxAliveText;

			public string HealthText;

			public string DamageText;

			public string SpeedText;
		}

		private const string Version = "1.0.7";

		private static readonly string[] EnemyNames = new string[12]
		{
			"Redcap", "Rat", "Roach", "Cat", "Human", "Mole", "Vacuum", "Fairy", "Boar", "Pitbull",
			"Bibi", "Spider"
		};

		private static readonly BindingFlags AnyInstance = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

		private static FieldInfo runtimeConfigField;

		private static FieldInfo maxHealthField;

		private static FieldInfo aiAttackDamageField;

		private static FieldInfo aiAttackDealDamageField;

		private static ManualLogSource log;

		private readonly Dictionary<string, EnemyRule> rules = new Dictionary<string, EnemyRule>(StringComparer.OrdinalIgnoreCase);

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

		private readonly Dictionary<string, float> baseSpeedByField = new Dictionary<string, float>();

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

		private readonly Dictionary<string, int> liveCounts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

		private readonly HashSet<string> discoveredEnemies = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private Harmony harmony;

		private ConfigEntry<bool> enableMod;

		private ConfigEntry<string> menuKey;

		private ConfigEntry<bool> debugHp;

		private ConfigEntry<bool> hostOnly;

		private ConfigEntry<float> scanInterval;

		private ConfigEntry<bool> logChanges;

		private Rect windowRect = new Rect(80f, 80f, 720f, 520f);

		private Vector2 scroll;

		private bool showMenu;

		private bool centered;

		private bool modalActive;

		private bool oldCursorVisible;

		private CursorLockMode oldCursorLock;

		private PlayerController modalController;

		private bool hadModalControllerState;

		private bool oldControllerUiOpen;

		private int selectedTab;

		private float nextScanTime;

		private string lastAction = "Ready. Use Apply now after changing spawn values.";

		private int lastToggleFrame = -1;

		private int lastHealthApplications;

		private int lastSpeedApplications;

		private int lastDamageScales;

		private GUIStyle windowStyle;

		private GUIStyle titleStyle;

		private GUIStyle labelStyle;

		private GUIStyle smallStyle;

		private GUIStyle buttonStyle;

		private GUIStyle activeButtonStyle;

		private GUIStyle toggleStyle;

		private GUIStyle boxStyle;

		private GUIStyle textFieldStyle;

		private GUIStyle hpStyle;

		private Texture2D dimTexture;

		private Texture2D panelTexture;

		public static EnemyControlPlugin Instance { get; private set; }

		private void Awake()
		{
			Instance = this;
			log = ((BaseUnityPlugin)this).Logger;
			runtimeConfigField = AccessTools.Field(typeof(RuntimeEntityCollection), "myConfig");
			maxHealthField = AccessTools.Field(typeof(HealthBase), "maxHealth");
			enableMod = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableMod", true, "Enable enemy control.");
			menuKey = ((BaseUnityPlugin)this).Config.Bind<string>("General", "MenuKey", "F8", "Menu key.");
			debugHp = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "ShowPlayerHP", false, "Show local player HP at the bottom right.");
			hostOnly = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "HostOnlyWorldChanges", true, "Only host/server changes enemy spawning and enemy stats.");
			scanInterval = ((BaseUnityPlugin)this).Config.Bind<float>("General", "ScanInterval", 1f, "Seconds between enemy scans.");
			logChanges = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "LogChanges", false, "Write enemy control actions to the BepInEx log.");
			string[] enemyNames = EnemyNames;
			foreach (string enemyName in enemyNames)
			{
				RegisterEnemy(enemyName);
			}
			PatchGame();
			((BaseUnityPlugin)this).Logger.LogInfo((object)("BG Enemy Control 1.0.7 loaded. Menu key: " + menuKey.Value));
		}

		private void OnDestroy()
		{
			SetMenuVisible(visible: false);
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
		}

		private void Update()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			if (Input.GetKeyDown(ParseKey(menuKey.Value, (KeyCode)289)) || Input.GetKeyDown((KeyCode)289) || Input.GetKeyDown((KeyCode)287) || Input.GetKeyDown((KeyCode)277))
			{
				ToggleMenu("Update");
			}
			if (enableMod.Value && Time.unscaledTime >= nextScanTime)
			{
				nextScanTime = Time.unscaledTime + Mathf.Max(0.25f, scanInterval.Value);
				ScanAndApply();
			}
		}

		private void OnGUI()
		{
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: 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_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Invalid comparison between Unknown and I4
			//IL_0019: 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_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Invalid comparison between Unknown and I4
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Invalid comparison between Unknown and I4
			//IL_01d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01de: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f3: Expected O, but got Unknown
			//IL_01ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0270: Unknown result type (might be due to invalid IL or missing references)
			//IL_0276: Unknown result type (might be due to invalid IL or missing references)
			//IL_027c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Invalid comparison between Unknown and I4
			EnsureStyles();
			Event current = Event.current;
			if (current != null && (int)current.type == 4 && (current.keyCode == ParseKey(menuKey.Value, (KeyCode)289) || (int)current.keyCode == 289 || (int)current.keyCode == 287 || (int)current.keyCode == 277))
			{
				ToggleMenu("OnGUI");
				current.Use();
			}
			Color color = GUI.color;
			Color contentColor = GUI.contentColor;
			Color backgroundColor = GUI.backgroundColor;
			bool enabled = GUI.enabled;
			int depth = GUI.depth;
			GUI.color = Color.white;
			GUI.contentColor = Color.white;
			GUI.backgroundColor = Color.white;
			GUI.enabled = true;
			if (debugHp.Value)
			{
				DrawPlayerHp();
			}
			if (!showMenu)
			{
				GUI.color = color;
				GUI.contentColor = contentColor;
				GUI.backgroundColor = backgroundColor;
				GUI.enabled = enabled;
				GUI.depth = depth;
				return;
			}
			KeepModalState();
			GUI.depth = -9000;
			if ((Object)(object)dimTexture != (Object)null)
			{
				GUI.DrawTexture(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), (Texture)(object)dimTexture);
			}
			if (!centered)
			{
				centered = true;
				((Rect)(ref windowRect)).width = Mathf.Min(760f, (float)Screen.width - 80f);
				((Rect)(ref windowRect)).height = Mathf.Min(560f, (float)Screen.height - 80f);
				((Rect)(ref windowRect)).x = ((float)Screen.width - ((Rect)(ref windowRect)).width) * 0.5f;
				((Rect)(ref windowRect)).y = ((float)Screen.height - ((Rect)(ref windowRect)).height) * 0.5f;
			}
			windowRect = GUI.Window(44104, windowRect, new WindowFunction(DrawWindow), "", windowStyle);
			((Rect)(ref windowRect)).x = Mathf.Clamp(((Rect)(ref windowRect)).x, 0f, Mathf.Max(0f, (float)Screen.width - ((Rect)(ref windowRect)).width));
			((Rect)(ref windowRect)).y = Mathf.Clamp(((Rect)(ref windowRect)).y, 0f, Mathf.Max(0f, (float)Screen.height - ((Rect)(ref windowRect)).height));
			GUI.color = color;
			GUI.contentColor = contentColor;
			GUI.backgroundColor = backgroundColor;
			GUI.enabled = enabled;
			GUI.depth = depth;
		}

		private void PatchGame()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Expected O, but got Unknown
			harmony = new Harmony("joive.bg.enemycontrol");
			Type typeFromHandle = typeof(RuntimeEntityCollection);
			Patch(typeFromHandle, "GetWantedCount", null, "GetWantedCountPostfix");
			Patch(typeFromHandle, "GetCurrentSpawnChance", null, "GetCurrentSpawnChancePostfix");
			Patch(typeFromHandle, "SpawnNew", "SpawnNewPrefix", null, Type.EmptyTypes);
			Patch(typeFromHandle, "SpawnNew", "SpawnNewPrefix", null, new Type[1] { typeof(Vector3) });
			Patch(typeof(HealthBase), "Respawn", null, "HealthRespawnPostfix");
			Patch(typeof(GameEntityBase), "OnReceiveDamage", "GameEntityDamagePrefix", null, new Type[1] { typeof(float) });
			Type type = AccessTools.TypeByName("AiAttackAction");
			if (type != null)
			{
				aiAttackDamageField = AccessTools.Field(type, "damage");
				aiAttackDealDamageField = AccessTools.Field(type, "dealDamage");
				Patch(type, "OnExecute", "AiAttackPrefix");
				Patch(type, "OnUpdate", "AiAttackPrefix");
			}
		}

		private void Patch(Type type, string methodName, string prefix = null, string postfix = null, Type[] parameters = null)
		{
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				MethodInfo methodInfo = ((parameters == null) ? AccessTools.Method(type, methodName, (Type[])null, (Type[])null) : AccessTools.Method(type, methodName, parameters, (Type[])null));
				if (methodInfo == null)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Patch target not found: " + type.Name + "." + methodName));
					return;
				}
				HarmonyMethod val = ((prefix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(EnemyControlPlugin).GetMethod(prefix, BindingFlags.Static | BindingFlags.Public)));
				HarmonyMethod val2 = ((postfix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(EnemyControlPlugin).GetMethod(postfix, BindingFlags.Static | BindingFlags.Public)));
				harmony.Patch((MethodBase)methodInfo, val, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Patch failed: " + type.Name + "." + methodName + ": " + ex.Message));
			}
		}

		private void SetMenuVisible(bool visible)
		{
			if (showMenu != visible)
			{
				showMenu = visible;
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Enemy Control menu " + (showMenu ? "opened" : "closed") + "."));
				if (showMenu)
				{
					centered = false;
					EnableModalMode();
				}
				else
				{
					DisableModalMode();
				}
			}
		}

		private void ToggleMenu(string source)
		{
			if (Time.frameCount != lastToggleFrame)
			{
				lastToggleFrame = Time.frameCount;
				SetMenuVisible(!showMenu);
				lastAction = "Menu toggled by " + source + ".";
			}
		}

		private void EnableModalMode()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			if (!modalActive)
			{
				modalActive = true;
				oldCursorVisible = Cursor.visible;
				oldCursorLock = Cursor.lockState;
				Cursor.visible = true;
				Cursor.lockState = (CursorLockMode)0;
				SetGameplayUiOpen(value: true);
			}
		}

		private void DisableModalMode()
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			if (modalActive)
			{
				modalActive = false;
				SetGameplayUiOpen(value: false);
				Cursor.visible = oldCursorVisible;
				Cursor.lockState = oldCursorLock;
			}
		}

		private void KeepModalState()
		{
			Cursor.visible = true;
			Cursor.lockState = (CursorLockMode)0;
			SetGameplayUiOpen(value: true);
		}

		private void SetGameplayUiOpen(bool value)
		{
			if (!value)
			{
				RestoreGameplayUiOpen();
				return;
			}
			PlayerController val = Object.FindAnyObjectByType<PlayerController>();
			if (!((Object)(object)val == (Object)null))
			{
				if ((Object)(object)modalController != (Object)(object)val)
				{
					RestoreGameplayUiOpen();
					modalController = val;
					oldControllerUiOpen = GetControllerUiOpen(val);
					hadModalControllerState = true;
				}
				if (SetControllerUiOpen(val, value: true))
				{
					val.RefreshMouseState();
				}
			}
		}

		private void RestoreGameplayUiOpen()
		{
			if ((Object)(object)modalController == (Object)null || !hadModalControllerState)
			{
				modalController = null;
				hadModalControllerState = false;
				return;
			}
			if (SetControllerUiOpen(modalController, oldControllerUiOpen))
			{
				modalController.RefreshMouseState();
			}
			modalController = null;
			hadModalControllerState = false;
		}

		private static bool GetControllerUiOpen(PlayerController controller)
		{
			try
			{
				FieldInfo field = typeof(PlayerController).GetField("isUiOpen", AnyInstance);
				if (field != null)
				{
					return (bool)field.GetValue(controller);
				}
			}
			catch
			{
			}
			return false;
		}

		private static bool SetControllerUiOpen(PlayerController controller, bool value)
		{
			try
			{
				FieldInfo field = typeof(PlayerController).GetField("isUiOpen", AnyInstance);
				if (field == null)
				{
					return false;
				}
				field.SetValue(controller, value);
				return true;
			}
			catch
			{
				return false;
			}
		}

		private void FixOldConfigValues()
		{
			ResetNonRedcapStats();
			EnemyRule rule = GetRule("Redcap");
			if (rule != null)
			{
				rule.Enabled.Value = true;
				if (rule.SpawnPercent.Value <= 0f)
				{
					rule.SpawnPercent.Value = 35f;
					rule.SpawnText = "35";
				}
				if (rule.MaxAlive.Value == 0)
				{
					rule.MaxAlive.Value = 2;
					rule.MaxAliveText = "2";
				}
			}
			((BaseUnityPlugin)this).Config.Save();
		}

		private void ResetNonRedcapStats()
		{
			foreach (EnemyRule value in rules.Values)
			{
				if (!value.Name.Equals("Redcap", StringComparison.OrdinalIgnoreCase))
				{
					value.Enabled.Value = true;
					value.HealthPercent.Value = 100f;
					value.DamagePercent.Value = 100f;
					value.SpeedPercent.Value = 100f;
					value.HealthText = "100";
					value.DamageText = "100";
					value.SpeedText = "100";
				}
			}
			((BaseUnityPlugin)this).Config.Save();
		}

		private void RegisterEnemy(string enemyName)
		{
			if (!rules.ContainsKey(enemyName))
			{
				bool flag = enemyName.Equals("Redcap", StringComparison.OrdinalIgnoreCase);
				EnemyRule value = new EnemyRule
				{
					Name = enemyName,
					Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>(enemyName, "Enabled", true, "Allow this enemy to spawn and stay alive."),
					SpawnPercent = ((BaseUnityPlugin)this).Config.Bind<float>(enemyName, "SpawnPercent", flag ? 35f : 100f, "Spawn percent. 0 disables new spawns, 100 is normal."),
					MaxAlive = ((BaseUnityPlugin)this).Config.Bind<int>(enemyName, "MaxAlive", flag ? 2 : (-1), "Maximum alive enemies of this type. -1 means no cap."),
					HealthPercent = ((BaseUnityPlugin)this).Config.Bind<float>(enemyName, "HealthPercent", flag ? 75f : 100f, "Enemy max health percent."),
					DamagePercent = ((BaseUnityPlugin)this).Config.Bind<float>(enemyName, "DamagePercent", flag ? 60f : 100f, "Enemy attack damage percent."),
					SpeedPercent = ((BaseUnityPlugin)this).Config.Bind<float>(enemyName, "SpeedPercent", flag ? 90f : 100f, "Enemy movement speed percent."),
					SpawnText = (flag ? 35f : 100f).ToString(CultureInfo.InvariantCulture),
					HealthText = (flag ? 75f : 100f).ToString(CultureInfo.InvariantCulture),
					DamageText = (flag ? 60f : 100f).ToString(CultureInfo.InvariantCulture),
					SpeedText = (flag ? 90f : 100f).ToString(CultureInfo.InvariantCulture),
					MaxAliveText = (flag ? 2 : (-1)).ToString(CultureInfo.InvariantCulture)
				};
				rules[enemyName] = value;
			}
		}

		private void ScanAndApply()
		{
			try
			{
				DiscoverDirectorEnemies();
				liveCounts.Clear();
				GameEntityAI[] array = Object.FindObjectsByType<GameEntityAI>((FindObjectsSortMode)0);
				GameEntityAI[] array2 = array;
				foreach (GameEntityAI val in array2)
				{
					if ((Object)(object)val == (Object)null)
					{
						continue;
					}
					string enemyName = GetEnemyName(val);
					if (!string.IsNullOrEmpty(enemyName))
					{
						discoveredEnemies.Add(enemyName);
						if (!rules.ContainsKey(enemyName))
						{
							RegisterEnemy(enemyName);
						}
						liveCounts.TryGetValue(enemyName, out var value);
						liveCounts[enemyName] = value + 1;
						if (CanChangeWorld())
						{
							ApplyHealthTo(val);
							ApplySpeedTo(val);
						}
					}
				}
				if (CanChangeWorld())
				{
					EnforceCaps(array);
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Enemy scan failed: " + ex.Message));
			}
		}

		private void ApplyAllNow()
		{
			lastHealthApplications = 0;
			lastSpeedApplications = 0;
			lastDamageScales = 0;
			ScanAndApply();
			lastAction = "Applied now. Health changes: " + lastHealthApplications + ", speed changes: " + lastSpeedApplications + ". Damage scales when enemies hit.";
		}

		private void DiscoverDirectorEnemies()
		{
			AiDirector val = Object.FindAnyObjectByType<AiDirector>();
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			EnemySpawnConfig[] array = val.SpawnConfig ?? Array.Empty<EnemySpawnConfig>();
			foreach (EnemySpawnConfig val2 in array)
			{
				string text = (((Object)(object)val2?.entityData != (Object)null) ? ((Object)val2.entityData).name : "");
				if (!string.IsNullOrEmpty(text))
				{
					discoveredEnemies.Add(text);
					RegisterEnemy(text);
				}
			}
		}

		private void EnforceCaps(GameEntityAI[] entities)
		{
			Dictionary<string, List<GameEntityAI>> dictionary = new Dictionary<string, List<GameEntityAI>>(StringComparer.OrdinalIgnoreCase);
			foreach (GameEntityAI val in entities)
			{
				if ((Object)(object)val == (Object)null || ((GameEntityBase)val).IsDead)
				{
					continue;
				}
				string enemyName = GetEnemyName(val);
				if (!string.IsNullOrEmpty(enemyName))
				{
					if (!dictionary.TryGetValue(enemyName, out var value))
					{
						value = (dictionary[enemyName] = new List<GameEntityAI>());
					}
					value.Add(val);
				}
			}
			foreach (KeyValuePair<string, List<GameEntityAI>> item in dictionary)
			{
				EnemyRule rule = GetRule(item.Key);
				if (rule == null)
				{
					continue;
				}
				int num = (rule.Enabled.Value ? rule.MaxAlive.Value : 0);
				if (num >= 0)
				{
					List<GameEntityAI> list2 = item.Value.OrderBy((GameEntityAI e) => Vector3.SqrMagnitude(((Component)e).transform.position)).ToList();
					for (int num2 = num; num2 < list2.Count; num2++)
					{
						DespawnEnemy(list2[num2], item.Key);
					}
				}
			}
		}

		private void DespawnEnemy(GameEntityAI entity, string name)
		{
			if ((Object)(object)entity == (Object)null)
			{
				return;
			}
			try
			{
				AiDirector val = Object.FindAnyObjectByType<AiDirector>();
				if ((Object)(object)val != (Object)null)
				{
					val.DespawnEnemy((GameEntityBase)(object)entity);
				}
				else if ((Object)(object)((NetworkBehaviour)entity).NetworkObject != (Object)null && ((NetworkBehaviour)entity).NetworkObject.IsSpawned)
				{
					((NetworkBehaviour)entity).NetworkObject.Despawn(true);
				}
				else
				{
					Object.Destroy((Object)(object)((Component)entity).gameObject);
				}
				if (logChanges.Value)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Despawning " + name + " due to Enemy Control cap."));
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Despawn failed for " + name + ": " + ex.Message));
			}
		}

		private int DespawnByName(string enemyName)
		{
			if (!CanChangeWorld())
			{
				lastAction = "Cannot despawn: you are not host/server.";
				return 0;
			}
			int num = 0;
			GameEntityAI[] array = Object.FindObjectsByType<GameEntityAI>((FindObjectsSortMode)0);
			foreach (GameEntityAI val in array)
			{
				if (!((Object)(object)val == (Object)null) && !((GameEntityBase)val).IsDead && GetEnemyName(val).Equals(enemyName, StringComparison.OrdinalIgnoreCase))
				{
					DespawnEnemy(val, enemyName);
					num++;
				}
			}
			ScanAndApply();
			return num;
		}

		private int SpawnEnemyByName(string enemyName, int amount)
		{
			if (!CanChangeWorld())
			{
				lastAction = "Cannot spawn: you are not host/server.";
				return 0;
			}
			AiDirector val = Object.FindAnyObjectByType<AiDirector>();
			if ((Object)(object)val == (Object)null)
			{
				lastAction = "Cannot spawn: AiDirector was not found on this scene.";
				return 0;
			}
			int num = 0;
			for (int i = 0; i < amount; i++)
			{
				try
				{
					val.SpawnEnemy(enemyName);
					num++;
				}
				catch (Exception ex)
				{
					lastAction = "Spawn failed for " + enemyName + ": " + ex.Message;
					break;
				}
			}
			ScanAndApply();
			return num;
		}

		private void ApplyRedcapNow()
		{
			EnemyRule rule = GetRule("Redcap");
			if (rule == null)
			{
				lastAction = "Redcap rule was not found.";
				return;
			}
			if (!CanChangeWorld())
			{
				lastAction = "Cannot apply: you are not host/server.";
				return;
			}
			ScanAndApply();
			if (!rule.Enabled.Value || rule.SpawnPercent.Value <= 0f || rule.MaxAlive.Value == 0)
			{
				lastAction = "Redcaps cleared now: " + DespawnByName("Redcap") + ". Future Redcaps are blocked.";
				return;
			}
			int liveCount = GetLiveCount("Redcap");
			int num = ((rule.MaxAlive.Value > 0) ? rule.MaxAlive.Value : liveCount);
			int num2 = 0;
			if (rule.MaxAlive.Value > 0 && liveCount < num)
			{
				num2 = SpawnEnemyByName("Redcap", Mathf.Clamp(num - liveCount, 0, 30));
			}
			else
			{
				ScanAndApply();
			}
			lastAction = "Redcap applied: alive " + GetLiveCount("Redcap") + ((num > 0) ? ("/" + num) : "") + ", spawned " + num2 + ".";
		}

		private void ApplyHealthTo(GameEntityAI entity)
		{
			EnemyRule rule = GetRule(GetEnemyName(entity));
			if (rule != null && !Mathf.Approximately(rule.HealthPercent.Value, 100f))
			{
				HealthBase health = ((Component)entity).GetComponent<HealthBase>() ?? ((Component)entity).GetComponentInChildren<HealthBase>(true);
				ApplyHealthTo(health);
			}
		}

		private void ApplyHealthTo(HealthBase health)
		{
			if ((Object)(object)health == (Object)null || maxHealthField == null || !CanChangeWorld())
			{
				return;
			}
			GameEntityAI val = ((Component)health).GetComponent<GameEntityAI>() ?? ((Component)health).GetComponentInParent<GameEntityAI>();
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			EnemyRule rule = GetRule(GetEnemyName(val));
			if (rule == null)
			{
				return;
			}
			int instanceID = ((Object)health).GetInstanceID();
			if (!baseHealthByInstance.TryGetValue(instanceID, out var value))
			{
				value = Convert.ToSingle(maxHealthField.GetValue(health), CultureInfo.InvariantCulture);
				baseHealthByInstance[instanceID] = Mathf.Max(1f, value);
			}
			float num = Mathf.Max(1f, value * Mathf.Max(0f, rule.HealthPercent.Value) / 100f);
			float num2 = Convert.ToSingle(maxHealthField.GetValue(health), CultureInfo.InvariantCulture);
			if (!Mathf.Approximately(num2, num))
			{
				maxHealthField.SetValue(health, num);
				if (health.Health > num)
				{
					health.TakeDamage(health.Health - num);
				}
				else if (health.Health > 0f && health.Health >= num2 - 0.5f && num > health.Health)
				{
					health.Heal(num - health.Health);
				}
				lastHealthApplications++;
			}
		}

		private void ApplySpeedTo(GameEntityAI entity)
		{
			EnemyRule rule = GetRule(GetEnemyName(entity));
			if (rule == null || Mathf.Approximately(rule.SpeedPercent.Value, 100f))
			{
				return;
			}
			Component[] componentsInChildren = ((Component)entity).GetComponentsInChildren<Component>(true);
			foreach (Component val in componentsInChildren)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				Type type = ((object)val).GetType();
				string name = type.Name;
				if (name.IndexOf("Follower", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("Movement", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("Mecanim", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("AiLink", StringComparison.OrdinalIgnoreCase) < 0)
				{
					continue;
				}
				FieldInfo[] fields = type.GetFields(AnyInstance);
				foreach (FieldInfo fieldInfo in fields)
				{
					if (!(fieldInfo.FieldType != typeof(float)) && LooksLikeSpeed(fieldInfo.Name))
					{
						string key = ((Object)val).GetInstanceID() + ":" + fieldInfo.MetadataToken;
						float num = Convert.ToSingle(fieldInfo.GetValue(val), CultureInfo.InvariantCulture);
						if (!baseSpeedByField.TryGetValue(key, out var value))
						{
							value = num;
							baseSpeedByField[key] = value;
						}
						float num2 = value * Mathf.Max(0f, rule.SpeedPercent.Value) / 100f;
						if (!float.IsNaN(num2) && !float.IsInfinity(num2) && !Mathf.Approximately(num, num2))
						{
							fieldInfo.SetValue(val, num2);
							lastSpeedApplications++;
						}
					}
				}
				PropertyInfo[] properties = type.GetProperties(AnyInstance);
				foreach (PropertyInfo propertyInfo in properties)
				{
					if (!propertyInfo.CanRead || !propertyInfo.CanWrite || propertyInfo.PropertyType != typeof(float) || !LooksLikeSpeed(propertyInfo.Name))
					{
						continue;
					}
					try
					{
						string key2 = ((Object)val).GetInstanceID() + ":p:" + propertyInfo.MetadataToken;
						float num3 = Convert.ToSingle(propertyInfo.GetValue(val, null), CultureInfo.InvariantCulture);
						if (!baseSpeedByField.TryGetValue(key2, out var value2))
						{
							value2 = num3;
							baseSpeedByField[key2] = value2;
						}
						float num4 = value2 * Mathf.Max(0f, rule.SpeedPercent.Value) / 100f;
						if (!float.IsNaN(num4) && !float.IsInfinity(num4) && !Mathf.Approximately(num3, num4))
						{
							propertyInfo.SetValue(val, num4, null);
							lastSpeedApplications++;
						}
					}
					catch
					{
					}
				}
			}
		}

		private static bool LooksLikeSpeed(string name)
		{
			string text = name.ToLowerInvariant();
			switch (text)
			{
			default:
				if (!text.Contains("movespeed") && !text.Contains("movementspeed"))
				{
					return text.Contains("maxspeed");
				}
				break;
			case "speed":
			case "maxspeed":
			case "naturalspeed":
			case "maxspeedatdistance":
				break;
			}
			return true;
		}

		private bool CanChangeWorld()
		{
			if (!hostOnly.Value)
			{
				return true;
			}
			NetworkManager singleton = NetworkManager.Singleton;
			if (!((Object)(object)singleton == (Object)null) && singleton.IsListening && !singleton.IsServer)
			{
				return singleton.IsHost;
			}
			return true;
		}

		private EnemyRule GetRule(string enemyName)
		{
			if (string.IsNullOrEmpty(enemyName))
			{
				return null;
			}
			rules.TryGetValue(enemyName, out var value);
			return value;
		}

		private string GetEnemyName(GameEntityAI entity)
		{
			if ((Object)(object)entity == (Object)null)
			{
				return "";
			}
			if ((Object)(object)((GameEntityBase)entity).EntityData != (Object)null && !string.IsNullOrEmpty(((Object)((GameEntityBase)entity).EntityData).name))
			{
				return CleanName(((Object)((GameEntityBase)entity).EntityData).name);
			}
			Component[] components = ((Component)entity).GetComponents<Component>();
			foreach (Component val in components)
			{
				if (!((Object)(object)val == (Object)null))
				{
					string name = ((object)val).GetType().Name;
					if (!name.Equals("GameEntityAI", StringComparison.OrdinalIgnoreCase) && !name.Equals("NetworkObject", StringComparison.OrdinalIgnoreCase) && !name.Equals("Transform", StringComparison.OrdinalIgnoreCase))
					{
						return CleanName(name);
					}
				}
			}
			return CleanName(((Object)((Component)entity).gameObject).name);
		}

		private static string CleanName(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return "";
			}
			return value.Replace("(Clone)", "").Trim();
		}

		private void DrawWindow(int id)
		{
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_0199: Unknown result type (might be due to invalid IL or missing references)
			GUILayout.BeginVertical(Array.Empty<GUILayoutOption>());
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.Label("BG Enemy Control", titleStyle, Array.Empty<GUILayoutOption>());
			GUILayout.FlexibleSpace();
			GUILayout.Label("Joive", smallStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) });
			if (GUILayout.Button("X", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(28f) }))
			{
				SetMenuVisible(visible: false);
			}
			GUILayout.EndHorizontal();
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			TabButton("Main", 0);
			TabButton("Spawn", 1);
			TabButton("Stats", 2);
			TabButton("Debug", 3);
			GUILayout.EndHorizontal();
			GUILayout.Space(8f);
			scroll = GUILayout.BeginScrollView(scroll, false, true, Array.Empty<GUILayoutOption>());
			switch (selectedTab)
			{
			case 0:
				DrawMainTab();
				break;
			case 1:
				DrawSpawnTab();
				break;
			case 2:
				DrawStatsTab();
				break;
			case 3:
				DrawDebugTab();
				break;
			}
			GUILayout.EndScrollView();
			GUILayout.FlexibleSpace();
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.Label("BG Enemy Control by Joive", smallStyle, Array.Empty<GUILayoutOption>());
			GUILayout.FlexibleSpace();
			if (GUILayout.Button("Close", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) }))
			{
				SetMenuVisible(visible: false);
			}
			GUILayout.EndHorizontal();
			GUI.DragWindow(new Rect(0f, 0f, ((Rect)(ref windowRect)).width, 28f));
			GUILayout.EndVertical();
		}

		private void DrawMainTab()
		{
			Toggle(enableMod, "Enable mod");
			Toggle(debugHp, "Show player HP");
			Toggle(hostOnly, "Host/server changes only");
			Toggle(logChanges, "Log changes");
			GUILayout.Label("Menu key: " + menuKey.Value, labelStyle, Array.Empty<GUILayoutOption>());
			GUILayout.Label("Open keys: F8, F6, Insert", smallStyle, Array.Empty<GUILayoutOption>());
			GUILayout.Label("World changes: " + (CanChangeWorld() ? "YES, this player can apply enemy changes." : "NO, only the host/server can change enemies."), labelStyle, Array.Empty<GUILayoutOption>());
			GUILayout.Label("Last action: " + lastAction, smallStyle, Array.Empty<GUILayoutOption>());
			GUILayout.Label("Spawn % changes future enemy checks. Apply now changes the current map.", smallStyle, Array.Empty<GUILayoutOption>());
			GUILayout.Space(8f);
			DrawRedcapQuickControls();
			GUILayout.Space(8f);
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			if (GUILayout.Button("Save config", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(140f) }))
			{
				((BaseUnityPlugin)this).Config.Save();
				lastAction = "Config saved.";
			}
			if (GUILayout.Button("Refresh counts", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(140f) }))
			{
				ScanAndApply();
				lastAction = "Counts refreshed.";
			}
			if (GUILayout.Button("Fix old config", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(140f) }))
			{
				FixOldConfigValues();
				ScanAndApply();
				lastAction = "Old config values fixed.";
			}
			GUILayout.EndHorizontal();
			DrawLiveCounts();
		}

		private void DrawRedcapQuickControls()
		{
			EnemyRule rule = GetRule("Redcap");
			if (rule == null)
			{
				return;
			}
			GUILayout.Label("Redcap quick control", titleStyle, Array.Empty<GUILayoutOption>());
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			Toggle(rule.Enabled, "Redcap enabled", GUILayout.Width(150f));
			PercentEditor(rule, rule.SpawnPercent, ref rule.SpawnText, "Spawn %", 5f, 0f, 300f);
			GUILayout.EndHorizontal();
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			if (GUILayout.Button("No redcaps", buttonStyle, Array.Empty<GUILayoutOption>()))
			{
				rule.Enabled.Value = false;
				rule.SpawnPercent.Value = 0f;
				rule.SpawnText = "0";
				rule.MaxAlive.Value = 0;
				rule.MaxAliveText = "0";
				ApplyRedcapNow();
			}
			if (GUILayout.Button("10%", buttonStyle, Array.Empty<GUILayoutOption>()))
			{
				rule.Enabled.Value = true;
				rule.SpawnPercent.Value = 10f;
				rule.SpawnText = "10";
				rule.MaxAlive.Value = -1;
				rule.MaxAliveText = "-1";
				lastAction = "Redcap future spawn chance set to 10%.";
			}
			if (GUILayout.Button("35%", buttonStyle, Array.Empty<GUILayoutOption>()))
			{
				rule.Enabled.Value = true;
				rule.SpawnPercent.Value = 35f;
				rule.SpawnText = "35";
				rule.MaxAlive.Value = 2;
				rule.MaxAliveText = "2";
				ApplyRedcapNow();
			}
			if (GUILayout.Button("100%", buttonStyle, Array.Empty<GUILayoutOption>()))
			{
				rule.Enabled.Value = true;
				rule.SpawnPercent.Value = 100f;
				rule.SpawnText = "100";
				rule.MaxAlive.Value = -1;
				rule.MaxAliveText = "-1";
				lastAction = "Redcap future spawn chance set to normal.";
			}
			if (GUILayout.Button("10 alive", buttonStyle, Array.Empty<GUILayoutOption>()))
			{
				rule.Enabled.Value = true;
				rule.SpawnPercent.Value = 300f;
				rule.SpawnText = "300";
				rule.MaxAlive.Value = 10;
				rule.MaxAliveText = "10";
				ApplyRedcapNow();
			}
			GUILayout.EndHorizontal();
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			if (GUILayout.Button("Apply Redcap now", activeButtonStyle, Array.Empty<GUILayoutOption>()))
			{
				ApplyRedcapNow();
			}
			if (GUILayout.Button("Spawn one Redcap", buttonStyle, Array.Empty<GUILayoutOption>()))
			{
				rule.Enabled.Value = true;
				if (rule.SpawnPercent.Value <= 0f)
				{
					rule.SpawnPercent.Value = 100f;
					rule.SpawnText = "100";
				}
				int num = SpawnEnemyByName("Redcap", 1);
				lastAction = "Spawn one Redcap: spawned " + num + ". Alive now: " + GetLiveCount("Redcap") + ".";
			}
			if (GUILayout.Button("Clear Redcaps now", buttonStyle, Array.Empty<GUILayoutOption>()))
			{
				lastAction = "Redcaps removed now: " + DespawnByName("Redcap") + ".";
			}
			GUILayout.EndHorizontal();
			GUILayout.Label("Use Apply Redcap now after changing Spawn % or Max alive. No redcaps clears current Redcaps and blocks new ones.", smallStyle, Array.Empty<GUILayoutOption>());
		}

		private void DrawSpawnTab()
		{
			GUILayout.Label("Spawn control", titleStyle, Array.Empty<GUILayoutOption>());
			foreach (EnemyRule item in OrderedRules())
			{
				GUILayout.BeginVertical(boxStyle, Array.Empty<GUILayoutOption>());
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				Toggle(item.Enabled, item.Name, GUILayout.Width(170f));
				GUILayout.Label("Alive: " + GetLiveCount(item.Name), smallStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) });
				PercentEditor(item, item.SpawnPercent, ref item.SpawnText, "Spawn %", 10f, 0f, 300f);
				IntEditor(item.MaxAlive, ref item.MaxAliveText, "Max alive", 1, -1, 99);
				GUILayout.EndHorizontal();
				GUILayout.EndVertical();
			}
			GUILayout.Label("Max alive -1 means no cap. Disabled enemies are despawned by host/server.", smallStyle, Array.Empty<GUILayoutOption>());
		}

		private void DrawStatsTab()
		{
			GUILayout.Label("Enemy stats", titleStyle, Array.Empty<GUILayoutOption>());
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			if (GUILayout.Button("Apply now", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(130f) }))
			{
				ApplyAllNow();
			}
			if (GUILayout.Button("Reset non-redcap stats", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(190f) }))
			{
				ResetNonRedcapStats();
				ScanAndApply();
				lastAction = "Non-redcap stats reset to 100%.";
			}
			GUILayout.EndHorizontal();
			foreach (EnemyRule item in OrderedRules())
			{
				GUILayout.BeginVertical(boxStyle, Array.Empty<GUILayoutOption>());
				GUILayout.Label(item.Name + "  alive: " + GetLiveCount(item.Name), labelStyle, Array.Empty<GUILayoutOption>());
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				PercentEditor(item, item.HealthPercent, ref item.HealthText, "Health %", 10f, 1f, 500f);
				PercentEditor(item, item.DamagePercent, ref item.DamageText, "Damage %", 10f, 0f, 500f);
				PercentEditor(item, item.SpeedPercent, ref item.SpeedText, "Speed %", 10f, 0f, 300f);
				GUILayout.EndHorizontal();
				GUILayout.EndVertical();
			}
			GUILayout.Label("Health/speed apply to alive enemies now. Damage applies when an enemy actually hits something.", smallStyle, Array.Empty<GUILayoutOption>());
			GUILayout.Label("Last damage scales this run: " + lastDamageScales, smallStyle, Array.Empty<GUILayoutOption>());
		}

		private void DrawDebugTab()
		{
			GUILayout.Label("Debug", titleStyle, Array.Empty<GUILayoutOption>());
			Toggle(debugHp, "Show player HP bottom right");
			GUILayout.Label("World changes: " + (CanChangeWorld() ? "YES" : "NO - host/server only"), labelStyle, Array.Empty<GUILayoutOption>());
			GUILayout.Label("Last action: " + lastAction, smallStyle, Array.Empty<GUILayoutOption>());
			GUILayout.Label("Discovered enemies: " + string.Join(", ", (from r in OrderedRules()
				select r.Name).ToArray()), smallStyle, Array.Empty<GUILayoutOption>());
			DrawLiveCounts();
		}

		private void DrawLiveCounts()
		{
			GUILayout.Space(8f);
			GUILayout.Label("Alive enemies", titleStyle, Array.Empty<GUILayoutOption>());
			foreach (EnemyRule item in OrderedRules())
			{
				GUILayout.Label(item.Name + ": " + GetLiveCount(item.Name), smallStyle, Array.Empty<GUILayoutOption>());
			}
		}

		private IEnumerable<EnemyRule> OrderedRules()
		{
			string[] enemyNames = EnemyNames;
			foreach (string key in enemyNames)
			{
				if (rules.TryGetValue(key, out var value))
				{
					yield return value;
				}
			}
			foreach (string name in discoveredEnemies.OrderBy((string x) => x))
			{
				if (!EnemyNames.Any((string x) => x.Equals(name, StringComparison.OrdinalIgnoreCase)) && rules.TryGetValue(name, out var value2))
				{
					yield return value2;
				}
			}
		}

		private int GetLiveCount(string name)
		{
			liveCounts.TryGetValue(name, out var value);
			return value;
		}

		private void TabButton(string text, int tab)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			if (GUILayout.Button(text, (selectedTab == tab) ? activeButtonStyle : buttonStyle, Array.Empty<GUILayoutOption>()))
			{
				selectedTab = tab;
				scroll = Vector2.zero;
			}
		}

		private void Toggle(ConfigEntry<bool> entry, string text, params GUILayoutOption[] options)
		{
			bool flag = GUILayout.Toggle(entry.Value, text, toggleStyle, options);
			if (flag != entry.Value)
			{
				entry.Value = flag;
			}
		}

		private void PercentEditor(EnemyRule rule, ConfigEntry<float> entry, ref string text, string label, float step, float min, float max)
		{
			GUILayout.Label(label, smallStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) });
			if (GUILayout.Button("-", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(28f) }))
			{
				entry.Value = Mathf.Clamp(entry.Value - step, min, max);
				text = entry.Value.ToString("0.##", CultureInfo.InvariantCulture);
			}
			string text2 = GUILayout.TextField(text, textFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(54f) });
			if (text2 != text)
			{
				text = text2;
				if (TryParseFloat(text, out var value))
				{
					entry.Value = Mathf.Clamp(value, min, max);
				}
			}
			if (GUILayout.Button("+", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(28f) }))
			{
				entry.Value = Mathf.Clamp(entry.Value + step, min, max);
				text = entry.Value.ToString("0.##", CultureInfo.InvariantCulture);
			}
		}

		private void IntEditor(ConfigEntry<int> entry, ref string text, string label, int step, int min, int max)
		{
			GUILayout.Label(label, smallStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) });
			if (GUILayout.Button("-", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(28f) }))
			{
				entry.Value = Mathf.Clamp(entry.Value - step, min, max);
				text = entry.Value.ToString(CultureInfo.InvariantCulture);
			}
			string text2 = GUILayout.TextField(text, textFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(44f) });
			if (text2 != text)
			{
				text = text2;
				if (int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
				{
					entry.Value = Mathf.Clamp(result, min, max);
				}
			}
			if (GUILayout.Button("+", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(28f) }))
			{
				entry.Value = Mathf.Clamp(entry.Value + step, min, max);
				text = entry.Value.ToString(CultureInfo.InvariantCulture);
			}
		}

		private static bool TryParseFloat(string text, out float value)
		{
			return float.TryParse((text ?? "").Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out value);
		}

		private void DrawPlayerHp()
		{
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			PlayerNetworking val = FindLocalPlayer();
			if (!((Object)(object)val == (Object)null) && !((Object)(object)val.Health == (Object)null))
			{
				float num = ((maxHealthField != null) ? Convert.ToSingle(maxHealthField.GetValue(val.Health), CultureInfo.InvariantCulture) : 100f);
				string text = "HP " + ((HealthBase)val.Health).Health.ToString("0", CultureInfo.InvariantCulture) + " / " + num.ToString("0", CultureInfo.InvariantCulture);
				GUI.Label(new Rect((float)Screen.width - 170f, (float)Screen.height - 42f, 155f, 28f), text, hpStyle);
			}
		}

		private static PlayerNetworking FindLocalPlayer()
		{
			PlayerNetworking[] array = Object.FindObjectsByType<PlayerNetworking>((FindObjectsSortMode)0);
			PlayerNetworking[] array2 = array;
			foreach (PlayerNetworking val in array2)
			{
				if ((Object)(object)val != (Object)null && ((NetworkBehaviour)val).IsLocalPlayer)
				{
					return val;
				}
			}
			return ((IEnumerable<PlayerNetworking>)array).FirstOrDefault((Func<PlayerNetworking, bool>)((PlayerNetworking p) => (Object)(object)p != (Object)null));
		}

		private void EnsureStyles()
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: 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_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Expected O, but got Unknown
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Expected O, but got Unknown
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Expected O, but got Unknown
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Expected O, but got Unknown
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0128: Expected O, but got Unknown
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: 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_015f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0165: Unknown result type (might be due to invalid IL or missing references)
			//IL_016f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			//IL_0198: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01db: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ef: Expected O, but got Unknown
			//IL_01f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0206: Unknown result type (might be due to invalid IL or missing references)
			//IL_020b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0211: Unknown result type (might be due to invalid IL or missing references)
			//IL_021b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0235: Unknown result type (might be due to invalid IL or missing references)
			//IL_0249: Expected O, but got Unknown
			//IL_024f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0264: Unknown result type (might be due to invalid IL or missing references)
			//IL_0269: Unknown result type (might be due to invalid IL or missing references)
			//IL_0271: Unknown result type (might be due to invalid IL or missing references)
			//IL_0277: Unknown result type (might be due to invalid IL or missing references)
			//IL_0281: Unknown result type (might be due to invalid IL or missing references)
			//IL_0296: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a5: Expected O, but got Unknown
			//IL_02ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_02cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0317: Unknown result type (might be due to invalid IL or missing references)
			//IL_033b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0350: Unknown result type (might be due to invalid IL or missing references)
			//IL_0355: Unknown result type (might be due to invalid IL or missing references)
			//IL_035a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0364: Expected O, but got Unknown
			//IL_0364: Unknown result type (might be due to invalid IL or missing references)
			//IL_0369: Unknown result type (might be due to invalid IL or missing references)
			//IL_0373: Expected O, but got Unknown
			//IL_0373: Unknown result type (might be due to invalid IL or missing references)
			//IL_038d: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a1: Expected O, but got Unknown
			//IL_03a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_03bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_03cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0407: Expected O, but got Unknown
			//IL_040d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0422: Unknown result type (might be due to invalid IL or missing references)
			//IL_0427: 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_0436: Unknown result type (might be due to invalid IL or missing references)
			//IL_043d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0452: Unknown result type (might be due to invalid IL or missing references)
			//IL_0461: Expected O, but got Unknown
			//IL_0476: Unknown result type (might be due to invalid IL or missing references)
			if (windowStyle == null)
			{
				dimTexture = MakeTexture(new Color(0f, 0f, 0f, 0.12f));
				panelTexture = MakeTexture(new Color(0.18f, 0.22f, 0.26f, 0.98f));
				GUIStyle val = new GUIStyle(GUI.skin.window)
				{
					padding = new RectOffset(14, 14, 12, 12)
				};
				val.normal.background = panelTexture;
				val.onNormal.background = panelTexture;
				windowStyle = val;
				titleStyle = new GUIStyle(GUI.skin.label)
				{
					fontSize = 16,
					fontStyle = (FontStyle)1
				};
				SetText(titleStyle, new Color(0.88f, 1f, 1f));
				labelStyle = new GUIStyle(GUI.skin.label)
				{
					fontSize = 13
				};
				SetText(labelStyle, Color.white);
				smallStyle = new GUIStyle(GUI.skin.label)
				{
					fontSize = 11
				};
				SetText(smallStyle, new Color(0.9f, 0.97f, 1f));
				GUIStyle val2 = new GUIStyle(GUI.skin.button)
				{
					fontSize = 12
				};
				val2.normal.textColor = Color.white;
				val2.normal.background = MakeTexture(new Color(0.16f, 0.28f, 0.34f, 1f));
				val2.hover.background = MakeTexture(new Color(0.2f, 0.35f, 0.42f, 1f));
				val2.active.background = MakeTexture(new Color(0.05f, 0.56f, 0.7f, 1f));
				buttonStyle = val2;
				SetText(buttonStyle, Color.white);
				GUIStyle val3 = new GUIStyle(buttonStyle);
				val3.normal.textColor = Color.white;
				val3.normal.background = MakeTexture(new Color(0.04f, 0.58f, 0.72f, 1f));
				activeButtonStyle = val3;
				SetText(activeButtonStyle, Color.white);
				GUIStyle val4 = new GUIStyle(GUI.skin.toggle)
				{
					fontSize = 12
				};
				val4.normal.textColor = Color.white;
				val4.onNormal.textColor = new Color(0.7f, 1f, 0.88f);
				toggleStyle = val4;
				SetText(toggleStyle, Color.white);
				toggleStyle.onNormal.textColor = new Color(0.7f, 1f, 0.88f);
				toggleStyle.onHover.textColor = new Color(0.7f, 1f, 0.88f);
				toggleStyle.onActive.textColor = new Color(0.7f, 1f, 0.88f);
				toggleStyle.onFocused.textColor = new Color(0.7f, 1f, 0.88f);
				GUIStyle val5 = new GUIStyle(GUI.skin.box)
				{
					padding = new RectOffset(8, 8, 6, 6),
					margin = new RectOffset(2, 2, 3, 3)
				};
				val5.normal.background = MakeTexture(new Color(0.14f, 0.17f, 0.2f, 0.94f));
				boxStyle = val5;
				SetText(boxStyle, Color.white);
				GUIStyle val6 = new GUIStyle(GUI.skin.textField)
				{
					fontSize = 12
				};
				val6.normal.textColor = Color.white;
				val6.normal.background = MakeTexture(new Color(0.08f, 0.1f, 0.12f, 1f));
				textFieldStyle = val6;
				SetText(textFieldStyle, Color.white);
				GUIStyle val7 = new GUIStyle(GUI.skin.label)
				{
					fontSize = 15,
					fontStyle = (FontStyle)1,
					alignment = (TextAnchor)5
				};
				val7.normal.textColor = new Color(0.8f, 1f, 0.88f);
				hpStyle = val7;
				SetText(hpStyle, new Color(0.8f, 1f, 0.88f));
			}
		}

		private static void SetText(GUIStyle style, Color color)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: 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_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: 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)
			style.normal.textColor = color;
			style.hover.textColor = color;
			style.active.textColor = color;
			style.focused.textColor = color;
			style.onNormal.textColor = color;
			style.onHover.textColor = color;
			style.onActive.textColor = color;
			style.onFocused.textColor = color;
		}

		private static Texture2D MakeTexture(Color color)
		{
			//IL_0002: 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_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Expected O, but got Unknown
			Texture2D val = new Texture2D(1, 1);
			val.SetPixel(0, 0, color);
			val.Apply();
			return val;
		}

		private static KeyCode ParseKey(string value, KeyCode fallback)
		{
			//IL_000d: 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)
			if (Enum.TryParse<KeyCode>(value, ignoreCase: true, out KeyCode result))
			{
				return result;
			}
			return fallback;
		}

		public static void GetWantedCountPostfix(object __instance, ref int __result)
		{
			Instance?.ScaleWantedCount(__instance, ref __result);
		}

		public static void GetCurrentSpawnChancePostfix(object __instance, ref float __result)
		{
			Instance?.ScaleSpawnChance(__instance, ref __result);
		}

		public static bool SpawnNewPrefix(object __instance)
		{
			if (!((Object)(object)Instance == (Object)null))
			{
				return Instance.AllowSpawn(__instance);
			}
			return true;
		}

		public static void HealthRespawnPostfix(HealthBase __instance)
		{
			Instance?.ApplyHealthTo(__instance);
		}

		public static void AiAttackPrefix(object __instance)
		{
			Instance?.ApplyAttackDamage(__instance);
		}

		public static void GameEntityDamagePrefix(GameEntityBase __instance, ref float damage)
		{
			Instance?.ScaleIncomingDamage(__instance, ref damage);
		}

		private void ScaleWantedCount(object runtimeCollection, ref int result)
		{
			if (!CanChangeWorld())
			{
				return;
			}
			EnemyRule rule = GetRule(GetRuntimeEnemyName(runtimeCollection));
			if (rule == null)
			{
				return;
			}
			if (!enableMod.Value || !rule.Enabled.Value || rule.SpawnPercent.Value <= 0f)
			{
				result = 0;
				return;
			}
			int num = result;
			result = Mathf.FloorToInt((float)num * Mathf.Max(0f, rule.SpawnPercent.Value) / 100f);
			if (num > 0 && rule.SpawnPercent.Value > 0f && result == 0)
			{
				result = 1;
			}
			if (rule.MaxAlive.Value > 0 && rule.SpawnPercent.Value >= 100f)
			{
				result = Mathf.Max(result, rule.MaxAlive.Value);
			}
			if (rule.MaxAlive.Value >= 0)
			{
				result = Mathf.Min(result, rule.MaxAlive.Value);
			}
		}

		private void ScaleSpawnChance(object runtimeCollection, ref float result)
		{
			if (!CanChangeWorld())
			{
				return;
			}
			EnemyRule rule = GetRule(GetRuntimeEnemyName(runtimeCollection));
			if (rule != null)
			{
				if (!enableMod.Value || !rule.Enabled.Value || rule.SpawnPercent.Value <= 0f)
				{
					result = 0f;
				}
				else
				{
					result = Mathf.Clamp01(result * Mathf.Max(0f, rule.SpawnPercent.Value) / 100f);
				}
			}
		}

		private bool AllowSpawn(object runtimeCollection)
		{
			if (!CanChangeWorld())
			{
				return true;
			}
			if (!enableMod.Value)
			{
				return true;
			}
			EnemyRule rule = GetRule(GetRuntimeEnemyName(runtimeCollection));
			if (rule == null)
			{
				return true;
			}
			if (rule.Enabled.Value)
			{
				return rule.SpawnPercent.Value > 0f;
			}
			return false;
		}

		private string GetRuntimeEnemyName(object runtimeCollection)
		{
			try
			{
				object? obj = runtimeConfigField?.GetValue(runtimeCollection);
				EnemySpawnConfig val = (EnemySpawnConfig)((obj is EnemySpawnConfig) ? obj : null);
				return ((Object)(object)val?.entityData != (Object)null) ? CleanName(((Object)val.entityData).name) : "";
			}
			catch
			{
				return "";
			}
		}

		private void ApplyAttackDamage(object action)
		{
			if (!enableMod.Value || !CanChangeWorld() || action == null || aiAttackDamageField == null || aiAttackDealDamageField == null || !(bool)aiAttackDealDamageField.GetValue(action))
			{
				return;
			}
			GameEntityAI actionAgent = GetActionAgent(action);
			EnemyRule rule = GetRule(GetEnemyName(actionAgent));
			if (rule != null)
			{
				int hashCode = RuntimeHelpers.GetHashCode(action);
				float num = Convert.ToSingle(aiAttackDamageField.GetValue(action), CultureInfo.InvariantCulture);
				if (!baseDamageByAction.TryGetValue(hashCode, out var value))
				{
					value = num;
					baseDamageByAction[hashCode] = value;
				}
				float num2 = value * Mathf.Max(0f, rule.DamagePercent.Value) / 100f;
				if (!Mathf.Approximately(num, num2))
				{
					aiAttackDamageField.SetValue(action, num2);
				}
			}
		}

		private void ScaleIncomingDamage(GameEntityBase target, ref float damage)
		{
			if (!enableMod.Value || !CanChangeWorld() || (Object)(object)target == (Object)null || damage <= 0f)
			{
				return;
			}
			GameEntityBase mostRecentHitter = target.MostRecentHitter;
			GameEntityAI val = (GameEntityAI)(object)((mostRecentHitter is GameEntityAI) ? mostRecentHitter : null);
			if (!((Object)(object)val == (Object)null))
			{
				EnemyRule rule = GetRule(GetEnemyName(val));
				if (rule != null && !Mathf.Approximately(rule.DamagePercent.Value, 100f))
				{
					float num = damage;
					damage = Mathf.Max(0f, num * Mathf.Max(0f, rule.DamagePercent.Value) / 100f);
					lastDamageScales++;
				}
			}
		}

		private static GameEntityAI GetActionAgent(object action)
		{
			Type type = action.GetType();
			while (type != null)
			{
				PropertyInfo property = type.GetProperty("agent", AnyInstance);
				if (property != null && typeof(GameEntityAI).IsAssignableFrom(property.PropertyType))
				{
					object? value = property.GetValue(action, null);
					return (GameEntityAI)((value is GameEntityAI) ? value : null);
				}
				FieldInfo fieldInfo = type.GetField("agent", AnyInstance) ?? type.GetField("_agent", AnyInstance);
				if (fieldInfo != null && typeof(GameEntityAI).IsAssignableFrom(fieldInfo.FieldType))
				{
					object? value2 = fieldInfo.GetValue(action);
					return (GameEntityAI)((value2 is GameEntityAI) ? value2 : null);
				}
				type = type.BaseType;
			}
			return null;
		}
	}
}