Decompiled source of SpeedrunGym v0.3.0

SpeedrunGym.dll

Decompiled 2 months ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using GlobalEnums;
using HarmonyLib;
using HutongGames.PlayMaker;
using HutongGames.PlayMaker.Actions;
using InControl;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
using SpeedrunGym.Source.Moves;
using SpeedrunGym.Source.WorldToasts;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[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("SpeedrunGym")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.3.0.0")]
[assembly: AssemblyInformationalVersion("0.3.0+5703f9893ae30a0bd67bd0996095106d4d675a64")]
[assembly: AssemblyProduct("SpeedrunGym")]
[assembly: AssemblyTitle("SpeedrunGym")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/jakobhellermann/SpeedrunGym")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.3.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 BepInEx
{
	[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
	[Conditional("CodeGeneration")]
	[Embedded]
	internal sealed class BepInAutoPluginAttribute : Attribute
	{
		public BepInAutoPluginAttribute(string? id = null, string? name = null, string? version = null)
		{
		}
	}
}
namespace BepInEx.Preloader.Core.Patching
{
	[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
	[Conditional("CodeGeneration")]
	[Embedded]
	internal sealed class PatcherAutoPluginAttribute : Attribute
	{
		public PatcherAutoPluginAttribute(string? id = null, string? name = null, string? version = null)
		{
		}
	}
}
namespace Microsoft.CodeAnalysis
{
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace SpeedrunGym.Source
{
	[PublicAPI]
	internal static class Log
	{
		private static ManualLogSource? logSource;

		internal static void Init(ManualLogSource logSource)
		{
			Log.logSource = logSource;
		}

		internal static void Debug(object data)
		{
			ManualLogSource? obj = logSource;
			if (obj != null)
			{
				obj.LogDebug(data);
			}
		}

		internal static void Error(object data)
		{
			ManualLogSource? obj = logSource;
			if (obj != null)
			{
				obj.LogError(data);
			}
		}

		internal static void Fatal(object data)
		{
			ManualLogSource? obj = logSource;
			if (obj != null)
			{
				obj.LogFatal(data);
			}
		}

		internal static void Info(object data)
		{
			ManualLogSource? obj = logSource;
			if (obj != null)
			{
				obj.LogInfo(data);
			}
		}

		internal static void Message(object data)
		{
			ManualLogSource? obj = logSource;
			if (obj != null)
			{
				obj.LogMessage(data);
			}
		}

		internal static void Warning(object data)
		{
			ManualLogSource? obj = logSource;
			if (obj != null)
			{
				obj.LogWarning(data);
			}
		}
	}
	[BepInPlugin("io.github.jakobhellermann.speedrungym", "SpeedrunGym", "0.3.0")]
	public class SpeedrunGymPlugin : BaseUnityPlugin
	{
		private Harmony harmony;

		private WorldToastManager worldToasts;

		public const string Id = "io.github.jakobhellermann.speedrungym";

		public static string Name => "SpeedrunGym";

		public static string Version => "0.3.0";

		private void Awake()
		{
			Log.Init(((BaseUnityPlugin)this).Logger);
			Log.Info("Plugin " + Name + " (io.github.jakobhellermann.speedrungym) has loaded!");
			ForceCrawPogo.BindConfig(((BaseUnityPlugin)this).Config);
			PogoEndlagDetector.BindConfig(((BaseUnityPlugin)this).Config);
			JumpTimingDetector.BindConfig(((BaseUnityPlugin)this).Config);
			WorldToastManager.MaxAge = ((BaseUnityPlugin)this).Config.Bind<float>("Toasts", "Lifetime seconds", 3f, "How long feedback popups stay on screen before fading out.").Value;
			worldToasts = WorldToastManager.Create();
			SceneManager.sceneLoaded += OnSceneLoaded;
			try
			{
				harmony = Harmony.CreateAndPatchAll(((object)this).GetType().Assembly, (string)null);
			}
			catch (Exception arg)
			{
				Log.Info(string.Format("Plugin {0} ({1}) failed to initialize: {2}", Name, "io.github.jakobhellermann.speedrungym", arg));
			}
		}

		private void LateUpdate()
		{
			try
			{
				PogoEndlagDetector.LateUpdate();
				JumpTimingDetector.LateUpdate();
				worldToasts.Update();
			}
			catch (Exception arg)
			{
				Log.Error($"Error during LateUpdate: {arg}");
			}
		}

		private void OnDestroy()
		{
			try
			{
				SceneManager.sceneLoaded -= OnSceneLoaded;
				harmony.UnpatchSelf();
				ForceCrawPogo.Cleanup();
				worldToasts.Destroy();
			}
			catch (Exception arg)
			{
				Log.Info(string.Format("Plugin {0} ({1}) failed to clean up: {2}", Name, "io.github.jakobhellermann.speedrungym", arg));
			}
			Log.Info("Plugin " + Name + " (io.github.jakobhellermann.speedrungym) has been unloaded!");
		}

		private static void OnSceneLoaded(Scene scene, LoadSceneMode loadMode)
		{
			try
			{
				WorldToastManager.ClearAll();
				ForceCrawPogo.OnSceneLoaded();
			}
			catch (Exception arg)
			{
				Log.Error($"Error during OnSceneLoaded: {arg}");
			}
		}
	}
	[HarmonyPatch]
	internal static class FsmStatePatches
	{
		[HarmonyPrefix]
		[HarmonyPatch(typeof(Fsm), "EnterState", new Type[] { typeof(FsmState) })]
		private static void EnterState(Fsm __instance, FsmState state)
		{
			try
			{
				ForceCrawPogo.OnEnterState(__instance, state);
			}
			catch (Exception data)
			{
				Log.Error(data);
			}
		}
	}
}
namespace SpeedrunGym.Source.WorldToasts
{
	internal record struct WorldToastEntry
	{
		public GameObject Go { get; set; }

		public Text Text { get; set; }

		public Image Backdrop { get; set; }

		public float StartTime { get; set; }

		public Vector3 WorldPos { get; set; }

		public Color Color { get; set; }

		public bool Fade { get; set; }

		public bool MoveUp { get; set; }

		public bool Clamped;

		public WorldToastEntry(GameObject Go, Text Text, Image Backdrop, float StartTime, Vector3 WorldPos, Color Color, bool Fade, bool MoveUp)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			Clamped = false;
			this.Go = Go;
			this.Text = Text;
			this.Backdrop = Backdrop;
			this.StartTime = StartTime;
			this.WorldPos = WorldPos;
			this.Color = Color;
			this.Fade = Fade;
			this.MoveUp = MoveUp;
		}

		[CompilerGenerated]
		public readonly void Deconstruct(out GameObject Go, out Text Text, out Image Backdrop, out float StartTime, out Vector3 WorldPos, out Color Color, out bool Fade, out bool MoveUp)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			Go = this.Go;
			Text = this.Text;
			Backdrop = this.Backdrop;
			StartTime = this.StartTime;
			WorldPos = this.WorldPos;
			Color = this.Color;
			Fade = this.Fade;
			MoveUp = this.MoveUp;
		}
	}
	internal class WorldToastManager
	{
		internal static float MaxAge = 3f;

		private const float FadeFraction = 1f / 3f;

		private const float FloatSpeed = 0.5f;

		private const float BackdropAlpha = 0.5f;

		private const int DefaultFontSize = 10;

		private const float ScreenMargin = 40f;

		private static WorldToastManager? instance;

		private readonly Canvas canvas;

		private readonly List<WorldToastEntry> entries = new List<WorldToastEntry>();

		private WorldToastManager(Canvas canvas)
		{
			this.canvas = canvas;
		}

		internal static WorldToastManager Create()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			GameObject val = new GameObject("SpeedrunGymToastCanvas");
			Canvas val2 = val.AddComponent<Canvas>();
			val2.renderMode = (RenderMode)0;
			val.AddComponent<CanvasScaler>().uiScaleMode = (ScaleMode)1;
			Object.DontDestroyOnLoad((Object)val);
			return instance = new WorldToastManager(val2);
		}

		internal static void ClearAll()
		{
			instance?.ClearAllInner();
		}

		private void ClearAllInner()
		{
			foreach (WorldToastEntry entry in entries)
			{
				if (Object.op_Implicit((Object)(object)entry.Go))
				{
					Object.Destroy((Object)(object)entry.Go);
				}
			}
			entries.Clear();
		}

		internal void Destroy()
		{
			if (Object.op_Implicit((Object)(object)canvas))
			{
				Object.Destroy((Object)(object)((Component)canvas).gameObject);
			}
			if (instance == this)
			{
				instance = null;
			}
		}

		internal static void Show(string message, Vector3 worldPos, Color? color = null, int fontSize = 10, bool fade = true, bool moveUp = true)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			instance?.ShowInner(message, worldPos, color, fontSize, fade, moveUp);
		}

		private void ShowInner(string message, Vector3 worldPos, Color? color, int fontSize, bool fade, bool moveUp)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Expected O, but got Unknown
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			Color color2 = (Color)(((??)color) ?? Color.white);
			GameObject val = new GameObject("WorldToast");
			val.transform.SetParent(((Component)canvas).transform, false);
			Image val2 = val.AddComponent<Image>();
			((Graphic)val2).color = new Color(0f, 0f, 0f, 0.5f);
			((Graphic)val2).raycastTarget = false;
			HorizontalLayoutGroup obj = val.AddComponent<HorizontalLayoutGroup>();
			((LayoutGroup)obj).padding = new RectOffset(4, 4, 2, 2);
			((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true;
			((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true;
			ContentSizeFitter obj2 = val.AddComponent<ContentSizeFitter>();
			obj2.horizontalFit = (FitMode)2;
			obj2.verticalFit = (FitMode)2;
			GameObject val3 = new GameObject("Text");
			val3.transform.SetParent(val.transform, false);
			Text val4 = val3.AddComponent<Text>();
			val4.text = message;
			val4.fontSize = fontSize;
			((Graphic)val4).color = color2;
			val4.alignment = (TextAnchor)4;
			val4.font = Resources.GetBuiltinResource<Font>("Arial.ttf");
			((Graphic)val4).raycastTarget = false;
			val4.horizontalOverflow = (HorizontalWrapMode)1;
			entries.Add(new WorldToastEntry(val, val4, val2, Time.time, worldPos, color2, fade, moveUp));
			List<WorldToastEntry> list = entries;
			UpdatePosition(list[list.Count - 1]);
		}

		internal void Update()
		{
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			float time = Time.time;
			for (int num = entries.Count - 1; num >= 0; num--)
			{
				WorldToastEntry worldToastEntry = entries[num];
				bool flag = UpdatePosition(worldToastEntry);
				float num2 = time - worldToastEntry.StartTime;
				float num3 = MaxAge * 0.6666666f;
				if (worldToastEntry.Clamped && !flag && num2 < num3)
				{
					worldToastEntry.StartTime = time;
					num2 = 0f;
				}
				worldToastEntry.Clamped = flag;
				if (num2 > MaxAge)
				{
					Object.Destroy((Object)(object)worldToastEntry.Go);
					entries.RemoveAt(num);
				}
				else
				{
					float num4 = (worldToastEntry.Fade ? Mathf.Clamp01((MaxAge - num2) / (MaxAge * (1f / 3f))) : 1f);
					((Graphic)worldToastEntry.Text).color = new Color(worldToastEntry.Color.r, worldToastEntry.Color.g, worldToastEntry.Color.b, worldToastEntry.Color.a * num4);
					((Graphic)worldToastEntry.Backdrop).color = new Color(0f, 0f, 0f, 0.5f * num4);
					entries[num] = worldToastEntry;
				}
			}
		}

		private bool UpdatePosition(WorldToastEntry entry)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: 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_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: 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_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: Unknown result type (might be due to invalid IL or missing references)
			Camera main = Camera.main;
			if ((Object)(object)main == (Object)null)
			{
				return false;
			}
			float num = Time.time - entry.StartTime;
			Vector3 val = (entry.MoveUp ? (entry.WorldPos + Vector3.up * (num * 0.5f)) : entry.WorldPos);
			Vector3 val2 = main.WorldToScreenPoint(val);
			if (val2.z < 0f)
			{
				val2.x = (float)Screen.width - val2.x;
				val2.y = (float)Screen.height - val2.y;
			}
			RectTransform component = ((Component)canvas).GetComponent<RectTransform>();
			Vector2 val3 = default(Vector2);
			RectTransformUtility.ScreenPointToLocalPointInRectangle(component, Vector2.op_Implicit(val2), ((int)canvas.renderMode == 0) ? null : canvas.worldCamera, ref val3);
			Rect rect = component.rect;
			float num2 = Mathf.Clamp(val3.x, ((Rect)(ref rect)).xMin + 40f, ((Rect)(ref rect)).xMax - 40f);
			float num3 = Mathf.Clamp(val3.y, ((Rect)(ref rect)).yMin + 40f, ((Rect)(ref rect)).yMax - 40f);
			bool result = !Mathf.Approximately(num2, val3.x) || !Mathf.Approximately(num3, val3.y);
			((Transform)entry.Go.GetComponent<RectTransform>()).localPosition = Vector2.op_Implicit(new Vector2(num2, num3));
			return result;
		}
	}
}
namespace SpeedrunGym.Source.Moves
{
	internal static class ForceCrawPogo
	{
		private enum Mode
		{
			Normal,
			Early,
			Late1,
			Late2
		}

		private static ConfigEntry<Mode> mode = null;

		private static int decisionCount;

		private static SendRandomEventV4? patchedAction;

		private static float[] origWeights = Array.Empty<float>();

		private static int[] origEventMax = Array.Empty<int>();

		private static int[] origMissedMax = Array.Empty<int>();

		internal static void BindConfig(ConfigFile config)
		{
			mode = config.Bind<Mode>("Normalization", "Force craw pogo", Mode.Normal, "Force how often the craw in Greymoor_15 flaps before diving.");
			mode.SettingChanged += delegate
			{
				decisionCount = 0;
				Restore();
			};
		}

		internal static void Cleanup()
		{
			Restore();
		}

		internal static void OnSceneLoaded()
		{
			decisionCount = 0;
			patchedAction = null;
		}

		private static void Restore()
		{
			if (patchedAction != null)
			{
				for (int i = 0; i < origWeights.Length; i++)
				{
					patchedAction.weights[i].Value = origWeights[i];
				}
				for (int j = 0; j < origEventMax.Length; j++)
				{
					patchedAction.eventMax[j].Value = origEventMax[j];
				}
				for (int k = 0; k < origMissedMax.Length; k++)
				{
					patchedAction.missedMax[k].Value = origMissedMax[k];
				}
				patchedAction = null;
			}
		}

		internal static void OnEnterState(Fsm fsm, FsmState state)
		{
			if (mode.Value == Mode.Normal || state.Name != "Flap Move" || fsm.Name != "Behaviour")
			{
				return;
			}
			GameObject gameObject = fsm.GameObject;
			if (!Object.op_Implicit((Object)(object)gameObject) || ((Object)gameObject).name != "Crow (3)")
			{
				return;
			}
			FsmStateAction? obj = Array.Find(state.Actions, (FsmStateAction a) => a is SendRandomEventV4);
			SendRandomEventV4 val = (SendRandomEventV4)(object)((obj is SendRandomEventV4) ? obj : null);
			if (val == null)
			{
				return;
			}
			int num = Array.FindIndex(val.events, (FsmEvent e) => e.Name == "ATTACK");
			int num2 = Array.FindIndex(val.events, (FsmEvent e) => e.Name == "SHIFT");
			if (num < 0 || num2 < 0)
			{
				return;
			}
			if (patchedAction != val)
			{
				patchedAction = val;
				origWeights = Array.ConvertAll(val.weights, (FsmFloat w) => w.Value);
				origEventMax = Array.ConvertAll(val.eventMax, (FsmInt m) => m.Value);
				origMissedMax = Array.ConvertAll(val.missedMax, (FsmInt m) => m.Value);
			}
			int num3 = mode.Value switch
			{
				Mode.Early => 1, 
				Mode.Late1 => 2, 
				Mode.Late2 => 3, 
				_ => 0, 
			};
			decisionCount++;
			bool flag = decisionCount >= num3;
			Log.Info(string.Format("[craw] normalize flap-move decision #{0} (target {1}) -> {2}", decisionCount, num3, flag ? "ATTACK" : "SHIFT"));
			if (flag)
			{
				decisionCount = 0;
			}
			int num4 = (flag ? num : num2);
			for (int num5 = 0; num5 < val.weights.Length; num5++)
			{
				val.weights[num5].Value = ((num5 == num4) ? 1f : 0f);
			}
			FsmInt[] eventMax = val.eventMax;
			for (int num6 = 0; num6 < eventMax.Length; num6++)
			{
				eventMax[num6].Value = int.MaxValue;
			}
			eventMax = val.missedMax;
			for (int num6 = 0; num6 < eventMax.Length; num6++)
			{
				eventMax[num6].Value = int.MaxValue;
			}
		}
	}
	internal static class JumpTimingDetector
	{
		private const string Section = "Jump Timing";

		private static ConfigEntry<bool> repress = null;

		private static ConfigEntry<bool> sprintjumpRelease = null;

		private static readonly Color Color = new Color(0.4f, 1f, 0.4f);

		private const float ResetMs = 250f;

		private static bool prevPressed;

		private static PogoEndlagDetector.FrameTime? released;

		private static bool shuttlecockActive;

		internal static void BindConfig(ConfigFile config)
		{
			repress = config.Bind<bool>("Jump Timing", "Repress", false, "Report how long each air jump repress took (release → re-press) in a popup next to Hornet.");
			sprintjumpRelease = config.Bind<bool>("Jump Timing", "Sprintjump Release", false, "Out of a dashjump (shuttlecock), report the vertical velocity at the moment jump was released.");
		}

		internal static void OnInputCleared()
		{
			released = null;
			prevPressed = false;
		}

		internal static void OnShuttleCockJump()
		{
			shuttlecockActive = true;
		}

		internal static void LateUpdate()
		{
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			if (!repress.Value && !sprintjumpRelease.Value)
			{
				return;
			}
			InputHandler silentInstance = ManagerSingleton<InputHandler>.SilentInstance;
			if ((Object)(object)silentInstance == (Object)null)
			{
				return;
			}
			bool isPressed = ((OneAxisInputControl)silentInstance.inputActions.Jump).IsPressed;
			HeroController silentInstance2 = HeroController.SilentInstance;
			if (Object.op_Implicit((Object)(object)silentInstance2) && (silentInstance2.cState.onGround || silentInstance2.cState.dashing))
			{
				released = null;
				shuttlecockActive = false;
				prevPressed = isPressed;
				return;
			}
			PogoEndlagDetector.FrameTime now = PogoEndlagDetector.FrameTime.Now;
			if (prevPressed && !isPressed)
			{
				released = now;
				if (shuttlecockActive)
				{
					if (sprintjumpRelease.Value)
					{
						HeroToast(silentInstance2, $"release yvel={0f - YVel(silentInstance2):+0.0;-0.0}", Color, 1.9f);
					}
					shuttlecockActive = false;
				}
			}
			else
			{
				PogoEndlagDetector.FrameTime? frameTime;
				if (!prevPressed && isPressed)
				{
					frameTime = released;
					if (frameTime.HasValue)
					{
						PogoEndlagDetector.FrameTime valueOrDefault = frameTime.GetValueOrDefault();
						PogoEndlagDetector.FrameTimeDelta delta = now - valueOrDefault;
						released = null;
						if (repress.Value && delta.Ms <= 250f)
						{
							HeroToast(silentInstance2, $"repress +{Discount(delta)}", Color, 1.2f);
						}
						goto IL_0190;
					}
				}
				frameTime = released;
				if (frameTime.HasValue)
				{
					PogoEndlagDetector.FrameTime valueOrDefault2 = frameTime.GetValueOrDefault();
					if ((now - valueOrDefault2).Ms > 250f)
					{
						released = null;
					}
				}
			}
			goto IL_0190;
			IL_0190:
			prevPressed = isPressed;
		}

		private static float YVel(HeroController? hero)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)hero))
			{
				return 0f;
			}
			Rigidbody2D component = ((Component)hero).GetComponent<Rigidbody2D>();
			if (!((Object)(object)component != (Object)null))
			{
				return 0f;
			}
			return component.linearVelocity.y;
		}

		private static PogoEndlagDetector.FrameTimeDelta Discount(PogoEndlagDetector.FrameTimeDelta delta)
		{
			int num = Math.Max(0, delta.Frames - 1);
			float ms = ((delta.Frames > 0) ? (delta.Ms * (float)num / (float)delta.Frames) : 0f);
			return new PogoEndlagDetector.FrameTimeDelta
			{
				Frames = num,
				Ms = ms
			};
		}

		private static void HeroToast(HeroController? hero, string message, Color color, float heightOffset)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			if (Object.op_Implicit((Object)(object)hero))
			{
				WorldToastManager.Show(message, hero.transform.position + Vector3.up * heightOffset + Vector3.right * (float)(hero.cState.facingRight ? 1 : (-1)), color, 10, fade: true, moveUp: false);
			}
		}
	}
	[HarmonyPatch]
	internal static class JumpTimingPatches
	{
		[HarmonyPostfix]
		[HarmonyPatch(typeof(HeroController), "ClearJumpInputState")]
		private static void ClearJumpInputState()
		{
			try
			{
				JumpTimingDetector.OnInputCleared();
			}
			catch (Exception data)
			{
				Log.Error(data);
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(HeroController), "OnShuttleCockJump")]
		private static void OnShuttleCockJump()
		{
			try
			{
				JumpTimingDetector.OnShuttleCockJump();
			}
			catch (Exception data)
			{
				Log.Error(data);
			}
		}
	}
	internal static class PogoEndlagDetector
	{
		internal record struct FrameTime
		{
			public static FrameTime Now => new FrameTime
			{
				frame = Time.frameCount,
				Time = Time.realtimeSinceStartup
			};

			private int frame;

			private float Time;

			public static FrameTimeDelta operator -(FrameTime a, FrameTime b)
			{
				return new FrameTimeDelta
				{
					Frames = a.frame - b.frame,
					Ms = (a.Time - b.Time) * 1000f
				};
			}

			[CompilerGenerated]
			private readonly bool PrintMembers(StringBuilder builder)
			{
				return false;
			}
		}

		internal record struct FrameTimeDelta
		{
			public int Frames;

			public float Ms;

			public override string ToString()
			{
				if (!showFrameCounts.Value)
				{
					return $"{Ms:0}ms";
				}
				return $"{Frames}f ({Ms:0}ms)";
			}
		}

		private record CurrentDownspike
		{
			public FrameTime? DirectionPress;

			public FrameTime? FirstCanJump;

			public FrameTime? JumpExecuted;

			public FrameTime LastInput;

			public FrameTime? NeutralDeadline;

			public bool ReleasedNeutral;

			public FrameTime? RelevantInput;

			public FrameTimeDelta? RepressDelta;

			public bool Success;

			public bool WasJumping;
		}

		private const string Section = "Pogo Endlag";

		private static ConfigEntry<bool> enabled = null;

		private static ConfigEntry<bool> showGood = null;

		private static ConfigEntry<float> slowRepressThresholdMs = null;

		private static ConfigEntry<float> slowJumpThresholdMs = null;

		private static ConfigEntry<bool> showFrameCounts = null;

		private static readonly Color GoodColor = new Color(0.4f, 1f, 0.4f);

		private static readonly Color SlowColor = new Color(1f, 1f, 0.4f);

		private static readonly Color FailColor = new Color(1f, 0.3f, 0.4f);

		private static CurrentDownspike? startedDownspike;

		internal static void BindConfig(ConfigFile config)
		{
			enabled = config.Bind<bool>("Pogo Endlag", "Enabled", false, "Detect pogo (downspike) endlag cancels and show feedback popups next to Hornet.");
			showGood = config.Bind<bool>("Pogo Endlag", "Show good", true, "Show a popup on a successful endlag cancel.");
			slowRepressThresholdMs = config.Bind<float>("Pogo Endlag", "Slow repress threshold", 60f, "Repress slower than this many ms turns the popup yellow.");
			slowJumpThresholdMs = config.Bind<float>("Pogo Endlag", "Slow jump threshold", 60f, "Jump slower than this many ms turns the popup yellow.");
			showFrameCounts = config.Bind<bool>("Pogo Endlag", "Show frame counts", false, "Include the frame count in popups (e.g. '3f (50ms)') instead of just milliseconds.");
		}

		internal static void OnDownAttack(HeroController hero)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			if (enabled.Value && (int)hero.Config.DownSlashType == 0 && !hero.cState.shuttleCock)
			{
				startedDownspike = new CurrentDownspike();
			}
		}

		internal static void OnUpdateState(HeroAnimationController anim, ActorStates newState)
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Expected I4, but got Unknown
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			if ((object)startedDownspike == null)
			{
				return;
			}
			FrameTime? relevantInput = startedDownspike.RelevantInput;
			if (relevantInput.HasValue)
			{
				return;
			}
			HeroControllerStates cState = HeroController.SilentInstance.cState;
			if (!cState.downSpiking && !cState.downSpikeRecovery)
			{
				return;
			}
			FrameTime now = FrameTime.Now;
			switch (newState - 1)
			{
			case 0:
				startedDownspike = startedDownspike with
				{
					RelevantInput = now
				};
				startedDownspike.Success = true;
				startedDownspike.WasJumping = cState.jumping;
				break;
			case 1:
				startedDownspike = startedDownspike with
				{
					RelevantInput = now,
					NeutralDeadline = startedDownspike.LastInput,
					Success = false
				};
				relevantInput = startedDownspike.DirectionPress;
				if (relevantInput.HasValue)
				{
					FrameTime valueOrDefault = relevantInput.GetValueOrDefault();
					HeroToast($"dir repress {now - valueOrDefault} early", FailColor);
				}
				break;
			case 2:
				break;
			}
		}

		private static void HeroToast(string message, Color color)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			HeroController silentInstance = HeroController.SilentInstance;
			if (Object.op_Implicit((Object)(object)silentInstance))
			{
				WorldToastManager.Show(message, silentInstance.transform.position + Vector3.up * 1.5f + Vector3.right * (float)(silentInstance.cState.facingRight ? 1 : (-1)), color, 10, fade: true, moveUp: false);
			}
		}

		internal static void LateUpdate()
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_023e: Unknown result type (might be due to invalid IL or missing references)
			HeroController silentInstance = HeroController.SilentInstance;
			if (!Object.op_Implicit((Object)(object)silentInstance))
			{
				return;
			}
			CurrentDownspike currentDownspike = startedDownspike;
			if ((object)currentDownspike == null)
			{
				return;
			}
			if (silentInstance.cState.dashing)
			{
				startedDownspike = null;
				return;
			}
			float x = ((TwoAxisInputControl)ManagerSingleton<InputHandler>.SilentInstance.inputActions.MoveVector).Vector.x;
			FrameTime? directionPress;
			if (x == 0f)
			{
				currentDownspike.ReleasedNeutral = true;
			}
			else if ((object)currentDownspike != null && currentDownspike.ReleasedNeutral)
			{
				directionPress = currentDownspike.DirectionPress;
				if (!directionPress.HasValue)
				{
					currentDownspike.DirectionPress = FrameTime.Now;
				}
			}
			currentDownspike.LastInput = FrameTime.Now;
			if ((object)currentDownspike == null)
			{
				return;
			}
			directionPress = currentDownspike.RelevantInput;
			if (!directionPress.HasValue)
			{
				return;
			}
			FrameTime valueOrDefault = directionPress.GetValueOrDefault();
			FrameTimeDelta value = FrameTime.Now - valueOrDefault;
			bool flag = value.Ms > 300f;
			if (currentDownspike.Success)
			{
				if (!showGood.Value)
				{
					startedDownspike = null;
					return;
				}
				FrameTimeDelta? repressDelta = currentDownspike.RepressDelta;
				if (!repressDelta.HasValue && x != 0f)
				{
					currentDownspike.RepressDelta = value;
				}
				FrameTime? firstCanJump;
				if ((object)currentDownspike != null)
				{
					directionPress = currentDownspike.JumpExecuted;
					if (!directionPress.HasValue)
					{
						firstCanJump = currentDownspike.FirstCanJump;
						if (!firstCanJump.HasValue && silentInstance.CanJump())
						{
							currentDownspike.FirstCanJump = FrameTime.Now;
						}
					}
				}
				firstCanJump = currentDownspike.JumpExecuted;
				if (!firstCanJump.HasValue && silentInstance.cState.jumping && !currentDownspike.WasJumping)
				{
					currentDownspike.JumpExecuted = FrameTime.Now;
				}
				currentDownspike.WasJumping = silentInstance.cState.jumping;
				if ((object)currentDownspike != null)
				{
					repressDelta = currentDownspike.RepressDelta;
					if (repressDelta.HasValue)
					{
						firstCanJump = currentDownspike.JumpExecuted;
						if (firstCanJump.HasValue)
						{
							goto IL_01c3;
						}
					}
				}
				if (!flag)
				{
					return;
				}
				goto IL_01c3;
			}
			if (flag)
			{
				FrameTime? firstCanJump = currentDownspike.DirectionPress;
				if (!firstCanJump.HasValue)
				{
					HeroToast("no neutral", FailColor);
				}
				startedDownspike = null;
			}
			else if (x == 0f)
			{
				FrameTime? firstCanJump = currentDownspike.DirectionPress;
				if (!firstCanJump.HasValue)
				{
					FrameTime frameTime = currentDownspike.NeutralDeadline ?? valueOrDefault;
					HeroToast($"neutral {FrameTime.Now - frameTime} late", FailColor);
				}
				startedDownspike = null;
			}
			return;
			IL_01c3:
			ShowSuccessResult(currentDownspike);
			startedDownspike = null;
		}

		private static void ShowSuccessResult(CurrentDownspike d)
		{
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			bool flag = false;
			string text = null;
			FrameTimeDelta? repressDelta = d.RepressDelta;
			if (repressDelta.HasValue)
			{
				FrameTimeDelta valueOrDefault = repressDelta.GetValueOrDefault();
				if (valueOrDefault.Ms >= slowRepressThresholdMs.Value)
				{
					flag = true;
				}
				text = $"repress +{valueOrDefault}";
			}
			string text2 = null;
			FrameTime? jumpExecuted = d.JumpExecuted;
			if (jumpExecuted.HasValue)
			{
				FrameTime valueOrDefault2 = jumpExecuted.GetValueOrDefault();
				jumpExecuted = d.FirstCanJump;
				FrameTimeDelta obj;
				if (jumpExecuted.HasValue)
				{
					FrameTime valueOrDefault3 = jumpExecuted.GetValueOrDefault();
					obj = valueOrDefault2 - valueOrDefault3;
				}
				else
				{
					obj = default(FrameTimeDelta);
				}
				FrameTimeDelta frameTimeDelta = obj;
				if (frameTimeDelta.Ms >= slowJumpThresholdMs.Value)
				{
					flag = true;
				}
				text2 = $"jump +{frameTimeDelta}";
			}
			string text3 = ((text == null) ? text2 : ((text2 == null) ? text : (text + "\n" + text2)));
			if (text3 != null)
			{
				HeroToast(text3, flag ? SlowColor : GoodColor);
			}
		}
	}
	[HarmonyPatch]
	internal static class PogoEndlagPatches
	{
		[HarmonyPrefix]
		[HarmonyPatch(typeof(HeroController), "DownAttack")]
		private static void DownAttack(HeroController __instance)
		{
			try
			{
				PogoEndlagDetector.OnDownAttack(__instance);
			}
			catch (Exception data)
			{
				Log.Error(data);
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(HeroAnimationController), "UpdateState")]
		private static void UpdateState(HeroAnimationController __instance, ActorStates newState)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				PogoEndlagDetector.OnUpdateState(__instance, newState);
			}
			catch (Exception data)
			{
				Log.Error(data);
			}
		}
	}
}