Decompiled source of RoundTimer v1.0.0

RoundTimer.dll

Decompiled an hour ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
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("RoundTimer")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("RoundTimer")]
[assembly: AssemblyTitle("RoundTimer")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[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 RoundTimer
{
	[BepInPlugin("com.splinez.repo.muchbetterroundtimer", "Round Timer", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.splinez.repo.muchbetterroundtimer";

		public const string PluginName = "Round Timer";

		public const string PluginVersion = "1.0.0";

		internal static ManualLogSource Log;

		private ConfigEntry<bool> _enabled;

		private ConfigEntry<bool> _showDecimals;

		private ConfigEntry<bool> _showWhenIdle;

		private ConfigEntry<bool> _blendWithHud;

		private ConfigEntry<bool> _useGameMaterial;

		private ConfigEntry<bool> _logCanvases;

		private ConfigEntry<string> _hudCanvasName;

		private ConfigEntry<float> _fontSize;

		private ConfigEntry<float> _offsetX;

		private ConfigEntry<float> _offsetY;

		private ConfigEntry<Color> _color;

		private Canvas _fallbackCanvas;

		private TextMeshProUGUI _text;

		private TMP_FontAsset _font;

		private Material _gameMaterial;

		private bool _attachedToHud;

		private float _elapsed;

		private bool _inRound;

		private float _nextHudTry;

		private int _lastScreenHeight;

		private string _lastCanvasLog;

		private void Awake()
		{
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			_enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Turn the timer on or off.");
			_showDecimals = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowDecimals", false, "Show hundredths of a second (MM:SS.cc). If off, shows MM:SS.");
			_showWhenIdle = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowWhenIdle", false, "If true, shows 00:00 between rounds instead of hiding the timer.");
			_blendWithHud = ((BaseUnityPlugin)this).Config.Bind<bool>("Look", "BlendWithGameHud", true, "Attach the timer to the game's own HUD canvas so it gets the same post-processing (chromatic aberration, distortion, etc.) as the rest of the UI.");
			_useGameMaterial = ((BaseUnityPlugin)this).Config.Bind<bool>("Look", "UseGameTextMaterial", true, "Copy the material (glow/outline/shader) from the game's HUD text.");
			_hudCanvasName = ((BaseUnityPlugin)this).Config.Bind<string>("Look", "HudCanvasName", "", "Optional: exact name of the canvas to attach to (see the BepInEx log, which lists all canvases once per launch). Leave empty for auto-detection.");
			_logCanvases = ((BaseUnityPlugin)this).Config.Bind<bool>("Look", "LogCanvases", true, "Log all UI canvases (name, render mode, camera) once, to help pick the right HUD canvas.");
			_fontSize = ((BaseUnityPlugin)this).Config.Bind<float>("Look", "FontSize", 84f, "Font size (relative to a 1080p-tall canvas; scales with the HUD).");
			_color = ((BaseUnityPlugin)this).Config.Bind<Color>("Look", "Color", new Color(1f, 0.3f, 0.01f, 1f), "Text color (default is the orange-red of the game's HUD text, #FF4D03).");
			_offsetX = ((BaseUnityPlugin)this).Config.Bind<float>("Position", "OffsetFromRight", 58f, "Distance from the right edge of the screen (text is right-aligned).");
			_offsetY = ((BaseUnityPlugin)this).Config.Bind<float>("Position", "OffsetFromTop", 245f, "Distance from the top edge of the screen (default sits under the extraction counter).");
			BuildFallbackCanvas();
			CreateText();
			((BaseUnityPlugin)this).Config.SettingChanged += delegate
			{
				ApplyStyle();
			};
			Log.LogInfo((object)"Round Timer 1.0.0 loaded.");
		}

		private void BuildFallbackCanvas()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("RoundTimerCanvas");
			Object.DontDestroyOnLoad((Object)(object)val);
			_fallbackCanvas = val.AddComponent<Canvas>();
			_fallbackCanvas.renderMode = (RenderMode)0;
			_fallbackCanvas.sortingOrder = 500;
			CanvasScaler obj = val.AddComponent<CanvasScaler>();
			obj.uiScaleMode = (ScaleMode)1;
			obj.referenceResolution = new Vector2(1920f, 1080f);
			obj.matchWidthOrHeight = 0.5f;
		}

		private void CreateText()
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Expected O, but got Unknown
			//IL_00b2: 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_00db: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_fallbackCanvas == (Object)null)
			{
				BuildFallbackCanvas();
			}
			GameObject val = new GameObject("RoundTimerText");
			val.layer = ((Component)_fallbackCanvas).gameObject.layer;
			val.transform.SetParent(((Component)_fallbackCanvas).transform, false);
			_text = val.AddComponent<TextMeshProUGUI>();
			((TMP_Text)_text).alignment = (TextAlignmentOptions)516;
			((Graphic)_text).raycastTarget = false;
			((TMP_Text)_text).enableWordWrapping = false;
			((TMP_Text)_text).text = string.Empty;
			((Behaviour)_text).enabled = false;
			RectTransform rectTransform = ((TMP_Text)_text).rectTransform;
			rectTransform.anchorMin = new Vector2(1f, 1f);
			rectTransform.anchorMax = new Vector2(1f, 1f);
			rectTransform.pivot = new Vector2(1f, 1f);
			_attachedToHud = false;
			ApplyStyle();
		}

		private void ApplyStyle()
		{
			//IL_0082: 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_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_text == (Object)null)
			{
				return;
			}
			float num = 1f;
			Transform parent = ((TMP_Text)_text).transform.parent;
			RectTransform val = (RectTransform)(object)((parent is RectTransform) ? parent : null);
			if ((Object)(object)val != (Object)null)
			{
				Rect rect = val.rect;
				if (((Rect)(ref rect)).height > 1f)
				{
					rect = val.rect;
					num = ((Rect)(ref rect)).height / 1080f;
				}
			}
			((TMP_Text)_text).fontSize = _fontSize.Value * num;
			((Graphic)_text).color = _color.Value;
			RectTransform rectTransform = ((TMP_Text)_text).rectTransform;
			rectTransform.anchoredPosition = new Vector2((0f - _offsetX.Value) * num, (0f - _offsetY.Value) * num);
			rectTransform.sizeDelta = new Vector2(500f * num, 100f * num);
			if ((Object)(object)_font != (Object)null)
			{
				((TMP_Text)_text).font = _font;
				Material val2 = ((_attachedToHud && _useGameMaterial.Value && (Object)(object)_gameMaterial != (Object)null) ? _gameMaterial : ((TMP_Asset)_font).material);
				if ((Object)(object)((TMP_Text)_text).fontSharedMaterial != (Object)(object)val2)
				{
					((TMP_Text)_text).fontSharedMaterial = val2;
				}
			}
			_lastScreenHeight = Screen.height;
		}

		private void Update()
		{
			if ((Object)(object)_text == (Object)null)
			{
				CreateText();
			}
			if (!_enabled.Value)
			{
				((Behaviour)_text).enabled = false;
				return;
			}
			bool flag = IsInRound();
			if (flag != _inRound)
			{
				_elapsed = 0f;
				_inRound = flag;
			}
			if (flag)
			{
				_elapsed += Time.deltaTime;
				MaybeAttachToHud();
			}
			if (!_blendWithHud.Value && _attachedToHud)
			{
				DetachFromHud();
			}
			if (_attachedToHud && ((Object)(object)((Graphic)_text).canvas == (Object)null || !((Component)((Graphic)_text).canvas).gameObject.activeInHierarchy))
			{
				_attachedToHud = false;
			}
			bool flag2 = flag || _showWhenIdle.Value;
			((Behaviour)_text).enabled = flag2;
			if (flag2)
			{
				((TMP_Text)_text).text = Format(_elapsed);
			}
			if (Screen.height != _lastScreenHeight)
			{
				ApplyStyle();
			}
		}

		private static bool IsInRound()
		{
			try
			{
				if (!SemiFunc.RunIsLevel())
				{
					return false;
				}
				LevelGenerator instance = LevelGenerator.Instance;
				return (Object)(object)instance != (Object)null && instance.Generated;
			}
			catch
			{
				return false;
			}
		}

		private string Format(float seconds)
		{
			int num = (int)(seconds / 60f);
			float num2 = seconds - (float)num * 60f;
			if (!_showDecimals.Value)
			{
				return $"{num:00}:{(int)num2:00}";
			}
			return $"{num:00}:{num2:00.00}";
		}

		private void MaybeAttachToHud()
		{
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_014b: Unknown result type (might be due to invalid IL or missing references)
			if (!_blendWithHud.Value || _attachedToHud || Time.unscaledTime < _nextHudTry)
			{
				return;
			}
			_nextHudTry = Time.unscaledTime + 1f;
			if (TryFindHud(out var canvas, out var font, out var material))
			{
				_font = font;
				_gameMaterial = material;
				((TMP_Text)_text).transform.SetParent(((Component)canvas).transform, false);
				SetLayerRecursive(((Component)_text).gameObject, ((Component)canvas).gameObject.layer);
				((TMP_Text)_text).transform.SetAsLastSibling();
				_attachedToHud = true;
				string text = (((Object)(object)canvas.worldCamera != (Object)null) ? ((Object)canvas.worldCamera).name : "none");
				Log.LogInfo((object)($"Attached to HUD canvas '{((Object)canvas).name}' (mode: {canvas.renderMode}, " + "camera: " + text + ", layer: " + LayerMask.LayerToName(((Component)canvas).gameObject.layer) + "), font: " + ((Object)font).name + ", material: " + (((Object)(object)material != (Object)null) ? ((Object)material).name : "none")));
				if ((int)canvas.renderMode == 0)
				{
					Log.LogWarning((object)"The chosen canvas is Screen Space - Overlay, which can't receive post-processing. If the timer still looks clean, set Look/HudCanvasName to a ScreenSpaceCamera/WorldSpace canvas from the canvas list above.");
				}
				ApplyStyle();
			}
		}

		private void DetachFromHud()
		{
			if (!((Object)(object)_fallbackCanvas == (Object)null) && !((Object)(object)_text == (Object)null))
			{
				((TMP_Text)_text).transform.SetParent(((Component)_fallbackCanvas).transform, false);
				SetLayerRecursive(((Component)_text).gameObject, ((Component)_fallbackCanvas).gameObject.layer);
				_attachedToHud = false;
				ApplyStyle();
			}
		}

		private static void SetLayerRecursive(GameObject go, int layer)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			go.layer = layer;
			foreach (Transform item in go.transform)
			{
				SetLayerRecursive(((Component)item).gameObject, layer);
			}
		}

		private bool TryFindHud(out Canvas canvas, out TMP_FontAsset font, out Material material)
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_027b: Unknown result type (might be due to invalid IL or missing references)
			canvas = null;
			font = null;
			material = null;
			Dictionary<Canvas, List<TMP_Text>> dictionary = new Dictionary<Canvas, List<TMP_Text>>();
			TMP_Text[] array = Resources.FindObjectsOfTypeAll<TMP_Text>();
			foreach (TMP_Text val in array)
			{
				if ((Object)(object)val == (Object)null || (Object)(object)val == (Object)(object)_text)
				{
					continue;
				}
				Scene scene = ((Component)val).gameObject.scene;
				if (!((Scene)(ref scene)).IsValid() || !((Component)val).gameObject.activeInHierarchy || (Object)(object)val.font == (Object)null)
				{
					continue;
				}
				Canvas canvas2 = ((Graphic)val).canvas;
				if ((Object)(object)canvas2 == (Object)null)
				{
					continue;
				}
				Canvas rootCanvas = canvas2.rootCanvas;
				if (!((Object)(object)rootCanvas == (Object)null) && !((Object)(object)rootCanvas == (Object)(object)_fallbackCanvas))
				{
					if (!dictionary.TryGetValue(rootCanvas, out var value))
					{
						value = (dictionary[rootCanvas] = new List<TMP_Text>());
					}
					value.Add(val);
				}
			}
			if (dictionary.Count == 0)
			{
				return false;
			}
			if (_logCanvases.Value)
			{
				StringBuilder stringBuilder = new StringBuilder("UI canvases found:");
				foreach (KeyValuePair<Canvas, List<TMP_Text>> item in dictionary)
				{
					Canvas key = item.Key;
					stringBuilder.Append($"\n  '{((Object)key).name}' mode={key.renderMode} texts={item.Value.Count} " + "camera=" + (((Object)(object)key.worldCamera != (Object)null) ? ((Object)key.worldCamera).name : "none") + " layer=" + LayerMask.LayerToName(((Component)key).gameObject.layer));
				}
				string text = stringBuilder.ToString();
				if (text != _lastCanvasLog)
				{
					_lastCanvasLog = text;
					Log.LogInfo((object)text);
				}
			}
			string value2 = _hudCanvasName.Value;
			int num = int.MinValue;
			foreach (KeyValuePair<Canvas, List<TMP_Text>> item2 in dictionary)
			{
				Canvas key2 = item2.Key;
				int num2 = item2.Value.Count;
				if (!string.IsNullOrEmpty(value2) && ((Object)key2).name == value2)
				{
					num2 += 100000;
				}
				if (((Object)key2).name.IndexOf("HUD", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					num2 += 1000;
				}
				if ((int)key2.renderMode != 0)
				{
					num2 += 500;
				}
				if (num2 > num)
				{
					num = num2;
					canvas = key2;
				}
			}
			if ((Object)(object)canvas == (Object)null)
			{
				return false;
			}
			if ((string.IsNullOrEmpty(value2) || !(((Object)canvas).name == value2)) && dictionary[canvas].Count < 4)
			{
				return false;
			}
			Dictionary<TMP_FontAsset, int> dictionary2 = new Dictionary<TMP_FontAsset, int>();
			Dictionary<TMP_FontAsset, TMP_Text> dictionary3 = new Dictionary<TMP_FontAsset, TMP_Text>();
			foreach (TMP_Text item3 in dictionary[canvas])
			{
				dictionary2[item3.font] = ((!dictionary2.TryGetValue(item3.font, out var value3)) ? 1 : (value3 + 1));
				if (!dictionary3.ContainsKey(item3.font))
				{
					dictionary3[item3.font] = item3;
				}
			}
			int num3 = 0;
			foreach (KeyValuePair<TMP_FontAsset, int> item4 in dictionary2)
			{
				if (item4.Value > num3)
				{
					font = item4.Key;
					num3 = item4.Value;
				}
			}
			if ((Object)(object)font == (Object)null)
			{
				return false;
			}
			material = dictionary3[font].fontSharedMaterial;
			return true;
		}
	}
}