Decompiled source of EnemySpawnControl v1.0.0

BepInEx/plugins/EnemySpawnControl-1.0.0.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
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 UnityEngine;

[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("EnemySpawnControl")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+930ccf738288baaceab0ae7e541eae9cdbb446b7")]
[assembly: AssemblyProduct("EnemySpawnControl")]
[assembly: AssemblyTitle("EnemySpawnControl")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 EnemySpawnControl
{
	[BepInPlugin("com.out_pu22led.EnemySpawnControl", "Enemy Spawn Control", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		private const string PLUGIN_GUID = "com.out_pu22led.EnemySpawnControl";

		public static ConfigEntry<bool> UseWhitelist = null;

		public static ConfigEntry<string> AllowedEnemies = null;

		public static ConfigEntry<string> BlockedEnemies = null;

		public static ConfigEntry<bool> EnableLogging = null;

		public static ManualLogSource Log = null;

		private static HashSet<string> _allowedCache = null;

		private static HashSet<string> _blockedCache = null;

		private static readonly Dictionary<string, string> _aliases = new Dictionary<string, string>
		{
			{ "animal", "animal" },
			{ "banger", "bangdirector" },
			{ "clown", "beamer" },
			{ "birthdayboy", "birthdayboy" },
			{ "cleanupcrew", "bombthrower" },
			{ "bowtie", "bowtie" },
			{ "peeper", "ceilingeye" },
			{ "apexpredator", "duck" },
			{ "elsa", "elsa" },
			{ "mentalist", "floater" },
			{ "gnome", "gnomedirector" },
			{ "headman", "head" },
			{ "headgrab", "headgrabber" },
			{ "hearthugger", "hearthugger" },
			{ "hidden", "hidden" },
			{ "huntsman", "hunter" },
			{ "oogly", "oogly" },
			{ "robe", "robe" },
			{ "reaper", "runner" },
			{ "loom", "shadow" },
			{ "spewer", "slowmouth" },
			{ "trudge", "slowwalker" },
			{ "gambit", "spinny" },
			{ "shadowchild", "thinman" },
			{ "tick", "tick" },
			{ "bella", "tricycle" },
			{ "chef", "tumbler" },
			{ "upscream", "upscream" },
			{ "rugrat", "valuablethrower" }
		};

		private void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			UseWhitelist = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "UseWhitelist", false, "If true, only spawn enemies in AllowedEnemies list. If false, block enemies in BlockedEnemies list.");
			AllowedEnemies = ((BaseUnityPlugin)this).Config.Bind<string>("Whitelist", "AllowedEnemies", "Gnome,Runner,Duck", "Comma-separated list of allowed enemy names (case-insensitive). Press F7 in-game to see all enemy names in log.");
			BlockedEnemies = ((BaseUnityPlugin)this).Config.Bind<string>("Blacklist", "BlockedEnemies", "Shadow,Hunter,ThinMan", "Comma-separated list of blocked enemy names (case-insensitive). Press F7 in-game to see all enemy names in log.");
			EnableLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "EnableLogging", true, "Enable detailed logging of enemy spawn filtering");
			RebuildCaches();
			Harmony.CreateAndPatchAll(typeof(EnemyDirectorPatches), (string)null);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"=================================");
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Enemy Spawn Control Loaded!");
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Mode: " + (UseWhitelist.Value ? "WHITELIST" : "BLACKLIST")));
			if (UseWhitelist.Value)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Allowed: " + AllowedEnemies.Value));
			}
			else
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Blocked: " + BlockedEnemies.Value));
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Press F7 to list all available enemies");
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Press Ctrl+R to reload config");
			((BaseUnityPlugin)this).Logger.LogInfo((object)"=================================");
		}

		private void Update()
		{
			if (Input.GetKeyDown((KeyCode)288))
			{
				ListAllEnemyTypes();
			}
			if (Input.GetKey((KeyCode)306) && Input.GetKeyDown((KeyCode)114))
			{
				((BaseUnityPlugin)this).Config.Reload();
				RebuildCaches();
				((BaseUnityPlugin)this).Logger.LogInfo((object)"=================================");
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Config Reloaded!");
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Mode: " + (UseWhitelist.Value ? "WHITELIST" : "BLACKLIST")));
				if (UseWhitelist.Value)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Allowed: " + AllowedEnemies.Value));
				}
				else
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Blocked: " + BlockedEnemies.Value));
				}
				((BaseUnityPlugin)this).Logger.LogInfo((object)"=================================");
			}
		}

		public static void RebuildCaches()
		{
			_allowedCache = new HashSet<string>(from s in AllowedEnemies.Value.Split(',')
				select ResolveAlias(s.ToLower().Trim().Replace(" ", "")), StringComparer.OrdinalIgnoreCase);
			_blockedCache = new HashSet<string>(from s in BlockedEnemies.Value.Split(',')
				select ResolveAlias(s.ToLower().Trim().Replace(" ", "")), StringComparer.OrdinalIgnoreCase);
		}

		private static string ResolveAlias(string name)
		{
			if (!_aliases.TryGetValue(name, out string value))
			{
				return name;
			}
			return value;
		}

		public static string? GetSpawnName(EnemySetup? setup)
		{
			List<PrefabRef> list = setup?.spawnObjects;
			if (list == null || list.Count <= 0)
			{
				return null;
			}
			return list[0].PrefabName;
		}

		public static string NormalizeName(string rawName)
		{
			string text = rawName.Replace("(Clone)", "").Trim();
			if (text.StartsWith("Enemy", StringComparison.OrdinalIgnoreCase))
			{
				string text2 = text;
				text = text2.Substring(5, text2.Length - 5).Trim();
			}
			if (text.StartsWith('-'))
			{
				string text2 = text;
				text = text2.Substring(1, text2.Length - 1).Trim();
			}
			return text.ToLower().Replace(" ", "");
		}

		private void ListAllEnemyTypes()
		{
			if (!Object.op_Implicit((Object)(object)EnemyDirector.instance))
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"EnemyDirector not found! Start a game first.");
				return;
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)"=================================");
			((BaseUnityPlugin)this).Logger.LogInfo((object)"ALL ENEMY TYPES (use these names in config):");
			((BaseUnityPlugin)this).Logger.LogInfo((object)"=================================");
			HashSet<string> hashSet = new HashSet<string>();
			List<EnemySetup>[] array = new List<EnemySetup>[3]
			{
				EnemyDirector.instance.enemiesDifficulty1,
				EnemyDirector.instance.enemiesDifficulty2,
				EnemyDirector.instance.enemiesDifficulty3
			};
			for (int i = 0; i < array.Length; i++)
			{
				foreach (EnemySetup item in array[i])
				{
					string spawnName = GetSpawnName(item);
					if (spawnName != null)
					{
						hashSet.Add(NormalizeName(spawnName));
					}
				}
			}
			List<string> list = hashSet.OrderBy((string x) => x).ToList();
			((BaseUnityPlugin)this).Logger.LogInfo((object)$"Found {list.Count} unique enemy types:");
			foreach (string item2 in list)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("  - " + item2));
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)"=================================");
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Copy these names to your config file!");
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Config location: BepInEx\\config\\com.out_pu22led.EnemySpawnControl.cfg");
			((BaseUnityPlugin)this).Logger.LogInfo((object)"=================================");
		}

		public static bool IsEnemyAllowed(string enemyName)
		{
			if (string.IsNullOrEmpty(enemyName))
			{
				return true;
			}
			string text = NormalizeName(enemyName);
			if (UseWhitelist.Value)
			{
				bool flag = _allowedCache.Contains(text);
				if (EnableLogging.Value && !flag)
				{
					Log.LogInfo((object)("[BLOCKED] " + text + " (not in whitelist)"));
				}
				return flag;
			}
			bool flag2 = _blockedCache.Contains(text);
			if (EnableLogging.Value && flag2)
			{
				Log.LogInfo((object)("[BLOCKED] " + text + " (in blacklist)"));
			}
			return !flag2;
		}
	}
	[HarmonyPatch]
	public class EnemyDirectorPatches
	{
		[HarmonyPrefix]
		[HarmonyPatch(typeof(EnemyDirector), "AmountSetup")]
		private static void AmountSetup_Prefix(EnemyDirector __instance)
		{
			Plugin.Log.LogInfo((object)"=================================");
			Plugin.Log.LogInfo((object)"Filtering enemy spawn lists...");
			int count = __instance.enemiesDifficulty1.Count;
			int count2 = __instance.enemiesDifficulty2.Count;
			int count3 = __instance.enemiesDifficulty3.Count;
			__instance.enemiesDifficulty1 = __instance.enemiesDifficulty1.Where(delegate(EnemySetup setup)
			{
				string spawnName2 = Plugin.GetSpawnName(setup);
				return spawnName2 == null || Plugin.IsEnemyAllowed(spawnName2);
			}).ToList();
			__instance.enemiesDifficulty2 = __instance.enemiesDifficulty2.Where(delegate(EnemySetup setup)
			{
				string spawnName2 = Plugin.GetSpawnName(setup);
				return spawnName2 == null || Plugin.IsEnemyAllowed(spawnName2);
			}).ToList();
			__instance.enemiesDifficulty3 = __instance.enemiesDifficulty3.Where(delegate(EnemySetup setup)
			{
				string spawnName2 = Plugin.GetSpawnName(setup);
				return spawnName2 == null || Plugin.IsEnemyAllowed(spawnName2);
			}).ToList();
			Plugin.Log.LogInfo((object)$"Difficulty 1: {count} -> {__instance.enemiesDifficulty1.Count}");
			Plugin.Log.LogInfo((object)$"Difficulty 2: {count2} -> {__instance.enemiesDifficulty2.Count}");
			Plugin.Log.LogInfo((object)$"Difficulty 3: {count3} -> {__instance.enemiesDifficulty3.Count}");
			if (Plugin.EnableLogging.Value)
			{
				HashSet<string> hashSet = new HashSet<string>();
				List<EnemySetup>[] array = new List<EnemySetup>[3] { __instance.enemiesDifficulty1, __instance.enemiesDifficulty2, __instance.enemiesDifficulty3 };
				for (int num = 0; num < array.Length; num++)
				{
					foreach (EnemySetup item in array[num])
					{
						string spawnName = Plugin.GetSpawnName(item);
						if (spawnName != null)
						{
							hashSet.Add(Plugin.NormalizeName(spawnName));
						}
					}
				}
				Plugin.Log.LogInfo((object)"Remaining enemies:");
				foreach (string item2 in hashSet.OrderBy((string x) => x))
				{
					Plugin.Log.LogInfo((object)("  - " + item2));
				}
			}
			Plugin.Log.LogInfo((object)"=================================");
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(EnemyDirector), "AmountSetup")]
		private static void AmountSetup_Postfix(EnemyDirector __instance)
		{
			List<EnemySetup> list = (List<EnemySetup>)AccessTools.Field(typeof(EnemyDirector), "enemyList").GetValue(__instance);
			if (list != null)
			{
				int count = list.Count;
				list.RemoveAll((EnemySetup e) => (Object)(object)e == (Object)null);
				int num = count - list.Count;
				if (num > 0)
				{
					FieldInfo fieldInfo = AccessTools.Field(typeof(EnemyDirector), "totalAmount");
					int num2 = (int)fieldInfo.GetValue(__instance);
					num2 -= num;
					fieldInfo.SetValue(__instance, num2);
					Plugin.Log.LogInfo((object)$"Removed {num} null slot(s) from enemyList. Adjusted totalAmount: {num2}");
				}
			}
		}
	}
}