using System; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using OpenShot.Core; using OpenShot.Events; using OpenShot.UI; using UnityEngine; using UnityEngine.SceneManagement; [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("LuckyShotSpeedrun")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("LuckyShot Speedrun Timer Mod. Configure via OpenShot MODS hub.")] [assembly: AssemblyFileVersion("0.2.3.0")] [assembly: AssemblyInformationalVersion("0.2.3+4135dd1490a61fe647f2a0e54287f36471f5cf7f")] [assembly: AssemblyProduct("LuckyShotSpeedrun")] [assembly: AssemblyTitle("LuckyShotSpeedrun")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.2.3.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 LuckyShotSpeedrun { [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("com.liam.luckyshot.speedrun", "LuckyShotSpeedrun", "0.2.3")] public class Plugin : BaseUnityPlugin { [Serializable] public class RunResult { public bool Success; public double Time; public int Shots; public int Hits; public string Category; public string Timestamp; } [Serializable] private class LegacySettings { public bool AutoStart = true; public bool StartOnFirstShot = true; public bool StartOnSceneLoad; public bool StartOnSpawnerStart; public bool PauseOnMenu = true; public bool ShowAlways; public string Category = "Any%"; public bool EndRunOnFail = true; public int LowShotThreshold = 5; public int TimerPosition; } public static ManualLogSource Logger; public static Stopwatch sw = new Stopwatch(); public static bool running; public static bool paused; public static int Shots; public static int Hits; public static float lastShotTime; public static bool pendingShot; public static bool runFailed; public static readonly string[] CategoriesList = new string[9] { "Any%", "Any% (No-Buy)", "100%", "One-Magazine", "First-Target", "No-Miss", "Low-Shot", "Glitchless", "Set-Seed" }; private Harmony harmony; private static readonly string[] TimerPositions = new string[3] { "TOP RIGHT", "TOP LEFT", "TOP CENTER" }; public static ConfigEntry Enabled { get; private set; } public static ConfigEntry AutoStart { get; private set; } public static ConfigEntry StartOnFirstShot { get; private set; } public static ConfigEntry StartOnSceneLoad { get; private set; } public static ConfigEntry StartOnSpawnerStart { get; private set; } public static ConfigEntry PauseOnMenu { get; private set; } public static ConfigEntry ShowAlways { get; private set; } public static ConfigEntry EndRunOnFail { get; private set; } public static ConfigEntry MissGracePeriod { get; private set; } public static ConfigEntry LowShotThreshold { get; private set; } public static ConfigEntry TimerPosition { get; private set; } public static ConfigEntry CategoryIndex { get; private set; } public static string CurrentCategory => CategoriesList[Mathf.Clamp(CategoryIndex.Value, 0, CategoriesList.Length - 1)]; private void Awake() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Expected O, but got Unknown //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Expected O, but got Unknown //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Expected O, but got Unknown Logger = ((BaseUnityPlugin)this).Logger; BindConfig(); RegisterOpenShotMenu(); MigrateLegacySave(); GameEvents.OnShotFired += new CancelableEvent(OnShotFired); GameEvents.OnSceneLoaded += OnSceneLoaded; GameEvents.OnTargetSpawnerStarted += OnTargetSpawnerStarted; GameEvents.OnWeaponReloaded += OnWeaponReloaded_Event; GameEvents.OnShopTryBuy += OnShopTryBuy_Event; try { harmony = new Harmony("com.liam.luckyshot.speedrun.harmony"); Type type = AccessTools.TypeByName("Target"); MethodInfo methodInfo = ((type != null) ? AccessTools.Method(type, "OnDisable", (Type[])null, (Type[])null) : null); if (methodInfo != null) { MethodInfo method = typeof(Plugin).GetMethod("Target_OnDisable_Postfix", BindingFlags.Static | BindingFlags.NonPublic); harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Logger.LogInfo((object)"Patched Target.OnDisable for hit detection"); } else { Logger.LogWarning((object)"Target.OnDisable not found — hit detection disabled"); } } catch (Exception ex) { Logger.LogError((object)("Failed to apply Target patch: " + ex.Message)); } GameObject val = new GameObject("LuckyShotSpeedrun_Timer"); Object.DontDestroyOnLoad((Object)val); val.AddComponent(); Logger.LogInfo((object)"LuckyShotSpeedrun v0.2.3 loaded (OpenShot 2.0.3)."); } private void OnDestroy() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown GameEvents.OnShotFired -= new CancelableEvent(OnShotFired); GameEvents.OnSceneLoaded -= OnSceneLoaded; GameEvents.OnTargetSpawnerStarted -= OnTargetSpawnerStarted; GameEvents.OnWeaponReloaded -= OnWeaponReloaded_Event; GameEvents.OnShopTryBuy -= OnShopTryBuy_Event; try { Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } } catch { } } private void BindConfig() { //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Expected O, but got Unknown //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Expected O, but got Unknown //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Expected O, but got Unknown //IL_01ca: Unknown result type (might be due to invalid IL or missing references) //IL_01d4: Expected O, but got Unknown Enabled = ((BaseUnityPlugin)this).Config.Bind("General", "Enabled", true, "Master switch for the speedrun timer."); AutoStart = ((BaseUnityPlugin)this).Config.Bind("General", "AutoStart", true, "Allow auto-start triggers."); StartOnFirstShot = ((BaseUnityPlugin)this).Config.Bind("Triggers", "StartOnFirstShot", true, "Start timer on first shot."); StartOnSceneLoad = ((BaseUnityPlugin)this).Config.Bind("Triggers", "StartOnSceneLoad", false, "Start timer when a scene loads."); StartOnSpawnerStart = ((BaseUnityPlugin)this).Config.Bind("Triggers", "StartOnSpawnerStart", false, "Start when FairTargetSpawner starts."); PauseOnMenu = ((BaseUnityPlugin)this).Config.Bind("Display", "PauseOnMenu", true, "Pause timer while timeScale is 0 (menus)."); ShowAlways = ((BaseUnityPlugin)this).Config.Bind("Display", "ShowAlways", false, "Show overlay even when stopped."); EndRunOnFail = ((BaseUnityPlugin)this).Config.Bind("Rules", "EndRunOnFail", true, "Stop the timer when a category fail triggers."); MissGracePeriod = ((BaseUnityPlugin)this).Config.Bind("Rules", "MissGracePeriod", 0.5f, new ConfigDescription("Seconds after a shot with no hit before counting a miss.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 2f), Array.Empty())); LowShotThreshold = ((BaseUnityPlugin)this).Config.Bind("Rules", "LowShotThreshold", 5, new ConfigDescription("Max shots allowed in Low-Shot category.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 50), Array.Empty())); TimerPosition = ((BaseUnityPlugin)this).Config.Bind("Display", "TimerPosition", 0, new ConfigDescription("0=TopRight, 1=TopLeft, 2=TopCenter.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 2), Array.Empty())); CategoryIndex = ((BaseUnityPlugin)this).Config.Bind("Rules", "CategoryIndex", 0, new ConfigDescription("Index into CategoriesList.", (AcceptableValueBase)(object)new AcceptableValueRange(0, CategoriesList.Length - 1), Array.Empty())); } private void RegisterOpenShotMenu() { ModMenu.Tab("Speedrun").Toggle(Enabled).Info("— Auto start —") .Bool("Auto Start", AutoStart) .Bool("On First Shot", StartOnFirstShot) .Bool("On Scene Load", StartOnSceneLoad) .Bool("On Spawner", StartOnSpawnerStart) .Info("— Display —") .Bool("Show Always", ShowAlways) .Bool("Pause On Menu", PauseOnMenu) .Cycle("Position", (Func)(() => TimerPositions[Mathf.Clamp(TimerPosition.Value, 0, 2)]), (Action)delegate { TimerPosition.Value = (TimerPosition.Value + 1) % 3; }) .Info("— Rules —") .Cycle("Category", (Func)(() => CategoriesList[Mathf.Clamp(CategoryIndex.Value, 0, CategoriesList.Length - 1)]), (Action)delegate { CategoryIndex.Value = (CategoryIndex.Value + 1) % CategoriesList.Length; }) .Bool("End On Fail", EndRunOnFail) .Slider("Miss Grace", MissGracePeriod, 0.05f, "0.00") .Int("Low-Shot Max", LowShotThreshold, 1) .Info("— Timer —") .Button("Start", (Action)delegate { ResetTimer(); StartTimer(); }) .Button("Pause / Resume", (Action)TogglePause) .Button("Stop (OK)", (Action)delegate { EndRun(success: true); }) .Button("Reset", (Action)ResetTimer); } private void MigrateLegacySave() { try { LegacySettings data = SaveAPI.GetData("LuckyShotSpeedrun.Settings"); if (data != null) { AutoStart.Value = data.AutoStart; StartOnFirstShot.Value = data.StartOnFirstShot; StartOnSceneLoad.Value = data.StartOnSceneLoad; StartOnSpawnerStart.Value = data.StartOnSpawnerStart; PauseOnMenu.Value = data.PauseOnMenu; ShowAlways.Value = data.ShowAlways; EndRunOnFail.Value = data.EndRunOnFail; LowShotThreshold.Value = Mathf.Clamp(data.LowShotThreshold, 1, 50); TimerPosition.Value = Mathf.Clamp(data.TimerPosition, 0, 2); int num = Array.IndexOf(CategoriesList, data.Category); if (num >= 0) { CategoryIndex.Value = num; } Logger.LogInfo((object)"Migrated legacy SaveAPI settings → BepInEx config."); } } catch (Exception ex) { Logger.LogWarning((object)("Legacy settings migrate skipped: " + ex.Message)); } } private static void TogglePause() { if (paused) { ResumeTimer(); } else if (running) { PauseTimer(); } } private void OnShotFired(ref bool cancel) { if (!Enabled.Value) { return; } try { if (AutoStart.Value && StartOnFirstShot.Value && !running) { StartTimer(); } if (running || paused) { Shots++; pendingShot = true; lastShotTime = Time.realtimeSinceStartup; if (CurrentCategory == "Low-Shot" && Shots > LowShotThreshold.Value) { FailRun("Low-Shot threshold exceeded"); } } } catch (Exception ex) { Logger.LogError((object)("Error in OnShotFired: " + ex.Message)); } } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (!Enabled.Value) { return; } try { if (AutoStart.Value && StartOnSceneLoad.Value) { ResetTimer(); StartTimer(); } } catch (Exception ex) { Logger.LogError((object)("Error in OnSceneLoaded: " + ex.Message)); } } private void OnTargetSpawnerStarted(object obj) { if (!Enabled.Value) { return; } try { if (AutoStart.Value && StartOnSpawnerStart.Value && !running) { StartTimer(); } } catch (Exception ex) { Logger.LogError((object)("Error in OnTargetSpawnerStarted: " + ex.Message)); } } public static void StartTimer() { if (Enabled.Value && !running) { sw.Start(); running = true; paused = false; Logger.LogInfo((object)"Speedrun timer started"); } } public static void StopTimer() { if (running) { sw.Stop(); running = false; paused = false; Logger.LogInfo((object)"Speedrun timer stopped"); } } public static void PauseTimer() { if (running) { sw.Stop(); paused = true; Logger.LogInfo((object)"Speedrun timer paused"); } } public static void ResumeTimer() { if (paused) { sw.Start(); paused = false; Logger.LogInfo((object)"Speedrun timer resumed"); } } public static void ResetTimer() { sw.Reset(); running = false; paused = false; runFailed = false; Shots = 0; Hits = 0; pendingShot = false; Logger.LogInfo((object)"Speedrun timer reset"); } private static void Target_OnDisable_Postfix(object __instance) { try { if (Enabled.Value) { Hits++; pendingShot = false; if (running && CurrentCategory == "First-Target") { EndRun(success: true); } } } catch (Exception ex) { Logger.LogError((object)("Error in Target_OnDisable_Postfix: " + ex.Message)); } } private void OnWeaponReloaded_Event(object weapon, int amount, ref bool cancel) { if (!Enabled.Value) { return; } try { if (running && CurrentCategory == "One-Magazine") { Logger.LogInfo((object)"Blocking reload due to One-Magazine mode"); cancel = true; } } catch (Exception ex) { Logger.LogError((object)("Error in OnWeaponReloaded_Event: " + ex.Message)); } } private void OnShopTryBuy_Event(int cost, ref bool cancel) { if (!Enabled.Value) { return; } try { if (running && CurrentCategory == "Any% (No-Buy)") { Logger.LogInfo((object)"Blocking purchase due to No-Buy mode"); cancel = true; } } catch (Exception ex) { Logger.LogError((object)("Error in OnShopTryBuy_Event: " + ex.Message)); } } public static void CheckPendingShotTimeout() { if (Enabled.Value && running && pendingShot && !(Time.realtimeSinceStartup - lastShotTime <= MissGracePeriod.Value)) { pendingShot = false; Logger.LogInfo((object)"Miss detected"); if (CurrentCategory == "No-Miss") { FailRun("No-Miss"); } } } public static void FailRun(string reason) { Logger.LogInfo((object)("Run fail trigger: " + reason)); if (EndRunOnFail.Value) { EndRun(success: false); } else { runFailed = true; } } public static void EndRun(bool success) { if (running || paused) { sw.Stop(); running = false; paused = false; runFailed = !success; Logger.LogInfo((object)string.Format("Run {0} - Time: {1} Shots:{2} Hits:{3}", success ? "completed" : "failed", TimerDisplay.FormatTime(sw.Elapsed.TotalSeconds), Shots, Hits)); RunResult runResult = new RunResult { Success = success, Time = sw.Elapsed.TotalSeconds, Shots = Shots, Hits = Hits, Category = CurrentCategory, Timestamp = DateTime.UtcNow.ToString("o") }; SaveAPI.SetData("LuckyShotSpeedrun.LastRun", runResult); } } } public class TimerDisplay : MonoBehaviour { private GUIStyle timerStyle; private GUIStyle boxStyle; private bool menuPaused; private void Update() { if (Plugin.PauseOnMenu != null && Plugin.PauseOnMenu.Value && (Plugin.running || Plugin.paused)) { bool flag = Time.timeScale <= 0.001f; if (flag && Plugin.running && !Plugin.paused) { Plugin.PauseTimer(); menuPaused = true; } else if (!flag && menuPaused && Plugin.paused) { Plugin.ResumeTimer(); menuPaused = false; } } } private void OnGUI() { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: 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) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Expected O, but got Unknown //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0158: 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_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) if (Plugin.Enabled == null || !Plugin.Enabled.Value) { return; } if (!Plugin.running) { ConfigEntry showAlways = Plugin.ShowAlways; if (showAlways == null || !showAlways.Value) { return; } } if (timerStyle == null) { GUIStyle val = new GUIStyle(GUI.skin.label) { fontSize = 32 }; val.normal.textColor = Color.white; timerStyle = val; } if (boxStyle == null) { GUIStyle val2 = new GUIStyle(GUI.skin.box); val2.normal.background = Texture2D.whiteTexture; boxStyle = val2; } float num = 150f; float num2 = 80f; float num3 = 10f; Rect val3 = (Rect)((Plugin.TimerPosition?.Value ?? 0) switch { 1 => new Rect(num3, num3, num, num2), 2 => new Rect(((float)Screen.width - num) / 2f, num3, num, num2), _ => new Rect((float)Screen.width - num - num3, num3, num, num2), }); GUI.color = new Color(0f, 0f, 0f, 0.7f); GUI.Box(val3, "", boxStyle); GUI.color = Color.white; GUI.Label(new Rect(((Rect)(ref val3)).x, ((Rect)(ref val3)).y + 5f, ((Rect)(ref val3)).width, 40f), FormatTime(Plugin.sw.Elapsed.TotalSeconds), timerStyle); timerStyle.fontSize = 12; GUI.Label(new Rect(((Rect)(ref val3)).x, ((Rect)(ref val3)).y + 45f, ((Rect)(ref val3)).width, 30f), $"S:{Plugin.Shots} H:{Plugin.Hits}", timerStyle); timerStyle.fontSize = 32; Plugin.CheckPendingShotTimeout(); } public static string FormatTime(double seconds) { return $"{(int)seconds / 60:D2}:{(int)seconds % 60:D2}"; } } public static class PluginInfo { public const string PLUGIN_GUID = "com.liam.luckyshot.speedrun"; public const string PLUGIN_NAME = "LuckyShotSpeedrun"; public const string PLUGIN_VERSION = "0.2.3"; } }