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>();
}