Decompiled source of Monster Respawn Timer v1.0.0

BepInEx/plugins/MonsterRespawnTimer.dll

Decompiled 9 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("MonsterRespawnTimer")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("Monster Respawn Timer")]
[assembly: AssemblyTitle("MonsterRespawnTimer")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace MonsterRespawnTimer
{
	internal readonly struct EnemySnapshot
	{
		public string StableId { get; }

		public string DisplayName { get; }

		public string GroupName { get; }

		public bool IsSpawned { get; }

		public float RespawnTimer { get; }

		public EnemySnapshot(string stableId, string displayName, bool isSpawned, float respawnTimer)
			: this(stableId, displayName, displayName, isSpawned, respawnTimer)
		{
		}

		public EnemySnapshot(string stableId, string displayName, string groupName, bool isSpawned, float respawnTimer)
		{
			StableId = stableId;
			DisplayName = displayName;
			GroupName = groupName;
			IsSpawned = isSpawned;
			RespawnTimer = respawnTimer;
		}
	}
	internal sealed class EnemySlotTracker
	{
		public const int MaxSlots = 11;

		public const float FlashDurationSeconds = 1f;

		public const float SuddenDecreaseThresholdSeconds = 2f;

		private readonly List<EnemySlotState> slots = new List<EnemySlotState>(11);

		public IReadOnlyList<EnemySlotState> Slots => slots;

		public void Reset()
		{
			slots.Clear();
		}

		public void Update(IReadOnlyList<EnemySnapshot> snapshots, float elapsedSeconds)
		{
			float elapsedSeconds2 = Math.Max(0f, elapsedSeconds);
			AddNewSlots(snapshots);
			foreach (EnemySlotState slot in slots)
			{
				slot.Apply(snapshots, elapsedSeconds2);
			}
		}

		private void AddNewSlots(IReadOnlyList<EnemySnapshot> snapshots)
		{
			foreach (EnemySnapshot snapshot in snapshots)
			{
				if (!string.IsNullOrWhiteSpace(snapshot.StableId))
				{
					string displayKey = EnemySlotState.CreateDisplayKey(snapshot);
					EnemySlotState enemySlotState = slots.FirstOrDefault((EnemySlotState slot) => slot.DisplayKey == displayKey);
					if (enemySlotState != null)
					{
						enemySlotState.AddMemberIfMissing(snapshot);
					}
					else if (slots.Count < 11)
					{
						slots.Add(new EnemySlotState(displayKey, snapshot));
					}
				}
			}
		}
	}
	internal sealed class EnemySlotState
	{
		private readonly List<EnemyMemberState> members = new List<EnemyMemberState>();

		public string DisplayKey { get; }

		public string StableId { get; }

		public string BaseDisplayName { get; private set; }

		public bool IsAssigned => members.Count > 0;

		public bool IsVisible { get; private set; } = true;

		public bool IsSpawned => members.Where((EnemyMemberState member) => member.IsVisible).All((EnemyMemberState member) => member.IsSpawned);

		public float RespawnTimer => (from member in members
			where member.IsVisible && !member.IsSpawned
			select member.RespawnTimer).DefaultIfEmpty(0f).Min();

		public string DisplayName
		{
			get
			{
				if (!IsSwarmKey(DisplayKey) || members.Count <= 1)
				{
					return BaseDisplayName;
				}
				EnemyMemberState[] array = members.Where((EnemyMemberState member) => member.IsVisible).ToArray();
				int num = ((array.Length != 0) ? array.Length : members.Count);
				int num2 = array.Count((EnemyMemberState member) => member.IsSpawned);
				return BaseDisplayName + " " + num2.ToString(CultureInfo.InvariantCulture) + "/" + num.ToString(CultureInfo.InvariantCulture);
			}
		}

		public IReadOnlyList<string> TimerTexts => (from member in members
			where member.IsVisible && !member.IsSpawned
			select member.TimerText into text
			where text.Length > 0
			select text).ToArray();

		public string TimerText => string.Join(" ", TimerTexts);

		public string FlashText => (from member in members
			where member.IsVisible
			select member.FlashText).LastOrDefault((string text) => text.Length > 0) ?? string.Empty;

		public string StatusText => BuildStatusText(TimerTexts, FlashText);

		public EnemySlotState(string displayKey, EnemySnapshot snapshot)
		{
			DisplayKey = displayKey;
			StableId = (displayKey.StartsWith("group:", StringComparison.Ordinal) ? displayKey : snapshot.StableId);
			BaseDisplayName = NormalizeDisplayName(snapshot.DisplayName);
			AddMemberIfMissing(snapshot);
		}

		public static string CreateDisplayKey(EnemySnapshot snapshot)
		{
			string text = NormalizeDisplayName(snapshot.GroupName);
			if (IsSwarmName(text))
			{
				return "group:" + text.ToLowerInvariant();
			}
			return "enemy:" + snapshot.StableId;
		}

		public void AddMemberIfMissing(EnemySnapshot snapshot)
		{
			if (!members.Any((EnemyMemberState member) => member.StableId == snapshot.StableId))
			{
				members.Add(new EnemyMemberState(snapshot));
			}
		}

		public void Apply(IReadOnlyList<EnemySnapshot> snapshots, float elapsedSeconds)
		{
			foreach (EnemyMemberState member in members)
			{
				EnemySnapshot snapshot = snapshots.FirstOrDefault((EnemySnapshot candidate) => candidate.StableId == member.StableId);
				if (string.IsNullOrEmpty(snapshot.StableId))
				{
					member.MarkUnavailable(elapsedSeconds);
				}
				else
				{
					member.Apply(snapshot, elapsedSeconds);
				}
			}
			EnemyMemberState[] array = members.Where((EnemyMemberState enemyMemberState) => enemyMemberState.IsVisible).ToArray();
			IsVisible = array.Length != 0;
			string displayName = ((array.Length != 0) ? array[0].DisplayName : members[0].DisplayName);
			BaseDisplayName = NormalizeDisplayName(displayName);
		}

		private static string BuildStatusText(IReadOnlyList<string> timerTexts, string flashText)
		{
			if (timerTexts.Count == 0)
			{
				return string.Empty;
			}
			if (string.IsNullOrEmpty(flashText))
			{
				return string.Join(" ", timerTexts);
			}
			if (timerTexts.Count == 1)
			{
				return flashText + " " + timerTexts[0];
			}
			List<string> list = new List<string>(timerTexts.Count + 1);
			for (int i = 0; i < timerTexts.Count - 1; i++)
			{
				list.Add(timerTexts[i]);
			}
			list.Add(flashText);
			list.Add(timerTexts[timerTexts.Count - 1]);
			return string.Join(" ", list);
		}

		private static bool IsSwarmKey(string displayKey)
		{
			return displayKey.StartsWith("group:", StringComparison.Ordinal);
		}

		private static bool IsSwarmName(string displayName)
		{
			if (!string.Equals(displayName, "Gnome", StringComparison.OrdinalIgnoreCase))
			{
				return string.Equals(displayName, "Banger", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		private static string NormalizeDisplayName(string displayName)
		{
			if (!string.IsNullOrWhiteSpace(displayName))
			{
				return displayName.Trim();
			}
			return "Enemy";
		}
	}
	internal sealed class EnemyMemberState
	{
		private float lastRespawnTimer;

		private float flashRemainingSeconds;

		public string StableId { get; }

		public string DisplayName { get; private set; }

		public bool IsVisible { get; private set; }

		public bool IsSpawned { get; private set; }

		public float RespawnTimer { get; private set; }

		public string TimerText
		{
			get
			{
				if (!IsSpawned)
				{
					return FormatSeconds(RespawnTimer);
				}
				return string.Empty;
			}
		}

		public string FlashText { get; private set; }

		public EnemyMemberState(EnemySnapshot snapshot)
		{
			StableId = snapshot.StableId;
			DisplayName = NormalizeDisplayName(snapshot.DisplayName);
			IsVisible = true;
			IsSpawned = snapshot.IsSpawned;
			RespawnTimer = Math.Max(0f, snapshot.RespawnTimer);
			lastRespawnTimer = RespawnTimer;
			FlashText = string.Empty;
		}

		public void Apply(EnemySnapshot snapshot, float elapsedSeconds)
		{
			TickFlash(elapsedSeconds);
			bool flag = IsVisible && !IsSpawned;
			float num = lastRespawnTimer;
			IsVisible = true;
			DisplayName = NormalizeDisplayName(snapshot.DisplayName);
			IsSpawned = snapshot.IsSpawned;
			RespawnTimer = Math.Max(0f, snapshot.RespawnTimer);
			if (!IsSpawned && flag)
			{
				float num2 = num - RespawnTimer;
				if (num2 - Math.Max(0f, elapsedSeconds) >= 2f)
				{
					FlashText = "-" + Math.Max(1, (int)Math.Round(num2, MidpointRounding.AwayFromZero)).ToString(CultureInfo.InvariantCulture) + "s";
					flashRemainingSeconds = 1f;
				}
			}
			lastRespawnTimer = RespawnTimer;
		}

		public void MarkUnavailable(float elapsedSeconds)
		{
			TickFlash(elapsedSeconds);
			IsVisible = false;
		}

		private void TickFlash(float elapsedSeconds)
		{
			if (flashRemainingSeconds <= 0f)
			{
				FlashText = string.Empty;
				flashRemainingSeconds = 0f;
				return;
			}
			flashRemainingSeconds -= Math.Max(0f, elapsedSeconds);
			if (flashRemainingSeconds <= 0f)
			{
				FlashText = string.Empty;
				flashRemainingSeconds = 0f;
			}
		}

		private static string FormatSeconds(float seconds)
		{
			return Math.Max(0, (int)Math.Ceiling(seconds)).ToString(CultureInfo.InvariantCulture) + "s";
		}

		private static string NormalizeDisplayName(string displayName)
		{
			if (!string.IsNullOrWhiteSpace(displayName))
			{
				return displayName.Trim();
			}
			return "Enemy";
		}
	}
	internal static class EnemySnapshotReader
	{
		private static readonly FieldInfo? SpawnedField = AccessTools.Field(typeof(EnemyParent), "Spawned");

		private static bool spawnedReadErrorLogged;

		private static bool localizedNameErrorLogged;

		public static List<EnemySnapshot> ReadSnapshots()
		{
			List<EnemySnapshot> list = new List<EnemySnapshot>();
			List<EnemyParent> list2 = EnemyDirector.instance?.enemiesSpawned;
			if (list2 == null)
			{
				return list;
			}
			foreach (EnemyParent item in list2)
			{
				if (!((Object)(object)item == (Object)null))
				{
					string text = ReadDisplayName(item);
					list.Add(new EnemySnapshot(((Object)item).GetInstanceID().ToString(CultureInfo.InvariantCulture), text, ReadGroupName(item, text), ReadSpawned(item), item.DespawnedTimer));
				}
			}
			return list;
		}

		private static bool ReadSpawned(EnemyParent enemyParent)
		{
			try
			{
				object obj = SpawnedField?.GetValue(enemyParent);
				if (obj is bool)
				{
					return (bool)obj;
				}
			}
			catch (Exception arg)
			{
				if (!spawnedReadErrorLogged)
				{
					spawnedReadErrorLogged = true;
					Plugin.Logger.LogError((object)$"MonsterRespawnTimer could not read EnemyParent.Spawned and will use GameObject active state instead: {arg}");
				}
			}
			return ((Component)enemyParent).gameObject.activeInHierarchy;
		}

		private static string ReadDisplayName(EnemyParent enemyParent)
		{
			try
			{
				LocalizedAsset enemyNameLocalized = enemyParent.enemyNameLocalized;
				string text = ((enemyNameLocalized != null) ? enemyNameLocalized.GetLocalizedString() : null);
				if (!string.IsNullOrWhiteSpace(text))
				{
					return text;
				}
			}
			catch (Exception arg)
			{
				if (!localizedNameErrorLogged)
				{
					localizedNameErrorLogged = true;
					Plugin.Logger.LogError((object)$"MonsterRespawnTimer could not localize an enemy name and will use the raw enemy name instead: {arg}");
				}
			}
			if (!string.IsNullOrWhiteSpace(enemyParent.enemyName))
			{
				return enemyParent.enemyName;
			}
			return "Enemy";
		}

		private static string ReadGroupName(EnemyParent enemyParent, string fallbackDisplayName)
		{
			if (!string.IsNullOrWhiteSpace(enemyParent.enemyName))
			{
				return enemyParent.enemyName;
			}
			return fallbackDisplayName;
		}
	}
	internal static class MonsterRespawnTimerController
	{
		private const float RefreshIntervalSeconds = 0.25f;

		private static readonly EnemySlotTracker Tracker = new EnemySlotTracker();

		private static MonsterRespawnTimerHudView? hudView;

		private static float nextRefreshTime;

		private static float lastRefreshTime;

		private static bool readinessErrorLogged;

		public static void Tick(string source)
		{
			if (!(Time.unscaledTime < nextRefreshTime))
			{
				float unscaledTime = Time.unscaledTime;
				float elapsedSeconds = ((lastRefreshTime > 0f) ? (unscaledTime - lastRefreshTime) : 0f);
				lastRefreshTime = unscaledTime;
				nextRefreshTime = unscaledTime + 0.25f;
				TickNow(elapsedSeconds);
			}
		}

		public static void OnRunManagerChangeLevel()
		{
			nextRefreshTime = 0f;
			Tick("RunManager.ChangeLevel");
		}

		public static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			ResetRefresh();
			Tick("SceneManager.sceneLoaded");
		}

		public static void OnActiveSceneChanged(Scene oldScene, Scene newScene)
		{
			DestroyHud();
			Tick("SceneManager.activeSceneChanged");
		}

		public static void OnWillRenderCanvases()
		{
			Tick("Canvas.willRenderCanvases");
		}

		public static void DestroyActiveHud()
		{
			DestroyHud();
		}

		private static void TickNow(float elapsedSeconds)
		{
			if (!IsHudReady())
			{
				MonsterRespawnTimerHudView monsterRespawnTimerHudView = hudView;
				if (monsterRespawnTimerHudView != null && monsterRespawnTimerHudView.IsAlive)
				{
					DestroyHud();
				}
			}
			else if (!EnsureHud())
			{
				Tracker.Reset();
			}
			else
			{
				List<EnemySnapshot> snapshots = EnemySnapshotReader.ReadSnapshots();
				Tracker.Update(snapshots, elapsedSeconds);
				hudView?.Render(Tracker.Slots);
			}
		}

		private static bool EnsureHud()
		{
			MonsterRespawnTimerHudView monsterRespawnTimerHudView = hudView;
			if (monsterRespawnTimerHudView != null && monsterRespawnTimerHudView.IsAlive)
			{
				return true;
			}
			hudView?.Destroy();
			hudView = null;
			if (!MonsterRespawnTimerHudView.TryCreate(out MonsterRespawnTimerHudView view))
			{
				return false;
			}
			hudView = view;
			Tracker.Reset();
			return true;
		}

		private static void DestroyHud()
		{
			hudView?.Destroy();
			hudView = null;
			Tracker.Reset();
			lastRefreshTime = 0f;
		}

		private static void ResetRefresh()
		{
			nextRefreshTime = 0f;
			lastRefreshTime = 0f;
			readinessErrorLogged = false;
		}

		private static bool IsHudReady()
		{
			try
			{
				if ((Object)(object)RunManager.instance == (Object)null)
				{
					return false;
				}
				if (!SemiFunc.RunIsLevel())
				{
					return false;
				}
				if (!MonsterRespawnTimerHudStatus.ShouldRenderForSpectateState(SemiFunc.IsSpectating()))
				{
					return false;
				}
			}
			catch (Exception arg)
			{
				if (!readinessErrorLogged)
				{
					readinessErrorLogged = true;
					Plugin.Logger.LogError((object)$"MonsterRespawnTimer could not check whether the run is in a level: {arg}");
				}
				return false;
			}
			return (Object)(object)GameObject.Find("Game Hud") != (Object)null;
		}
	}
	internal static class MonsterRespawnTimerHudLayout
	{
		public const float NameColumnMinWidth = 88f;

		public const float NameColumnMaxWidth = 154f;

		public const float StatusColumnWidth = 154f;

		public const float NameStatusGap = 4f;

		public const float FlashTimerGapEm = 0.5f;

		public const float SlotWidth = 312f;

		public const float SlotHeight = 20f;

		public const float SlotGap = 1f;

		public const float RightMargin = 14f;

		public const float BottomMargin = 6f;

		public const float NameFontSize = 16f;

		public const float StatusFontSize = 15.5f;

		public static float CalculateSlotX(float startX, int index)
		{
			return startX;
		}

		public static float CalculateSlotY(float bottomY, int index)
		{
			return bottomY + (float)index * 21f;
		}
	}
	internal static class MonsterRespawnTimerHudStatus
	{
		public const string ProbeText = "EC";

		public static bool ShouldRenderForSpectateState(bool isSpectating)
		{
			return !isSpectating;
		}

		public static bool ShouldShowProbe(IReadOnlyList<EnemySlotState> slotStates)
		{
			for (int i = 0; i < slotStates.Count; i++)
			{
				if (slotStates[i].IsAssigned && slotStates[i].IsVisible)
				{
					return false;
				}
			}
			return true;
		}
	}
	internal sealed class MonsterRespawnTimerHudView
	{
		private sealed class SlotView
		{
			public GameObject Root { get; }

			public TextMeshProUGUI NameText { get; }

			public TextMeshProUGUI StatusText { get; }

			public SlotView(GameObject root, TextMeshProUGUI nameText, TextMeshProUGUI statusText)
			{
				Root = root;
				NameText = nameText;
				StatusText = statusText;
			}

			public void SetNameColumnWidth(float nameColumnWidth)
			{
				//IL_000b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0016: Unknown result type (might be due to invalid IL or missing references)
				//IL_002b: Unknown result type (might be due to invalid IL or missing references)
				//IL_003d: Unknown result type (might be due to invalid IL or missing references)
				((RectTransform)((TMP_Text)NameText).transform).sizeDelta = new Vector2(nameColumnWidth, 20f);
				((RectTransform)((TMP_Text)StatusText).transform).anchoredPosition = new Vector2(0f - (nameColumnWidth + 4f), 0f);
			}
		}

		private static readonly Color NameColor = new Color(0.79f, 0.91f, 0.9f, 1f);

		private static readonly Color TimerColor = new Color(1f, 0.72f, 0.28f, 1f);

		private static readonly Color FlashColor = new Color(0.45f, 0.9f, 1f, 1f);

		private readonly List<SlotView> slots = new List<SlotView>(11);

		private TextMeshProUGUI? probeText;

		private GameObject? root;

		public bool IsAlive => (Object)(object)root != (Object)null;

		public static bool TryCreate(out MonsterRespawnTimerHudView view)
		{
			view = new MonsterRespawnTimerHudView();
			return view.Create();
		}

		public void Render(IReadOnlyList<EnemySlotState> slotStates)
		{
			if ((Object)(object)root == (Object)null)
			{
				return;
			}
			if ((Object)(object)probeText != (Object)null)
			{
				((TMP_Text)probeText).text = "EC";
				((Component)probeText).gameObject.SetActive(MonsterRespawnTimerHudStatus.ShouldShowProbe(slotStates));
			}
			float nameColumnWidth = CalculateNameColumnWidth(slotStates);
			for (int i = 0; i < slots.Count; i++)
			{
				bool flag = i < slotStates.Count && slotStates[i].IsAssigned && slotStates[i].IsVisible;
				slots[i].Root.SetActive(flag);
				if (flag)
				{
					EnemySlotState enemySlotState = slotStates[i];
					slots[i].SetNameColumnWidth(nameColumnWidth);
					((TMP_Text)slots[i].NameText).text = enemySlotState.DisplayName;
					((TMP_Text)slots[i].StatusText).text = BuildStatusRichText(enemySlotState);
					((Component)slots[i].StatusText).gameObject.SetActive(!string.IsNullOrEmpty(enemySlotState.StatusText));
				}
			}
		}

		public void Destroy()
		{
			if ((Object)(object)root != (Object)null)
			{
				Object.Destroy((Object)(object)root);
				root = null;
			}
			slots.Clear();
			probeText = null;
		}

		private bool Create()
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Expected O, but got Unknown
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			Transform val = FindHudParent();
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			TMP_FontAsset val2 = FindHudFont();
			if ((Object)(object)val2 == (Object)null)
			{
				return false;
			}
			root = new GameObject("MonsterRespawnTimer HUD", new Type[1] { typeof(RectTransform) });
			root.SetActive(true);
			root.transform.SetParent(val, false);
			root.layer = ((Component)val).gameObject.layer;
			RectTransform val3 = (RectTransform)root.transform;
			val3.anchorMin = Vector2.zero;
			val3.anchorMax = Vector2.one;
			val3.pivot = new Vector2(0.5f, 0.5f);
			val3.offsetMin = Vector2.zero;
			val3.offsetMax = Vector2.zero;
			((Transform)val3).localScale = Vector3.one;
			for (int i = 0; i < 11; i++)
			{
				slots.Add(CreateSlot(i, val2, root.transform));
			}
			probeText = CreateProbe(val2, root.transform);
			return true;
		}

		private static Transform? FindHudParent()
		{
			GameObject val = GameObject.Find("Game Hud");
			if ((Object)(object)val != (Object)null)
			{
				return val.transform;
			}
			return null;
		}

		private static TMP_FontAsset? FindHudFont()
		{
			GameObject val = GameObject.Find("Tax Haul");
			if ((Object)(object)val != (Object)null)
			{
				TMP_Text component = val.GetComponent<TMP_Text>();
				if ((Object)(object)((component != null) ? component.font : null) != (Object)null)
				{
					return component.font;
				}
			}
			HealthUI instance = HealthUI.instance;
			object obj;
			if (instance == null)
			{
				obj = null;
			}
			else
			{
				TextMeshProUGUI uiText = ((SemiUI)instance).uiText;
				obj = ((uiText != null) ? ((TMP_Text)uiText).font : null);
			}
			if ((Object)obj != (Object)null)
			{
				return ((TMP_Text)((SemiUI)HealthUI.instance).uiText).font;
			}
			TextMeshProUGUI obj2 = Object.FindObjectOfType<TextMeshProUGUI>();
			if (obj2 == null)
			{
				return null;
			}
			return ((TMP_Text)obj2).font;
		}

		private static SlotView CreateSlot(int index, TMP_FontAsset font, Transform parent)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			//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)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject($"MonsterRespawnTimer Slot {index}", new Type[1] { typeof(RectTransform) });
			val.transform.SetParent(parent, false);
			val.layer = ((Component)parent).gameObject.layer;
			RectTransform val2 = (RectTransform)val.transform;
			val2.anchorMin = new Vector2(1f, 0f);
			val2.anchorMax = new Vector2(1f, 0f);
			val2.pivot = new Vector2(1f, 0f);
			val2.anchoredPosition = new Vector2(MonsterRespawnTimerHudLayout.CalculateSlotX(-14f, index), MonsterRespawnTimerHudLayout.CalculateSlotY(6f, index));
			val2.sizeDelta = new Vector2(312f, 20f);
			((Transform)val2).localScale = Vector3.one;
			TextMeshProUGUI nameText = CreateRightAnchoredText("Name", font, val.transform, Vector2.zero, new Vector2(154f, 20f), NameColor, 16f);
			TextMeshProUGUI statusText = CreateRightAnchoredText("Status", font, val.transform, new Vector2(-92f, 0f), new Vector2(154f, 20f), TimerColor, 15.5f);
			val.SetActive(false);
			return new SlotView(val, nameText, statusText);
		}

		private static TextMeshProUGUI CreateProbe(TMP_FontAsset font, Transform parent)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: 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)
			//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)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: 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)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("Probe", new Type[1] { typeof(RectTransform) });
			val.transform.SetParent(parent, false);
			val.layer = ((Component)parent).gameObject.layer;
			RectTransform val2 = (RectTransform)val.transform;
			val2.anchorMin = new Vector2(1f, 0f);
			val2.anchorMax = new Vector2(1f, 0f);
			val2.pivot = new Vector2(1f, 0f);
			val2.anchoredPosition = new Vector2(-14f, 6f);
			val2.sizeDelta = new Vector2(42f, 18f);
			((Transform)val2).localScale = Vector3.one;
			TextMeshProUGUI obj = val.AddComponent<TextMeshProUGUI>();
			((TMP_Text)obj).font = font;
			((Graphic)obj).color = NameColor;
			((TMP_Text)obj).fontSize = 16f;
			((TMP_Text)obj).enableAutoSizing = true;
			((TMP_Text)obj).fontSizeMin = 8f;
			((TMP_Text)obj).fontSizeMax = 16f;
			((TMP_Text)obj).enableWordWrapping = false;
			((TMP_Text)obj).overflowMode = (TextOverflowModes)1;
			((TMP_Text)obj).alignment = (TextAlignmentOptions)516;
			((Graphic)obj).raycastTarget = false;
			((TMP_Text)obj).text = "EC";
			return obj;
		}

		private static TextMeshProUGUI CreateRightAnchoredText(string name, TMP_FontAsset font, Transform parent, Vector2 position, Vector2 size, Color color, float fontSize)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: 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)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name, new Type[1] { typeof(RectTransform) });
			val.transform.SetParent(parent, false);
			val.layer = ((Component)parent).gameObject.layer;
			RectTransform val2 = (RectTransform)val.transform;
			val2.anchorMin = new Vector2(1f, 0.5f);
			val2.anchorMax = new Vector2(1f, 0.5f);
			val2.pivot = new Vector2(1f, 0.5f);
			val2.anchoredPosition = position;
			val2.sizeDelta = size;
			((Transform)val2).localScale = Vector3.one;
			TextMeshProUGUI obj = val.AddComponent<TextMeshProUGUI>();
			((TMP_Text)obj).font = font;
			((Graphic)obj).color = color;
			((TMP_Text)obj).fontSize = fontSize;
			((TMP_Text)obj).enableAutoSizing = true;
			((TMP_Text)obj).fontSizeMin = 9f;
			((TMP_Text)obj).fontSizeMax = fontSize;
			((TMP_Text)obj).enableWordWrapping = false;
			((TMP_Text)obj).overflowMode = (TextOverflowModes)1;
			((TMP_Text)obj).alignment = (TextAlignmentOptions)516;
			((Graphic)obj).raycastTarget = false;
			((TMP_Text)obj).richText = true;
			return obj;
		}

		private float CalculateNameColumnWidth(IReadOnlyList<EnemySlotState> slotStates)
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			float num = 88f;
			TextMeshProUGUI val = slots.FirstOrDefault()?.NameText;
			if ((Object)(object)val == (Object)null)
			{
				return num;
			}
			foreach (EnemySlotState slotState in slotStates)
			{
				if (slotState.IsAssigned && slotState.IsVisible)
				{
					float num2 = ((TMP_Text)val).GetPreferredValues(slotState.DisplayName).x + 2f;
					num = Mathf.Max(num, Mathf.Min(154f, num2));
				}
			}
			return num;
		}

		private static string BuildStatusRichText(EnemySlotState state)
		{
			IReadOnlyList<string> timerTexts = state.TimerTexts;
			string flashText = state.FlashText;
			if (timerTexts.Count == 0)
			{
				return string.Empty;
			}
			if (string.IsNullOrEmpty(flashText))
			{
				return string.Join(" ", timerTexts.Select(FormatTimer));
			}
			if (timerTexts.Count == 1)
			{
				return FormatFlash(flashText) + FormatFlashTimerGap() + FormatTimer(timerTexts[0]);
			}
			List<string> list = new List<string>(timerTexts.Count + 1);
			for (int i = 0; i < timerTexts.Count - 1; i++)
			{
				list.Add(FormatTimer(timerTexts[i]));
			}
			list.Add(FormatFlash(flashText) + FormatFlashTimerGap() + FormatTimer(timerTexts[timerTexts.Count - 1]));
			return string.Join(" ", list);
		}

		private static string FormatFlashTimerGap()
		{
			return "<space=" + 0.5f.ToString("0.###", CultureInfo.InvariantCulture) + "em>";
		}

		private static string FormatTimer(string text)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			return Colorize(text, TimerColor);
		}

		private static string FormatFlash(string text)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			return Colorize(text, FlashColor);
		}

		private static string Colorize(string text, Color color)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			return "<color=#" + ColorUtility.ToHtmlStringRGB(color) + ">" + text + "</color>";
		}
	}
	internal sealed class MonsterRespawnTimerRunner : MonoBehaviour
	{
		private void Awake()
		{
			Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject);
		}

		private void Update()
		{
			MonsterRespawnTimerController.Tick("Runner.Update");
		}
	}
	[BepInPlugin("fangjx114514.monsterrespawntimer", "Monster Respawn Timer", "1.0.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static UnityAction<Scene, LoadSceneMode> <0>__OnSceneLoaded;

			public static UnityAction<Scene, Scene> <1>__OnActiveSceneChanged;

			public static WillRenderCanvases <2>__OnWillRenderCanvases;
		}

		private Harmony? harmony;

		private GameObject? runnerObject;

		internal static ManualLogSource Logger { get; private set; }

		private void Awake()
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Expected O, but got Unknown
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Expected O, but got Unknown
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Expected O, but got Unknown
			Logger = ((BaseUnityPlugin)this).Logger;
			((Component)this).gameObject.transform.parent = null;
			((Object)((Component)this).gameObject).hideFlags = (HideFlags)61;
			harmony = new Harmony("fangjx114514.monsterrespawntimer");
			try
			{
				harmony.PatchAll();
			}
			catch (Exception arg)
			{
				harmony.UnpatchSelf();
				harmony = null;
				Logger.LogError((object)$"MonsterRespawnTimer failed to patch game methods and will stay disabled: {arg}");
				((Behaviour)this).enabled = false;
				return;
			}
			runnerObject = new GameObject("MonsterRespawnTimer Runner");
			Object.DontDestroyOnLoad((Object)(object)runnerObject);
			((Object)runnerObject).hideFlags = (HideFlags)61;
			runnerObject.AddComponent<MonsterRespawnTimerRunner>();
			SceneManager.sceneLoaded += MonsterRespawnTimerController.OnSceneLoaded;
			SceneManager.activeSceneChanged += MonsterRespawnTimerController.OnActiveSceneChanged;
			object obj = <>O.<2>__OnWillRenderCanvases;
			if (obj == null)
			{
				WillRenderCanvases val = MonsterRespawnTimerController.OnWillRenderCanvases;
				<>O.<2>__OnWillRenderCanvases = val;
				obj = (object)val;
			}
			Canvas.willRenderCanvases += (WillRenderCanvases)obj;
			Logger.LogInfo((object)"Plugin fangjx114514.monsterrespawntimer build 1.0.0 is loaded.");
		}

		private void Update()
		{
			MonsterRespawnTimerController.Tick("Plugin.Update");
		}

		private void LateUpdate()
		{
			MonsterRespawnTimerController.Tick("Plugin.LateUpdate");
		}

		private void FixedUpdate()
		{
			MonsterRespawnTimerController.Tick("Plugin.FixedUpdate");
		}

		private void OnDestroy()
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Expected O, but got Unknown
			SceneManager.sceneLoaded -= MonsterRespawnTimerController.OnSceneLoaded;
			SceneManager.activeSceneChanged -= MonsterRespawnTimerController.OnActiveSceneChanged;
			object obj = <>O.<2>__OnWillRenderCanvases;
			if (obj == null)
			{
				WillRenderCanvases val = MonsterRespawnTimerController.OnWillRenderCanvases;
				<>O.<2>__OnWillRenderCanvases = val;
				obj = (object)val;
			}
			Canvas.willRenderCanvases -= (WillRenderCanvases)obj;
			MonsterRespawnTimerController.DestroyActiveHud();
			if ((Object)(object)runnerObject != (Object)null)
			{
				Object.Destroy((Object)(object)runnerObject);
				runnerObject = null;
			}
			Harmony? obj2 = harmony;
			if (obj2 != null)
			{
				obj2.UnpatchSelf();
			}
			harmony = null;
		}
	}
	internal static class PluginMetadata
	{
		public const string Guid = "fangjx114514.monsterrespawntimer";

		public const string Name = "Monster Respawn Timer";

		public const string Version = "1.0.0";
	}
	[HarmonyPatch]
	internal static class MonsterRespawnTimerHarmonyPatches
	{
		[HarmonyPatch(typeof(SemiFunc), "OnSceneSwitch")]
		[HarmonyPrefix]
		private static void Prefix()
		{
			MonsterRespawnTimerController.DestroyActiveHud();
		}

		[HarmonyPatch(typeof(RoundDirector), "Update")]
		[HarmonyPostfix]
		private static void Postfix()
		{
			MonsterRespawnTimerController.Tick("RoundDirector.Update");
		}

		[HarmonyPatch(typeof(RunManager), "Update")]
		[HarmonyPostfix]
		private static void RunManagerUpdatePostfix()
		{
			MonsterRespawnTimerController.Tick("RunManager.Update");
		}

		[HarmonyPatch(typeof(EnemyDirector), "Update")]
		[HarmonyPostfix]
		private static void EnemyDirectorUpdatePostfix()
		{
			MonsterRespawnTimerController.Tick("EnemyDirector.Update");
		}

		[HarmonyPatch(typeof(RunManager), "ChangeLevel")]
		[HarmonyPostfix]
		private static void RunManagerChangeLevelPostfix()
		{
			MonsterRespawnTimerController.OnRunManagerChangeLevel();
		}
	}
}