Decompiled source of Speedrun Mod v1.1.1

SpeedrunMod.dll

Decompiled 3 weeks ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using Aggro.Core.Networking;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Mirror;
using Newtonsoft.Json;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("crashout_crew_mod")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+f0fc558d1260eaeeb28906b67a032ba2acbefb96")]
[assembly: AssemblyProduct("crashout_crew_mod")]
[assembly: AssemblyTitle("crashout_crew_mod")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace SpeedrunMod;

[BepInPlugin("com.sialala.speedrun", "Speedrun Mod", "1.1.1")]
public class Plugin : BaseUnityPlugin
{
	public static class PluginInfo
	{
		public const string PLUGIN_GUID = "com.sialala.speedrun";

		public const string PLUGIN_NAME = "Speedrun Mod";

		public const string PLUGIN_VERSION = "1.1.1";
	}

	public static bool IsTimerRunning = false;

	public static float ElapsedTime = 0f;

	public static TextMeshProUGUI TimerText;

	public static TextMeshProUGUI DeltaText;

	private static GameObject UiCanvasObject;

	public static string CurrentLevelName = "UnknownLevel";

	public static List<float> CurrentSplits = new List<float>();

	public static int CurrentSplitIndex = 0;

	public static bool HasFinishedSplit = false;

	public static List<float> LoadedPBSplits = new List<float>();

	public static List<float> LoadedBestSegments = new List<float>();

	public static ConfigEntry<bool> ConfigEnableTimer;

	public static ConfigEntry<bool> ConfigEnableDelta;

	public static ConfigEntry<bool> ConfigEnableQuickReset;

	public static ConfigEntry<KeyCode> ConfigQuickResetKey;

	public static ConfigEntry<bool> ConfigShowMenuTime;

	public static ConfigEntry<bool> ConfigShowSumOfBest;

	public static ConfigEntry<bool> ConfigShowAttempts;

	public static ConfigEntry<string> ConfigTimerColor;

	public static ConfigEntry<string> ConfigDeltaColorNegative;

	public static ConfigEntry<string> ConfigDeltaColorPositive;

	public static ConfigEntry<string> ConfigDeltaColorGold;

	public static ConfigEntry<string> ConfigMenuTimeColor;

	public static ConfigEntry<int> ConfigTimerFontSize;

	public static ConfigEntry<int> ConfigDeltaFontSize;

	private void Awake()
	{
		//IL_01f2: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f8: Expected O, but got Unknown
		ConfigEnableTimer = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableTimer", true, "Enables or disables the main speedrun timer.");
		ConfigEnableDelta = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableDeltaTimer", true, "Enables or disables live delta comparisons.");
		ConfigEnableQuickReset = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableQuickReset", true, "Enables quick reset shortcut.");
		ConfigQuickResetKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("General", "QuickResetKey", (KeyCode)290, "The key used for quick reset (returns to lobby or restarts run).");
		ConfigShowMenuTime = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowMenuTime", true, "Enables or disables displaying mod PB time in the level selection menu.");
		ConfigShowSumOfBest = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowSumOfBest", true, "Enables or disables displaying Sum of Best in the level selection menu.");
		ConfigShowAttempts = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowAttempts", true, "Enables or disables displaying the attempt counter in the level selection menu.");
		ConfigTimerColor = ((BaseUnityPlugin)this).Config.Bind<string>("UI.Colors", "TimerColor", "#FFFFFF", "Hex color code for the main timer.");
		ConfigDeltaColorNegative = ((BaseUnityPlugin)this).Config.Bind<string>("UI.Colors", "DeltaColorNegative", "#00FF00", "Hex color code for negative delta (time save).");
		ConfigDeltaColorPositive = ((BaseUnityPlugin)this).Config.Bind<string>("UI.Colors", "DeltaColorPositive", "#FF0000", "Hex color code for positive delta (time loss).");
		ConfigDeltaColorGold = ((BaseUnityPlugin)this).Config.Bind<string>("UI.Colors", "DeltaColorGold", "#FFD700", "Hex color code for a gold split (best segment).");
		ConfigMenuTimeColor = ((BaseUnityPlugin)this).Config.Bind<string>("UI.Colors", "MenuTimeColor", "#9544B7", "Hex color code for the PB time displayed in the level selection menu.");
		ConfigTimerFontSize = ((BaseUnityPlugin)this).Config.Bind<int>("UI.Sizes", "TimerFontSize", 70, "Font size for the main timer.");
		ConfigDeltaFontSize = ((BaseUnityPlugin)this).Config.Bind<int>("UI.Sizes", "DeltaFontSize", 50, "Font size for the live delta timer.");
		SceneManager.sceneLoaded += OnSceneLoaded;
		Harmony val = new Harmony("com.sialala.speedrun");
		val.PatchAll();
		((BaseUnityPlugin)this).Logger.LogInfo((object)"Mod Speedrun Mod loaded and Harmony injected");
	}

	public static string FormatTime(float timeInSeconds)
	{
		if (timeInSeconds == float.MaxValue || float.IsInfinity(timeInSeconds))
		{
			return "-";
		}
		TimeSpan timeSpan = TimeSpan.FromSeconds(timeInSeconds);
		return $"{Math.Floor(timeSpan.TotalMinutes):00}:{timeSpan.Seconds:00}:{timeSpan.Milliseconds / 10:00}";
	}

	private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
	{
		//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cc: Expected O, but got Unknown
		//IL_011c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0122: Expected O, but got Unknown
		//IL_0181: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ba: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d0: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e6: Unknown result type (might be due to invalid IL or missing references)
		//IL_01fc: Unknown result type (might be due to invalid IL or missing references)
		//IL_021b: Unknown result type (might be due to invalid IL or missing references)
		//IL_023c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0243: Expected O, but got Unknown
		//IL_02c1: Unknown result type (might be due to invalid IL or missing references)
		//IL_02d8: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ef: Unknown result type (might be due to invalid IL or missing references)
		//IL_031c: Unknown result type (might be due to invalid IL or missing references)
		//IL_033c: Unknown result type (might be due to invalid IL or missing references)
		ElapsedTime = 0f;
		IsTimerRunning = false;
		CurrentSplitIndex = 0;
		HasFinishedSplit = false;
		CurrentSplits.Clear();
		LoadedPBSplits.Clear();
		LoadedBestSegments.Clear();
		if ((Object)(object)UiCanvasObject != (Object)null)
		{
			if ((Object)(object)TimerText != (Object)null)
			{
				((TMP_Text)TimerText).text = "00:00:00";
				((Component)TimerText).gameObject.SetActive(false);
			}
			if ((Object)(object)DeltaText != (Object)null)
			{
				((TMP_Text)DeltaText).text = "";
				((Component)DeltaText).gameObject.SetActive(false);
			}
			return;
		}
		UiCanvasObject = new GameObject("ModTimerCanvas");
		Canvas val = UiCanvasObject.AddComponent<Canvas>();
		val.renderMode = (RenderMode)0;
		val.sortingOrder = 999;
		UiCanvasObject.AddComponent<CanvasScaler>();
		UiCanvasObject.AddComponent<GraphicRaycaster>();
		UiCanvasObject.AddComponent<TimerUpdater>();
		Object.DontDestroyOnLoad((Object)(object)UiCanvasObject);
		GameObject val2 = new GameObject("ModTimerText");
		val2.transform.SetParent(UiCanvasObject.transform, false);
		TimerText = val2.AddComponent<TextMeshProUGUI>();
		((TMP_Text)TimerText).text = "00:00:00";
		((TMP_Text)TimerText).fontSize = ConfigTimerFontSize.Value;
		Color color = default(Color);
		ColorUtility.TryParseHtmlString(ConfigTimerColor.Value, ref color);
		((Graphic)TimerText).color = color;
		((TMP_Text)TimerText).alignment = (TextAlignmentOptions)1025;
		((TMP_Text)TimerText).enableWordWrapping = false;
		RectTransform component = ((Component)TimerText).GetComponent<RectTransform>();
		component.anchorMin = new Vector2(0f, 0f);
		component.anchorMax = new Vector2(0f, 0f);
		component.pivot = new Vector2(0f, 0f);
		component.anchoredPosition = new Vector2(20f, 20f);
		component.sizeDelta = new Vector2(500f, (float)(ConfigTimerFontSize.Value + 20));
		((Component)TimerText).gameObject.SetActive(false);
		GameObject val3 = new GameObject("ModDeltaText");
		val3.transform.SetParent(UiCanvasObject.transform, false);
		DeltaText = val3.AddComponent<TextMeshProUGUI>();
		((TMP_Text)DeltaText).text = "";
		((TMP_Text)DeltaText).fontSize = ConfigDeltaFontSize.Value;
		((TMP_Text)DeltaText).alignment = (TextAlignmentOptions)1025;
		((TMP_Text)DeltaText).enableWordWrapping = false;
		RectTransform component2 = ((Component)DeltaText).GetComponent<RectTransform>();
		component2.anchorMin = new Vector2(0f, 0f);
		component2.anchorMax = new Vector2(0f, 0f);
		component2.pivot = new Vector2(0f, 0f);
		float num = 20f + (float)ConfigTimerFontSize.Value + 10f;
		component2.anchoredPosition = new Vector2(20f, num);
		component2.sizeDelta = new Vector2(500f, (float)(ConfigDeltaFontSize.Value + 20));
		((Component)DeltaText).gameObject.SetActive(false);
		Debug.Log((object)"[SpeedrunMod] Timer UI successfully injected into the scene.");
	}
}
public class TimerUpdater : MonoBehaviour
{
	private void Update()
	{
		//IL_0301: Unknown result type (might be due to invalid IL or missing references)
		//IL_035b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0331: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
		//IL_0256: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)Plugin.TimerText != (Object)null && !Plugin.ConfigEnableTimer.Value)
		{
			if (((Component)Plugin.TimerText).gameObject.activeSelf)
			{
				((Component)Plugin.TimerText).gameObject.SetActive(false);
			}
			if ((Object)(object)Plugin.DeltaText != (Object)null && ((Component)Plugin.DeltaText).gameObject.activeSelf)
			{
				((Component)Plugin.DeltaText).gameObject.SetActive(false);
			}
		}
		if (Plugin.IsTimerRunning && (Object)(object)Plugin.TimerText != (Object)null)
		{
			Plugin.ElapsedTime += Time.deltaTime;
			((TMP_Text)Plugin.TimerText).text = Plugin.FormatTime(Plugin.ElapsedTime);
			if (Plugin.ConfigEnableTimer.Value && Plugin.ConfigEnableDelta.Value)
			{
				if (!Plugin.HasFinishedSplit && (Object)(object)Plugin.DeltaText != (Object)null && Plugin.LoadedPBSplits != null && Plugin.CurrentSplitIndex < Plugin.LoadedPBSplits.Count)
				{
					float num = Plugin.LoadedPBSplits[Plugin.CurrentSplitIndex];
					float num2 = ((Plugin.LoadedBestSegments.Count > Plugin.CurrentSplitIndex) ? Plugin.LoadedBestSegments[Plugin.CurrentSplitIndex] : float.MaxValue);
					float num3 = ((Plugin.CurrentSplitIndex == 0) ? Plugin.ElapsedTime : (Plugin.ElapsedTime - Plugin.CurrentSplits[Plugin.CurrentSplitIndex - 1]));
					float value = Plugin.ElapsedTime - num;
					if (Plugin.ElapsedTime > num)
					{
						((TMP_Text)Plugin.DeltaText).text = "+" + Plugin.FormatTime(Math.Abs(value));
						Color color = default(Color);
						if (ColorUtility.TryParseHtmlString(Plugin.ConfigDeltaColorPositive.Value, ref color))
						{
							((Graphic)Plugin.DeltaText).color = color;
						}
						if (!((Component)Plugin.DeltaText).gameObject.activeSelf)
						{
							((Component)Plugin.DeltaText).gameObject.SetActive(true);
						}
					}
					else if (num3 > num2)
					{
						((TMP_Text)Plugin.DeltaText).text = "-" + Plugin.FormatTime(Math.Abs(value));
						Color color2 = default(Color);
						if (ColorUtility.TryParseHtmlString(Plugin.ConfigDeltaColorNegative.Value, ref color2))
						{
							((Graphic)Plugin.DeltaText).color = color2;
						}
						if (!((Component)Plugin.DeltaText).gameObject.activeSelf)
						{
							((Component)Plugin.DeltaText).gameObject.SetActive(true);
						}
					}
					else if (((Component)Plugin.DeltaText).gameObject.activeSelf)
					{
						((Component)Plugin.DeltaText).gameObject.SetActive(false);
					}
				}
			}
			else if ((Object)(object)Plugin.DeltaText != (Object)null && ((Component)Plugin.DeltaText).gameObject.activeSelf)
			{
				((Component)Plugin.DeltaText).gameObject.SetActive(false);
			}
		}
		if (Plugin.ConfigEnableQuickReset.Value && Input.GetKeyDown(Plugin.ConfigQuickResetKey.Value))
		{
			if (GameUtil.isLobby)
			{
				GameManager.NextRun();
				Debug.Log((object)$"[SpeedrunMod] Quick start from Lobby ({Plugin.ConfigQuickResetKey.Value})!");
			}
			else
			{
				GameManager.Next((GameNextType)1);
				Debug.Log((object)$"[SpeedrunMod] Run aborted! Safe return to Lobby ({Plugin.ConfigQuickResetKey.Value}).");
			}
		}
	}
}
public static class SplitsManager
{
	public static string SaveFilePath => Path.Combine(Paths.PluginPath, "SpeedrunSplits.json");

	public static int CurrentPlayerCount
	{
		get
		{
			int num = 1;
			if (NetworkServer.active)
			{
				num = NetworkServer.connections.Count;
			}
			else if (NetworkClient.active)
			{
				if (NetworkAggroManagerBase<PlayersManager>.ManagerExists() && (Object)(object)NetworkAggroManagerBase<PlayersManager>.instance != (Object)null)
				{
					int playerCount = NetworkAggroManagerBase<PlayersManager>.instance.playerCount;
					if (playerCount > 0)
					{
						return playerCount;
					}
				}
				try
				{
					LobbyPlayer[] array = Object.FindObjectsOfType<LobbyPlayer>();
					if (array != null && array.Length != 0)
					{
						num = array.Length;
					}
				}
				catch
				{
				}
			}
			return (num <= 0) ? 1 : num;
		}
	}

	public static SplitsSaveFile LoadSplits()
	{
		if (!File.Exists(SaveFilePath))
		{
			Debug.Log((object)"[SpeedrunMod] No existing splits file found. Creating a new one.");
			return new SplitsSaveFile();
		}
		try
		{
			string text = File.ReadAllText(SaveFilePath);
			SplitsSaveFile splitsSaveFile = JsonConvert.DeserializeObject<SplitsSaveFile>(text) ?? new SplitsSaveFile();
			bool flag = false;
			foreach (SplitRecord record in splitsSaveFile.records)
			{
				if (!record.levelName.Contains("_"))
				{
					record.levelName += "_1P";
					flag = true;
				}
			}
			if (flag)
			{
				SaveSplits(splitsSaveFile);
				Debug.Log((object)"[SpeedrunMod] Migrated old save data to support multiplayer tracking (appended _1P).");
			}
			return splitsSaveFile;
		}
		catch (Exception ex)
		{
			Debug.LogError((object)("[SpeedrunMod] Error loading splits: " + ex.Message));
			return new SplitsSaveFile();
		}
	}

	public static void SaveSplits(SplitsSaveFile data)
	{
		try
		{
			string contents = JsonConvert.SerializeObject((object)data, (Formatting)1);
			File.WriteAllText(SaveFilePath, contents);
			Debug.Log((object)"[SpeedrunMod] Splits saved successfully.");
		}
		catch (Exception ex)
		{
			Debug.LogError((object)("[SpeedrunMod] Failed to save splits: " + ex.Message));
		}
	}

	public static void LoadLevelDataIntoMemory(string levelName)
	{
		Plugin.LoadedPBSplits.Clear();
		Plugin.LoadedBestSegments.Clear();
		SplitsSaveFile splitsSaveFile = LoadSplits();
		SplitRecord splitRecord = splitsSaveFile.records.Find((SplitRecord r) => r.levelName == levelName);
		if (splitRecord != null)
		{
			if (splitRecord.pbSplits != null)
			{
				Plugin.LoadedPBSplits = new List<float>(splitRecord.pbSplits);
			}
			if (splitRecord.bestSegments != null)
			{
				Plugin.LoadedBestSegments = new List<float>(splitRecord.bestSegments);
			}
			if (splitRecord.totalTime < float.MaxValue)
			{
				Debug.Log((object)("[SpeedrunMod] Loaded PB data for " + levelName + ". Total time to beat: " + Plugin.FormatTime(splitRecord.totalTime)));
			}
			else
			{
				Debug.Log((object)("[SpeedrunMod] PB not established yet for " + levelName + "."));
			}
		}
		else
		{
			Debug.Log((object)("[SpeedrunMod] No previous data found for " + levelName + ". This is a new run!"));
		}
	}

	public static void ProcessAndSaveRun(string levelName, List<float> runSplits, bool isRunCompleted)
	{
		if (runSplits.Count == 0 || string.IsNullOrEmpty(levelName) || levelName == "UnknownLevel")
		{
			return;
		}
		SplitsSaveFile splitsSaveFile = LoadSplits();
		SplitRecord splitRecord = splitsSaveFile.records.Find((SplitRecord r) => r.levelName == levelName);
		bool flag = false;
		if (splitRecord == null)
		{
			splitRecord = new SplitRecord
			{
				levelName = levelName,
				totalTime = float.MaxValue
			};
			splitsSaveFile.records.Add(splitRecord);
			flag = true;
		}
		if (splitRecord.bestSegments == null)
		{
			splitRecord.bestSegments = new List<float>();
		}
		if (splitRecord.pbSplits == null)
		{
			splitRecord.pbSplits = new List<float>();
		}
		for (int num = 0; num < runSplits.Count; num++)
		{
			float num2 = runSplits[num];
			if (num > 0)
			{
				num2 -= runSplits[num - 1];
			}
			if (splitRecord.bestSegments.Count <= num)
			{
				splitRecord.bestSegments.Add(num2);
				flag = true;
			}
			else if (num2 < splitRecord.bestSegments[num])
			{
				splitRecord.bestSegments[num] = num2;
				flag = true;
				Debug.Log((object)$"[SpeedrunMod] GOLD SPLIT! Shift {num + 1} completed in {num2:F2}s");
			}
		}
		if (isRunCompleted)
		{
			float num3 = runSplits[runSplits.Count - 1];
			if (num3 < splitRecord.totalTime)
			{
				splitRecord.totalTime = num3;
				splitRecord.pbSplits = new List<float>(runSplits);
				flag = true;
				Debug.Log((object)$"[SpeedrunMod] NEW PB! Total time: {num3:F2}s");
			}
		}
		if (flag)
		{
			SaveSplits(splitsSaveFile);
		}
	}

	public static void IncrementAttempt(string levelName)
	{
		if (!string.IsNullOrEmpty(levelName) && !(levelName == "UnknownLevel"))
		{
			SplitsSaveFile splitsSaveFile = LoadSplits();
			SplitRecord splitRecord = splitsSaveFile.records.Find((SplitRecord r) => r.levelName == levelName);
			if (splitRecord == null)
			{
				splitRecord = new SplitRecord
				{
					levelName = levelName,
					totalTime = float.MaxValue,
					attempts = 0
				};
				splitsSaveFile.records.Add(splitRecord);
			}
			splitRecord.attempts++;
			SaveSplits(splitsSaveFile);
			Debug.Log((object)$"[SpeedrunMod] Attempt #{splitRecord.attempts} started for {levelName}.");
		}
	}
}
[HarmonyPatch(typeof(ShiftManager))]
public class ShiftManagerPatches
{
	[HarmonyPatch("ShiftChanged")]
	[HarmonyPostfix]
	public static void Postfix_ShiftChanged(ShiftPhase phase, int shift, int outboundsRequired)
	{
		//IL_0043: Unknown result type (might be due to invalid IL or missing references)
		//IL_0045: Invalid comparison between Unknown and I4
		if ((Object)(object)Plugin.TimerText != (Object)null && !((Component)Plugin.TimerText).gameObject.activeSelf && Plugin.ConfigEnableTimer.Value)
		{
			((Component)Plugin.TimerText).gameObject.SetActive(true);
		}
		if ((int)phase == 3)
		{
			Plugin.HasFinishedSplit = false;
			if ((Object)(object)Plugin.DeltaText != (Object)null)
			{
				((Component)Plugin.DeltaText).gameObject.SetActive(false);
			}
			if (shift == 1 && !Plugin.IsTimerRunning && Plugin.ElapsedTime < 0.1f && (Object)(object)GameUtil.contract != (Object)null)
			{
				Plugin.CurrentLevelName = ((Object)GameUtil.contract).name + "_" + SplitsManager.CurrentPlayerCount + "P";
				SplitsManager.LoadLevelDataIntoMemory(Plugin.CurrentLevelName);
				SplitsManager.IncrementAttempt(Plugin.CurrentLevelName);
				Debug.Log((object)("[SpeedrunMod] Started: " + Plugin.CurrentLevelName));
			}
			Plugin.IsTimerRunning = true;
		}
		else
		{
			Plugin.IsTimerRunning = false;
		}
	}

	private static void RecordSplit(byte shiftCount, bool isRunCompleted)
	{
		//IL_0147: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ad: Unknown result type (might be due to invalid IL or missing references)
		//IL_0183: Unknown result type (might be due to invalid IL or missing references)
		Debug.Log((object)$"[SpeedrunMod] Shift {shiftCount} completed at {Plugin.FormatTime(Plugin.ElapsedTime)}");
		Plugin.IsTimerRunning = false;
		int count = Plugin.CurrentSplits.Count;
		Plugin.CurrentSplits.Add(Plugin.ElapsedTime);
		if (Plugin.ConfigEnableTimer.Value && Plugin.ConfigEnableDelta.Value && Plugin.LoadedPBSplits != null && count < Plugin.LoadedPBSplits.Count)
		{
			float num = Plugin.LoadedPBSplits[count];
			float num2 = Plugin.ElapsedTime - num;
			float num3 = ((count == 0) ? Plugin.ElapsedTime : (Plugin.ElapsedTime - Plugin.CurrentSplits[count - 1]));
			bool flag = false;
			if (Plugin.LoadedBestSegments != null && count < Plugin.LoadedBestSegments.Count && num3 < Plugin.LoadedBestSegments[count])
			{
				flag = true;
			}
			string text = ((num2 > 0f) ? "+" : "-");
			((TMP_Text)Plugin.DeltaText).text = text + Plugin.FormatTime(Math.Abs(num2));
			Color color3 = default(Color);
			if (flag)
			{
				Color color = default(Color);
				if (ColorUtility.TryParseHtmlString(Plugin.ConfigDeltaColorGold.Value, ref color))
				{
					((Graphic)Plugin.DeltaText).color = color;
				}
			}
			else if (num2 <= 0f)
			{
				Color color2 = default(Color);
				if (ColorUtility.TryParseHtmlString(Plugin.ConfigDeltaColorNegative.Value, ref color2))
				{
					((Graphic)Plugin.DeltaText).color = color2;
				}
			}
			else if (ColorUtility.TryParseHtmlString(Plugin.ConfigDeltaColorPositive.Value, ref color3))
			{
				((Graphic)Plugin.DeltaText).color = color3;
			}
			((Component)Plugin.DeltaText).gameObject.SetActive(true);
			Plugin.HasFinishedSplit = true;
		}
		else if ((Object)(object)Plugin.DeltaText != (Object)null)
		{
			((Component)Plugin.DeltaText).gameObject.SetActive(false);
		}
		Plugin.CurrentSplitIndex = Plugin.CurrentSplits.Count;
		SplitsManager.ProcessAndSaveRun(Plugin.CurrentLevelName, Plugin.CurrentSplits, isRunCompleted);
	}

	[HarmonyPatch("UserCode_RpcTransitionShiftToShiftWon__Byte__ContractScore__Vector3")]
	[HarmonyPostfix]
	public static void StopTimer_ShiftWon(byte shiftCount)
	{
		RecordSplit(shiftCount, isRunCompleted: false);
	}

	[HarmonyPatch("UserCode_RpcTransitionShiftToShiftWonPhase1__Byte__ContractScore__Vector3")]
	[HarmonyPostfix]
	public static void StopTimer_ShiftWonPhase1(byte shiftCount)
	{
		RecordSplit(shiftCount, isRunCompleted: false);
	}

	[HarmonyPatch("UserCode_RpcTransitionShiftToGameWon__Byte__ContractScore__ContractScore__Int32__Vector3__PlayerResult[]")]
	[HarmonyPostfix]
	public static void StopTimer_GameWon(byte shiftCount)
	{
		RecordSplit(shiftCount, isRunCompleted: true);
	}

	[HarmonyPatch("UserCode_RpcTransitionShiftToGameLost__Byte__Int32__Vector3__PlayerResult[]")]
	[HarmonyPostfix]
	public static void StopTimer_GameLost(byte shiftCount)
	{
		Plugin.IsTimerRunning = false;
		Debug.Log((object)"[SpeedrunMod] Run lost! Timer stopped. Splits ignored.");
	}
}
[HarmonyPatch(typeof(ContractSelectionUI))]
public class ContractSelectionUIPatches
{
	private static int LastPlayerCount = -1;

	[HarmonyPatch("OnUpdateSimulation")]
	[HarmonyPostfix]
	public static void Postfix_OnUpdateSimulation(ContractSelectionUI __instance)
	{
		int currentPlayerCount = SplitsManager.CurrentPlayerCount;
		if (LastPlayerCount != -1 && LastPlayerCount != currentPlayerCount)
		{
			LastPlayerCount = currentPlayerCount;
			RefreshModUI(__instance);
		}
		else
		{
			LastPlayerCount = currentPlayerCount;
		}
	}

	[HarmonyPatch("SetUp")]
	[HarmonyPostfix]
	public static void Postfix_SetUp(ContractSelectionUI __instance)
	{
		LastPlayerCount = SplitsManager.CurrentPlayerCount;
		RefreshModUI(__instance);
	}

	private static void RefreshModUI(ContractSelectionUI __instance)
	{
		//IL_018e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0194: Invalid comparison between Unknown and I4
		if (!Plugin.ConfigShowMenuTime.Value && !Plugin.ConfigShowAttempts.Value && !Plugin.ConfigShowSumOfBest.Value)
		{
			return;
		}
		SplitsSaveFile splitsSaveFile = SplitsManager.LoadSplits();
		int num = __instance.contractGroup.childCount - __instance._contracts.Count;
		if (num < 0)
		{
			num = 0;
		}
		for (int i = 0; i < __instance._contracts.Count; i++)
		{
			int num2 = num + i;
			if (num2 >= __instance.contractGroup.childCount)
			{
				continue;
			}
			ContractObject val = __instance._contracts[i];
			ContractUI component = ((Component)__instance.contractGroup.GetChild(num2)).GetComponent<ContractUI>();
			if (!((Object)(object)component != (Object)null) || !((Object)(object)component.bestTimeText != (Object)null))
			{
				continue;
			}
			string dynamicLevelName = ((Object)val).name + "_" + SplitsManager.CurrentPlayerCount + "P";
			SplitRecord splitRecord = splitsSaveFile.records.Find((SplitRecord r) => r.levelName == dynamicLevelName);
			bool flag = splitRecord != null && splitRecord.totalTime < float.MaxValue;
			int num3 = splitRecord?.attempts ?? 0;
			bool flag2 = flag && Plugin.ConfigShowMenuTime.Value;
			bool flag3 = flag && Plugin.ConfigShowSumOfBest.Value && splitRecord.bestSegments != null && splitRecord.bestSegments.Count > 0;
			bool value = Plugin.ConfigShowAttempts.Value;
			Transform val2 = (((int)val.type == 1) ? ((Component)component).transform : ((TMP_Text)component.bestTimeText).transform.parent);
			if ((Object)(object)val2 != (Object)null && !((Component)val2).gameObject.activeSelf && (flag2 || flag3 || value))
			{
				((Component)val2).gameObject.SetActive(true);
			}
			if (flag2)
			{
				((Component)component.bestTimeText).gameObject.SetActive(false);
			}
			else
			{
				((Component)component.bestTimeText).gameObject.SetActive(true);
			}
			float num4 = -45f;
			float num5 = -290f;
			if (!flag2)
			{
				num4 -= 60f;
				num5 -= 60f;
			}
			Transform val3 = val2.Find("ModSpeedrunText");
			if (flag2)
			{
				string text = Plugin.FormatTime(splitRecord.totalTime);
				val3 = CreateOrUpdateTextObj(val3, val2, component, val, "ModSpeedrunText", num4, num5, 60);
				((TMP_Text)((Component)val3).GetComponent<TextMeshProUGUI>()).text = "<color=" + Plugin.ConfigMenuTimeColor.Value + "><b>" + text + "</b></color>";
				num4 -= 60f;
				num5 -= 60f;
			}
			else if ((Object)(object)val3 != (Object)null)
			{
				((Component)val3).gameObject.SetActive(false);
			}
			Transform val4 = val2.Find("ModSpeedrunSoBText");
			if (flag3)
			{
				float num6 = 0f;
				foreach (float bestSegment in splitRecord.bestSegments)
				{
					num6 += bestSegment;
				}
				val4 = CreateOrUpdateTextObj(val4, val2, component, val, "ModSpeedrunSoBText", num4, num5, 30);
				((TMP_Text)((Component)val4).GetComponent<TextMeshProUGUI>()).text = "<color=" + Plugin.ConfigMenuTimeColor.Value + "><b>SoB " + Plugin.FormatTime(num6) + "</b></color>";
				num4 -= 35f;
				num5 -= 35f;
			}
			else if ((Object)(object)val4 != (Object)null)
			{
				((Component)val4).gameObject.SetActive(false);
			}
			Transform val5 = val2.Find("ModSpeedrunAttemptsText");
			if (value)
			{
				val5 = CreateOrUpdateTextObj(val5, val2, component, val, "ModSpeedrunAttemptsText", num4, num5, 30);
				((TMP_Text)((Component)val5).GetComponent<TextMeshProUGUI>()).text = $"<color={Plugin.ConfigMenuTimeColor.Value}><b>Attempts {num3}</b></color>";
			}
			else if ((Object)(object)val5 != (Object)null)
			{
				((Component)val5).gameObject.SetActive(false);
			}
		}
	}

	private static Transform CreateOrUpdateTextObj(Transform existingTransform, Transform parent, ContractUI ui, ContractObject contract, string objName, float yNormal, float yRandom, int fontSize)
	{
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0019: Expected O, but got Unknown
		//IL_0048: 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)
		//IL_0064: 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_0087: Unknown result type (might be due to invalid IL or missing references)
		//IL_0100: Unknown result type (might be due to invalid IL or missing references)
		//IL_0106: Invalid comparison between Unknown and I4
		//IL_0133: Unknown result type (might be due to invalid IL or missing references)
		//IL_013f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0144: Unknown result type (might be due to invalid IL or missing references)
		//IL_0111: Unknown result type (might be due to invalid IL or missing references)
		//IL_011d: 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)
		RectTransform val2;
		TextMeshProUGUI val3;
		if ((Object)(object)existingTransform == (Object)null)
		{
			GameObject val = new GameObject(objName);
			val.transform.SetParent(parent, false);
			val2 = val.AddComponent<RectTransform>();
			val3 = val.AddComponent<TextMeshProUGUI>();
			RectTransform component = ((Component)ui.bestTimeText).GetComponent<RectTransform>();
			val2.anchorMin = component.anchorMin;
			val2.anchorMax = component.anchorMax;
			val2.pivot = component.pivot;
			val2.sizeDelta = new Vector2(component.sizeDelta.x * 1.5f, 150f);
			((TMP_Text)val3).font = ((TMP_Text)ui.bestTimeText).font;
			((TMP_Text)val3).alignment = (TextAlignmentOptions)257;
			((TMP_Text)val3).enableAutoSizing = false;
			((TMP_Text)val3).overflowMode = (TextOverflowModes)0;
			existingTransform = val.transform;
		}
		else
		{
			val2 = ((Component)existingTransform).GetComponent<RectTransform>();
			val3 = ((Component)existingTransform).GetComponent<TextMeshProUGUI>();
			((Component)existingTransform).gameObject.SetActive(true);
		}
		((TMP_Text)val3).fontSize = fontSize;
		RectTransform component2 = ((Component)ui.bestTimeText).GetComponent<RectTransform>();
		if ((int)contract.type == 1)
		{
			val2.anchoredPosition = component2.anchoredPosition + new Vector2(95f, yRandom);
		}
		else
		{
			val2.anchoredPosition = component2.anchoredPosition + new Vector2(63f, yNormal);
		}
		return existingTransform;
	}
}
[Serializable]
public class SplitRecord
{
	public string levelName;

	public float totalTime;

	public int attempts = 0;

	public List<float> pbSplits = new List<float>();

	public List<float> bestSegments = new List<float>();
}
[Serializable]
public class SplitsSaveFile
{
	public List<SplitRecord> records = new List<SplitRecord>();
}