Decompiled source of SpeedrunGym v0.2.0

SpeedrunGym.dll

Decompiled 3 days 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.2.0.0")]
[assembly: AssemblyInformationalVersion("0.2.0+02730305992a1b7c27d0e63f319a0af21f69692d")]
[assembly: AssemblyProduct("SpeedrunGym")]
[assembly: AssemblyTitle("SpeedrunGym")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/jakobhellermann/SpeedrunGym")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.2.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.2.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.2.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);
			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();
				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
			{
				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(GameObject Go, Text Text, Image Backdrop, float StartTime, Vector3 WorldPos, Color Color, bool Fade, bool MoveUp);
	internal class WorldToastManager
	{
		private const float MaxAge = 3f;

		private const float FloatSpeed = 0.5f;

		private const float BackdropAlpha = 0.5f;

		private const int DefaultFontSize = 10;

		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 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_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: 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)
			float time = Time.time;
			for (int num = entries.Count - 1; num >= 0; num--)
			{
				WorldToastEntry entry = entries[num];
				float num2 = time - entry.StartTime;
				if (num2 > 3f)
				{
					Object.Destroy((Object)(object)entry.Go);
					entries.RemoveAt(num);
				}
				else
				{
					UpdatePosition(entry);
					float num3 = (entry.Fade ? (1f - num2 / 3f) : 1f);
					((Graphic)entry.Text).color = new Color(entry.Color.r, entry.Color.g, entry.Color.b, entry.Color.a * num3);
					((Graphic)entry.Backdrop).color = new Color(0f, 0f, 0f, 0.5f * num3);
				}
			}
		}

		private void UpdatePosition(WorldToastEntry entry)
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: 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_0048: 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_004d: 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_0050: 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_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: 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)
			Camera main = Camera.main;
			if (!((Object)(object)main == (Object)null))
			{
				float num = Time.time - entry.StartTime;
				Vector3 val = (entry.MoveUp ? (entry.WorldPos + Vector3.up * (num * 0.5f)) : entry.WorldPos);
				Vector3 val2 = main.WorldToScreenPoint(val);
				Vector2 val3 = default(Vector2);
				RectTransformUtility.ScreenPointToLocalPointInRectangle(((Component)canvas).GetComponent<RectTransform>(), Vector2.op_Implicit(val2), ((int)canvas.renderMode == 0) ? null : canvas.worldCamera, ref val3);
				((Transform)entry.Go.GetComponent<RectTransform>()).localPosition = Vector2.op_Implicit(val3);
			}
		}
	}
}
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 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);
			}
		}
	}
}