Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of EnemyCountOverlay v1.0.0
BepInEx\plugins\EnemyCountOverlay\EnemyCountOverlay.dll
Decompiled 6 days agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: IgnoresAccessChecksTo("")] [assembly: AssemblyCompany("LATA")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("EnemyCountOverlay")] [assembly: AssemblyTitle("EnemyCountOverlay")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [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 EnemyCountOverlay { [BepInPlugin("lata.repo.enemycountoverlay", "Enemy Count Overlay", "1.0.0")] public sealed class EnemyCountOverlayPlugin : BaseUnityPlugin { public const string PluginGuid = "lata.repo.enemycountoverlay"; public const string PluginName = "Enemy Count Overlay"; public const string PluginVersion = "1.0.0"; private ConfigEntry<bool> _enabled; private ConfigEntry<bool> _fallbackContainsEnemy; private ConfigEntry<float> _scanInterval; private ConfigEntry<int> _fontSize; private ConfigEntry<int> _overlayWidth; private ConfigEntry<int> _rightOffset; private ConfigEntry<int> _bottomOffset; private ConfigEntry<string> _enemyComponentNames; private ConfigEntry<string> _excludeComponentNames; private ConfigEntry<string> _excludeObjectNames; private ConfigEntry<string> _blockedSceneNameParts; private ConfigEntry<KeyboardShortcut> _toggleKey; private ConfigEntry<KeyboardShortcut> _dumpKey; private bool _runtimeVisible; private float _nextScanTime; private readonly Dictionary<string, int> _breakdown = new Dictionary<string, int>(); private GUIStyle _labelStyle; private GUIStyle _shadowStyle; private static readonly Regex CloneRegex = new Regex("\\(Clone\\)", RegexOptions.Compiled); private static readonly Regex TrailingNumberRegex = new Regex("\\s*\\(\\d+\\)$", RegexOptions.Compiled); private static readonly Regex NoiseRegex = new Regex("[^a-zA-Z0-9]", RegexOptions.Compiled); private static readonly Dictionary<string, string> CanonicalEnemyNames = BuildCanonicalEnemyNames(); private void Awake() { //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) _enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "敵数オーバーレイを有効にします。"); _fallbackContainsEnemy = ((BaseUnityPlugin)this).Config.Bind<bool>("Detection", "FallbackContainsEnemy", true, "指定した敵コンポーネント名が見つからない場合、名前に Enemy を含むコンポーネントを敵候補として探索します。"); _scanInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Detection", "ScanIntervalSeconds", 0.5f, "敵数を再集計する間隔です。短すぎると負荷が増えます。"); _enemyComponentNames = ((BaseUnityPlugin)this).Config.Bind<string>("Detection", "EnemyComponentNames", "EnemyParent,Enemy", "敵本体として扱うコンポーネント名です。カンマ区切りで指定します。"); _excludeComponentNames = ((BaseUnityPlugin)this).Config.Bind<string>("Detection", "ExcludeComponentNames", "EnemyDirector,EnemySpawner,EnemySpawn,EnemyManager,EnemySetup,EnemyValuable,EnemyDebug,EnemyUI,EnemyCatch,EnemyNear,EnemySighting", "敵数カウントから除外するコンポーネント名です。カンマ区切りで指定します。"); _excludeObjectNames = ((BaseUnityPlugin)this).Config.Bind<string>("Detection", "ExcludeObjectNames", "Manager,Director,Spawner,SpawnPoint,UI,Canvas,Debug,Menu,Password,Shop,Service Station,Enemy Catch,Enemy Near,Enemy Sighting", "敵数カウントから除外するGameObject名です。カンマ区切りで指定します。"); _blockedSceneNameParts = ((BaseUnityPlugin)this).Config.Bind<string>("Detection", "BlockedSceneNameParts", "Lobby,Lobby Menu,Level - Lobby Menu,Menu,Main Menu,Password,Shop,Service Station,Level - Shop,Level - Service Station", "この文字列を含むシーンではオーバーレイを表示しません。カンマ区切りで指定します。"); _fontSize = ((BaseUnityPlugin)this).Config.Bind<int>("Overlay", "FontSize", 14, "表示文字サイズです。"); _overlayWidth = ((BaseUnityPlugin)this).Config.Bind<int>("Overlay", "OverlayWidth", 280, "オーバーレイ表示領域の横幅です。"); _rightOffset = ((BaseUnityPlugin)this).Config.Bind<int>("Overlay", "RightOffset", 24, "画面右端からの距離です。"); _bottomOffset = ((BaseUnityPlugin)this).Config.Bind<int>("Overlay", "BottomOffset", 36, "画面下からの距離です。"); _toggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Keybinds", "ToggleOverlayKey", new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>()), "オーバーレイ表示/非表示の切り替えキーです。"); _dumpKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Keybinds", "DumpCandidateTypesKey", new KeyboardShortcut((KeyCode)290, Array.Empty<KeyCode>()), "Enemyを含むコンポーネント名をBepInExログへ出力します。検出調整用です。"); _runtimeVisible = _enabled.Value; ((BaseUnityPlugin)this).Logger.LogInfo((object)"Enemy Count Overlay 1.0.0 loaded."); ((BaseUnityPlugin)this).Logger.LogInfo((object)"F8: toggle overlay / F9: dump enemy-like component types"); } private void Update() { //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_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) KeyboardShortcut value = _toggleKey.Value; if (((KeyboardShortcut)(ref value)).IsDown()) { _runtimeVisible = !_runtimeVisible; ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Overlay visible: {_runtimeVisible}"); } value = _dumpKey.Value; if (((KeyboardShortcut)(ref value)).IsDown()) { DumpEnemyCandidateTypes(); } if (_enabled.Value && Time.unscaledTime >= _nextScanTime) { _nextScanTime = Time.unscaledTime + Mathf.Max(0.1f, _scanInterval.Value); ScanEnemies(); } } private void OnGUI() { //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) if (_enabled.Value && _runtimeVisible && !IsBlockedScene() && _breakdown.Count != 0) { EnsureGuiStyle(); string text = BuildOverlayText(); int num = Mathf.Max(1, text.Split('\n').Length); float num2 = Mathf.Max(_fontSize.Value + 4, 16); float num3 = Mathf.Clamp(_overlayWidth.Value, 120, Screen.width); float num4 = (float)num * num2 + 8f; float num5 = (float)Screen.width - num3 - (float)Mathf.Max(0, _rightOffset.Value); float num6 = (float)Screen.height - num4 - (float)Mathf.Max(0, _bottomOffset.Value); Rect val = default(Rect); ((Rect)(ref val))..ctor(num5 + 2f, num6 + 2f, num3, num4); Rect val2 = default(Rect); ((Rect)(ref val2))..ctor(num5, num6, num3, num4); GUI.Label(val, text, _shadowStyle); GUI.Label(val2, text, _labelStyle); } } private void EnsureGuiStyle() { //IL_0037: 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) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0051: 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_0067: Expected O, but got Unknown //IL_006c: Expected O, but got Unknown //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Expected O, but got Unknown //IL_00b1: Unknown result type (might be due to invalid IL or missing references) int num = Mathf.Clamp(_fontSize.Value, 10, 48); if (_labelStyle == null || _labelStyle.fontSize != num) { _labelStyle = new GUIStyle(GUI.skin.label) { fontSize = num, alignment = (TextAnchor)8, wordWrap = true, richText = true, padding = new RectOffset(0, 0, 0, 0) }; _labelStyle.normal.textColor = Color.white; _shadowStyle = new GUIStyle(_labelStyle); _shadowStyle.normal.textColor = new Color(0f, 0f, 0f, 0.85f); } } private string BuildOverlayText() { return string.Join("\n", from pair in _breakdown orderby pair.Value descending, pair.Key select $"{pair.Key}:{pair.Value}"); } private void ScanEnemies() { _breakdown.Clear(); if (!IsBlockedScene()) { HashSet<string> exactEnemyNames = ToNameSet(_enemyComponentNames.Value); HashSet<string> excludedComponentNames = ToNameSet(_excludeComponentNames.Value); HashSet<string> excludedObjectNameParts = ToNameSet(_excludeObjectNames.Value); HashSet<int> countedInstanceIds = new HashSet<int>(); MonoBehaviour[] behaviours = Object.FindObjectsOfType<MonoBehaviour>(); if (ScanByExactComponentNames(behaviours, exactEnemyNames, excludedComponentNames, excludedObjectNameParts, countedInstanceIds) == 0 && _fallbackContainsEnemy.Value) { ScanByFallbackEnemyName(behaviours, excludedComponentNames, excludedObjectNameParts, countedInstanceIds); } } } private int ScanByExactComponentNames(MonoBehaviour[] behaviours, HashSet<string> exactEnemyNames, HashSet<string> excludedComponentNames, HashSet<string> excludedObjectNameParts, HashSet<int> countedInstanceIds) { int count = countedInstanceIds.Count; foreach (MonoBehaviour val in behaviours) { if (!((Object)(object)val == (Object)null)) { Type type = ((object)val).GetType(); string name = type.Name; if (exactEnemyNames.Contains(name) && !IsExcludedComponent(name, excludedComponentNames)) { GameObject enemyObject = ResolveEnemyRootObject(((Component)val).gameObject, exactEnemyNames, excludedComponentNames, excludedObjectNameParts, fallbackMode: false); AddEnemyObject(enemyObject, countedInstanceIds); } } } return countedInstanceIds.Count - count; } private void ScanByFallbackEnemyName(MonoBehaviour[] behaviours, HashSet<string> excludedComponentNames, HashSet<string> excludedObjectNameParts, HashSet<int> countedInstanceIds) { foreach (MonoBehaviour val in behaviours) { if (!((Object)(object)val == (Object)null)) { Type type = ((object)val).GetType(); string name = type.Name; if (name.IndexOf("Enemy", StringComparison.OrdinalIgnoreCase) >= 0 && !IsExcludedComponent(name, excludedComponentNames) && !IsExcludedObject(((Component)val).gameObject, excludedObjectNameParts)) { GameObject enemyObject = ResolveEnemyRootObject(((Component)val).gameObject, new HashSet<string>(), excludedComponentNames, excludedObjectNameParts, fallbackMode: true); AddEnemyObject(enemyObject, countedInstanceIds); } } } } private GameObject ResolveEnemyRootObject(GameObject source, HashSet<string> exactEnemyNames, HashSet<string> excludedComponentNames, HashSet<string> excludedObjectNameParts, bool fallbackMode) { if ((Object)(object)source == (Object)null) { return null; } Transform val = source.transform; Transform val2 = val; while ((Object)(object)val != (Object)null) { GameObject gameObject = ((Component)val).gameObject; if (!IsExcludedObject(gameObject, excludedObjectNameParts) && HasEnemyMarkerComponent(gameObject, exactEnemyNames, excludedComponentNames, fallbackMode)) { val2 = val; } val = val.parent; } if (!((Object)(object)val2 != (Object)null)) { return source; } return ((Component)val2).gameObject; } private bool HasEnemyMarkerComponent(GameObject obj, HashSet<string> exactEnemyNames, HashSet<string> excludedComponentNames, bool fallbackMode) { if ((Object)(object)obj == (Object)null) { return false; } MonoBehaviour[] components = obj.GetComponents<MonoBehaviour>(); MonoBehaviour[] array = components; foreach (MonoBehaviour val in array) { if ((Object)(object)val == (Object)null) { continue; } string name = ((object)val).GetType().Name; if (!IsExcludedComponent(name, excludedComponentNames)) { if (exactEnemyNames.Contains(name)) { return true; } if (fallbackMode && name.IndexOf("Enemy", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } } return false; } private void AddEnemyObject(GameObject enemyObject, HashSet<int> countedInstanceIds) { if ((Object)(object)enemyObject == (Object)null || !enemyObject.activeInHierarchy) { return; } string text = ResolveCanonicalEnemyName(enemyObject); if (string.IsNullOrWhiteSpace(text)) { return; } int instanceID = ((Object)enemyObject).GetInstanceID(); if (countedInstanceIds.Add(instanceID)) { if (!_breakdown.ContainsKey(text)) { _breakdown[text] = 0; } _breakdown[text]++; } } private string ResolveCanonicalEnemyName(GameObject obj) { if ((Object)(object)obj == (Object)null) { return null; } List<string> list = new List<string>(); list.Add(((Object)obj).name); Transform val = obj.transform; while ((Object)(object)val != (Object)null) { list.Add(((Object)((Component)val).gameObject).name); val = val.parent; } MonoBehaviour[] componentsInChildren = obj.GetComponentsInChildren<MonoBehaviour>(true); MonoBehaviour[] array = componentsInChildren; foreach (MonoBehaviour val2 in array) { if (!((Object)(object)val2 == (Object)null)) { list.Add(((object)val2).GetType().Name); } } foreach (string item in list) { string text = TryGetCanonicalEnemyName(item); if (!string.IsNullOrWhiteSpace(text)) { return text; } } return null; } private string TryGetCanonicalEnemyName(string rawName) { if (string.IsNullOrWhiteSpace(rawName)) { return null; } string text = CleanName(rawName); if (string.IsNullOrWhiteSpace(text)) { return null; } string text2 = NormalizeKey(text); if (CanonicalEnemyNames.TryGetValue(text2, out var value)) { return value; } foreach (KeyValuePair<string, string> canonicalEnemyName in CanonicalEnemyNames) { if (text2.Contains(canonicalEnemyName.Key)) { return canonicalEnemyName.Value; } } return null; } private string CleanName(string name) { if (string.IsNullOrWhiteSpace(name)) { return ""; } string input = name; input = CloneRegex.Replace(input, ""); input = TrailingNumberRegex.Replace(input, ""); input = input.Replace("_", " "); input = input.Replace("-", " "); input = input.Replace("EnemyParent", ""); input = input.Replace("Enemy Parent", ""); input = input.Replace("Enemy", ""); input = input.Replace("Parent", ""); input = input.Replace("(Clone)", ""); return input.Trim(); } private string NormalizeKey(string name) { if (string.IsNullOrWhiteSpace(name)) { return ""; } return NoiseRegex.Replace(name, "").ToLowerInvariant(); } private bool IsBlockedScene() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); string text = ((Scene)(ref activeScene)).name ?? ""; HashSet<string> hashSet = ToNameSet(_blockedSceneNameParts.Value); foreach (string item in hashSet) { if (text.IndexOf(item, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } return false; } private bool IsExcludedComponent(string componentName, HashSet<string> excludedComponentNames) { if (string.IsNullOrWhiteSpace(componentName)) { return true; } foreach (string excludedComponentName in excludedComponentNames) { if (componentName.IndexOf(excludedComponentName, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } return false; } private bool IsExcludedObject(GameObject obj, HashSet<string> excludedObjectNameParts) { if ((Object)(object)obj == (Object)null) { return true; } string text = ((Object)obj).name ?? ""; foreach (string excludedObjectNamePart in excludedObjectNameParts) { if (text.IndexOf(excludedObjectNamePart, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } return false; } private HashSet<string> ToNameSet(string csv) { HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrWhiteSpace(csv)) { return hashSet; } string[] array = csv.Split(','); string[] array2 = array; foreach (string text in array2) { string text2 = text.Trim(); if (!string.IsNullOrWhiteSpace(text2)) { hashSet.Add(text2); } } return hashSet; } private static Dictionary<string, string> BuildCanonicalEnemyNames() { Dictionary<string, string> result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); Add("Apex Predator", new string[4] { "Duck", "Ducky", "Mr Ducky", "EnemyDuck" }); Add("Animal", new string[1] { "EnemyAnimal" }); Add("Banger", new string[2] { "Bang", "EnemyBang" }); Add("Bella", new string[3] { "Tricycle", "EnemyTricycle", "EnemyTricycleVisuals" }); Add("Birthday Boy", new string[2] { "BirthdayBoy", "EnemyBirthdayBoy" }); Add("Bowtie", new string[1] { "EnemyBowtie" }); Add("Chef", new string[1] { "EnemyChef" }); Add("Cleanup Crew", new string[4] { "CleanupCrew", "BombThrower", "Bomb Thrower", "EnemyBombThrower" }); Add("Clown", new string[2] { "Beamer", "EnemyBeamer" }); Add("Elsa", new string[1] { "EnemyElsa" }); Add("Gambit", new string[3] { "Spinny", "EnemyGambit", "EnemySpinny" }); Add("Gnome", new string[1] { "EnemyGnome" }); Add("Headgrab", new string[3] { "HeadGrabber", "Head Grabber", "EnemyHeadGrabber" }); Add("Headman", new string[2] { "Head", "EnemyHead" }); Add("Heart Hugger", new string[2] { "HeartHugger", "EnemyHeartHugger" }); Add("Hidden", new string[1] { "EnemyHidden" }); Add("Huntsman", new string[2] { "Hunter", "EnemyHunter" }); Add("Loom", new string[2] { "Shadow", "EnemyShadow" }); Add("Mentalist", new string[3] { "Floater", "EnemyFloater", "EnemyMentalist" }); Add("Oogly", new string[2] { "Ugly", "EnemyOogly" }); Add("Peeper", new string[3] { "CeilingEye", "Ceiling Eye", "EnemyCeilingEye" }); Add("Reaper", new string[2] { "Runner", "EnemyRunner" }); Add("Robe", new string[1] { "EnemyRobe" }); Add("Rugrat", new string[3] { "ValuableThrower", "Valuable Thrower", "EnemyValuableThrower" }); Add("Shadow Child", new string[2] { "ShadowChild", "EnemyShadowChild" }); Add("Spewer", new string[3] { "SlowMouth", "Slow Mouth", "EnemySlowMouth" }); Add("Tick", new string[1] { "EnemyTick" }); Add("Trudge", new string[3] { "SlowWalker", "Slow Walker", "EnemySlowWalker" }); Add("Upscream", new string[1] { "EnemyUpscream" }); return result; void Add(string canonicalName, params string[] aliases) { string key = NoiseRegex.Replace(canonicalName, "").ToLowerInvariant(); if (!result.ContainsKey(key)) { result.Add(key, canonicalName); } foreach (string input in aliases) { string key2 = NoiseRegex.Replace(input, "").ToLowerInvariant(); if (!result.ContainsKey(key2)) { result.Add(key2, canonicalName); } } } } private void DumpEnemyCandidateTypes() { Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); MonoBehaviour[] array = Object.FindObjectsOfType<MonoBehaviour>(); MonoBehaviour[] array2 = array; foreach (MonoBehaviour val in array2) { if ((Object)(object)val == (Object)null) { continue; } string name = ((object)val).GetType().Name; if (name.IndexOf("Enemy", StringComparison.OrdinalIgnoreCase) >= 0) { if (!dictionary.ContainsKey(name)) { dictionary[name] = 0; } dictionary[name]++; } } ((BaseUnityPlugin)this).Logger.LogInfo((object)"==== Enemy-like component types ===="); foreach (KeyValuePair<string, int> item in dictionary.OrderBy((KeyValuePair<string, int> pair) => pair.Key)) { ((BaseUnityPlugin)this).Logger.LogInfo((object)$"{item.Key}: {item.Value}"); } ((BaseUnityPlugin)this).Logger.LogInfo((object)"===================================="); } } }