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 UseWhitelist = null; public static ConfigEntry AllowedEnemies = null; public static ConfigEntry BlockedEnemies = null; public static ConfigEntry EnableLogging = null; public static ManualLogSource Log = null; private static HashSet _allowedCache = null; private static HashSet _blockedCache = null; private static readonly Dictionary _aliases = new Dictionary { { "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("General", "UseWhitelist", false, "If true, only spawn enemies in AllowedEnemies list. If false, block enemies in BlockedEnemies list."); AllowedEnemies = ((BaseUnityPlugin)this).Config.Bind("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("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("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(from s in AllowedEnemies.Value.Split(',') select ResolveAlias(s.ToLower().Trim().Replace(" ", "")), StringComparer.OrdinalIgnoreCase); _blockedCache = new HashSet(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 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 hashSet = new HashSet(); List[] array = new List[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 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 hashSet = new HashSet(); List[] array = new List[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 list = (List)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}"); } } } } }