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.3.0
SpeedrunGym.dll
Decompiled 2 months 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.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); } } } }