Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of SpeedrunGym v0.2.0
SpeedrunGym.dll
Decompiled 3 days agousing 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); } } } }