Decompiled source of SpeedrunChrono v2.0.0

BepInEx/plugins/speedrunchrono.dll

Decompiled 3 weeks ago
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using HarmonyLib;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using YAPYAP;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("speedrunchrono")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("speedrunchrono")]
[assembly: AssemblyCopyright("Copyright ©  2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("6cd78860-c1c2-4a61-a3a2-ce05ca1f4adf")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
[BepInPlugin("Xemotal.SpeedrunChrono", "Speedrun Chrono", "2.0.0")]
public class SpeedrunChrono : BaseUnityPlugin
{
	public static SpeedrunChrono Instance;

	private Harmony harmony;

	private float timer = 0f;

	private bool timerStarted = false;

	private bool timerPaused = false;

	private bool gameRunning = false;

	private KeyCode pauseKey = (KeyCode)96;

	private const string SavedKeyName = "Xemotal.SpeedrunChrono.PauseKey";

	private bool waitingForKey = false;

	private GameObject chronoCanvasObject;

	private Canvas chronoCanvas;

	private TextMeshProUGUI chronoText;

	private GameObject messageCanvasObject;

	private Canvas messageCanvas;

	private TextMeshProUGUI messageText;

	private float messageTimer = 0f;

	private bool showSettings = false;

	private GUIStyle windowStyle;

	private GUIStyle titleStyle;

	private GUIStyle normalStyle;

	private GUIStyle buttonStyle;

	private bool guiInitialized = false;

	private void Awake()
	{
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		//IL_001e: Expected O, but got Unknown
		//IL_008f: Unknown result type (might be due to invalid IL or missing references)
		Instance = this;
		LoadPauseKey();
		harmony = new Harmony("Xemotal.SpeedrunChrono");
		try
		{
			harmony.PatchAll(typeof(SpeedrunChrono));
			((BaseUnityPlugin)this).Logger.LogInfo((object)"[SpeedrunChrono] Harmony patches installed.");
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)("[SpeedrunChrono] Harmony error: " + ex));
		}
		((BaseUnityPlugin)this).Logger.LogInfo((object)"[SpeedrunChrono] Speedrun Chrono loaded.");
		((BaseUnityPlugin)this).Logger.LogInfo((object)("[SpeedrunChrono] Pause key: " + GetReadableKeyName(pauseKey)));
	}

	private void OnDestroy()
	{
		try
		{
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
		catch
		{
		}
		DestroyChronoHUD();
		DestroyMessage();
		Instance = null;
	}

	private void Update()
	{
		//IL_003e: Unknown result type (might be due to invalid IL or missing references)
		if (timerStarted && !timerPaused)
		{
			timer += Time.unscaledDeltaTime;
		}
		if (!waitingForKey && timerStarted && Input.GetKeyDown(pauseKey))
		{
			TogglePause();
		}
		if (!waitingForKey && Input.GetKeyDown((KeyCode)289))
		{
			showSettings = !showSettings;
		}
		if (messageTimer > 0f)
		{
			messageTimer -= Time.unscaledDeltaTime;
			if (messageTimer <= 0f)
			{
				DestroyMessage();
			}
		}
		UpdateChronoHUD();
	}

	private void TogglePause()
	{
		timerPaused = !timerPaused;
		if (timerPaused)
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)"[SpeedrunChrono] Timer paused.");
		}
		else
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)"[SpeedrunChrono] Timer resumed.");
		}
		UpdateChronoHUD();
	}

	[HarmonyPatch(typeof(GameManager), "SvStartRound")]
	[HarmonyPostfix]
	private static void SvStartRoundPostfix()
	{
		if (!((Object)(object)Instance == (Object)null))
		{
			Instance.OnRoundStarted();
		}
	}

	private void OnRoundStarted()
	{
		if (!gameRunning)
		{
			StartNewGame();
		}
		else
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)"[SpeedrunChrono] New round - timer continues.");
		}
		timerStarted = true;
		UpdateChronoHUD();
	}

	private void StartNewGame()
	{
		gameRunning = true;
		timer = 0f;
		timerPaused = false;
		timerStarted = true;
		CreateChronoHUD();
		ShowStartMessage();
		((BaseUnityPlugin)this).Logger.LogInfo((object)"[SpeedrunChrono] New game detected.");
		((BaseUnityPlugin)this).Logger.LogInfo((object)"[SpeedrunChrono] Timer started at 00:00.000.");
	}

	[HarmonyPatch(typeof(GameManager), "RestartGame")]
	[HarmonyPostfix]
	private static void RestartGamePostfix()
	{
		if (!((Object)(object)Instance == (Object)null))
		{
			Instance.PrepareNextGame();
		}
	}

	private void PrepareNextGame()
	{
		gameRunning = false;
		timerStarted = false;
		timerPaused = false;
		timer = 0f;
		DestroyChronoHUD();
		DestroyMessage();
		((BaseUnityPlugin)this).Logger.LogInfo((object)"[SpeedrunChrono] Game ended. Waiting for next game.");
	}

	private void CreateChronoHUD()
	{
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Expected O, but got Unknown
		//IL_0080: Unknown result type (might be due to invalid IL or missing references)
		//IL_009c: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a2: Expected O, but got Unknown
		//IL_011d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0133: Unknown result type (might be due to invalid IL or missing references)
		//IL_0149: Unknown result type (might be due to invalid IL or missing references)
		//IL_015f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0175: Unknown result type (might be due to invalid IL or missing references)
		if (!((Object)(object)chronoCanvasObject != (Object)null))
		{
			chronoCanvasObject = new GameObject("SpeedrunChronoHUD");
			Object.DontDestroyOnLoad((Object)(object)chronoCanvasObject);
			chronoCanvas = chronoCanvasObject.AddComponent<Canvas>();
			chronoCanvas.renderMode = (RenderMode)0;
			chronoCanvas.sortingOrder = 500;
			CanvasScaler val = chronoCanvasObject.AddComponent<CanvasScaler>();
			val.uiScaleMode = (ScaleMode)1;
			val.referenceResolution = new Vector2(1920f, 1080f);
			chronoCanvasObject.AddComponent<GraphicRaycaster>();
			GameObject val2 = new GameObject("ChronoText");
			val2.transform.SetParent(chronoCanvasObject.transform, false);
			chronoText = val2.AddComponent<TextMeshProUGUI>();
			((TMP_Text)chronoText).text = "00:00.000";
			((TMP_Text)chronoText).fontSize = 32f;
			((TMP_Text)chronoText).alignment = (TextAlignmentOptions)514;
			((TMP_Text)chronoText).textWrappingMode = (TextWrappingModes)0;
			RectTransform component = ((Component)chronoText).GetComponent<RectTransform>();
			component.anchorMin = new Vector2(0.5f, 1f);
			component.anchorMax = new Vector2(0.5f, 1f);
			component.pivot = new Vector2(0.5f, 1f);
			component.anchoredPosition = new Vector2(0f, -35f);
			component.sizeDelta = new Vector2(500f, 60f);
			TryUseGameFont();
			UpdateChronoHUD();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"[SpeedrunChrono] Chrono HUD created.");
		}
	}

	private void TryUseGameFont()
	{
		if ((Object)(object)chronoText == (Object)null)
		{
			return;
		}
		try
		{
			PawnView val = Object.FindFirstObjectByType<PawnView>();
			if (!((Object)(object)val == (Object)null) && (Object)(object)val.PlayerNameText != (Object)null && (Object)(object)val.PlayerNameText.font != (Object)null)
			{
				((TMP_Text)chronoText).font = val.PlayerNameText.font;
				((BaseUnityPlugin)this).Logger.LogInfo((object)"[SpeedrunChrono] Game font applied.");
			}
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("[SpeedrunChrono] Could not get game font: " + ex.Message));
		}
	}

	private void UpdateChronoHUD()
	{
		if (!((Object)(object)chronoText == (Object)null))
		{
			TimeSpan timeSpan = TimeSpan.FromSeconds(timer);
			string text = $"{(int)timeSpan.TotalMinutes:00}:{timeSpan.Seconds:00}.{timeSpan.Milliseconds:000}";
			if (timerPaused)
			{
				((TMP_Text)chronoText).text = text + " [PAUSED]";
			}
			else
			{
				((TMP_Text)chronoText).text = text;
			}
		}
	}

	private void DestroyChronoHUD()
	{
		if ((Object)(object)chronoCanvasObject != (Object)null)
		{
			Object.Destroy((Object)(object)chronoCanvasObject);
		}
		chronoCanvasObject = null;
		chronoCanvas = null;
		chronoText = null;
	}

	private void ShowStartMessage()
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		CreateMessage();
		string readableKeyName = GetReadableKeyName(pauseKey);
		((TMP_Text)messageText).text = "The key to pause/resume the timer is " + readableKeyName + ", and you can change it in the settings.";
		messageTimer = 7f;
	}

	private void CreateMessage()
	{
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Expected O, but got Unknown
		//IL_0080: Unknown result type (might be due to invalid IL or missing references)
		//IL_009c: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a2: Expected O, but got Unknown
		//IL_010c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0122: Unknown result type (might be due to invalid IL or missing references)
		//IL_0138: Unknown result type (might be due to invalid IL or missing references)
		//IL_014e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0164: Unknown result type (might be due to invalid IL or missing references)
		if (!((Object)(object)messageCanvasObject != (Object)null))
		{
			messageCanvasObject = new GameObject("SpeedrunChronoMessage");
			Object.DontDestroyOnLoad((Object)(object)messageCanvasObject);
			messageCanvas = messageCanvasObject.AddComponent<Canvas>();
			messageCanvas.renderMode = (RenderMode)0;
			messageCanvas.sortingOrder = 501;
			CanvasScaler val = messageCanvasObject.AddComponent<CanvasScaler>();
			val.uiScaleMode = (ScaleMode)1;
			val.referenceResolution = new Vector2(1920f, 1080f);
			messageCanvasObject.AddComponent<GraphicRaycaster>();
			GameObject val2 = new GameObject("MessageText");
			val2.transform.SetParent(messageCanvasObject.transform, false);
			messageText = val2.AddComponent<TextMeshProUGUI>();
			((TMP_Text)messageText).fontSize = 28f;
			((TMP_Text)messageText).alignment = (TextAlignmentOptions)514;
			((TMP_Text)messageText).textWrappingMode = (TextWrappingModes)1;
			RectTransform component = ((Component)messageText).GetComponent<RectTransform>();
			component.anchorMin = new Vector2(0.5f, 0.5f);
			component.anchorMax = new Vector2(0.5f, 0.5f);
			component.pivot = new Vector2(0.5f, 0.5f);
			component.anchoredPosition = new Vector2(0f, -100f);
			component.sizeDelta = new Vector2(1200f, 160f);
			TryUseMessageFont();
		}
	}

	private void TryUseMessageFont()
	{
		if ((Object)(object)messageText == (Object)null)
		{
			return;
		}
		try
		{
			PawnView val = Object.FindFirstObjectByType<PawnView>();
			if (!((Object)(object)val == (Object)null) && (Object)(object)val.PlayerNameText != (Object)null && (Object)(object)val.PlayerNameText.font != (Object)null)
			{
				((TMP_Text)messageText).font = val.PlayerNameText.font;
			}
		}
		catch
		{
		}
	}

	private void DestroyMessage()
	{
		if ((Object)(object)messageCanvasObject != (Object)null)
		{
			Object.Destroy((Object)(object)messageCanvasObject);
		}
		messageCanvasObject = null;
		messageCanvas = null;
		messageText = null;
		messageTimer = 0f;
	}

	private void LoadPauseKey()
	{
		//IL_004b: Unknown result type (might be due to invalid IL or missing references)
		//IL_001f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_003e: Unknown result type (might be due to invalid IL or missing references)
		string value = PlayerPrefs.GetString("Xemotal.SpeedrunChrono.PauseKey", "");
		if (string.IsNullOrEmpty(value))
		{
			pauseKey = (KeyCode)96;
			return;
		}
		try
		{
			pauseKey = (KeyCode)Enum.Parse(typeof(KeyCode), value);
		}
		catch
		{
			pauseKey = (KeyCode)96;
		}
	}

	private void SavePauseKey()
	{
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		PlayerPrefs.SetString("Xemotal.SpeedrunChrono.PauseKey", ((object)Unsafe.As<KeyCode, KeyCode>(ref pauseKey)/*cast due to .constrained prefix*/).ToString());
		PlayerPrefs.Save();
		((BaseUnityPlugin)this).Logger.LogInfo((object)("[SpeedrunChrono] Key saved: " + GetReadableKeyName(pauseKey)));
	}

	private unsafe string GetReadableKeyName(KeyCode key)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0004: Invalid comparison between Unknown and I4
		//IL_0015: 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_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: 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_001c: Invalid comparison between Unknown and I4
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0031: Invalid comparison between Unknown and I4
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Invalid comparison between Unknown and I4
		//IL_0035: 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_0051: Expected I4, but got Unknown
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		//IL_0027: Invalid comparison between Unknown and I4
		//IL_0053: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		//IL_0077: Expected I4, but got Unknown
		if ((int)key == 96)
		{
			return "²";
		}
		KeyCode val = key;
		KeyCode val2 = val;
		if ((int)val2 <= 13)
		{
			if ((int)val2 == 8)
			{
				return "Backspace";
			}
			if ((int)val2 == 13)
			{
				return "Enter";
			}
		}
		else
		{
			if ((int)val2 == 32)
			{
				return "Space";
			}
			switch (val2 - 273)
			{
			case 0:
				return "Up Arrow";
			case 1:
				return "Down Arrow";
			case 3:
				return "Left Arrow";
			case 2:
				return "Right Arrow";
			}
			switch (val2 - 303)
			{
			case 1:
				return "Left Shift";
			case 0:
				return "Right Shift";
			case 3:
				return "Left Ctrl";
			case 2:
				return "Right Ctrl";
			case 5:
				return "Left Alt";
			case 4:
				return "Right Alt";
			}
		}
		return ((object)(*(KeyCode*)(&key))/*cast due to .constrained prefix*/).ToString();
	}

	private void InitGUI()
	{
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_002c: Expected O, but got Unknown
		//IL_0037: Unknown result type (might be due to invalid IL or missing references)
		//IL_0041: Expected O, but got Unknown
		//IL_0067: Unknown result type (might be due to invalid IL or missing references)
		//IL_0071: Expected O, but got Unknown
		//IL_008a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0094: Expected O, but got Unknown
		if (!guiInitialized)
		{
			guiInitialized = true;
			windowStyle = new GUIStyle(GUI.skin.window);
			titleStyle = new GUIStyle(GUI.skin.label);
			titleStyle.fontSize = 22;
			titleStyle.alignment = (TextAnchor)4;
			normalStyle = new GUIStyle(GUI.skin.label);
			normalStyle.fontSize = 17;
			buttonStyle = new GUIStyle(GUI.skin.button);
			buttonStyle.fontSize = 17;
		}
	}

	private void OnGUI()
	{
		//IL_006a: 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_0081: Expected O, but got Unknown
		//IL_007c: Unknown result type (might be due to invalid IL or missing references)
		InitGUI();
		if (waitingForKey)
		{
			HandleKeyCapture();
			DrawKeyCaptureWindow();
		}
		else if (showSettings)
		{
			float num = 520f;
			float num2 = 330f;
			Rect val = default(Rect);
			((Rect)(ref val))..ctor(((float)Screen.width - num) / 2f, ((float)Screen.height - num2) / 2f, num, num2);
			GUI.ModalWindow(987654, val, new WindowFunction(DrawSettingsWindow), "Speedrun Chrono");
		}
	}

	private void HandleKeyCapture()
	{
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Invalid comparison between Unknown and I4
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		//IL_0029: 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_002c: Invalid comparison between Unknown and I4
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		//IL_003c: Invalid comparison between Unknown and I4
		//IL_0047: Unknown result type (might be due to invalid IL or missing references)
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		Event current = Event.current;
		if (current != null && (int)current.type == 4)
		{
			KeyCode keyCode = current.keyCode;
			if ((int)keyCode != 0 && (int)keyCode != 289)
			{
				pauseKey = keyCode;
				waitingForKey = false;
				SavePauseKey();
				current.Use();
			}
		}
	}

	private void DrawKeyCaptureWindow()
	{
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		//IL_006a: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
		//IL_010e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0114: Invalid comparison between Unknown and I4
		//IL_0117: Unknown result type (might be due to invalid IL or missing references)
		//IL_011e: Invalid comparison between Unknown and I4
		float num = 500f;
		float num2 = 190f;
		Rect val = default(Rect);
		((Rect)(ref val))..ctor(((float)Screen.width - num) / 2f, ((float)Screen.height - num2) / 2f, num, num2);
		GUI.Box(val, "Speedrun Chrono");
		GUI.Label(new Rect(((Rect)(ref val)).x + 30f, ((Rect)(ref val)).y + 55f, ((Rect)(ref val)).width - 60f, 35f), "Press a key...", titleStyle);
		GUI.Label(new Rect(((Rect)(ref val)).x + 30f, ((Rect)(ref val)).y + 95f, ((Rect)(ref val)).width - 60f, 25f), "Press Escape to cancel.", normalStyle);
		if (GUI.Button(new Rect(((Rect)(ref val)).x + 150f, ((Rect)(ref val)).y + 135f, 200f, 35f), "Cancel"))
		{
			waitingForKey = false;
		}
		Event current = Event.current;
		if (current != null && (int)current.type == 4 && (int)current.keyCode == 27)
		{
			waitingForKey = false;
			current.Use();
		}
	}

	private void DrawSettingsWindow(int windowID)
	{
		//IL_0055: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
		//IL_0163: Unknown result type (might be due to invalid IL or missing references)
		GUILayout.Space(10f);
		GUILayout.Label("Speedrun Chrono", titleStyle, Array.Empty<GUILayoutOption>());
		GUILayout.Space(15f);
		GUILayout.Label("Pause / Resume Timer", normalStyle, Array.Empty<GUILayoutOption>());
		GUILayout.Space(5f);
		GUILayout.Label("Current key: " + GetReadableKeyName(pauseKey), normalStyle, Array.Empty<GUILayoutOption>());
		GUILayout.Space(10f);
		if (GUILayout.Button("Change Key", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(42f) }))
		{
			waitingForKey = true;
		}
		GUILayout.Space(10f);
		if (GUILayout.Button("Reset to ²", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(42f) }))
		{
			pauseKey = (KeyCode)96;
			SavePauseKey();
		}
		GUILayout.Space(15f);
		GUILayout.Label("F8: Open / close this menu", normalStyle, Array.Empty<GUILayoutOption>());
		GUILayout.Space(10f);
		if (GUILayout.Button("Close", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(40f) }))
		{
			showSettings = false;
		}
		GUI.DragWindow(new Rect(0f, 0f, 10000f, 25f));
	}
}