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.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")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0")] [assembly: AssemblyProduct("LuckyShotSpeedrun")] [assembly: AssemblyTitle("LuckyShotSpeedrun")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace LuckyShotSpeedrun { [BepInPlugin("com.liam.luckyshot.speedrun", "LuckyShotSpeedrun", "0.1.0")] public class Plugin : BaseUnityPlugin { [Serializable] public class Settings { 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 string Seed = ""; } [Serializable] public class RunResult { public bool Success; public double Time; public int Shots; public int Hits; public string Category; public string Timestamp; } public static ManualLogSource Logger; public static Settings settings; public static Stopwatch sw = new Stopwatch(); public static bool running = false; public static bool paused = false; public static int Shots = 0; public static int Hits = 0; public static float lastShotTime = 0f; public static bool pendingShot = false; public static float hitWindow = 0.5f; public static bool runFailed = false; 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 void Awake() { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Expected O, but got Unknown //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Expected O, but got Unknown //IL_00d8: 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_00e9: Expected O, but got Unknown Logger = ((BaseUnityPlugin)this).Logger; Logger.LogInfo((object)"LuckyShotSpeedrun loaded"); settings = SaveAPI.GetData("LuckyShotSpeedrun.Settings") ?? new Settings(); 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"); if (type != null) { MethodInfo methodInfo = AccessTools.Method(type, "OnDisable", (Type[])null, (Type[])null); if (methodInfo != null) { HarmonyMethod val = new HarmonyMethod(typeof(Plugin).GetMethod("Target_OnDisable_Postfix", BindingFlags.Static | BindingFlags.NonPublic)) { priority = 400 }; harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Logger.LogInfo((object)"Patched Target.OnDisable for hit detection"); } else { Logger.LogWarning((object)"Target.OnDisable method not found"); } } else { Logger.LogWarning((object)"Target type not found - hit detection disabled"); } } catch (Exception ex) { Logger.LogError((object)("Failed to apply Target patch: " + ex.Message + "\n" + ex.StackTrace)); } GameObject val2 = new GameObject("LuckyShotSpeedrun_Timer"); Object.DontDestroyOnLoad((Object)val2); val2.AddComponent(); } 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 { } SaveAPI.SetData("LuckyShotSpeedrun.Settings", settings); } private void OnShotFired(ref bool cancel) { try { if (settings.AutoStart && settings.StartOnFirstShot && !running) { StartTimer(); } Shots++; pendingShot = true; lastShotTime = Time.realtimeSinceStartup; } catch (Exception ex) { Logger.LogError((object)("Error in OnShotFired: " + ex.Message)); } } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { try { if (settings.AutoStart && settings.StartOnSceneLoad) { ResetTimer(); StartTimer(); } } catch (Exception ex) { Logger.LogError((object)("Error in OnSceneLoaded: " + ex.Message)); } } private void OnTargetSpawnerStarted(object obj) { try { if (settings.AutoStart && settings.StartOnSpawnerStart && !running) { StartTimer(); } } catch (Exception ex) { Logger.LogError((object)("Error in OnTargetSpawnerStarted: " + ex.Message)); } } public static void StartTimer() { if (!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 { Hits++; pendingShot = false; Logger.LogInfo((object)$"Target disabled - hits={Hits}"); if (running && settings != null && settings.Category == "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) { try { if (running && settings != null && settings.Category == "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) { try { if (running && settings != null && settings.Category == "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 (running && pendingShot && Time.realtimeSinceStartup - lastShotTime > hitWindow) { pendingShot = false; Logger.LogInfo((object)"Miss detected"); if (settings != null && settings.Category == "No-Miss") { EndRun(success: false); } } } 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 = (settings?.Category ?? ""), Timestamp = DateTime.UtcNow.ToString("o") }; SaveAPI.SetData("LuckyShotSpeedrun.LastRun", runResult); } } [OpenShotMenuTab("Speedrun Timer")] public static void DrawMenu() { UIHelper.DrawHeader("LuckyShot Speedrun Timer"); if (settings == null) { settings = new Settings(); } bool flag = UIHelper.DrawSettings((object)settings); UIHelper.DrawLabel("Category:"); GUILayout.BeginHorizontal(Array.Empty()); string[] categoriesList = CategoriesList; foreach (string text in categoriesList) { if (text == settings.Category) { if (GUILayout.Button(text, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) })) { settings.Category = text; flag = true; } } else if (GUILayout.Button(text, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) })) { settings.Category = text; flag = true; } } GUILayout.EndHorizontal(); UIHelper.DrawLabel("Time: " + TimerDisplay.FormatTime(sw.Elapsed.TotalSeconds)); UIHelper.DrawLabel($"Shots: {Shots}, Hits: {Hits}"); GUILayout.BeginHorizontal(Array.Empty()); if (!running && !paused) { if (UIHelper.DrawButton("Start")) { ResetTimer(); StartTimer(); } } else if (running) { if (UIHelper.DrawButton("Pause")) { PauseTimer(); } } else if (paused && UIHelper.DrawButton("Resume")) { ResumeTimer(); } if (UIHelper.DrawButton("Stop")) { EndRun(success: true); } if (UIHelper.DrawButton("Reset")) { ResetTimer(); } GUILayout.EndHorizontal(); if (settings.Category == "Set-Seed") { UIHelper.DrawLabel("Seed (for run submission):"); string text2 = GUILayout.TextField(settings.Seed ?? "", Array.Empty()); if (text2 != settings.Seed) { settings.Seed = text2; flag = true; } } if (flag) { SaveAPI.SetData("LuckyShotSpeedrun.Settings", settings); } } } public class TimerDisplay : MonoBehaviour { private GUIStyle labelStyle; private void OnGUI() { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown if (!Plugin.running) { Plugin.Settings settings = Plugin.settings; if (settings == null || !settings.ShowAlways) { return; } } if (labelStyle == null) { labelStyle = new GUIStyle(GUI.skin.label) { fontSize = 24 }; } GUI.Label(new Rect((float)(Screen.width - 300), 20f, 280f, 60f), FormatTime(Plugin.sw.Elapsed.TotalSeconds), labelStyle); Plugin.Settings settings2 = Plugin.settings; if (settings2 != null && settings2.PauseOnMenu && Input.GetKeyDown((KeyCode)282)) { if (Plugin.running) { Plugin.PauseTimer(); } else if (Plugin.paused) { Plugin.ResumeTimer(); } } Plugin.CheckPendingShotTimeout(); } public static string FormatTime(double seconds) { return $"{(int)seconds / 60:D2}:{(int)seconds % 60:D2}"; } } public class PluginInfo { public const string PLUGIN_GUID = "com.liam.luckyshot.speedrun"; public const string PLUGIN_NAME = "LuckyShotSpeedrun"; public const string PLUGIN_VERSION = "0.1.0"; } }