using 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 _enabled; private ConfigEntry _fallbackContainsEnemy; private ConfigEntry _scanInterval; private ConfigEntry _fontSize; private ConfigEntry _overlayWidth; private ConfigEntry _rightOffset; private ConfigEntry _bottomOffset; private ConfigEntry _enemyComponentNames; private ConfigEntry _excludeComponentNames; private ConfigEntry _excludeObjectNames; private ConfigEntry _blockedSceneNameParts; private ConfigEntry _toggleKey; private ConfigEntry _dumpKey; private bool _runtimeVisible; private float _nextScanTime; private readonly Dictionary _breakdown = new Dictionary(); 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 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("General", "Enabled", true, "敵数オーバーレイを有効にします。"); _fallbackContainsEnemy = ((BaseUnityPlugin)this).Config.Bind("Detection", "FallbackContainsEnemy", true, "指定した敵コンポーネント名が見つからない場合、名前に Enemy を含むコンポーネントを敵候補として探索します。"); _scanInterval = ((BaseUnityPlugin)this).Config.Bind("Detection", "ScanIntervalSeconds", 0.5f, "敵数を再集計する間隔です。短すぎると負荷が増えます。"); _enemyComponentNames = ((BaseUnityPlugin)this).Config.Bind("Detection", "EnemyComponentNames", "EnemyParent,Enemy", "敵本体として扱うコンポーネント名です。カンマ区切りで指定します。"); _excludeComponentNames = ((BaseUnityPlugin)this).Config.Bind("Detection", "ExcludeComponentNames", "EnemyDirector,EnemySpawner,EnemySpawn,EnemyManager,EnemySetup,EnemyValuable,EnemyDebug,EnemyUI,EnemyCatch,EnemyNear,EnemySighting", "敵数カウントから除外するコンポーネント名です。カンマ区切りで指定します。"); _excludeObjectNames = ((BaseUnityPlugin)this).Config.Bind("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("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("Overlay", "FontSize", 14, "表示文字サイズです。"); _overlayWidth = ((BaseUnityPlugin)this).Config.Bind("Overlay", "OverlayWidth", 280, "オーバーレイ表示領域の横幅です。"); _rightOffset = ((BaseUnityPlugin)this).Config.Bind("Overlay", "RightOffset", 24, "画面右端からの距離です。"); _bottomOffset = ((BaseUnityPlugin)this).Config.Bind("Overlay", "BottomOffset", 36, "画面下からの距離です。"); _toggleKey = ((BaseUnityPlugin)this).Config.Bind("Keybinds", "ToggleOverlayKey", new KeyboardShortcut((KeyCode)289, Array.Empty()), "オーバーレイ表示/非表示の切り替えキーです。"); _dumpKey = ((BaseUnityPlugin)this).Config.Bind("Keybinds", "DumpCandidateTypesKey", new KeyboardShortcut((KeyCode)290, Array.Empty()), "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 exactEnemyNames = ToNameSet(_enemyComponentNames.Value); HashSet excludedComponentNames = ToNameSet(_excludeComponentNames.Value); HashSet excludedObjectNameParts = ToNameSet(_excludeObjectNames.Value); HashSet countedInstanceIds = new HashSet(); MonoBehaviour[] behaviours = Object.FindObjectsOfType(); if (ScanByExactComponentNames(behaviours, exactEnemyNames, excludedComponentNames, excludedObjectNameParts, countedInstanceIds) == 0 && _fallbackContainsEnemy.Value) { ScanByFallbackEnemyName(behaviours, excludedComponentNames, excludedObjectNameParts, countedInstanceIds); } } } private int ScanByExactComponentNames(MonoBehaviour[] behaviours, HashSet exactEnemyNames, HashSet excludedComponentNames, HashSet excludedObjectNameParts, HashSet 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 excludedComponentNames, HashSet excludedObjectNameParts, HashSet 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(), excludedComponentNames, excludedObjectNameParts, fallbackMode: true); AddEnemyObject(enemyObject, countedInstanceIds); } } } } private GameObject ResolveEnemyRootObject(GameObject source, HashSet exactEnemyNames, HashSet excludedComponentNames, HashSet 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 exactEnemyNames, HashSet excludedComponentNames, bool fallbackMode) { if ((Object)(object)obj == (Object)null) { return false; } MonoBehaviour[] components = obj.GetComponents(); 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 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 list = new List(); 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(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 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 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 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 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 ToNameSet(string csv) { HashSet hashSet = new HashSet(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 BuildCanonicalEnemyNames() { Dictionary result = new Dictionary(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 dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); MonoBehaviour[] array = Object.FindObjectsOfType(); 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 item in dictionary.OrderBy((KeyValuePair pair) => pair.Key)) { ((BaseUnityPlugin)this).Logger.LogInfo((object)$"{item.Key}: {item.Value}"); } ((BaseUnityPlugin)this).Logger.LogInfo((object)"===================================="); } } }