Decompiled source of OverlayHUD v26.11.4

OverlayHUD.dll

Decompiled 4 days ago
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