Decompiled source of Luckyrun v0.2.3

LuckyShotSpeedrun.dll

Decompiled 2 weeks ago
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<bool> Enabled { get; private set; }

		public static ConfigEntry<bool> AutoStart { get; private set; }

		public static ConfigEntry<bool> StartOnFirstShot { get; private set; }

		public static ConfigEntry<bool> StartOnSceneLoad { get; private set; }

		public static ConfigEntry<bool> StartOnSpawnerStart { get; private set; }

		public static ConfigEntry<bool> PauseOnMenu { get; private set; }

		public static ConfigEntry<bool> ShowAlways { get; private set; }

		public static ConfigEntry<bool> EndRunOnFail { get; private set; }

		public static ConfigEntry<float> MissGracePeriod { get; private set; }

		public static ConfigEntry<int> LowShotThreshold { get; private set; }

		public static ConfigEntry<int> TimerPosition { get; private set; }

		public static ConfigEntry<int> 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<TimerDisplay>();
			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<bool>("General", "Enabled", true, "Master switch for the speedrun timer.");
			AutoStart = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "AutoStart", true, "Allow auto-start triggers.");
			StartOnFirstShot = ((BaseUnityPlugin)this).Config.Bind<bool>("Triggers", "StartOnFirstShot", true, "Start timer on first shot.");
			StartOnSceneLoad = ((BaseUnityPlugin)this).Config.Bind<bool>("Triggers", "StartOnSceneLoad", false, "Start timer when a scene loads.");
			StartOnSpawnerStart = ((BaseUnityPlugin)this).Config.Bind<bool>("Triggers", "StartOnSpawnerStart", false, "Start when FairTargetSpawner starts.");
			PauseOnMenu = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "PauseOnMenu", true, "Pause timer while timeScale is 0 (menus).");
			ShowAlways = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "ShowAlways", false, "Show overlay even when stopped.");
			EndRunOnFail = ((BaseUnityPlugin)this).Config.Bind<bool>("Rules", "EndRunOnFail", true, "Stop the timer when a category fail triggers.");
			MissGracePeriod = ((BaseUnityPlugin)this).Config.Bind<float>("Rules", "MissGracePeriod", 0.5f, new ConfigDescription("Seconds after a shot with no hit before counting a miss.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 2f), Array.Empty<object>()));
			LowShotThreshold = ((BaseUnityPlugin)this).Config.Bind<int>("Rules", "LowShotThreshold", 5, new ConfigDescription("Max shots allowed in Low-Shot category.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 50), Array.Empty<object>()));
			TimerPosition = ((BaseUnityPlugin)this).Config.Bind<int>("Display", "TimerPosition", 0, new ConfigDescription("0=TopRight, 1=TopLeft, 2=TopCenter.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 2), Array.Empty<object>()));
			CategoryIndex = ((BaseUnityPlugin)this).Config.Bind<int>("Rules", "CategoryIndex", 0, new ConfigDescription("Index into CategoriesList.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, CategoriesList.Length - 1), Array.Empty<object>()));
		}

		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<string>)(() => TimerPositions[Mathf.Clamp(TimerPosition.Value, 0, 2)]), (Action)delegate
				{
					TimerPosition.Value = (TimerPosition.Value + 1) % 3;
				})
				.Info("— Rules —")
				.Cycle("Category", (Func<string>)(() => 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<LegacySettings>("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<RunResult>("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<bool> 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";
	}
}