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 OverlayHUD v26.11.4
OverlayHUD.dll
Decompiled 4 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using ExitGames.Client.Photon; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Photon.Pun; using Photon.Realtime; using UnityEngine; using UnityEngine.Localization; using UnityEngine.Localization.Settings; using UnityEngine.Localization.Tables; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.SceneManagement; using UnityEngine.UI; [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("OverlayHUD")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("26.11.4.0")] [assembly: AssemblyInformationalVersion("26.11.4+517b0ad35e7a2307efbee7a3a1702b950ede9bcb")] [assembly: AssemblyProduct("OverlayHUD")] [assembly: AssemblyTitle("OverlayHUD")] [assembly: AssemblyVersion("26.11.4.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 OverlayHUD { internal sealed class HudNameLocalization { private static readonly Dictionary<string, string> MonsterKeys = new Dictionary<string, string>(StringComparer.Ordinal) { { "Animal", "ENEMY.ANIMAL" }, { "Banger", "ENEMY.BANG" }, { "Clown", "ENEMY.BEAMER" }, { "Birthday Boy", "ENEMY.BIRTHDAY_BOY" }, { "Cleanup Crew", "ENEMY.BOMB_THROWER" }, { "Bowtie", "ENEMY.BOWTIE" }, { "Peeper", "ENEMY.CEILING_EYE" }, { "Apex Predator", "ENEMY.DUCK" }, { "Elsa", "ENEMY.ELSA" }, { "Mentalist", "ENEMY.FLOATER" }, { "Gnome", "ENEMY.GNOME" }, { "Gnomes", "ENEMY.GNOME" }, { "Headman", "ENEMY.HEAD" }, { "Headgrab", "ENEMY.HEAD_GRABBER" }, { "Heart Hugger", "ENEMY.HEART_HUGGER" }, { "Hidden", "ENEMY.HIDDEN" }, { "Huntsman", "ENEMY.HUNTER" }, { "Oogly", "ENEMY.OOGLY" }, { "Robe", "ENEMY.ROBE" }, { "Reaper", "ENEMY.RUNNER" }, { "Loom", "ENEMY.SHADOW" }, { "Spewer", "ENEMY.SLOW_MOUTH" }, { "Trudge", "ENEMY.SLOW_WALKER" }, { "Gambit", "ENEMY.SPINNY" }, { "Shadow Child", "ENEMY.THIN_MAN" }, { "Tick", "ENEMY.TICK" }, { "Bella", "ENEMY.TRICYCLE" }, { "Chef", "ENEMY.TUMBLER" }, { "Upscream", "ENEMY.UPSCREAM" }, { "Rugrat", "ENEMY.VALUABLE_THROWER" }, { "Snatcher", "ENEMY.SNATCHER" }, { "Pony", "ENEMY.SNATCHER" } }; private static readonly Dictionary<string, string> UpgradeKeys = new Dictionary<string, string>(StringComparer.Ordinal) { { "strength", "STATS.UPGRADE.STRENGTH" }, { "tumbleLaunch", "STATS.UPGRADE.LAUNCH" }, { "range", "STATS.UPGRADE.RANGE" }, { "sprintSpeed", "STATS.UPGRADE.SPEED" }, { "tumbleWings", "STATS.UPGRADE.TUMBLE_WINGS" }, { "crouchRest", "STATS.UPGRADE.CROUCH_REST" }, { "extraJump", "STATS.UPGRADE.EXTRA_JUMP" }, { "tumbleClimb", "STATS.UPGRADE.TUMBLE_CLIMB" }, { "health", "STATS.UPGRADE.HEALTH" }, { "stamina", "STATS.UPGRADE.STAMINA" }, { "mapPlayerCount", "STATS.UPGRADE.MAP_PLAYER_COUNT" }, { "deathHeadBattery", "STATS.UPGRADE.DEATH_HEAD_BATTERY" } }; private static readonly Dictionary<string, string> UpgradeFallbacks = new Dictionary<string, string>(StringComparer.Ordinal) { { "strength", "Strength" }, { "tumbleLaunch", "Tumble Launch" }, { "range", "Range" }, { "sprintSpeed", "Sprint Speed" }, { "tumbleWings", "Tumble Wings" }, { "crouchRest", "Crouch Rest" }, { "extraJump", "Extra Jump" }, { "tumbleClimb", "Tumble Climb" }, { "health", "Health" }, { "stamina", "Stamina" }, { "mapPlayerCount", "Map Player Count" }, { "deathHeadBattery", "Death Head Battery" } }; private readonly Dictionary<string, Dictionary<string, string>> tables = new Dictionary<string, Dictionary<string, string>>(StringComparer.Ordinal); internal void Clear() { tables.Clear(); } internal bool SetTable(string language, IEnumerable<KeyValuePair<string, string>> entries) { if ((language != "en" && language != "ru") || entries == null) { return false; } Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal); foreach (KeyValuePair<string, string> entry in entries) { if (entry.Key != null && (entry.Key.StartsWith("ENEMY.", StringComparison.Ordinal) || entry.Key.StartsWith("STATS.UPGRADE.", StringComparison.Ordinal)) && !string.IsNullOrWhiteSpace(entry.Value) && !(entry.Value == entry.Key)) { dictionary[entry.Key] = entry.Value; } } if (dictionary.Count == 0) { return false; } tables[language] = dictionary; return true; } internal string MonsterName(string canonicalName, string language, bool seen, bool unknown) { if (!seen || unknown) { return null; } string text; if (canonicalName == null || !MonsterKeys.TryGetValue(canonicalName, out var value)) { text = canonicalName; if (text == null) { return ""; } } else { text = Name(value, language, canonicalName); } return text; } internal string UpgradeName(string upgrade, string language) { string text; if (upgrade == null || !UpgradeKeys.TryGetValue(upgrade, out var value)) { text = upgrade; if (text == null) { return ""; } } else { text = Name(value, language, UpgradeFallbacks[upgrade]); } return text; } private string Name(string key, string language, string fallback) { if (tables.TryGetValue((language == "en") ? "en" : "ru", out var value) && value.TryGetValue(key, out var value2)) { return value2; } if (tables.TryGetValue("en", out var value3) && value3.TryGetValue(key, out value2)) { return value2; } return fallback; } internal Dictionary<string, Dictionary<string, string>> EnemyNames() { return ExportNames("ENEMY."); } internal Dictionary<string, Dictionary<string, string>> UpgradeNames() { return ExportNames("STATS.UPGRADE."); } private Dictionary<string, Dictionary<string, string>> ExportNames(string prefix) { Dictionary<string, Dictionary<string, string>> dictionary = new Dictionary<string, Dictionary<string, string>>(StringComparer.Ordinal); foreach (KeyValuePair<string, Dictionary<string, string>> table in tables) { foreach (KeyValuePair<string, string> item in table.Value) { if (item.Key.StartsWith(prefix, StringComparison.Ordinal)) { if (!dictionary.TryGetValue(item.Key, out var value)) { value = (dictionary[item.Key] = new Dictionary<string, string>(StringComparer.Ordinal)); } value[table.Key] = item.Value; } } } return dictionary; } } [BepInPlugin("local.overlay.overlay_hud", "OverlayHUD", "26.11.4")] public sealed class Plugin : BaseUnityPlugin { private static class PlayerUpgradeTypes { internal static readonly Type StatsManager = ResolveGameType("StatsManager"); internal static readonly Type PlayerController = ResolveGameType("PlayerController"); } private static class GameUiFields { private static readonly Type MapType = ResolveGameType("MapToolController"); private static readonly Type CurrencyType = ResolveGameType("CurrencyUI"); private static readonly Type SemiUiType = ResolveGameType("SemiUI"); private static readonly Type DirectorType = ResolveGameType("GameDirector"); private static readonly Type LoadingType = ResolveGameType("LoadingUI"); private static readonly Type PlayerType = ResolveGameType("PlayerController"); internal static readonly FieldInfo MapInstance = Find(MapType, "instance"); internal static readonly FieldInfo MapVisual = Find(MapType, "VisualTransform"); internal static readonly FieldInfo CurrencyInstance = Find(CurrencyType, "instance"); internal static readonly FieldInfo UiText = Find(SemiUiType, "uiText"); internal static readonly FieldInfo ShowTimer = Find(SemiUiType, "showTimer"); internal static readonly FieldInfo HidePosition = Find(SemiUiType, "hidePosition"); internal static readonly FieldInfo HidePositionCurrent = Find(SemiUiType, "hidePositionCurrent"); internal static readonly FieldInfo DirectorInstance = Find(DirectorType, "instance"); internal static readonly FieldInfo DirectorState = Find(DirectorType, "currentState"); internal static readonly FieldInfo DisableInput = Find(DirectorType, "DisableInput"); internal static readonly FieldInfo LoadingInstance = Find(LoadingType, "instance"); internal static readonly FieldInfo PlayerInstance = Find(PlayerType, "instance"); internal static readonly FieldInfo InputDisableTimer = Find(PlayerType, "InputDisableTimer"); private static FieldInfo Find(Type type, string name) { if (!(type == null)) { return AccessTools.Field(type, name); } return null; } } private struct EnemyCandidate { public Component Component; public GameObject Root; public Vector3 Center; } private struct ResolvedEnemyCandidate { public EnemyCandidate Candidate; public string MonsterName; } private struct VisionEnemyCache { public Component EnemyParent; public int InstanceId; } private sealed class NetworkHealthSample { internal long Sequence; internal int Master; internal float Health; internal float MaxHealth; } private sealed class NativeConfigurationManagerAttributes { public int Order; } private sealed class NetworkRespawnSample { internal double SentAt; internal float Remaining; internal bool Alive; } internal static readonly HudNameLocalization LocalizedHudNames = new HudNameLocalization(); private static Plugin instance; private static readonly List<string> seenMonsters = new List<string>(); private static readonly List<Component> knownEnemyParents = new List<Component>(); private static readonly HashSet<int> sentEnemyInstanceIds = new HashSet<int>(); private static readonly HashSet<int> valuablesInDollarHaul = new HashSet<int>(); private static readonly HashSet<int> destroyedValuablesAccounted = new HashSet<int>(); private static readonly object seenLock = new object(); private static readonly object enemyLock = new object(); private static readonly object reflectionCacheLock = new object(); private static readonly Dictionary<string, MemberInfo> memberCache = new Dictionary<string, MemberInfo>(); private static readonly HashSet<string> missingMemberCache = new HashSet<string>(); private static readonly Dictionary<string, MethodInfo> noArgMethodCache = new Dictionary<string, MethodInfo>(); private static readonly HashSet<string> missingNoArgMethodCache = new HashSet<string>(); private static float mapValue; private static float mapValueInitial; private static float lostValue; private static readonly ConditionalWeakTable<object, string> lastGrabberNameByPhysGrabObject = new ConditionalWeakTable<object, string>(); private static readonly Dictionary<string, string> KnownMonsters = new Dictionary<string, string> { { "apexpredator", "Apex Predator" }, { "animal", "Animal" }, { "banger", "Banger" }, { "bang", "Banger" }, { "bella", "Bella" }, { "beamer", "Clown" }, { "birthdayboy", "Birthday Boy" }, { "bowtie", "Bowtie" }, { "chef", "Chef" }, { "cheffrog", "Chef" }, { "ceilingeye", "Peeper" }, { "cleanupcrew", "Cleanup Crew" }, { "clown", "Clown" }, { "clownbeamer", "Clown" }, { "duck", "Rugrat" }, { "elsa", "Elsa" }, { "floater", "Mentalist" }, { "gambit", "Gambit" }, { "gnome", "Gnomes" }, { "headgrab", "Headgrab" }, { "headgrabber", "Headgrab" }, { "headman", "Headman" }, { "hearthugger", "Heart Hugger" }, { "hidden", "Hidden" }, { "huntsman", "Huntsman" }, { "hunter", "Huntsman" }, { "loom", "Loom" }, { "mentalist", "Mentalist" }, { "oogly", "Oogly" }, { "peeper", "Peeper" }, { "reaper", "Reaper" }, { "robe", "Robe" }, { "rugrat", "Rugrat" }, { "runner", "Gambit" }, { "shadowchild", "Shadow Child" }, { "shadow", "Shadow Child" }, { "spewer", "Spewer" }, { "slowmouth", "Spewer" }, { "slowwalker", "Trudge" }, { "spinny", "Bowtie" }, { "thinman", "Reaper" }, { "tick", "Tick" }, { "trudge", "Trudge" }, { "tricycle", "Birthday Boy" }, { "tumbler", "Apex Predator" }, { "upscream", "Upscream" }, { "valuablethrower", "Rugrat" } }; private static readonly KeyValuePair<string, string>[] TrackedPlayerUpgrades = new KeyValuePair<string, string>[12] { new KeyValuePair<string, string>("strength", "playerUpgradeStrength"), new KeyValuePair<string, string>("tumbleLaunch", "playerUpgradeLaunch"), new KeyValuePair<string, string>("range", "playerUpgradeRange"), new KeyValuePair<string, string>("sprintSpeed", "playerUpgradeSpeed"), new KeyValuePair<string, string>("tumbleWings", "playerUpgradeTumbleWings"), new KeyValuePair<string, string>("crouchRest", "playerUpgradeCrouchRest"), new KeyValuePair<string, string>("extraJump", "playerUpgradeExtraJump"), new KeyValuePair<string, string>("tumbleClimb", "playerUpgradeTumbleClimb"), new KeyValuePair<string, string>("health", "playerUpgradeHealth"), new KeyValuePair<string, string>("stamina", "playerUpgradeStamina"), new KeyValuePair<string, string>("mapPlayerCount", "playerUpgradeMapPlayerCount"), new KeyValuePair<string, string>("deathHeadBattery", "playerUpgradeDeathHeadBattery") }; private static readonly Dictionary<string, string> UpgradeStateKeyByPunMethod = new Dictionary<string, string> { { "UpgradePlayerGrabStrength", "strength" }, { "UpgradePlayerTumbleLaunch", "tumbleLaunch" }, { "UpgradePlayerGrabRange", "range" }, { "UpgradePlayerSprintSpeed", "sprintSpeed" }, { "UpgradePlayerTumbleWings", "tumbleWings" }, { "UpgradePlayerCrouchRest", "crouchRest" }, { "UpgradePlayerExtraJump", "extraJump" }, { "UpgradePlayerTumbleClimb", "tumbleClimb" }, { "UpgradePlayerHealth", "health" }, { "UpgradePlayerEnergy", "stamina" }, { "UpgradeMapPlayerCount", "mapPlayerCount" }, { "UpgradeDeathHeadBattery", "deathHeadBattery" } }; private static readonly HashSet<string> PlayerVisionLegacyFallbackMonsters = new HashSet<string>(StringComparer.Ordinal) { "Tick", "Upscream" }; private static readonly string[] CurrentHealthMemberNames = new string[7] { "currentHealth", "healthCurrent", "_currentSyncedHealth", "_syncedHealth", "currentHP", "healthValue", "HealthValue" }; private static readonly string[] MaxHealthMemberNames = new string[10] { "maxHealth", "MaxHealth", "healthMax", "HealthMax", "healthMaximum", "maximumHealth", "maxHP", "HPMax", "health", "Health" }; private readonly Dictionary<string, float> lastSeenLoggedAt = new Dictionary<string, float>(); private readonly Dictionary<int, string> resolvedMonsterNames = new Dictionary<int, string>(); private readonly Dictionary<int, string> rosterMetadataBySourceId = new Dictionary<int, string>(); private readonly Dictionary<int, int> sourceIdsByEnemyParent = new Dictionary<int, int>(); private readonly Dictionary<int, bool> lastAliveBySourceId = new Dictionary<int, bool>(); private readonly Dictionary<int, string> lastHealthDebugBySourceId = new Dictionary<int, string>(); private readonly Dictionary<int, object[]> healthSourcesByRootId = new Dictionary<int, object[]>(); private readonly Dictionary<int, string> lastImmediateStatusBySourceId = new Dictionary<int, string>(); private readonly Dictionary<int, string> lastTimerStatusBySourceId = new Dictionary<int, string>(); private readonly Dictionary<int, float> lastTimerStatusSentAtBySourceId = new Dictionary<int, float>(); private readonly Dictionary<int, string> lastFallbackStatusBySourceId = new Dictionary<int, string>(); private readonly Dictionary<int, bool> enemyHasVisionByParentId = new Dictionary<int, bool>(); private readonly Dictionary<int, bool> enemyHasOnScreenByParentId = new Dictionary<int, bool>(); private readonly Dictionary<int, VisionEnemyCache> visionEnemyCacheByVisionId = new Dictionary<int, VisionEnemyCache>(); private readonly HashSet<int> pendingEncounterIds = new HashSet<int>(); private float nextScanAt; private float nextStatusSyncAt; private float nextUpgradeSyncAt; private float nextMapValueSyncAt; private float nextDebugSummaryAt; private float nextBroadEnemyDiscoveryAt; private float nextStatusDirectorRecoveryAt; private float scanPausedUntil; private int fallbackLevel = 1; private int lastSyncedLevel; private readonly Dictionary<string, int> lastSyncedUpgrades = new Dictionary<string, int>(); private readonly HashSet<string> pendingUpgradeKeys = new HashSet<string>(); private string lastRosterFingerprint = ""; private string lastStatusFingerprint = ""; private string pendingRosterFingerprint = ""; private int rosterStableScans; private int broadEnemyDiscoveryAttempts; private int statusFallbackCursor; private bool rosterPublished; private bool enemyRosterDirty; private bool mapValueDirty; private bool pendingMapValueRefresh; private string pendingMapValueRefreshReason = ""; private string lastMapValueFingerprint = ""; private int cachedLocalPlayerViewId = int.MinValue; private bool gameplayActive; private string lastGameUiFingerprint; private float nextGameUiVisibilityAt; private Coroutine pendingGameplayActivation; private Coroutine pendingUpgradeSnapshot; private ConfigEntry<float> scanInterval; private ConfigEntry<float> statusInterval; private ConfigEntry<bool> requireLineOfSight; private ConfigEntry<bool> revealUnknownMonsters; private ConfigEntry<bool> preferPlayerVisionDetection; private ConfigEntry<bool> debugLogging; private Harmony harmony; private static readonly Dictionary<string, string> MonsterIconAliases = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { { "Snatcher", "snatcher" }, { "Pony", "snatcher" }, { "Пони", "snatcher" } }; private static readonly Dictionary<string, Type> gameTypeCache = new Dictionary<string, Type>(); private const string HealthProtocol = "OverlayHUD.Health.v1"; private readonly Dictionary<Component, NetworkHealthSample> networkHealth = new Dictionary<Component, NetworkHealthSample>(); private long healthSequence; private const string NativeGeneralSection = "03 Native - General"; private const string NativeLayoutSection = "04 Native - Layout"; private const string NativeInformationSection = "05 Native - Information"; private const string NativeMonstersSection = "06 Native - Monsters"; private const string NativeEffectsSection = "07 Native - Effects"; private const string NativeUpgradesSection = "08 Native - Upgrades"; private bool nativeMode; private NativeHud nativeHud; private NativeTimestampWriter nativeTimestamps; private ConfigEntry<bool> nativeSaveTimestamps; private readonly NativeHudState nativeState = new NativeHudState(); private ConfigEntry<float> nativeScale; private ConfigEntry<float> nativeOffsetX; private ConfigEntry<float> nativeOffsetY; private ConfigEntry<float> nativeMonsterSize; private ConfigEntry<float> nativeUpgradeSize; private ConfigEntry<int> nativeColumns; private ConfigEntry<int> nativeStripColumns; private ConfigEntry<int> nativeUpgradeRows; private ConfigEntry<string> nativeLanguage; private ConfigEntry<string> nativeAlignment; private ConfigEntry<bool> nativeHorizontal; private ConfigEntry<bool> nativeEnabled; private ConfigEntry<bool> nativeGameUIEffects; private ConfigEntry<bool> nativeLevel; private ConfigEntry<bool> nativeTimer; private ConfigEntry<bool> nativeMapValue; private ConfigEntry<bool> nativeLostValue; private ConfigEntry<bool> nativeMonsters; private ConfigEntry<bool> nativeProximityWaves; private ConfigEntry<bool> nativeRespawnIndicator; private ConfigEntry<bool> nativeDestroyedItemAlerts; private ConfigEntry<bool> nativeHealth; private ConfigEntry<bool> nativeRespawn; private ConfigEntry<bool> nativeUpgrades; private ConfigEntry<bool> nativeStrength; private ConfigEntry<bool> nativeMonsterCounts; private ConfigEntry<bool> nativeDamageNumbers; private ConfigEntry<bool> nativeAnimations; private ConfigEntry<bool> nativeOnlyAliveMonsters; private ConfigEntry<bool> nativeFilterUndetectedMonsters; private ConfigEntry<bool> nativeWrapValueLabels; private ConfigEntry<bool> nativeTooltips; private readonly Dictionary<string, ConfigEntry<bool>> nativeUpgradeVisibility = new Dictionary<string, ConfigEntry<bool>>(StringComparer.Ordinal); private NativeHudOptions nativeOptions; private volatile bool nativeOptionsDirty = true; private const byte RespawnEventCode = 173; private const string RespawnProtocol = "OverlayHUD.Respawn.v1"; private readonly Dictionary<Component, NetworkRespawnSample> networkRespawns = new Dictionary<Component, NetworkRespawnSample>(); private string respawnSession = Guid.NewGuid().ToString("N"); private Room respawnRoom; private int respawnMaster; private float nextRespawnNetworkAt; private bool respawnSubscribed; private IEnumerator SyncMonsterLocalization() { LocalizedHudNames.Clear(); AsyncOperationHandle<LocalizationSettings> initialization; try { initialization = LocalizationSettings.InitializationOperation; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Game localization unavailable; using fallback HUD names. " + ex.Message)); yield break; } yield return initialization; if ((int)initialization.Status != 1) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Game localization initialization failed; using fallback HUD names."); yield break; } HashSet<string> loaded = new HashSet<string>(); for (int attempt = 0; attempt < 6; attempt++) { if (loaded.Count >= 2) { break; } string[] array = new string[2] { "en-US", "ru-RU" }; foreach (string code in array) { if (loaded.Contains(code) || !TryRequestHudTable(code, out var operation)) { continue; } yield return operation; if ((int)operation.Status != 1 || (Object)(object)operation.Result == (Object)null) { continue; } List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>(); foreach (StringTableEntry value in ((DetailedLocalizationTable<StringTableEntry>)(object)operation.Result).Values) { list.Add(new KeyValuePair<string, string>(((TableEntry)value).Key, value.Value)); } if (LocalizedHudNames.SetTable(code.Substring(0, 2), list)) { loaded.Add(code); } operation = default(AsyncOperationHandle<StringTable>); } if (loaded.Count < 2 && attempt < 5) { yield return (object)new WaitForSecondsRealtime(10f); } } ((BaseUnityPlugin)this).Logger.LogInfo((object)("Loaded official HUD names (ENEMY.* / STATS.UPGRADE.*) for " + loaded.Count + "/2 ru/en locales.")); if (loaded.Count < 2) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Some HUD localization tables are unavailable; missing names use English/original fallbacks."); } } private bool TryRequestHudTable(string code, out AsyncOperationHandle<StringTable> operation) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0030: 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_003b: Unknown result type (might be due to invalid IL or missing references) operation = default(AsyncOperationHandle<StringTable>); try { Locale locale = LocalizationSettings.AvailableLocales.GetLocale(LocaleIdentifier.op_Implicit(code)); if ((Object)(object)locale == (Object)null) { return false; } operation = ((LocalizedDatabase<StringTable, StringTableEntry>)(object)LocalizationSettings.StringDatabase).GetTableAsync(TableReference.op_Implicit("HUD"), locale); return true; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Cannot load HUD localization for " + code + ": " + ex.Message)); return false; } } private void Awake() { instance = this; string[] array = new string[13] { "StatsManager", "PlayerController", "MapToolController", "CurrencyUI", "SemiUI", "GameDirector", "LoadingUI", "RunManager", "LevelGenerator", "SemiFunc", "ValuableObject", "RoundDirector", "PlayerAvatar" }; for (int i = 0; i < array.Length; i++) { ResolveGameType(array[i]); } KeepPluginObjectAlive(); requireLineOfSight = BindNativeSetting("02 Detection", "RequireLineOfSight", defaultValue: true, "Reveal monsters only after an encounter. Disable to reveal the complete level roster as soon as it is known.", 100); revealUnknownMonsters = BindNativeSetting("02 Detection", "RevealUnknownMonsters", defaultValue: false, "Reveal monsters using the four-pointed star placeholder icon as soon as they are discovered, regardless of RequireLineOfSight. Monsters with their own icons keep their usual detection rules.", 90); preferPlayerVisionDetection = BindNativeSetting("02 Detection", "PreferPlayerVisionDetection", defaultValue: true, "When enabled, show monsters when the player sees them. When disabled, use enemy-vision plus player-very-close detection.", 80); scanInterval = BindNativeSetting("02 Detection", "ScanIntervalSeconds", 3f, "How often pending enemy roster sync is retried.", 70); statusInterval = BindNativeSetting("02 Detection", "StatusIntervalSeconds", 10f, "How often monster health and respawn status are synced after the roster is known. Event-driven changes remain immediate; values below 10 are raised to 10.", 60); debugLogging = BindNativeSetting("99 Diagnostics", "Logging", defaultValue: false, "Write additional diagnostic logs.", 100); if (statusInterval.Value < 10f) { statusInterval.Value = 10f; } ((BaseUnityPlugin)this).Logger.LogInfo((object)("OverlayHUD is running. Logging=" + debugLogging.Value)); InitializeNativeHud(); PatchGameUpdates(); ((MonoBehaviour)this).StartCoroutine(SyncMonsterLocalization()); } private void PatchGameUpdates() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Expected O, but got Unknown //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Expected O, but got Unknown //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Expected O, but got Unknown //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Expected O, but got Unknown //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_01ed: Expected O, but got Unknown //IL_0203: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Expected O, but got Unknown //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Expected O, but got Unknown //IL_023d: Unknown result type (might be due to invalid IL or missing references) //IL_0244: Expected O, but got Unknown //IL_025a: Unknown result type (might be due to invalid IL or missing references) //IL_0261: Expected O, but got Unknown //IL_0277: Unknown result type (might be due to invalid IL or missing references) //IL_027e: Expected O, but got Unknown //IL_0294: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Expected O, but got Unknown //IL_02b1: Unknown result type (might be due to invalid IL or missing references) //IL_02b8: Expected O, but got Unknown //IL_02ce: Unknown result type (might be due to invalid IL or missing references) //IL_02d5: Expected O, but got Unknown //IL_02eb: Unknown result type (might be due to invalid IL or missing references) //IL_02f2: Expected O, but got Unknown //IL_0308: Unknown result type (might be due to invalid IL or missing references) //IL_030f: Expected O, but got Unknown //IL_0325: Unknown result type (might be due to invalid IL or missing references) //IL_032c: Expected O, but got Unknown //IL_0342: Unknown result type (might be due to invalid IL or missing references) //IL_0349: Expected O, but got Unknown //IL_035f: Unknown result type (might be due to invalid IL or missing references) //IL_0366: Expected O, but got Unknown //IL_037c: Unknown result type (might be due to invalid IL or missing references) //IL_0383: Expected O, but got Unknown //IL_0399: Unknown result type (might be due to invalid IL or missing references) //IL_03a0: Expected O, but got Unknown //IL_03b6: Unknown result type (might be due to invalid IL or missing references) //IL_03bd: Expected O, but got Unknown try { harmony = new Harmony("local.overlay.overlay_hud"); MethodInfo methodInfo = AccessTools.Method("EnemyParent:SpawnRPC", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method("EnemyParent:DespawnRPC", (Type[])null, (Type[])null); MethodInfo methodInfo3 = AccessTools.Method("EnemyParent:DisableDecrease", (Type[])null, (Type[])null); MethodInfo methodInfo4 = AccessTools.Method("EnemyParent:DespawnedTimerSet", (Type[])null, (Type[])null); MethodInfo methodInfo5 = AccessTools.Method("EnemyParent:PlayerCloseLogic", (Type[])null, (Type[])null); MethodInfo methodInfo6 = AccessTools.Method("EnemyOnScreen:Logic", (Type[])null, (Type[])null); MethodInfo methodInfo7 = AccessTools.Method("LevelGenerator:GenerateDone", (Type[])null, (Type[])null); MethodInfo methodInfo8 = AccessTools.Method("LevelGenerator:StartRoomGeneration", (Type[])null, (Type[])null); MethodInfo methodInfo9 = AccessTools.Method("RunManager:ChangeLevel", (Type[])null, (Type[])null); MethodInfo methodInfo10 = AccessTools.Method("RoundDirector:ExtractionCompleted", (Type[])null, (Type[])null); MethodInfo methodInfo11 = AccessTools.Method("ValuableObject:DollarValueSetRPC", (Type[])null, (Type[])null); MethodInfo methodInfo12 = AccessTools.Method("ValuableObject:DollarValueSetLogic", (Type[])null, (Type[])null); MethodInfo methodInfo13 = AccessTools.Method("ValuableObject:AddToDollarHaulList", (Type[])null, (Type[])null); MethodInfo methodInfo14 = AccessTools.Method("ValuableObject:AddToDollarHaulListRPC", (Type[])null, (Type[])null); MethodInfo methodInfo15 = AccessTools.Method("ValuableObject:RemoveFromDollarHaulList", (Type[])null, (Type[])null); MethodInfo methodInfo16 = AccessTools.Method("ValuableObject:RemoveFromDollarHaulListRPC", (Type[])null, (Type[])null); MethodInfo methodInfo17 = AccessTools.Method("PhysGrabObjectImpactDetector:BreakRPC", (Type[])null, (Type[])null); MethodInfo methodInfo18 = AccessTools.Method("PhysGrabObject:DestroyPhysGrabObject", (Type[])null, (Type[])null); MethodInfo methodInfo19 = AccessTools.Method("PhysGrabObject:DestroyPhysGrabObjectRPC", (Type[])null, (Type[])null); MethodInfo methodInfo20 = AccessTools.Method("PhysGrabObject:GrabStarted", (Type[])null, (Type[])null); MethodInfo methodInfo21 = AccessTools.Method("PhysGrabObject:GrabPlayerAddRPC", (Type[])null, (Type[])null); MethodInfo methodInfo22 = AccessTools.Method("EnemyVision:VisionTrigger", (Type[])null, (Type[])null); MethodInfo methodInfo23 = AccessTools.Method("EnemyHealth:Hurt", (Type[])null, (Type[])null); MethodInfo methodInfo24 = AccessTools.Method("EnemyHealth:HurtRPC", (Type[])null, (Type[])null); HarmonyMethod val = new HarmonyMethod(typeof(Plugin).GetMethod("LevelGeneratedPostfix", BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val2 = new HarmonyMethod(typeof(Plugin).GetMethod("LevelGenerationStartingPrefix", BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val3 = new HarmonyMethod(typeof(Plugin).GetMethod("EnemyParentSpawnedPostfix", BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val4 = new HarmonyMethod(typeof(Plugin).GetMethod("EnemyParentDespawnedPostfix", BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val5 = new HarmonyMethod(typeof(Plugin).GetMethod("EnemyParentTimerChangedPostfix", BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val6 = new HarmonyMethod(typeof(Plugin).GetMethod("EnemyParentPlayerCloseLogicPostfix", BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val7 = new HarmonyMethod(typeof(Plugin).GetMethod("EnemyOnScreenLogicPostfix", BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val8 = new HarmonyMethod(typeof(Plugin).GetMethod("LevelChangingPrefix", BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val9 = new HarmonyMethod(typeof(Plugin).GetMethod("LevelChangedPostfix", BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val10 = new HarmonyMethod(typeof(Plugin).GetMethod("PlayerUpgradeAppliedPostfix", BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val11 = new HarmonyMethod(typeof(Plugin).GetMethod("ExtractionCompletedPostfix", BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val12 = new HarmonyMethod(typeof(Plugin).GetMethod("ValuableDollarValueSetRpcPostfix", BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val13 = new HarmonyMethod(typeof(Plugin).GetMethod("ValuableDollarValueSetLogicPostfix", BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val14 = new HarmonyMethod(typeof(Plugin).GetMethod("ValuableDollarHaulAddPostfix", BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val15 = new HarmonyMethod(typeof(Plugin).GetMethod("ValuableDollarHaulRemovePostfix", BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val16 = new HarmonyMethod(typeof(Plugin).GetMethod("PhysGrabObjectBreakPostfix", BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val17 = new HarmonyMethod(typeof(Plugin).GetMethod("PhysGrabObjectDestroyingPrefix", BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val18 = new HarmonyMethod(typeof(Plugin).GetMethod("PhysGrabObjectGrabStartedPostfix", BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val19 = new HarmonyMethod(typeof(Plugin).GetMethod("PhysGrabObjectGrabPlayerAddRpcPostfix", BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val20 = new HarmonyMethod(typeof(Plugin).GetMethod("EnemyVisionTriggerPostfix", BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val21 = new HarmonyMethod(typeof(Plugin).GetMethod("EnemyHealthChangedPostfix", BindingFlags.Static | BindingFlags.NonPublic)); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val3, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched EnemyParent.SpawnRPC for enemy registration."); } if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, val4, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched EnemyParent.DespawnRPC for immediate status sync."); } if (methodInfo3 != null) { harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, val5, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched EnemyParent.DisableDecrease for event-driven respawn timer sync."); } if (methodInfo4 != null) { harmony.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, val5, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched EnemyParent.DespawnedTimerSet for event-driven respawn timer sync."); } if (methodInfo5 != null) { harmony.Patch((MethodBase)methodInfo5, (HarmonyMethod)null, val6, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched EnemyParent.PlayerCloseLogic for blind enemy very-close encounters."); } if (methodInfo6 != null) { harmony.Patch((MethodBase)methodInfo6, (HarmonyMethod)null, val7, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched EnemyOnScreen.Logic for player-visible enemy encounters."); } if (methodInfo7 != null) { harmony.Patch((MethodBase)methodInfo7, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched LevelGenerator.GenerateDone for monster reset."); } if (methodInfo8 != null) { harmony.Patch((MethodBase)methodInfo8, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched LevelGenerator.StartRoomGeneration for map value reset."); } if (methodInfo9 != null) { harmony.Patch((MethodBase)methodInfo9, val8, val9, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched RunManager.ChangeLevel for overlay visibility and activation."); } if (methodInfo10 != null) { harmony.Patch((MethodBase)methodInfo10, (HarmonyMethod)null, val11, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched RoundDirector.ExtractionCompleted for map value refresh."); } if (methodInfo11 != null) { harmony.Patch((MethodBase)methodInfo11, (HarmonyMethod)null, val12, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched ValuableObject.DollarValueSetRPC for map value tracking."); } if (methodInfo12 != null) { harmony.Patch((MethodBase)methodInfo12, (HarmonyMethod)null, val13, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched ValuableObject.DollarValueSetLogic for map value tracking."); } if (methodInfo13 != null) { harmony.Patch((MethodBase)methodInfo13, (HarmonyMethod)null, val14, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched ValuableObject.AddToDollarHaulList for submitted value tracking."); } if (methodInfo14 != null) { harmony.Patch((MethodBase)methodInfo14, (HarmonyMethod)null, val14, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched ValuableObject.AddToDollarHaulListRPC for submitted value tracking."); } if (methodInfo15 != null) { harmony.Patch((MethodBase)methodInfo15, (HarmonyMethod)null, val15, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched ValuableObject.RemoveFromDollarHaulList for submitted value tracking."); } if (methodInfo16 != null) { harmony.Patch((MethodBase)methodInfo16, (HarmonyMethod)null, val15, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched ValuableObject.RemoveFromDollarHaulListRPC for submitted value tracking."); } if (methodInfo17 != null) { harmony.Patch((MethodBase)methodInfo17, (HarmonyMethod)null, val16, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched PhysGrabObjectImpactDetector.BreakRPC for map value loss."); } if (methodInfo18 != null) { harmony.Patch((MethodBase)methodInfo18, val17, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched PhysGrabObject.DestroyPhysGrabObject for map value removal."); } if (methodInfo19 != null) { harmony.Patch((MethodBase)methodInfo19, val17, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched PhysGrabObject.DestroyPhysGrabObjectRPC as a network destruction fallback."); } if (methodInfo20 != null) { harmony.Patch((MethodBase)methodInfo20, (HarmonyMethod)null, val18, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched PhysGrabObject.GrabStarted for last-grabber tracking."); } if (methodInfo21 != null) { harmony.Patch((MethodBase)methodInfo21, (HarmonyMethod)null, val19, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched PhysGrabObject.GrabPlayerAddRPC for last-grabber tracking."); } if (methodInfo22 != null) { harmony.Patch((MethodBase)methodInfo22, (HarmonyMethod)null, val20, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched EnemyVision.VisionTrigger for sighted enemy encounters."); } if (methodInfo23 != null) { harmony.Patch((MethodBase)methodInfo23, (HarmonyMethod)null, val21, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched EnemyHealth.Hurt for immediate health sync."); } if (methodInfo24 != null) { harmony.Patch((MethodBase)methodInfo24, (HarmonyMethod)null, val21, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patched EnemyHealth.HurtRPC for immediate health sync."); } int num = 0; foreach (string key in UpgradeStateKeyByPunMethod.Keys) { MethodInfo methodInfo25 = AccessTools.Method("PunManager:" + key, (Type[])null, (Type[])null); if (!(methodInfo25 == null)) { harmony.Patch((MethodBase)methodInfo25, (HarmonyMethod)null, val10, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } } ((BaseUnityPlugin)this).Logger.LogInfo((object)("Patched " + num + " PunManager upgrade methods for event-driven sync.")); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Failed to patch game updates: " + ex.GetType().Name + ": " + ex.Message)); } SceneManager.sceneLoaded += OnSceneLoaded; } private void KeepPluginObjectAlive() { try { Transform transform = ((Component)this).gameObject.transform; if ((Object)(object)transform.parent != (Object)null) { transform.parent = null; } ((Object)((Component)this).gameObject).hideFlags = (HideFlags)61; Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Failed to detach plugin object: " + ex.GetType().Name + ": " + ex.Message)); } } private void Update() { TickNativeHud(); TickRespawnNetwork(); if (gameplayActive) { TickScan(); } } private void LateUpdate() { if (gameplayActive && !(Time.realtimeSinceStartup < nextGameUiVisibilityAt)) { nextGameUiVisibilityAt = Time.realtimeSinceStartup + 0.05f; SyncGameUiVisibilityIfChanged(); } } private static void LevelGeneratedPostfix() { instance?.HandleLevelGenerated(); } private static void LevelGenerationStartingPrefix() { instance?.ResetMapValue("room generation started"); } private static void LevelChangingPrefix() { instance?.HandleLevelChanging(); } private static void LevelChangedPostfix(object __instance) { instance?.HandleLevelChanged(__instance); } private static void PlayerUpgradeAppliedPostfix(MethodBase __originalMethod, string _steamID, int __result) { string text = __originalMethod?.Name; if (text != null && UpgradeStateKeyByPunMethod.TryGetValue(text, out var value)) { instance?.SyncPlayerUpgradeValueIfLocal(value, _steamID, __result); } } private static void EnemyParentSpawnedPostfix(object __instance) { Component val = (Component)((__instance is Component) ? __instance : null); if (val != null) { instance?.networkRespawns.Remove(val); RegisterEnemyParent(val); instance?.SendEnemyHealth(val); instance?.TryRevealUnknownEnemy(val); instance?.SyncEnemyParentStatusChanged(val); } } private static void EnemyParentDespawnedPostfix(object __instance) { Component val = (Component)((__instance is Component) ? __instance : null); if (val != null) { instance?.networkRespawns.Remove(val); instance?.ScheduleEnemyParentStatusChanged(val); } } private static void EnemyParentTimerChangedPostfix(object __instance) { Component val = (Component)((__instance is Component) ? __instance : null); if (val != null) { instance?.SyncEnemyParentTimerChanged(val); } } private static void EnemyParentPlayerCloseLogicPostfix(object __instance, ref IEnumerator __result) { if (__result != null) { Component val = (Component)((__instance is Component) ? __instance : null); if (val != null) { __result = WatchBlindEnemyPlayerClose(__result, val); } } } private static void EnemyOnScreenLogicPostfix(object __instance, ref IEnumerator __result) { if (__result != null) { Component val = (Component)((__instance is Component) ? __instance : null); if (val != null) { __result = WatchEnemyOnScreen(__result, val); } } } private static void ExtractionCompletedPostfix() { instance?.RefreshMapValue("extraction completed"); } private static void ValuableDollarValueSetRpcPostfix(object __instance, float value) { instance?.ScheduleMapValueRefresh("valuable rpc"); } private static void ValuableDollarValueSetLogicPostfix(object __instance) { if (IsMasterClientOrSingleplayer()) { instance?.ScheduleMapValueRefresh("valuable logic"); } } private static void ValuableDollarHaulAddPostfix(object __instance) { TrackDollarHaulValuable(__instance, inHaul: true); } private static void ValuableDollarHaulRemovePostfix(object __instance) { TrackDollarHaulValuable(__instance, inHaul: false); } private static void PhysGrabObjectBreakPostfix(object __instance, float valueLost, bool _loseValue) { if (_loseValue) { instance?.AddMapValue(0f - valueLost, "valuable break"); instance?.AddLostValue(valueLost, "valuable break"); } } private static void PhysGrabObjectDestroyingPrefix(object __instance) { if (!IsRunLevel()) { return; } Component valuableComponent = GetValuableComponent(__instance); bool flag = false; if ((Object)(object)valuableComponent != (Object)null) { int valuableKey = GetValuableKey(valuableComponent); if (valuableKey != 0 && !destroyedValuablesAccounted.Add(valuableKey)) { return; } float num = ReadValuableCurrentValue(valuableComponent); float num2 = ReadValuableOriginalValue(valuableComponent); bool num3 = num2 > 0f && num < num2 * 0.15f; object obj = ReadMember(__instance, "dead"); bool flag2 = default(bool); int num4; if (obj is bool) { flag2 = (bool)obj; num4 = 1; } else { num4 = 0; } flag = ((uint)num4 & (flag2 ? 1u : 0u)) == 0 && IsValuableInDollarHaul(valuableComponent); if (!num3) { instance?.AddMapValue(0f - num, "valuable destroyed"); if (!flag) { instance?.AddLostValue(num, "valuable destroyed"); } } } if ((Object)(object)valuableComponent != (Object)null && !flag) { instance?.ReportDestroyedItem(__instance); } } private static void PhysGrabObjectGrabStartedPostfix(object __instance, object player) { RecordLastGrabber(__instance, ReadMember(player, "playerAvatar")); } private static void PhysGrabObjectGrabPlayerAddRpcPostfix(object __instance, int photonViewID) { RecordLastGrabber(__instance, InvokePlayerAvatarGetFromPhotonID(photonViewID)); } private static void RecordLastGrabber(object physGrabObjectInstance, object playerAvatar) { if (physGrabObjectInstance != null && playerAvatar != null) { string value = ReadMember(playerAvatar, "playerName") as string; if (!string.IsNullOrWhiteSpace(value)) { lastGrabberNameByPhysGrabObject.Remove(physGrabObjectInstance); lastGrabberNameByPhysGrabObject.Add(physGrabObjectInstance, value); } } } private static void EnemyVisionTriggerPostfix(object __instance, int playerID, object player, bool culled, bool playerNear) { instance?.HandleEnemyVisionTrigger(__instance, playerID); } private static void EnemyHealthChangedPostfix(object __instance) { instance?.SyncEnemyHealthChanged(__instance); } private void TickScan() { long timestamp = Stopwatch.GetTimestamp(); try { TickScanCore(); } finally { double num = (double)(Stopwatch.GetTimestamp() - timestamp) * 1000.0 / (double)Stopwatch.Frequency; if (num >= 8.0) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("[Performance] TickScan: " + num.ToString("0.00", CultureInfo.InvariantCulture) + " ms.")); } } } private void TickScanCore() { if (!gameplayActive) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (mapValueDirty && realtimeSinceStartup >= nextMapValueSyncAt) { long timestamp = Stopwatch.GetTimestamp(); SyncMapValueIfChanged(); LogSlowScanStage("map value update", timestamp); } if (realtimeSinceStartup < scanPausedUntil || pendingUpgradeSnapshot != null) { return; } if (pendingUpgradeKeys.Count > 0 && realtimeSinceStartup >= nextUpgradeSyncAt) { long timestamp2 = Stopwatch.GetTimestamp(); HashSet<string> onlyKeys = new HashSet<string>(pendingUpgradeKeys); pendingUpgradeKeys.Clear(); pendingUpgradeSnapshot = ((MonoBehaviour)this).StartCoroutine(SyncPlayerUpgradesIfChanged(onlyKeys)); LogSlowScanStage("upgrade coroutine launch", timestamp2); return; } if (rosterPublished && realtimeSinceStartup >= nextStatusSyncAt) { long timestamp3 = Stopwatch.GetTimestamp(); nextStatusSyncAt = realtimeSinceStartup + Math.Max(10f, statusInterval.Value); SyncMonsterStatuses(new List<ResolvedEnemyCandidate>(), fallbackSlice: true); LogSlowScanStage("fallback monster status", timestamp3); } if (!enemyRosterDirty || realtimeSinceStartup < nextScanAt) { return; } nextScanAt = realtimeSinceStartup + Math.Max(0.05f, scanInterval.Value); try { long timestamp4 = Stopwatch.GetTimestamp(); SyncKnownEnemies(); LogSlowScanStage("monster discovery and roster", timestamp4); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Enemy sync failed: " + ex.GetType().Name + ": " + ex.Message + "\n" + ex.StackTrace)); } } private void LogSlowScanStage(string stage, long startedAt) { double num = (double)(Stopwatch.GetTimestamp() - startedAt) * 1000.0 / (double)Stopwatch.Frequency; if (num >= 4.0) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("[Performance] Scan stage " + stage + ": " + num.ToString("0.00", CultureInfo.InvariantCulture) + " ms.")); } } private void OnDestroy() { DisposeNativeConfig(); nativeHud?.Dispose(); StopRespawnNetwork(); SceneManager.sceneLoaded -= OnSceneLoaded; Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } if (gameplayActive) { HandleLevelChanging(); } if ((Object)(object)instance == (Object)(object)this) { instance = null; } } private void HandleLevelGenerated() { ScheduleGameplayActivation("level generator done"); } private void HandleLevelChanged(object runManager) { if (debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("RunManager.ChangeLevel completed, current level is " + DescribeCurrentLevel(runManager) + ".")); } ScheduleGameplayActivation("RunManager.ChangeLevel"); } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (gameplayActive && (IsNonGameplayContext() || (!IsRunLevel() && !IsRunLevelName(((Scene)(ref scene)).name)))) { HandleLevelChanging(); } } private void ScheduleGameplayActivation(string reason) { if (pendingGameplayActivation != null) { ((MonoBehaviour)this).StopCoroutine(pendingGameplayActivation); } pendingGameplayActivation = ((MonoBehaviour)this).StartCoroutine(ActivateGameplayAfterLevelChange(reason)); } private IEnumerator ActivateGameplayAfterLevelChange(string reason) { WaitForSecondsRealtime wait = new WaitForSecondsRealtime(0.1f); do { yield return wait; if (IsLocalGameplayReady() && HandleGameplayDetected(reason)) { pendingGameplayActivation = null; yield break; } } while (!IsNonGameplayContext()); if (debugLogging.Value) { IsGameplayLevelCandidate(out var details); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Gameplay activation was not confirmed after " + reason + " (" + details + ").")); } pendingGameplayActivation = null; } private static bool IsLocalGameplayReady() { object? obj = GameUiFields.DirectorInstance?.GetValue(null); Component val = (Component)((obj is Component) ? obj : null); if (val == null || (Object)(object)val == (Object)null) { return false; } if (GameUiFields.DirectorState?.GetValue(val)?.ToString() != "Main") { return false; } object obj2 = GameUiFields.DisableInput?.GetValue(val); bool flag = default(bool); int num; if (obj2 is bool) { flag = (bool)obj2; num = 1; } else { num = 0; } if (num == 0 || flag) { return false; } object? obj3 = GameUiFields.LoadingInstance?.GetValue(null); Component val2 = (Component)((obj3 is Component) ? obj3 : null); if (val2 == null || (Object)(object)val2 == (Object)null || val2.gameObject.activeInHierarchy) { return false; } object? obj4 = GameUiFields.PlayerInstance?.GetValue(null); Behaviour val3 = (Behaviour)((obj4 is Behaviour) ? obj4 : null); if (val3 != null && (Object)(object)val3 != (Object)null && val3.isActiveAndEnabled && GameUiFields.InputDisableTimer?.GetValue(val3) is float num2) { return num2 <= 0f; } return false; } private bool HandleGameplayDetected(string reason) { if (!IsGameplayLevelCandidate(out var details)) { if (debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Skipped gameplay activation from " + reason + " (" + details + ").")); } if (gameplayActive) { HandleLevelChanging(); } return false; } if (gameplayActive) { return true; } if (debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Activating gameplay overlay from " + reason + " (" + details + ").")); } ResetSeenMonsters("new level generated"); mapValue = 0f; mapValueInitial = 0f; lostValue = 0f; valuablesInDollarHaul.Clear(); destroyedValuablesAccounted.Clear(); lastMapValueFingerprint = ""; mapValueInitial = mapValue; gameplayActive = true; lastGameUiFingerprint = null; SyncGameUiVisibilityIfChanged(); ScheduleMapValueRefresh("local gameplay ready"); int num = ResolveCurrentLevel(); if (num > 0) { SyncLevelToOverlay(num, ResolveCurrentLevelName()); } return true; } private void HandleLevelChanging() { if (pendingGameplayActivation != null) { ((MonoBehaviour)this).StopCoroutine(pendingGameplayActivation); pendingGameplayActivation = null; } if (gameplayActive) { gameplayActive = false; SyncMapValueIfChanged(); ResetMapValue("level changing"); ((MonoBehaviour)this).StartCoroutine(PostVisibility(visible: false)); } } private void ResetMapValue(string reason) { mapValue = 0f; mapValueInitial = 0f; lostValue = 0f; valuablesInDollarHaul.Clear(); destroyedValuablesAccounted.Clear(); lastMapValueFingerprint = ""; mapValueDirty = false; pendingMapValueRefresh = false; pendingMapValueRefreshReason = ""; if (debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Reset map value: " + reason + ".")); } } private void RefreshMapValue(string reason) { if (IsRunLevel()) { mapValue = CalculateMapValue(); if (debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Refreshed map value after " + reason + ": " + mapValue.ToString("0", CultureInfo.InvariantCulture) + ".")); } MarkMapValueDirty(); } } private void ScheduleMapValueRefresh(string reason) { if (IsRunLevel()) { pendingMapValueRefresh = true; pendingMapValueRefreshReason = reason; MarkMapValueDirty(); } } private void AddMapValue(float delta, string reason) { if (IsRunLevel() && !float.IsNaN(delta) && !float.IsInfinity(delta) && !(Math.Abs(delta) < 0.01f)) { mapValue = Math.Max(0f, mapValue + delta); if (debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Updated map value from " + reason + ": delta=" + delta.ToString("0.#", CultureInfo.InvariantCulture) + ", value=" + mapValue.ToString("0", CultureInfo.InvariantCulture) + ".")); } MarkMapValueDirty(); } } private void AddLostValue(float value, string reason) { if (IsRunLevel() && !float.IsNaN(value) && !float.IsInfinity(value) && !(value < 0.01f)) { lostValue += value; if (debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Updated lost value from " + reason + ": value=" + value.ToString("0.#", CultureInfo.InvariantCulture) + ", total=" + lostValue.ToString("0", CultureInfo.InvariantCulture) + ".")); } MarkMapValueDirty(); } } private void MarkMapValueDirty() { mapValueDirty = true; nextMapValueSyncAt = Time.realtimeSinceStartup + 0.2f; } private void ReportDestroyedItem(object physGrabObjectInstance) { Component val = (Component)((physGrabObjectInstance is Component) ? physGrabObjectInstance : null); if (val == null) { if (debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Destroyed-item report skipped: destroyed instance is not a Component."); } return; } string text = ResolveLastGrabberName(physGrabObjectInstance); if (string.IsNullOrWhiteSpace(text)) { if (debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Destroyed-item report skipped: no known last grabber for the destroyed object."); } return; } string text2 = ResolveDestroyedItemName(val); if (debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Destroyed-item report: " + text + " destroyed " + text2 + ".")); } ((MonoBehaviour)this).StartCoroutine(PostItemDestroyed(text, text2)); } private static string ResolveLastGrabberName(object physGrabObjectInstance) { if (lastGrabberNameByPhysGrabObject.TryGetValue(physGrabObjectInstance, out var value) && !string.IsNullOrWhiteSpace(value)) { return value; } object obj = ReadMember(physGrabObjectInstance, "lastPlayerGrabbing"); if (obj == null) { return null; } return ReadMember(obj, "playerName") as string; } private static string ResolveDestroyedItemName(Component physGrabObjectComponent) { string text = ReadMember(physGrabObjectComponent.GetComponent("ItemAttributes"), "itemName") as string; if (!string.IsNullOrWhiteSpace(text)) { return text; } Component component = physGrabObjectComponent.GetComponent("ValuableObject"); string text2 = (((Object)(object)component != (Object)null) ? ((Object)component.gameObject).name : ((Object)physGrabObjectComponent.gameObject).name); if (string.IsNullOrWhiteSpace(text2)) { return "Item"; } return text2.Replace("(Clone)", "").Trim(); } private void SyncMapValueIfChanged() { mapValueDirty = false; if (pendingMapValueRefresh) { pendingMapValueRefresh = false; mapValue = CalculateMapValue(); if (mapValueInitial <= 0f && mapValue > 0f) { mapValueInitial = mapValue; } if (debugLogging.Value) { string text = (string.IsNullOrWhiteSpace(pendingMapValueRefreshReason) ? "scheduled refresh" : pendingMapValueRefreshReason); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Refreshed map value after " + text + ": " + mapValue.ToString("0", CultureInfo.InvariantCulture) + ".")); } pendingMapValueRefreshReason = ""; } int value = Math.Max(0, (int)Math.Round(mapValue)); int initial = Math.Max(0, (int)Math.Round(mapValueInitial)); int lost = Math.Max(0, (int)Math.Round(lostValue)); int? goal = ResolveExtractionGoal(); string text2 = value.ToString(CultureInfo.InvariantCulture) + ":" + initial.ToString(CultureInfo.InvariantCulture) + ":" + lost.ToString(CultureInfo.InvariantCulture) + ":" + (goal.HasValue ? goal.Value.ToString(CultureInfo.InvariantCulture) : ""); if (!(text2 == lastMapValueFingerprint)) { lastMapValueFingerprint = text2; ((MonoBehaviour)this).StartCoroutine(PostMapValue(value, initial, lost, goal)); } } private void ResetSeenMonsters(string reason) { ResetRespawnNetwork(); if (pendingUpgradeSnapshot != null) { ((MonoBehaviour)this).StopCoroutine(pendingUpgradeSnapshot); pendingUpgradeSnapshot = null; } lock (seenLock) { seenMonsters.Clear(); } lock (enemyLock) { knownEnemyParents.Clear(); sentEnemyInstanceIds.Clear(); } lastSeenLoggedAt.Clear(); resolvedMonsterNames.Clear(); rosterMetadataBySourceId.Clear(); sourceIdsByEnemyParent.Clear(); lastAliveBySourceId.Clear(); lastHealthDebugBySourceId.Clear(); healthSourcesByRootId.Clear(); lastImmediateStatusBySourceId.Clear(); lastTimerStatusBySourceId.Clear(); lastTimerStatusSentAtBySourceId.Clear(); lastFallbackStatusBySourceId.Clear(); enemyHasVisionByParentId.Clear(); enemyHasOnScreenByParentId.Clear(); visionEnemyCacheByVisionId.Clear(); pendingEncounterIds.Clear(); cachedLocalPlayerViewId = int.MinValue; lastRosterFingerprint = ""; lastStatusFingerprint = ""; pendingRosterFingerprint = ""; rosterStableScans = 0; broadEnemyDiscoveryAttempts = 0; statusFallbackCursor = 0; rosterPublished = false; enemyRosterDirty = true; scanPausedUntil = Time.realtimeSinceStartup + 2f; nextScanAt = scanPausedUntil; nextStatusSyncAt = scanPausedUntil; nextUpgradeSyncAt = scanPausedUntil; nextDebugSummaryAt = 0f; nextBroadEnemyDiscoveryAt = scanPausedUntil; nextStatusDirectorRecoveryAt = scanPausedUntil; ((BaseUnityPlugin)this).Logger.LogInfo((object)("Cleared seen monsters: " + reason + ".")); } private void SyncKnownEnemies() { List<EnemyCandidate> list = FindEnemyCandidates(); List<ResolvedEnemyCandidate> list2 = new List<ResolvedEnemyCandidate>(); int count = list.Count; foreach (EnemyCandidate item in list) { int instanceID = ((Object)item.Root).GetInstanceID(); if (!resolvedMonsterNames.TryGetValue(instanceID, out var value)) { value = ResolveMonsterName(item.Component); if (value != null) { resolvedMonsterNames[instanceID] = value; } } if (value == null) { if (debugLogging.Value && Time.realtimeSinceStartup >= nextDebugSummaryAt) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Unresolved enemy candidate: " + ((object)item.Component).GetType().Name + " / " + ((Object)item.Root).name)); } } else { list2.Add(new ResolvedEnemyCandidate { Candidate = item, MonsterName = value }); TryRevealUnknownEnemy(GetEnemyParent(item)); } } if (list2.Count == 0 && !rosterPublished && broadEnemyDiscoveryAttempts < 3) { return; } if (!SyncMonsterRoster(list2)) { if (debugLogging.Value && Time.realtimeSinceStartup >= nextDebugSummaryAt) { nextDebugSummaryAt = Time.realtimeSinceStartup + 30f; ((BaseUnityPlugin)this).Logger.LogInfo((object)("Collecting monster roster: candidates=" + count + ", resolved=" + list2.Count + ".")); } return; } enemyRosterDirty = false; ((MonoBehaviour)this).StartCoroutine(PublishInitialMonsterState(list2)); if (debugLogging.Value && Time.realtimeSinceStartup >= nextDebugSummaryAt) { nextDebugSummaryAt = Time.realtimeSinceStartup + 30f; ((BaseUnityPlugin)this).Logger.LogInfo((object)("Enemy sync summary: candidates=" + count + ", resolved=" + list2.Count + ".")); } } private IEnumerator PublishInitialMonsterState(List<ResolvedEnemyCandidate> enemies) { yield return null; if (!gameplayActive || !rosterPublished) { yield break; } RevealAllKnownEnemies(enemies); yield return null; if (gameplayActive && rosterPublished) { SyncMonsterStatuses(enemies); yield return null; if (gameplayActive && rosterPublished) { TryPublishPendingVisionEncounters(enemies); } } } private void TryRevealUnknownEnemy(Component enemyParent) { if (!gameplayActive || !revealUnknownMonsters.Value || (Object)(object)enemyParent == (Object)null) { return; } GameObject enemyRoot = GetEnemyRoot(enemyParent); if (!((Object)(object)enemyRoot == (Object)null) && enemyRoot.activeInHierarchy) { EnemyCandidate candidate = new EnemyCandidate { Component = enemyParent, Root = enemyRoot }; if (ShouldRevealUnknownMonster(candidate)) { int instanceId = ((Object)enemyRoot).GetInstanceID(); string monsterName = null; PublishEnemyParentEncounter(enemyParent, ref instanceId, ref monsterName); } } } private void RevealAllKnownEnemies(List<ResolvedEnemyCandidate> enemies) { for (int i = 0; i < enemies.Count; i++) { ResolvedEnemyCandidate resolvedEnemyCandidate = enemies[i]; EnemyCandidate candidate = resolvedEnemyCandidate.Candidate; if (requireLineOfSight.Value && !ShouldRevealUnknownMonster(candidate)) { continue; } int instanceID = ((Object)candidate.Root).GetInstanceID(); if (TryMarkEnemySent(instanceID)) { pendingEncounterIds.Remove(instanceID); MarkMonsterSeen(resolvedEnemyCandidate.MonsterName, candidate); if (debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Revealed level monster: " + resolvedEnemyCandidate.MonsterName + " from " + ((object)candidate.Component).GetType().Name + " / " + ((Object)candidate.Root).name)); } ((MonoBehaviour)this).StartCoroutine(PostSeenMonster(resolvedEnemyCandidate.MonsterName, instanceID, GetMonsterMetadata(candidate))); } } } private bool SyncMonsterRoster(List<ResolvedEnemyCandidate> enemies) { enemies.Sort((ResolvedEnemyCandidate left, ResolvedEnemyCandidate right) => ((Object)left.Candidate.Root).GetInstanceID().CompareTo(((Object)right.Candidate.Root).GetInstanceID())); StringBuilder stringBuilder = new StringBuilder(); for (int num = 0; num < enemies.Count; num++) { ResolvedEnemyCandidate resolvedEnemyCandidate = enemies[num]; int instanceID = ((Object)resolvedEnemyCandidate.Candidate.Root).GetInstanceID(); if (!rosterMetadataBySourceId.TryGetValue(instanceID, out var value)) { value = ",\"tier\":" + GetEnemyTierJson(resolvedEnemyCandidate.Candidate) + GetMonsterMetadata(resolvedEnemyCandidate.Candidate); rosterMetadataBySourceId[instanceID] = value; } stringBuilder.Append(instanceID).Append(':').Append(resolvedEnemyCandidate.MonsterName); stringBuilder.Append(value); stringBuilder.Append(';'); } string text = stringBuilder.ToString(); bool flag = !rosterPublished; if (!rosterPublished) { if (text != pendingRosterFingerprint) { pendingRosterFingerprint = text; rosterStableScans = 0; return false; } rosterStableScans++; if (rosterStableScans < 2) { return false; } rosterPublished = true; } if (!flag && text == lastRosterFingerprint) { return true; } lastRosterFingerprint = text; StringBuilder stringBuilder2 = new StringBuilder("{\"monsters\":["); for (int num2 = 0; num2 < enemies.Count; num2++) { ResolvedEnemyCandidate resolvedEnemyCandidate2 = enemies[num2]; if (num2 > 0) { stringBuilder2.Append(','); } int instanceID2 = ((Object)resolvedEnemyCandidate2.Candidate.Root).GetInstanceID(); stringBuilder2.Append("{\"id\":").Append(instanceID2).Append(",\"name\":\"") .Append(EscapeJson(resolvedEnemyCandidate2.MonsterName)) .Append('"'); stringBuilder2.Append(rosterMetadataBySourceId[instanceID2]); stringBuilder2.Append('}'); } stringBuilder2.Append("]}"); ((MonoBehaviour)this).StartCoroutine(PostMonsterRoster(stringBuilder2.ToString())); return true; } private void SyncMonsterStatuses(List<ResolvedEnemyCandidate> enemies, bool fallbackSlice = false) { StringBuilder stringBuilder = new StringBuilder("{\"statuses\":["); StringBuilder stringBuilder2 = new StringBuilder(); int num = 0; Dictionary<int, EnemyCandidate> dictionary = new Dictionary<int, EnemyCandidate>(); for (int i = 0; i < enemies.Count; i++) { ResolvedEnemyCandidate resolvedEnemyCandidate = enemies[i]; int instanceID = ((Object)resolvedEnemyCandidate.Candidate.Root).GetInstanceID(); dictionary[instanceID] = resolvedEnemyCandidate.Candidate; Component enemyParent = GetEnemyParent(resolvedEnemyCandidate.Candidate); if ((Object)(object)enemyParent != (Object)null) { sourceIdsByEnemyParent[((Object)enemyParent).GetInstanceID()] = instanceID; } } foreach (Component item2 in GetKnownEnemyParentsSnapshot()) { if (!((Object)(object)item2 == (Object)null)) { AddStatusCandidate(dictionary, item2); } } float realtimeSinceStartup = Time.realtimeSinceStartup; if (dictionary.Count == 0 || realtimeSinceStartup >= nextStatusDirectorRecoveryAt) { nextStatusDirectorRecoveryAt = realtimeSinceStartup + 30f; foreach (Component item3 in FindSpawnedEnemiesFromDirector()) { if (!((Object)(object)item3 == (Object)null)) { RegisterEnemyParent(item3); AddStatusCandidate(dictionary, item3); } } } List<int> list = new List<int>(dictionary.Keys); list.Sort(); if (fallbackSlice && list.Count > 1) { int num2 = statusFallbackCursor % list.Count; statusFallbackCursor = (num2 + 1) % list.Count; int item = list[num2]; list.Clear(); list.Add(item); } for (int j = 0; j < list.Count; j++) { int num3 = list[j]; EnemyCandidate candidate = dictionary[num3]; if (!TryGetEnemyRespawnStatus(candidate, out var alive, out var remaining)) { continue; } float health = 0f; float maxHealth = 0f; bool flag = IsEnemySent(num3) && TryGetEnemyHealth(candidate, out health, out maxHealth); Component enemyParent2 = GetEnemyParent(candidate); bool flag2 = IsEnemyParentPlayerClose(enemyParent2); bool flag3 = IsEnemyParentPlayerVeryClose(enemyParent2); if (!lastAliveBySourceId.TryGetValue(num3, out var value) || value != alive) { lastAliveBySourceId[num3] = alive; if (debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Monster respawn state " + num3 + ": " + (alive ? "alive" : "cooldown") + ", remaining=" + remaining.ToString("0.0", CultureInfo.InvariantCulture) + "s.")); } } string value2 = (alive ? 0f : ((float)Math.Ceiling(Math.Max(0f, remaining) * 10f) / 10f)).ToString("0.0", CultureInfo.InvariantCulture); string text = (flag ? Math.Max(0f, health).ToString("0.#", CultureInfo.InvariantCulture) : ""); string text2 = ((flag && maxHealth > 0f) ? maxHealth.ToString("0.#", CultureInfo.InvariantCulture) : ""); if (debugLogging.Value && flag) { string text3 = text + "/" + ((text2.Length > 0) ? text2 : "?"); if (!lastHealthDebugBySourceId.TryGetValue(num3, out var value3) || value3 != text3) { lastHealthDebugBySourceId[num3] = text3; ((BaseUnityPlugin)this).Logger.LogInfo((object)("Monster health state " + num3 + ": hp=" + text3 + ".")); } } stringBuilder2.Append(num3).Append(':').Append(alive ? '1' : '0') .Append(':') .Append(value2) .Append(':') .Append(text) .Append('/') .Append(text2) .Append(':') .Append(flag2 ? '1' : '0') .Append(':') .Append(flag3 ? '1' : '0') .Append(';'); string value4 = ""; if (!fallbackSlice && !rosterMetadataBySourceId.TryGetValue(num3, out value4)) { value4 = ",\"tier\":" + GetEnemyTierJson(candidate) + GetMonsterMetadata(candidate); rosterMetadataBySourceId[num3] = value4; } stringBuilder2.Append(value4); if (num++ > 0) { stringBuilder.Append(','); } stringBuilder.Append("{\"id\":").Append(num3).Append(",\"alive\":") .Append(alive ? "true" : "false") .Append(",\"respawnRemaining\":") .Append(value2) .Append(",\"playerClose\":") .Append(flag2 ? "true" : "false") .Append(",\"playerVeryClose\":") .Append(flag3 ? "true" : "false"); if (flag) { stringBuilder.Append(",\"health\":").Append((text.Length > 0) ? text : "0"); if (text2.Length > 0) { stringBuilder.Append(",\"maxHealth\":").Append(text2); } } stringBuilder.Append(value4).Append('}'); } if (num == 0) { return; } string text4 = stringBuilder2.ToString(); if (fallbackSlice) { int key = list[0]; if (lastFallbackStatusBySourceId.TryGetValue(key, out var value5) && value5 == text4) { return; } lastFallbackStatusBySourceId[key] = text4; } else { if (text4 == lastStatusFingerprint) { return; } lastStatusFingerprint = text4; } stringBuilder.Append("]}"); ((MonoBehaviour)this).StartCoroutine(PostMonsterStatuses(stringBuilder.ToString(), coalesce: true)); } private static bool TryGetEnemyRespawnStatus(EnemyCandidate candidate, out bool alive, out float remaining) { alive = true; remaining = 0f; Component enemyParent = GetEnemyParent(candidate); if ((Object)(object)enemyParent == (Object)null) { return false; } if (PhotonNetwork.InRoom && !PhotonNetwork.IsMasterClient) { alive = ReadEnemySpawned(enemyParent); remaining = (((Object)(object)instance != (Object)null) ? instance.ReadNetworkRespawnRemaining(enemyParent, alive) : 0f); return true; } if (!TryConvertFloat(ReadMember(enemyParent, "DespawnedTimer"), out remaining)) { return false; } remaining = Math.Max(0f, remaining); alive = ReadEnemySpawned(enemyParent); return true; } private void AddStatusCandidate(Dictionary<int, EnemyCandidate> statusCandidates, Component enemyParent) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) GameObject enemyRoot = GetEnemyRoot(enemyParent); int instanceID = ((Object)enemyParent).GetInstanceID(); int value; if ((Object)(object)enemyRoot != (Object)null) { value = ((Object)enemyRoot).GetInstanceID(); sourceIdsByEnemyParent[instanceID] = value; } else if (!sourceIdsByEnemyParent.TryGetValue(instanceID, out value)) { return; } statusCandidates[value] = new EnemyCandidate { Component = enemyParent, Root = enemyRoot, Center = Vector3.zero }; } private void SyncEnemyHealthChanged(object enemyHealthSource) { if (gameplayActive && enemyHealthSource != null) { object obj = ReadMember(enemyHealthSource, "enemy"); object obj2 = ReadMember((obj is Component) ? obj : null, "EnemyParent"); Component val = (Component)((obj2 is Component) ? obj2 : null); SendEnemyHealth(val); SyncEnemyParentStatusChanged(val); } } private void ScheduleEnemyParentStatusChanged(Component enemyParent) { if (gameplayActive && !((Object)(object)enemyParent == (Object)null)) { ((MonoBehaviour)this).StartCoroutine(SyncEnemyParentStatusChangedNextFrame(enemyParent)); } } private IEnumerator SyncEnemyParentStatusChangedNextFrame(Component enemyParent) { yield return null; SyncEnemyParentStatusChanged(enemyParent); } private void SyncEnemyParentTimerChanged(Component enemyParent) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) if (!gameplayActive || (Object)(object)enemyParent == (Object)null) { return; } GameObject enemyRoot = GetEnemyRoot(enemyParent); if ((Object)(object)enemyRoot == (Object)null) { return; } int instanceID = ((Object)enemyRoot).GetInstanceID(); if (!IsEnemySent(instanceID) || !TryGetEnemyRespawnStatus(new EnemyCandidate { Component = enemyParent, Root = enemyRoot, Center = Vector3.zero }, out var alive, out var remaining)) { return; } bool value; bool flag = !lastAliveBySourceId.TryGetValue(instanceID, out value) || value != alive; if (!flag && alive) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (flag || !lastTimerStatusSentAtBySourceId.TryGetValue(instanceID, out var value2) || !(realtimeSinceStartup - value2 < 0.5f)) { string text = (alive ? 0f : ((float)Math.Ceiling(Math.Max(0f, remaining) * 10f) / 10f)).ToString("0.0", CultureInfo.InvariantCulture); string text2 = instanceID + ":" + (alive ? "1" : "0") + ":" + text; if (flag || !lastTimerStatusBySourceId.TryGetValue(instanceID, out var value3) || !(value3 == text2)) { lastAliveBySourceId[instanceID] = alive; lastTimerStatusBySourceId[instanceID] = text2; lastTimerStatusSentAtBySourceId[instanceID] = realtimeSinceStartup; StringBuilder stringBuilder = new StringBuilder("{\"statuses\":[{\"id\":").Append(instanceID).Append(",\"alive\":").Append(alive ? "true" : "false") .Append(",\"respawnRemaining\":") .Append(text) .Append("}]}"); ((MonoBehaviour)this).StartCoroutine(PostMonsterStatuses(stringBuilder.ToString())); } } } private void SyncEnemyParentStatusChanged(Component enemyParent) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) if (!gameplayActive || (Object)(object)enemyParent == (Object)null) { return; } GameObject enemyRoot = GetEnemyRoot(enemyParent); if ((Object)(object)enemyRoot == (Object)null) { return; } int instanceID = ((Object)enemyRoot).GetInstanceID(); EnemyCandidate candidate = new EnemyCandidate { Component = enemyParent, Root = enemyRoot, Center = Vector3.zero }; if (!TryGetEnemyRespawnStatus(candidate, out var alive, out var remaining)) { return; } bool num = IsEnemySent(instanceID); float health = 0f; float maxHealth = 0f; bool flag = num && TryGetEnemyHealth(candidate, out health, out maxHealth); bool flag2 = IsEnemyParentPlayerClose(enemyParent); bool flag3 = IsEnemyParentPlayerVeryClose(enemyParent); string text = (alive ? 0f : ((float)Math.Ceiling(Math.Max(0f, remaining) * 10f) / 10f)).ToString("0.0", CultureInfo.InvariantCulture); string text2 = (flag ? Math.Max(0f, health).ToString("0.#", CultureInfo.InvariantCulture) : ""); string text3 = ((flag && maxHealth > 0f) ? maxHealth.ToString("0.#", CultureInfo.InvariantCulture) : ""); string text4 = ",\"tier\":" + GetEnemyTierJson(candidate); string monsterMetadata = GetMonsterMetadata(candidate); string text5 = instanceID + ":" + (alive ? "1" : "0") + ":" + text + ":" + text2 + "/" + text3 + ":" + (flag2 ? "1" : "0") + ":" + (flag3 ? "1" : "0") + text4 + monsterMetadata; if (lastImmediateStatusBySourceId.TryGetValue(instanceID, out var value) && value == text5) { return; } lastImmediateStatusBySourceId[instanceID] = text5; StringBuilder stringBuilder = new StringBuilder("{\"statuses\":[{\"id\":").Append(instanceID).Append(",\"alive\":").Append(alive ? "true" : "false") .Append(",\"respawnRemaining\":") .Append(text) .Append(",\"playerClose\":") .Append(flag2 ? "true" : "false") .Append(",\"playerVeryClose\":") .Append(flag3 ? "true" : "false"); if (flag) { stringBuilder.Append(",\"health\":").Append((text2.Length > 0) ? text2 : "0"); if (text3.Length > 0) { stringBuilder.Append(",\"maxHealth\":").Append(text3); } } stringBuilder.Append(text4).Append(monsterMetadata).Append("}]}"); ((MonoBehaviour)this).StartCoroutine(PostMonsterStatuses(stringBuilder.ToString())); } private static bool ReadEnemySpawned(Component enemyParent) { object obj = ReadMember(enemyParent, "Spawned"); if (obj is bool) { return (bool)obj; } if ((Object)(object)enemyParent.gameObject != (Object)null) { return enemyParent.gameObject.activeInHierarchy; } return false; } private bool TryGetEnemyHealth(EnemyCandidate candidate, out float health, out float maxHealth) { health = 0f; maxHealth = 0f; if (PhotonNetwork.InRoom && !PhotonNetwork.IsMasterClient) { return TryReadNetworkHealth(GetEnemyParent(candidate), out health, out maxHealth); } bool flag = false; bool flag2 = IsUnknownMonster(candidate); foreach (object enemyHealthSource in GetEnemyHealthSources(candidate)) { if (enemyHealthSource != null) { if (!flag && TryReadFirstFloat(enemyHealthSource, CurrentHealthMemberNames, out var value) && (!flag2 || (!float.IsNaN(value) && !float.IsInfinity(value)))) { health = Math.Max(0f, value); flag = true; } if (maxHealth <= 0f && TryReadFirstFloat(enemyHealthSource, MaxHealthMemberNames, out var value2) && (!flag2 || (!float.IsNaN(value2) && !float.IsInfinity(value2)))) { maxHealth = Math.Max(0f, value2); } if (flag && maxHealth > 0f) { return true; } } } if (!flag2 && !flag && maxHealth > 0f) { health = maxHealth; return true; } return flag; } private bool ReadHostHealth(Component parent, out float health, out float maxHealth) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) return TryGetEnemyHealth(new EnemyCandidate { Component = parent, Root = GetEnemyRoot(parent), Center = Vector3.zero }, out health, out maxHealth); } private void PublishNetworkHealth(Component parent) { GameObject enemyRoot = GetEnemyRoot(parent); if ((Object)(object)enemyRoot != (Object)null && IsEnemySent(((Object)enemyRoot).GetInstanceID())) { SyncEnemyParentStatusChanged(parent); } } private IEnumerable<object> GetEnemyHealthSources(EnemyCandidate candidate) { if ((Object)(object)candidate.Root != (Object)null) { int instanceID = ((Object)candidate.Root).GetInstanceID(); if (!healthSourcesByRootId.TryGetValue(instanceID, out var cachedSources)) { List<object> list = new List<object>(); Component component = candidate.Root.GetComponent("Enemy"); object obj = ReadMember(component, "Health"); if (obj != null) { list.Add(obj); } Component component2 = candidate.Root.GetComponent("EnemyHealth"); if ((Object)(object)component2 != (Object)null && !list.Contains(component2)) { list.Add(component2); } if ((Object)(object)component != (Object)null && !list.Contains(component)) { list.Add(component); } cachedSources = list.ToArray(); healthSourcesByRootId[instanceID] = cachedSources; } foreach (object obj2 in cachedSources) { Object val = (Object)((obj2 is Object) ? obj2 : null); if (val == null || !(val == (Object)null)) { yield return obj2; } } } if ((Object)(object)candidate.Component != (Object)null) { yield return candidate.Component; } Component enemyParent = GetEnemyParent(candidate); if ((Object)(object)enemyParent != (Object)null) { yield return enemyParent; } _ = (Object)(object)candidate.Root == (Object)null; } private static bool TryReadFirstFloat(object source, string[] memberNames, out float value) { for (int i = 0; i < memberNames.Length; i++) { object obj = ReadMember(source, memberNames[i]); if (obj != null && TryConvertFloat(obj, out value)) { return true; } } value = 0f; return false; } private static Component GetEnemyParent(EnemyCandidate candidate) { if ((Object)(object)candidate.Component != (Object)null && ((object)candidate.Component).GetType().Name == "EnemyParent") { return candidate.Component; } object obj = ReadMember((((Object)(object)candidate.Root == (Object)null) ? null : candidate.Root.GetComponent("Enemy")) ?? candidate.Component, "EnemyParent"); return (Component)((obj is Component) ? obj : null); } private static bool TryConvertFloat(object value, out float result) { try { result = Convert.ToSingle(value, CultureInfo.InvariantCulture); return true; } catch { result = 0f; return false; } } private static bool TryMarkEnemySent(int instanceId) { lock (enemyLock) { return sentEnemyInstanceIds.Add(instanceId); } } private static bool IsEnemySent(int instanceId) { lock (enemyLock) { return sentEnemyInstanceIds.Contains(instanceId); } } private void MarkMonsterSeen(string monsterName, EnemyCandidate candidate) { bool flag = false; lock (seenLock) { if (!seenMonsters.Contains(monsterName)) { seenMonsters.Add(monsterName); flag = true; } } if (flag || ShouldLogSeen(monsterName)) { lastSeenLoggedAt[monsterName] = Time.realtimeSinceStartup; ((BaseUnityPlugin)this).Logger.LogInfo((object)("Marked monster for web overlay: " + monsterName + " from " + ((object)candidate.Component).GetType().Name + " / " + ((Object)candidate.Root).name)); } } private bool ShouldLogSeen(string monsterName) { if (!debugLogging.Value) { return false; } if (!lastSeenLoggedAt.TryGetValue(monsterName, out var value)) { return true; } return Time.realtimeSinceStartup - value >= 30f; } private int ResolveCurrentLevel() { object obj = ReadMember(ReadMember(ResolveGameType("RunManager"), "instance"), "levelsCompleted"); if (obj != null) { try { int result = (fallbackLevel = Math.Max(1, Convert.ToInt32(obj) + 1)); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Resolved overlay level from completed levels: " + result + ".")); return result; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not convert RunManager.levelsCompleted: " + ex.Message)); } } fallbackLevel = Math.Max(1, lastSyncedLevel + 1); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Using generated-level counter for overlay level: " + fallbackLevel + ".")); return fallbackLevel; } private static bool IsRegularGameplayLevel() { object source = ReadMember(ResolveGameType("RunManager"), "instance"); object obj = ReadMember(source, "levelCurrent"); object obj2 = ReadMember(source, "levels"); if (obj != null && obj2 is IList list && list.Contains(obj)) { return true; } if (IsNamedRunLevel(obj)) { return true; } return IsRunLevel(); } private static bool IsGameplayLevelCandidate(out string details, bool allowExpensiveFallback = false) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) object source = ReadMember(ResolveGameType("RunManager"), "instance"); object obj = ReadMember(source, "levelCurrent"); object obj2 = ReadMember(source, "levels"); string text = DescribeLevelObject(obj); bool flag = IsNonGameplayContext(); bool flag2 = !flag && obj != null && obj2 is IList list && list.Contains(obj); bool flag3 = IsNamedRunLevel(obj); bool flag4 = !flag && IsRunLevel(); bool flag5 = false; bool flag6 = IsLevelGenerated(); Scene activeScene = SceneManager.GetActiveScene(); string name = ((Scene)(ref activeScene)).name; bool flag7 = IsNonGameplayLevelName(name); string text2 = ""; if (flag || flag7) { flag6 = false; } else if (allowExpensiveFallback) { flag5 = HasActiveLevelGenerator(); text2 = FindActiveNamedRunLevelObject(); } details = "current=" + text + ", listed=" + flag2 + ", named=" + flag3 + ", semiRunLevel=" + flag4 + ", levelGenerator=" + flag5 + ", generated=" + flag6 + ", scene=" + (string.IsNullOrWhiteSpace(name) ? "<none>" : name) + ", levelObject=" + (string.IsNullOrWhiteSpace(text2) ? "<none>" : text2); if (flag || flag7) { return false; } if (flag6) { if (!(flag2 || flag3 || flag4 || flag5) && !IsRunLevelName(name)) { return !string.IsNullOrWhiteSpace(text2); } return true; } return false; } private static bool HasActiveLevelGenerator() { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) Type type = ResolveGameType("LevelGenerator"); if (type == null) { return false; } Object[] array = Resources.FindObjectsOfTypeAll(type); foreach (Object obj in array) { Component val = (Component)(object)((obj is Component) ? obj : null); if (val != null && (Object)(object)val.gameObject != (Object)null) { Scene scene = val.gameObject.scene; if (((Scene)(ref scene)).IsValid()) { return true; } } } return false; } private static bool IsLevelGenerated() { Type source = ResolveGameType("LevelGenerator"); object obj = ReadMember(ReadMember(source, "Instance") ?? ReadMember(source, "instance"), "Generated"); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } private static string FindActiveNamedRunLevelObject() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) Object[] array = Resources.FindObjectsOfTypeAll(typeof(GameObject)); foreach (Object obj in array) { GameObject val = (GameObject)(object)((obj is GameObject) ? obj : null); if (val != null) { Scene scene = val.scene; if (((Scene)(ref scene)).IsValid() && IsRunLevelName(((Object)val).name)) { return ((Object)val).name; } } } return ""; } private static bool IsNamedRunLevel(object currentLevel) { if (currentLevel == null) { return false; } return IsRunLevelName(DescribeLevelObject(currentLevel)); } private static bool IsRunLevelName(string levelName) { if (!string.IsNullOrWhiteSpace(levelName) && levelName.StartsWith("Level - ", StringComparison.Ordinal)) { return !IsNonGameplayLevelName(levelName); } return false; } private static bool IsNonGameplayLevelName(string levelName) { if (!string.IsNullOrWhiteSpace(levelName)) { if (levelName.IndexOf("Lobby", StringComparison.OrdinalIgnoreCase) < 0 && levelName.IndexOf("Menu", StringComparison.OrdinalIgnoreCase) < 0 && levelName.IndexOf("Shop", StringComparison.OrdinalIgnoreCase) < 0 && levelName.IndexOf("Tutorial", StringComparison.OrdinalIgnoreCase) < 0 && levelName.IndexOf("Arena", StringComparison.OrdinalIgnoreCase) < 0) { return levelName.IndexOf("Splash", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } return false; } private static bool IsNonGameplayContext() { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) object obj = ReadMember(ReadMember(ResolveGameType("RunManager"), "instance"), "levelCurrent"); if (obj != null) { object obj2 = InvokeNoArgMethod(ResolveGameType("SemiFunc"), "RunIsLevel"); if (obj2 is bool && !(bool)obj2) { return true; } } string levelName = DescribeLevelObject(obj); Scene activeScene = SceneManager.GetActiveScene(); string name = ((Scene)(ref activeScene)).name; if (!IsNonGameplayLevelName(levelName)) { return IsNonGameplayLevelName(name); } return true; } private static string DescribeCurrentLevel(object runManager) { return DescribeLevelObject(ReadMember(runManager, "levelCurrent")); } private static string DescribeLevelObject(object currentLevel) { if (currentLevel == null) { return "<null>"; } Object val = (Object)((currentLevel is Object) ? currentLevel : null); if (val != null) { if (!string.IsNullOrWhiteSpace(val.name)) { return val.name; } return ((object)val).ToString(); } return currentLevel.ToString(); } private static bool IsRunLevel() { object obj = InvokeNoArgMethod(ResolveGameType("SemiFunc"), "RunIsLevel"); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } private static bool IsMasterClientOrSingleplayer() { object obj = InvokeNoArgMethod(ResolveGameType("SemiFunc"), "IsMasterClientOrSingleplayer"); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = ((1 == 0) ? 1 : 0); } else { num = 1; } return (byte)((uint)num | (flag ? 1u : 0u)) != 0; } private static float CalculateMapValue() { Type type = ResolveGameType("ValuableObject"); if (type == null) { return 0f; } long timestamp = Stopwatch.GetTimestamp(); float num = 0f; Object[] array = Object.FindObjectsOfType(type); long timestamp2 = Stopwatch.GetTimestamp(); for (int i = 0; i < array.Length; i++) { num += ReadFloatMember(array[i], "dollarValueCurrent"); } double num2 = (double)(Stopwatch.GetTimestamp() - timestamp) * 1000.0 / (double)Stopwatch.Frequency; if (num2 >= 8.0 && (Object)(object)instance != (Object)null) { double num3 = (double)(timestamp2 - timestamp) * 1000.0 / (double)Stopwatch.Frequency; ((BaseUnityPlugin)instance).Logger.LogWarning((object)("[Performance] Map value scan: " + num2.ToString("0.0", CultureInfo.InvariantCulture) + " ms, search=" + num3.ToString("0.0", CultureInfo.InvariantCulture) + " ms, valuables=" + array.Length + ".")); } return Math.Max(0f, num); } private static int? ResolveExtractionGoal() { object obj = ReadMember(ReadMember(ResolveGameType("RoundDirector"), "instance"), "extractionHaulGoal"); if (obj == null) { return null; } try { return Math.Max(0, Convert.ToInt32(obj)); } catch { return null; } } private static Component GetValuableComponent(object source) { Component val = (Component)((source is Component) ? source : null); if (val != null) { Component component = val.GetComponent("ValuableObject"); if ((Object)(object)component != (Object)null) { return component; } } GameObject val2 = (GameObject)((source is GameObject) ? source : null); if (val2 != null) { return val2.GetComponent("ValuableObject"); } return null; } private static float ReadValuableCurrentValue(object source) { return ReadFloatMember(GetValuableSource(source), "dollarValueCurrent"); } private static float ReadValuableOriginalValue(object source) { return ReadFloatMember(GetValuableSource(source), "dollarValueOriginal"); } private static void TrackDollarHaulValuable(object source, bool inHaul) { if (!IsRunLevel()) { return; } int valuableKey = GetValuableKey(source); if (valuableKey != 0) { if (inHaul) { valuablesInDollarHaul.Add(valuableKey); } else { valuablesInDollarHaul.Remove(valuableKey); } } } private static bool IsValuableInDollarHaul(object source) { int valuableKey = GetValuableKey(source); if (valuableKey != 0 && valuablesInDollarHaul.Contains(valuableKey)) { return true; } if (!(ReadMember(ReadMember(ResolveGameType("RoundDirector"), "instance"), "dollarHaulList") is IEnumerable enumerable)) { return false; } foreach (object item in enumerable) { if (item != null) { int valuableKey2 = GetValuableKey(item); if (valuableKey != 0 && valuableKey2 == valuableKey) { return true; } if (ReferencesSameUnityObject(source, item)) { return true; } } } return false; } private static int GetValuableKey(object source) { object valuableSource = GetValuableSource(source); if (valuableSource == null) { return 0; } int num = ReadIntMember(ReadMember(valuableSource, "photonView"), "ViewID"); if (num > 0) { return num; } Object val = (Object)((valuableSource is Object) ? valuableSource : null); if (val != null) { return val.GetInstanceID(); } return 0; } private static bool ReferencesSameUnityObject(object left, object right) { int unityObjectId = GetUnityObjectId(left); if (unityObjectId == 0) { return false; } if (unityObjectId == GetUnityObjectId(right)) { return true; } Component valuableComponent = GetValuableComponent(left); Component valuableComponent2 = GetValuableComponent(right); if ((Object)(object)valuableComponent != (Object)null && (Object)(object)valuableComponent2 != (Object)null && ((Object)valuableComponent).GetInstanceID() == ((Object)valuableComponent2).GetInstanceID()) { return true; } GameObject gameObject = GetGameObject(left); GameObject gameObject2 = GetGameObject(right); if ((Object)(object)gameObject != (Object)null && (Object)(object)gameObject2 != (Object)null) { return ((Object)gameObject).GetInstanceID() == ((Object)gameObject2).GetInstanceID(); } return false; } private static int GetUnityObjectId(object source) { Object val = (Object)((source is Object) ? source : null); if (val != null) { return val.GetInstanceID(); } return 0; } private static GameObject GetGameObject(object source) { GameObject val = (GameObject)((source is GameObject) ? source : null); if (val != null) { return val; } Component val2 = (Component)((source is Component) ? source : null); if (val2 != null) { return val2.gameObject; } return null; } private static object GetValuableSource(object source) { Component valuableComponent = GetValuableComponent(source); if (!((Object)(object)valuableComponent != (Object)null)) { return source; } return valuableComponent; } private static float ReadFloatMember(object source, string memberName) { object obj = ReadMember(source, memberName); if (obj == null) { return 0f; } try { return Convert.ToSingle(obj, CultureInfo.InvariantCulture); } catch { return 0f; } } private static int ReadIntMember(object source, string memberName) { object obj = ReadMember(source, memberName); if (obj == null) { return 0; } try { return Convert.ToInt32(obj, CultureInfo.InvariantCulture); } catch { return 0; } } private string ResolveCurrentLevelName() { string text = DescribeLevelObject(ReadMember(ReadMember(ResolveGameType("RunManager"), "instance"), "levelCurrent")); if (!(text == "<null>")) { return text; } return ""; } private void SyncLevelToOverlay(int level, string le