using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("Stride")] [assembly: AssemblyDescription("Invisible improvements to vanilla movement in Valheim")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Stride")] [assembly: AssemblyCopyright("Copyright (c) 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("77f1130e-4e67-4f2d-bd7e-d365e48f9120")] [assembly: AssemblyFileVersion("0.6.3.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("0.6.3.0")] namespace StrideMod; internal static class PluginInfo { internal const string Guid = "com.b0n3.stride"; internal const string Name = "Stride"; internal const string Version = "0.6.3"; } [BepInPlugin("com.b0n3.stride", "Stride", "0.6.3")] public class StridePlugin : BaseUnityPlugin { internal enum DebugLogFeature { CoyoteTime, JumpBuffering, WallJump, FallRoll, StaminaDebt } private sealed class FloatSetting { private readonly ConfigEntry entry; private readonly float defaultValue; private readonly float minValue; private readonly float maxValue; internal float Value => GetFloat(entry, defaultValue, minValue, maxValue); internal FloatSetting(ConfigEntry entry, float defaultValue, float minValue, float maxValue) { this.entry = entry; this.defaultValue = defaultValue; this.minValue = minValue; this.maxValue = maxValue; } } private const string LegacyConfigFileName = "com.b0n3.walljump.cfg"; private const string CoyoteTimeSection = "Coyote Time"; private const string JumpBufferingSection = "Jump Buffering"; private const string WallJumpSection = "Wall Jump"; private const string FallRollSection = "Fall Roll"; private const string StaminaDebtSection = "Stamina Debt"; private const string LoggingSection = "Logging"; internal static ManualLogSource Log; private static readonly Dictionary LastInvalidConfigValues = new Dictionary(); private Dictionary existingConfigValues = new Dictionary(StringComparer.OrdinalIgnoreCase); private static FloatSetting coyoteTimeSeconds; private static FloatSetting jumpBufferSeconds; private static FloatSetting jumpWindowSeconds; private static FloatSetting wallMemorySeconds; private static FloatSetting wallCheckDistance; private static FloatSetting wallCheckRadius; private static FloatSetting wallJumpTriggerDistance; private static FloatSetting minimumWallJumpEntrySpeed; private static FloatSetting fallRollBufferSeconds; private static FloatSetting fallRollDamageReduction; private static FloatSetting horizontalBounceMinSpeed; private static FloatSetting outwardBounceMinSpeed; private static FloatSetting reflectionResponsiveness; private static FloatSetting inputBiasFactor; private static FloatSetting verticalPushForce; private static FloatSetting cooldownSeconds; internal static ConfigEntry CoyoteTimeEnabled; internal static ConfigEntry JumpBufferingEnabled; internal static ConfigEntry WallJumpEnabled; internal static ConfigEntry FallRollEnabled; internal static ConfigEntry StaminaDebtEnabled; internal static ConfigEntry RequireForwardPress; internal static ConfigEntry EnableVerboseLogging; internal static ConfigEntry VerboseAllFeatures; internal static ConfigEntry CoyoteTimeVerboseLogging; internal static ConfigEntry JumpBufferVerboseLogging; internal static ConfigEntry WallJumpVerboseLogging; internal static ConfigEntry FallRollVerboseLogging; internal static ConfigEntry StaminaDebtVerboseLogging; internal static float CoyoteTimeSeconds => coyoteTimeSeconds.Value; internal static float JumpBufferSeconds => jumpBufferSeconds.Value; internal static float JumpWindowSeconds => jumpWindowSeconds.Value; internal static float WallMemorySeconds => wallMemorySeconds.Value; internal static float WallCheckDistance => wallCheckDistance.Value; internal static float WallCheckRadius => wallCheckRadius.Value; internal static float WallJumpTriggerDistance => wallJumpTriggerDistance.Value; internal static float MinimumWallJumpEntrySpeed => minimumWallJumpEntrySpeed.Value; internal static float FallRollBufferSeconds => fallRollBufferSeconds.Value; internal static float FallRollDamageReduction => fallRollDamageReduction.Value; internal static float HorizontalBounceMinSpeed => horizontalBounceMinSpeed.Value; internal static float OutwardBounceMinSpeed => outwardBounceMinSpeed.Value; internal static float ReflectionResponsiveness => reflectionResponsiveness.Value; internal static float InputBiasFactor => inputBiasFactor.Value; internal static float VerticalPushForce => verticalPushForce.Value; internal static float CooldownSeconds => cooldownSeconds.Value; private void Awake() { //IL_052a: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; EnsureLegacyConfigMigrated(); existingConfigValues = ReadConfigValues(((BaseUnityPlugin)this).Config.ConfigFilePath); CoyoteTimeEnabled = ((BaseUnityPlugin)this).Config.Bind("Coyote Time", "Enabled", true, "If this is off, Stride does not allow late jumps after leaving a ledge."); coyoteTimeSeconds = BindFloatSetting("Coyote Time", "WindowSeconds", 0.1f, 0f, 1f, "How long you can still jump after running off a ledge.", "General", "CoyoteTimeSeconds"); JumpBufferingEnabled = ((BaseUnityPlugin)this).Config.Bind("Jump Buffering", "Enabled", true, "If this is off, Stride does not save early jump presses before landing."); jumpBufferSeconds = BindFloatSetting("Jump Buffering", "WindowSeconds", 0.1f, 0f, 1f, "How early a jump button press is saved before you land.", "General", "JumpBufferSeconds"); WallJumpEnabled = ((BaseUnityPlugin)this).Config.Bind("Wall Jump", "Enabled", true, "If this is off, Stride does not allow wall jumps."); jumpWindowSeconds = BindFloatSetting("Wall Jump", "AirJumpWindowSeconds", 0.95f, 0f, 5f, "How long after a normal jump you are still allowed to trigger a wall jump.", "General", "JumpWindowSeconds"); wallMemorySeconds = BindFloatSetting("Wall Jump", "WallMemorySeconds", 0.05f, 0f, 1f, "How long a wall stays valid after you stop touching it.", "General", "WallMemorySeconds"); wallCheckDistance = BindFloatSetting("Wall Jump", "WallCheckDistance", 0.575f, 0.05f, 5f, "How far away Stride looks for a wall you can jump from.", "General", "WallCheckDistance"); wallCheckRadius = BindFloatSetting("Wall Jump", "WallCheckRadius", 0.225f, 0.05f, 3f, "How wide the wall search is around your character.", "General", "WallCheckRadius"); wallJumpTriggerDistance = BindFloatSetting("Wall Jump", "WallJumpTriggerDistance", 0.18f, 0.01f, 2f, "How close the chosen wall must be before the wall jump actually fires.", "General", "WallJumpTriggerDistance"); minimumWallJumpEntrySpeed = BindFloatSetting("Wall Jump", "MinimumEntrySpeed", 0.1f, 0f, 20f, "The minimum sideways movement speed that counts as a real wall approach.", "General", "MinimumWallJumpEntrySpeed"); horizontalBounceMinSpeed = BindFloatSetting("Wall Jump", "MinimumHorizontalBounceSpeed", 9.5f, 0f, 50f, "The minimum sideways speed you keep after bouncing off a wall.", "General", "HorizontalBounceMinSpeed"); outwardBounceMinSpeed = BindFloatSetting("Wall Jump", "MinimumOutwardBounceSpeed", 6.75f, 0f, 50f, "The minimum speed that pushes you away from the wall.", "General", "OutwardBounceMinSpeed"); reflectionResponsiveness = BindFloatSetting("Wall Jump", "ReflectionResponsiveness", 1f, 0f, 3f, "How much of your incoming wall approach speed carries into the bounce.", "General", "ReflectionResponsiveness"); inputBiasFactor = BindFloatSetting("Wall Jump", "InputBiasFactor", 0.12f, 0f, 1f, "How much your movement input can gently steer the wall jump along the wall.", "General", "InputBiasFactor"); verticalPushForce = BindFloatSetting("Wall Jump", "VerticalPushForce", 8.6f, 0f, 50f, "How much upward lift the wall jump gives you.", "General", "VerticalPushForce"); cooldownSeconds = BindFloatSetting("Wall Jump", "CooldownSeconds", 0.15f, 0f, 10f, "How long Stride waits before another wall jump can happen.", "General", "CooldownSeconds"); FallRollEnabled = ((BaseUnityPlugin)this).Config.Bind("Fall Roll", "Enabled", true, "If this is off, Stride does not buffer fall rolls or reduce fall damage on landing."); fallRollBufferSeconds = BindFloatSetting("Fall Roll", "BufferSeconds", 0.9f, 0f, 2f, "How early you can press dodge before landing and still get a fall roll.", "General", "FallRollBufferSeconds"); fallRollDamageReduction = BindFloatSetting("Fall Roll", "DamageReduction", 0.5f, 0f, 1f, "How much fall damage a successful fall roll removes. 0.5 means 50% less damage.", "General", "FallRollDamageReduction"); RequireForwardPress = BindBoolSetting("Wall Jump", "RequireForwardPress", defaultValue: true, "If on, you must be pressing forward to trigger a wall jump.", "General", "RequireForwardPress"); StaminaDebtEnabled = ((BaseUnityPlugin)this).Config.Bind("Stamina Debt", "Enabled", true, "If this is off, Stride leaves stamina spending and the stamina bar alone."); EnableVerboseLogging = BindBoolSetting("Logging", "EnableVerboseLogging", defaultValue: false, "Main on/off switch for verbose logs. If this is off, Stride prints no verbose logs at all.", "Debug", "VerboseLogging"); VerboseAllFeatures = BindBoolSetting("Logging", "VerboseAllFeatures", defaultValue: false, "Turns on verbose logs for every feature at once. This only works while EnableVerboseLogging is on.", "Debug", "VerboseLogging"); CoyoteTimeVerboseLogging = BindBoolSetting("Coyote Time", "VerboseLogging", defaultValue: false, "Extra logs just for coyote time. This only works while Logging.EnableVerboseLogging is on.", "Debug", "VerboseTimingLogging"); JumpBufferVerboseLogging = BindBoolSetting("Jump Buffering", "VerboseLogging", defaultValue: false, "Extra logs just for jump buffering. This only works while Logging.EnableVerboseLogging is on.", "Debug", "VerboseTimingLogging"); WallJumpVerboseLogging = BindBoolSetting("Wall Jump", "VerboseLogging", defaultValue: false, "Extra logs for wall detection and wall jumping. This only works while Logging.EnableVerboseLogging is on.", "Debug", "VerboseWallJumpLogging"); FallRollVerboseLogging = BindBoolSetting("Fall Roll", "VerboseLogging", defaultValue: false, "Extra logs just for fall rolls. This only works while Logging.EnableVerboseLogging is on.", "Debug", "VerboseTimingLogging"); StaminaDebtVerboseLogging = BindBoolSetting("Stamina Debt", "VerboseLogging", defaultValue: false, "Extra logs just for stamina debt. This only works while Logging.EnableVerboseLogging is on.", "Debug", "VerboseStaminaDebtLogging"); MigrateLegacyWallDetectionVerboseSetting(); new Harmony("com.b0n3.stride").PatchAll(); Log.LogInfo((object)"Stride loaded."); } private void EnsureLegacyConfigMigrated() { string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath; if (!File.Exists(configFilePath)) { string text = Path.Combine(Paths.ConfigPath, "com.b0n3.walljump.cfg"); if (File.Exists(text)) { File.Copy(text, configFilePath); Log.LogInfo((object)("Migrated config from 'com.b0n3.walljump.cfg' to '" + Path.GetFileName(configFilePath) + "'.")); } } } private FloatSetting BindFloatSetting(string section, string key, float defaultValue, float minValue, float maxValue, string description, string legacySection, string legacyKey) { ConfigEntry entry = ((BaseUnityPlugin)this).Config.Bind(section, key, FormatFloat(defaultValue), description + " Range: " + FormatFloat(minValue) + " to " + FormatFloat(maxValue) + ". Invalid values are reset automatically."); MigrateEntryValue(entry, legacySection, legacyKey); return new FloatSetting(entry, defaultValue, minValue, maxValue); } private ConfigEntry BindBoolSetting(string section, string key, bool defaultValue, string description, string legacySection, string legacyKey) { ConfigEntry val = ((BaseUnityPlugin)this).Config.Bind(section, key, defaultValue, description); MigrateEntryValue(val, legacySection, legacyKey); return val; } private void MigrateEntryValue(ConfigEntry entry, string legacySection, string legacyKey) { if (entry == null || string.IsNullOrEmpty(legacySection) || string.IsNullOrEmpty(legacyKey)) { return; } string key = BuildCompositeKey(((ConfigEntryBase)entry).Definition.Section, ((ConfigEntryBase)entry).Definition.Key); if (existingConfigValues.ContainsKey(key)) { return; } string key2 = BuildCompositeKey(legacySection, legacyKey); if (!existingConfigValues.TryGetValue(key2, out var value)) { return; } try { T val = (T)Convert.ChangeType(value, typeof(T), CultureInfo.InvariantCulture); if (!EqualityComparer.Default.Equals(entry.Value, val)) { entry.Value = val; ((ConfigEntryBase)entry).ConfigFile.Save(); } } catch { } } private void MigrateLegacyWallDetectionVerboseSetting() { string key = BuildCompositeKey("Wall Jump", "VerboseLogging"); if (!existingConfigValues.ContainsKey(key)) { string key2 = BuildCompositeKey("Debug", "VerboseWallDetectionLogging"); if (existingConfigValues.TryGetValue(key2, out var value) && bool.TryParse(value, out var result) && result && !WallJumpVerboseLogging.Value) { WallJumpVerboseLogging.Value = true; ((ConfigEntryBase)WallJumpVerboseLogging).ConfigFile.Save(); } } } private static Dictionary ReadConfigValues(string configFilePath) { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrEmpty(configFilePath) || !File.Exists(configFilePath)) { return dictionary; } string section = string.Empty; string[] array = File.ReadAllLines(configFilePath); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (string.IsNullOrEmpty(text) || text.StartsWith("#")) { continue; } if (text.StartsWith("[") && text.EndsWith("]")) { section = text.Substring(1, text.Length - 2).Trim(); continue; } int num = text.IndexOf('='); if (num > 0) { string key = text.Substring(0, num).Trim(); string value = text.Substring(num + 1).Trim(); dictionary[BuildCompositeKey(section, key)] = value; } } return dictionary; } private static string BuildCompositeKey(string section, string key) { return section + "::" + key; } private static float GetFloat(ConfigEntry entry, float defaultValue, float minValue, float maxValue) { string text = entry.Value?.Trim(); if (string.IsNullOrEmpty(text)) { return WarnAndFallback(entry, text, defaultValue); } if (!TryParseFloat(text, out var value)) { return WarnAndFallback(entry, text, defaultValue); } if (value < minValue) { return WarnAndFallback(entry, text, minValue); } if (value > maxValue) { return WarnAndFallback(entry, text, maxValue); } LastInvalidConfigValues.Remove(((ConfigEntryBase)entry).Definition.Key); return value; } private static float WarnAndFallback(ConfigEntry entry, string raw, float fallbackValue) { string key = ((ConfigEntryBase)entry).Definition.Key; string text = raw ?? ""; if (!LastInvalidConfigValues.TryGetValue(key, out var value) || value != text) { LastInvalidConfigValues[key] = text; Log.LogWarning((object)("Config value for " + key + " was invalid ('" + text + "'). Using " + FormatFloat(fallbackValue) + " instead.")); } string text2 = FormatFloat(fallbackValue); if (entry.Value != text2) { entry.Value = text2; ((ConfigEntryBase)entry).ConfigFile.Save(); } return fallbackValue; } private static bool TryParseFloat(string raw, out float value) { if (!float.TryParse(raw, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out value)) { return float.TryParse(raw, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.CurrentCulture, out value); } return true; } private static string FormatFloat(float value) { return value.ToString("0.###", CultureInfo.InvariantCulture); } internal static bool IsDebugEnabled(DebugLogFeature feature) { if (EnableVerboseLogging == null || !EnableVerboseLogging.Value) { return false; } if (VerboseAllFeatures != null && VerboseAllFeatures.Value) { return true; } switch (feature) { case DebugLogFeature.CoyoteTime: if (CoyoteTimeVerboseLogging != null) { return CoyoteTimeVerboseLogging.Value; } return false; case DebugLogFeature.JumpBuffering: if (JumpBufferVerboseLogging != null) { return JumpBufferVerboseLogging.Value; } return false; case DebugLogFeature.WallJump: if (WallJumpVerboseLogging != null) { return WallJumpVerboseLogging.Value; } return false; case DebugLogFeature.FallRoll: if (FallRollVerboseLogging != null) { return FallRollVerboseLogging.Value; } return false; case DebugLogFeature.StaminaDebt: if (StaminaDebtVerboseLogging != null) { return StaminaDebtVerboseLogging.Value; } return false; default: return false; } } } [HarmonyPatch(typeof(Character), "Jump")] internal static class CharacterJumpPatch { private static void Prefix(Character __instance, ref bool __state) { __state = IsTrackedPlayer(__instance) && __instance.IsOnGround(); } private static void Postfix(Character __instance, bool __state) { if (__state && IsTrackedPlayer(__instance)) { StrideState.RecordGroundJump(); } } private static bool IsTrackedPlayer(Character character) { if ((Object)(object)character != (Object)null) { return (Object)(object)character == (Object)(object)Player.m_localPlayer; } return false; } } [HarmonyPatch(typeof(Player), "Update")] internal static class PlayerUpdateWallJumpPatch { private struct WallCandidate { internal Vector3 Normal; internal Vector3 Point; internal float Distance; internal float PlaneDepth; internal float BaseScore; internal float VelocityFacingScore; internal float CastFacingScore; internal int PieceRootId; } private struct WallCluster { internal Vector3 Normal; internal Vector3 AveragePoint; internal float PlaneDepth; internal float Score; internal float BestCandidateScore; internal float BestVelocityFacingScore; internal float NearestDistance; internal float PlayerPlaneOffset; internal float ForwardFlowScore; internal float FrontFacePenalty; internal float Weight; internal bool RejectedForDistance; internal int HitCount; internal int PieceRootId; } internal struct WallDetectionResult { internal Vector3 Normal; internal float NearestDistance; } private struct MovementInputSnapshot { internal Vector2 RawLocalInput; internal Vector2 ClampedLocalInput; internal Vector3 WorldDirection; internal float Magnitude; internal float ForwardAmount; internal bool HasDirectionalInput { get { if (Magnitude >= 0.1f) { return ((Vector3)(ref WorldDirection)).sqrMagnitude >= 0.01f; } return false; } } } private const float MaxWallNormalY = 0.25f; private const float MinimumFacingDot = 0.15f; private const float MinimumMoveInputMagnitude = 0.1f; private const int WallCastDirectionCount = 8; private const int MaxWallHitsPerCast = 32; private const float WallClusterNormalDot = 0.92f; private const float WallClusterPlaneTolerance = 0.2f; private const float WallClusterFrontFaceTolerance = 0.18f; private const float WallClusterPointNeighborhood = 1.35f; private const float MinimumWallClusterConfidenceLead = 0.2f; private const float ForwardFlowBiasWeight = 6f; private const float AlternateFlowPreferenceMargin = 8.5f; private const float AlternateFlowPreferenceLead = 0.2f; private const float AlternateFlowMinimum = 0.45f; private const float AlternateFlowMinimumFacing = 0.05f; private const float MinimumForwardStickY = 0.35f; private const float MinimumVelocityScoreMagnitude = 0.35f; private const float SingleHitClusterPenalty = 1.1f; private const float StrongSingleHitFacing = 0.9f; private const float StrongSingleHitDistance = 0.06f; private const float MinimumBounceFloorScale = 0.1f; private const float MaximumRememberedApproachBoostRatio = 1.25f; private const float MaximumOutwardFloorBoost = 1.5f; private const float MaximumHorizontalFloorBoost = 2f; internal const float StickyWallNormalDot = 0.8f; internal const float StickyWallDistanceTolerance = 0.12f; internal const float MinimumApproachVelocityDot = 0.05f; internal const float MinimumRememberedWallApproachDot = 0.05f; private const float MaximumInputBiasFraction = 0.35f; private static readonly FieldRef CharacterBodyRef = AccessTools.FieldRefAccess("m_body"); private static readonly FieldRef CharacterSeManRef = AccessTools.FieldRefAccess("m_seman"); private static readonly FieldRef CharacterSpeedRef = AccessTools.FieldRefAccess("m_speed"); private static readonly FieldRef LastGroundTouchRef = AccessTools.FieldRefAccess("m_lastGroundTouch"); private static readonly FieldRef MaxAirAltitudeRef = AccessTools.FieldRefAccess("m_maxAirAltitude"); private static readonly FieldRef JumpStaminaUsageRef = AccessTools.FieldRefAccess("m_jumpStaminaUsage"); private static readonly FieldRef JumpForceTiredFactorRef = AccessTools.FieldRefAccess("m_jumpForceTiredFactor"); private static Player trackedPlayer; private static bool wasGroundedLastFrame; private static readonly List WallCandidateBuffer = new List(16); private static readonly List WallClusterBuffer = new List(16); private static readonly Vector3[] WallCastDirectionBuffer = (Vector3[])(object)new Vector3[8]; private static readonly RaycastHit[] WallHitBuffer = (RaycastHit[])(object)new RaycastHit[32]; private static void Postfix(Player __instance) { //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: 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_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } if ((Object)(object)__instance != (Object)(object)trackedPlayer) { trackedPlayer = __instance; wasGroundedLastFrame = false; StrideState.ResetAll(); StaminaDebt.ResetTracking(); } bool flag = ((Character)__instance).IsOnGround(); bool flag2 = StrideState.TryConsumeResolvedJumpPress(); if (flag || ((Character)__instance).IsSwimming() || ((Character)__instance).IsDead()) { if (flag) { StrideState.RecordGrounded(); if (!wasGroundedLastFrame && StrideState.TryConsumeBufferedGroundJump()) { LogDebug(StridePlugin.DebugLogFeature.JumpBuffering, "Jump buffer triggered."); ((Character)__instance).Jump(false); } } wasGroundedLastFrame = flag; StrideState.ResetAirborneState(); return; } wasGroundedLastFrame = false; Rigidbody val = TryGetBody((Character)(object)__instance); Vector3 horizontalVelocity = (((Object)(object)val != (Object)null) ? Vector3.ProjectOnPlane(val.linearVelocity, Vector3.up) : Vector3.zero); MovementInputSnapshot movementInput = SampleMovementInput(__instance); int num; if (StridePlugin.WallJumpEnabled != null) { num = (StridePlugin.WallJumpEnabled.Value ? 1 : 0); if (num != 0) { goto IL_00e0; } } else { num = 0; } StrideState.ClearWallJumpTracking(); goto IL_00e0; IL_00e0: WallDetectionResult wallDetection = default(WallDetectionResult); bool flag3 = num != 0 && TryFindWall(__instance, flag2, out wallDetection); if (flag3) { StrideState.RecordWallContact(wallDetection.Normal, horizontalVelocity, wallDetection.NearestDistance); } if (flag2 && StrideState.UsesBufferedJumpTracking()) { StrideState.RecordJumpPressed(); } if (!flag2) { return; } if (StrideState.TryConsumeCoyoteJump()) { LastGroundTouchRef.Invoke((Character)(object)__instance) = 0f; StrideState.RecordCoyoteJump(); LogDebug(StridePlugin.DebugLogFeature.CoyoteTime, "Coyote jump triggered."); ((Character)__instance).Jump(false); } else { if (!StrideState.CanWallJump(__instance) || (StridePlugin.RequireForwardPress.Value && !IsPressingForward(movementInput))) { return; } if (!StrideState.TryGetWallJumpContact(__instance, flag3, wallDetection, out var wallContact, out var usingRememberedWall)) { LogDebug(StridePlugin.DebugLogFeature.WallJump, "Jump pressed in air, but no valid wall was found."); return; } LogDebug(StridePlugin.DebugLogFeature.WallJump, "Wall jump using " + (usingRememberedWall ? "remembered" : "current") + " wall normal " + FormatVector(wallContact.Normal) + "."); if (!(((Vector3)(ref wallContact.Normal)).sqrMagnitude < 0.01f)) { PerformWallJump(__instance, wallContact, movementInput); } } } private static bool IsPressingForward(MovementInputSnapshot movementInput) { return movementInput.ForwardAmount >= 0.35f; } private static bool TryFindWall(Player player, bool logDetails, out WallDetectionResult wallDetection) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //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_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0073: 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_0077: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009b: 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_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) wallDetection = default(WallDetectionResult); Vector3 centerPoint = ((Character)player).GetCenterPoint(); Vector3 forward = ((Component)player).transform.forward; forward.y = 0f; ((Vector3)(ref forward)).Normalize(); Vector3 right = ((Component)player).transform.right; right.y = 0f; ((Vector3)(ref right)).Normalize(); Rigidbody val = TryGetBody((Character)(object)player); Vector3 val2 = (((Object)(object)val != (Object)null) ? Vector3.ProjectOnPlane(val.linearVelocity, Vector3.up) : Vector3.zero); Vector3 velocityDirection = val2; velocityDirection.y = 0f; if (((Vector3)(ref velocityDirection)).sqrMagnitude > 0.01f) { ((Vector3)(ref velocityDirection)).Normalize(); } PopulateWallCastDirections(forward, right, velocityDirection, WallCastDirectionBuffer); WallCandidateBuffer.Clear(); float num = Mathf.Max(0.05f, StridePlugin.WallCheckRadius); for (int i = 0; i < WallCastDirectionBuffer.Length; i++) { Vector3 val3 = WallCastDirectionBuffer[i]; if (((Vector3)(ref val3)).sqrMagnitude < 0.01f) { continue; } int num2 = Physics.SphereCastNonAlloc(centerPoint, num, val3, WallHitBuffer, StridePlugin.WallCheckDistance, -1, (QueryTriggerInteraction)1); if (num2 > 0) { if (num2 == WallHitBuffer.Length && logDetails) { LogDebug(StridePlugin.DebugLogFeature.WallJump, $"Wall cast hit buffer filled at {WallHitBuffer.Length} hits; some wall candidates may be skipped."); } AddWallCandidates(player, val3, val2, WallHitBuffer, num2, WallCandidateBuffer, logDetails); } } if (WallCandidateBuffer.Count == 0) { return false; } return TryGetSupportedWallNormal(player, WallCandidateBuffer, logDetails, out wallDetection); } private static void AddWallCandidates(Player player, Vector3 castDirection, Vector3 horizontalVelocity, RaycastHit[] hits, int hitCount, List candidates, bool logDetails) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001c: 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_0030: 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_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0094: 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_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) if (hits == null || hitCount <= 0) { return; } Vector3 normalized = ((Vector3)(ref castDirection)).normalized; for (int i = 0; i < hitCount; i++) { if (TryGetWallHitNormal(player, normalized, hits[i], logDetails, out var wallNormal)) { float num = Vector3.Dot(normalized, -wallNormal); float num2 = 0f; if (((Vector3)(ref horizontalVelocity)).sqrMagnitude >= 0.122499995f) { num2 = Vector3.Dot(((Vector3)(ref horizontalVelocity)).normalized, -wallNormal); } float num3 = Mathf.Clamp01(((RaycastHit)(ref hits[i])).distance / Mathf.Max(0.01f, StridePlugin.WallCheckDistance)); Vector3 point = ((RaycastHit)(ref hits[i])).point; Piece componentInParent = ((Component)((RaycastHit)(ref hits[i])).collider).GetComponentInParent(); candidates.Add(new WallCandidate { Normal = wallNormal, Point = point, Distance = ((RaycastHit)(ref hits[i])).distance, PlaneDepth = Vector3.Dot(wallNormal, point), BaseScore = num2 * 1.75f + num * 0.35f - num3 * 0.75f, VelocityFacingScore = num2, CastFacingScore = num, PieceRootId = GetPieceRootId(componentInParent, ((RaycastHit)(ref hits[i])).collider) }); } } } private static void PopulateWallCastDirections(Vector3 fallbackDirection, Vector3 rightDirection, Vector3 velocityDirection, Vector3[] directions) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: 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_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: 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_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0048: 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_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: 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_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_009f: 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) directions[0] = fallbackDirection; directions[1] = velocityDirection; directions[2] = Quaternion.Euler(0f, 30f, 0f) * fallbackDirection; directions[3] = Quaternion.Euler(0f, -30f, 0f) * fallbackDirection; directions[4] = Quaternion.Euler(0f, 60f, 0f) * fallbackDirection; directions[5] = Quaternion.Euler(0f, -60f, 0f) * fallbackDirection; directions[6] = rightDirection; directions[7] = -rightDirection; } private static bool TryGetSupportedWallNormal(Player player, List candidates, bool logDetails, out WallDetectionResult wallDetection) { //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01c2: Unknown result type (might be due to invalid IL or missing references) wallDetection = default(WallDetectionResult); BuildWallClusters(player, candidates, WallClusterBuffer); if (WallClusterBuffer.Count == 0) { return false; } int num = -1; float num2 = float.MinValue; float num3 = float.MinValue; for (int i = 0; i < WallClusterBuffer.Count; i++) { WallCluster wallCluster = WallClusterBuffer[i]; if (wallCluster.FrontFacePenalty > 0f) { continue; } float score = wallCluster.Score; if (score <= num2) { if (score > num3) { num3 = score; } } else { num3 = num2; num2 = score; num = i; } } if (num < 0) { return false; } WallCluster wallCluster2 = WallClusterBuffer[num]; int num4 = FindPreferredFlowCluster(WallClusterBuffer, num, num2); bool flag = false; if (num4 >= 0) { WallCluster wallCluster3 = WallClusterBuffer[num4]; if (logDetails) { LogDebug(StridePlugin.DebugLogFeature.WallJump, $"Switching wall cluster from {num + 1} to {num4 + 1} to preserve along-wall flow. Original flow {wallCluster2.ForwardFlowScore:0.###}, preferred flow {wallCluster3.ForwardFlowScore:0.###}."); } num = num4; num2 = wallCluster3.Score; wallCluster2 = wallCluster3; flag = true; } float num5 = num2 - num3; if (!flag && WallClusterBuffer.Count > 1 && num5 < 0.2f) { if (logDetails) { LogDebug(StridePlugin.DebugLogFeature.WallJump, $"Rejected wall cluster with weak confidence lead {num5:0.###}. Best score {num2:0.###}, runner-up {num3:0.###}."); LogWallClusters(WallClusterBuffer); } return false; } if (flag && logDetails) { LogDebug(StridePlugin.DebugLogFeature.WallJump, $"Accepted flow-preserving wall cluster despite confidence lead {num5:0.###}."); } wallDetection = new WallDetectionResult { Normal = wallCluster2.Normal, NearestDistance = wallCluster2.NearestDistance }; if (logDetails) { LogDebug(StridePlugin.DebugLogFeature.WallJump, $"Selected wall normal {FormatVector(wallDetection.Normal)} from cluster {num + 1}/{WallClusterBuffer.Count} with {wallCluster2.HitCount} hit(s), nearest {wallCluster2.NearestDistance:0.###}, flow {wallCluster2.ForwardFlowScore:0.###}, score {wallCluster2.Score:0.###}."); } return true; } private static int FindPreferredFlowCluster(List clusters, int bestIndex, float bestScore) { //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: 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) if (bestIndex < 0 || bestIndex >= clusters.Count) { return -1; } WallCluster wallCluster = clusters[bestIndex]; if (wallCluster.ForwardFlowScore >= 0.45f || wallCluster.BestVelocityFacingScore < 0.75f) { return -1; } int result = -1; float num = float.MinValue; for (int i = 0; i < clusters.Count; i++) { if (i == bestIndex) { continue; } WallCluster wallCluster2 = clusters[i]; if (wallCluster2.Score < bestScore - 8.5f || wallCluster2.ForwardFlowScore < 0.45f || wallCluster2.ForwardFlowScore < wallCluster.ForwardFlowScore + 0.2f || wallCluster2.BestVelocityFacingScore < 0.05f) { continue; } bool num2 = wallCluster2.PieceRootId != 0 && wallCluster2.PieceRootId == wallCluster.PieceRootId; Vector3 val = wallCluster2.AveragePoint - wallCluster.AveragePoint; bool flag = ((Vector3)(ref val)).sqrMagnitude <= 7.2900004f; if (num2 || flag) { float num3 = wallCluster2.Score + wallCluster2.ForwardFlowScore * 3f + wallCluster2.BestVelocityFacingScore; if (!(num3 <= num)) { num = num3; result = i; } } } return result; } private static void BuildWallClusters(Player player, List candidates, List clusters) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: 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_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_02d0: Unknown result type (might be due to invalid IL or missing references) //IL_02d5: Unknown result type (might be due to invalid IL or missing references) //IL_02e3: Unknown result type (might be due to invalid IL or missing references) //IL_02e6: Unknown result type (might be due to invalid IL or missing references) //IL_0354: Unknown result type (might be due to invalid IL or missing references) //IL_035b: Unknown result type (might be due to invalid IL or missing references) //IL_036e: Unknown result type (might be due to invalid IL or missing references) //IL_0375: Unknown result type (might be due to invalid IL or missing references) //IL_037a: Unknown result type (might be due to invalid IL or missing references) //IL_037f: Unknown result type (might be due to invalid IL or missing references) clusters.Clear(); Vector3 position = ((Component)player).transform.position; Rigidbody val = TryGetBody((Character)(object)player); Vector3 horizontalVelocity = (((Object)(object)val != (Object)null) ? Vector3.ProjectOnPlane(val.linearVelocity, Vector3.up) : Vector3.zero); for (int i = 0; i < candidates.Count; i++) { WallCandidate candidate = candidates[i]; int num = -1; for (int j = 0; j < clusters.Count; j++) { if (CanJoinCluster(candidate, clusters[j])) { num = j; break; } } if (num < 0) { clusters.Add(new WallCluster { Normal = candidate.Normal, PlaneDepth = candidate.PlaneDepth, AveragePoint = candidate.Point, Score = candidate.BaseScore, BestCandidateScore = candidate.BaseScore, BestVelocityFacingScore = candidate.VelocityFacingScore, NearestDistance = candidate.Distance, Weight = 1f, HitCount = 1, PieceRootId = candidate.PieceRootId }); continue; } WallCluster value = clusters[num]; float num2 = Mathf.Max(0.1f, candidate.BaseScore + 2f); float num3 = value.Weight + num2; Vector3 val2 = value.Normal * value.Weight + candidate.Normal * num2; value.Normal = ((Vector3)(ref val2)).normalized; value.PlaneDepth = (value.PlaneDepth * value.Weight + candidate.PlaneDepth * num2) / num3; value.AveragePoint = (value.AveragePoint * value.Weight + candidate.Point * num2) / num3; value.Weight = num3; value.Score += candidate.BaseScore; value.HitCount++; value.NearestDistance = Mathf.Min(value.NearestDistance, candidate.Distance); if (candidate.BaseScore > value.BestCandidateScore) { value.BestCandidateScore = candidate.BaseScore; value.BestVelocityFacingScore = candidate.VelocityFacingScore; value.PieceRootId = candidate.PieceRootId; } clusters[num] = value; } for (int k = 0; k < clusters.Count; k++) { WallCluster value2 = clusters[k]; if (value2.NearestDistance > StridePlugin.WallJumpTriggerDistance) { value2.RejectedForDistance = true; value2.Score = float.MinValue; clusters[k] = value2; } else { value2.Score += (float)value2.HitCount * 0.45f; value2.PlayerPlaneOffset = value2.PlaneDepth - Vector3.Dot(value2.Normal, position); value2.ForwardFlowScore = CalculateForwardFlowScore(horizontalVelocity, value2.Normal); clusters[k] = value2; } } for (int l = 0; l < clusters.Count; l++) { WallCluster value3 = clusters[l]; if (value3.RejectedForDistance) { continue; } float num4 = 0f; for (int m = 0; m < clusters.Count; m++) { if (l == m) { continue; } WallCluster wallCluster = clusters[m]; if (!(Vector3.Dot(value3.Normal, wallCluster.Normal) < 0.92f)) { Vector3 val3 = wallCluster.AveragePoint - value3.AveragePoint; if (!(((Vector3)(ref val3)).sqrMagnitude > 1.8225001f) && wallCluster.PlayerPlaneOffset > 0f && value3.PlayerPlaneOffset > 0f && wallCluster.PlayerPlaneOffset + 0.18f < value3.PlayerPlaneOffset) { num4 = Mathf.Max(num4, value3.PlayerPlaneOffset - wallCluster.PlayerPlaneOffset); } } } value3.FrontFacePenalty = num4; value3.Score -= num4 * 8f; value3.Score += Mathf.Clamp01(1f - value3.NearestDistance / Mathf.Max(0.01f, StridePlugin.WallCheckDistance)) * 0.6f; value3.Score += (float)Mathf.Min(value3.HitCount, 4) * 0.15f; if (value3.HitCount == 1 && (value3.BestVelocityFacingScore < 0.9f || value3.NearestDistance > 0.06f)) { value3.Score -= 1.1f; } value3.Score += value3.ForwardFlowScore * 6f; clusters[l] = value3; } } private static bool CanJoinCluster(WallCandidate candidate, WallCluster cluster) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) if (Vector3.Dot(candidate.Normal, cluster.Normal) < 0.92f) { return false; } if (Mathf.Abs(candidate.PlaneDepth - cluster.PlaneDepth) > 0.2f) { return false; } return true; } private static float CalculateForwardFlowScore(Vector3 horizontalVelocity, Vector3 wallNormal) { //IL_0014: 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_003f: 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_004a: 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_004c: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_007f: 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_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) if (((Vector3)(ref horizontalVelocity)).sqrMagnitude < 0.122499995f) { return 0f; } Vector3 val = wallNormal; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 0.01f) { return 0f; } ((Vector3)(ref val)).Normalize(); Vector3 normalized = ((Vector3)(ref horizontalVelocity)).normalized; float num = Mathf.Max(0f, Vector3.Dot(normalized, -val)); Vector3 val2 = Vector3.ProjectOnPlane(normalized, val); if (((Vector3)(ref val2)).sqrMagnitude < 0.0001f) { return 0f; } ((Vector3)(ref val2)).Normalize(); Vector3 val3 = Vector3.Reflect(normalized, val); val3.y = 0f; if (((Vector3)(ref val3)).sqrMagnitude < 0.0001f) { return 0f; } ((Vector3)(ref val3)).Normalize(); float num2 = Vector3.Dot(val3, val2); float num3 = Vector3.Dot(val3, normalized); return num2 * 0.75f + num3 * 0.25f - num * 0.15f; } private static bool TryGetWallHitNormal(Player player, Vector3 castDirection, RaycastHit hit, bool logDetails, out Vector3 wallNormal) { //IL_0002: 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_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_004a: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0060: 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_0076: Unknown result type (might be due to invalid IL or missing references) wallNormal = default(Vector3); if ((Object)(object)((RaycastHit)(ref hit)).collider == (Object)null) { return false; } Character componentInParent = ((Component)((RaycastHit)(ref hit)).collider).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null && (Object)(object)componentInParent == (Object)(object)player) { return false; } Vector3 normal = ((RaycastHit)(ref hit)).normal; Vector3 normalized = ((Vector3)(ref normal)).normalized; if (Mathf.Abs(normalized.y) > 0.25f) { return false; } if (Vector3.Dot(castDirection, -normalized) < 0.15f) { return false; } wallNormal = normalized; return true; } private static void PerformWallJump(Player player, StrideState.WallContactSnapshot wallContact, MovementInputSnapshot movementInput) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_003f: 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_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_021e: Unknown result type (might be due to invalid IL or missing references) //IL_022c: Unknown result type (might be due to invalid IL or missing references) //IL_0256: Unknown result type (might be due to invalid IL or missing references) //IL_0271: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Unknown result type (might be due to invalid IL or missing references) //IL_0297: Unknown result type (might be due to invalid IL or missing references) //IL_0298: Unknown result type (might be due to invalid IL or missing references) //IL_0299: Unknown result type (might be due to invalid IL or missing references) //IL_029a: Unknown result type (might be due to invalid IL or missing references) //IL_02ba: Unknown result type (might be due to invalid IL or missing references) //IL_02c6: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) Rigidbody val = TryGetBody((Character)(object)player); if ((Object)(object)val == (Object)null) { return; } Vector3 away = wallContact.Normal; away.y = 0f; ((Vector3)(ref away)).Normalize(); if (((Vector3)(ref away)).sqrMagnitude < 0.01f) { away = -((Component)player).transform.forward; away.y = 0f; ((Vector3)(ref away)).Normalize(); } Vector3 linearVelocity = val.linearVelocity; Vector3 val2 = Vector3.ProjectOnPlane(linearVelocity, Vector3.up); bool flag = false; if (((Vector3)(ref wallContact.ApproachVelocity)).sqrMagnitude > 0.01f && wallContact.ApproachTime > -100f && Time.time - wallContact.ApproachTime <= StridePlugin.CoyoteTimeSeconds && ((Vector3)(ref wallContact.ApproachVelocity)).magnitude > ((Vector3)(ref val2)).magnitude) { float num = Mathf.Max(0f, Time.time - wallContact.ApproachTime); float num2 = Mathf.Clamp01(1f - num / Mathf.Max(0.01f, StridePlugin.CoyoteTimeSeconds)); num2 *= num2; Vector3 val3 = Vector3.Lerp(val2, wallContact.ApproachVelocity, num2); float magnitude = ((Vector3)(ref val2)).magnitude; float num3 = ((magnitude > 0.01f) ? (magnitude * 1.25f) : ((Vector3)(ref wallContact.ApproachVelocity)).magnitude); float magnitude2 = ((Vector3)(ref val3)).magnitude; if (magnitude2 > num3 && magnitude2 > 0.01f) { val3 = ((Vector3)(ref val3)).normalized * num3; } val2 = val3; flag = true; } float magnitude3 = ((Vector3)(ref val2)).magnitude; if (magnitude3 < StridePlugin.MinimumWallJumpEntrySpeed) { LogDebug(StridePlugin.DebugLogFeature.WallJump, $"Allowing wall jump despite low incoming horizontal speed {magnitude3:0.###} because wall contact is valid."); } bool isTired = IsVanillaTiredJump(player, linearVelocity); Vector3 currentVelocity = default(Vector3); ((Vector3)(ref currentVelocity))..ctor(val2.x, linearVelocity.y, val2.z); Vector3 val4 = BuildWallJumpVelocity(player, currentVelocity, away, isTired, movementInput); LogDebug(StridePlugin.DebugLogFeature.WallJump, $"Wall jump incoming velocity {FormatVector(linearVelocity)} speed {((Vector3)(ref linearVelocity)).magnitude:0.###} direction {FormatDirection(linearVelocity)}."); if (flag) { LogDebug(StridePlugin.DebugLogFeature.WallJump, $"Wall jump used decayed stored wall approach horizontal {FormatVector(val2)} speed {magnitude3:0.###}."); } LogDebug(StridePlugin.DebugLogFeature.WallJump, $"Wall jump launch input local {FormatVector2(movementInput.RawLocalInput)} clamped {FormatVector2(movementInput.ClampedLocalInput)} magnitude {movementInput.Magnitude:0.###} forward {movementInput.ForwardAmount:0.###} world {FormatVector(movementInput.WorldDirection)}."); LogDebug(StridePlugin.DebugLogFeature.WallJump, $"Wall jump outgoing velocity {FormatVector(val4)} speed {((Vector3)(ref val4)).magnitude:0.###} direction {FormatDirection(val4)}."); LogDebug(StridePlugin.DebugLogFeature.WallJump, BuildWallJumpLaunchReport(wallContact, away, linearVelocity, val2, val4, movementInput, flag)); MaxAirAltitudeRef.Invoke((Character)(object)player) = ((Component)player).transform.position.y; ((Character)player).ForceJump(val4, true); StrideState.RecordWallJump(); LogDebug(StridePlugin.DebugLogFeature.WallJump, "Wall jump triggered."); } private static bool IsVanillaTiredJump(Player player, Vector3 currentVelocity) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) float num = JumpStaminaUsageRef.Invoke((Character)(object)player); bool num2 = !((Character)player).HaveStamina(num); if (num2 && ((Character)player).IsPlayer()) { Hud instance = Hud.instance; if (instance != null) { instance.StaminaBarEmptyFlash(); } } float num3 = CharacterSpeedRef.Invoke((Character)(object)player); SEMan val = CharacterSeManRef.Invoke((Character)(object)player); if (val != null) { val.ApplyStatusEffectSpeedMods(ref num3, currentVelocity); } if (!num2) { return num3 <= 0f; } return true; } private static Vector3 BuildWallJumpVelocity(Player player, Vector3 currentVelocity, Vector3 away, bool isTired, MovementInputSnapshot movementInput) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0014: 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_0036: 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_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: 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_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007e: 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_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01d5: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: 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_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_021d: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) Vector3 val = Vector3.ProjectOnPlane(currentVelocity, Vector3.up); float magnitude = ((Vector3)(ref val)).magnitude; float num = Vector3.Dot(val, away); float num2 = Mathf.Max(0f, 0f - num); float num3 = Mathf.Max(0f, num); Vector3 val2 = val - away * num + away * (num3 + num2 * StridePlugin.ReflectionResponsiveness); if (movementInput.HasDirectionalInput) { Vector3 worldDirection = movementInput.WorldDirection; float magnitude2 = movementInput.Magnitude; Vector3 val3 = Vector3.ProjectOnPlane(val2, away); Vector3 val4 = Vector3.ProjectOnPlane(worldDirection, away); if (((Vector3)(ref val4)).sqrMagnitude > 0.01f && (((Vector3)(ref val3)).sqrMagnitude < 0.01f || Vector3.Dot(((Vector3)(ref val3)).normalized, ((Vector3)(ref val4)).normalized) >= -0.1f)) { ((Vector3)(ref val4)).Normalize(); float num4 = Mathf.Max(0.1f, Mathf.Min(StridePlugin.HorizontalBounceMinSpeed * StridePlugin.InputBiasFactor, ((Vector3)(ref val2)).magnitude * 0.35f)); val2 += val4 * (num4 * magnitude2); } } float num5 = Mathf.Lerp(0.1f, 1f, Mathf.Clamp01(magnitude / Mathf.Max(0.01f, StridePlugin.HorizontalBounceMinSpeed))); float num6 = Mathf.Max(num2, StridePlugin.OutwardBounceMinSpeed * num5); float num7 = Mathf.Max(magnitude, StridePlugin.HorizontalBounceMinSpeed * num5); float num8 = Mathf.Min(num6, Mathf.Max(num2, magnitude + 1.5f)); float num9 = Mathf.Min(num7, magnitude + 2f); float num10 = Vector3.Dot(val2, away); if (num10 < num8) { val2 += away * (num8 - num10); } float magnitude3 = ((Vector3)(ref val2)).magnitude; if (magnitude3 < num9) { val2 = ((magnitude3 > 0.01f) ? (val2 / magnitude3) : away) * num9; } float num11 = Mathf.Max(currentVelocity.y * 0.25f, StridePlugin.VerticalPushForce); Vector3 val5 = val2 + Vector3.up * num11; if (isTired) { val5 *= JumpForceTiredFactorRef.Invoke((Character)(object)player); } SEMan val6 = CharacterSeManRef.Invoke((Character)(object)player); if (val6 != null) { val6.ApplyStatusEffectJumpMods(ref val5); } return val5; } private static MovementInputSnapshot SampleMovementInput(Player player) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: 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_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_01fc: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Unknown result type (might be due to invalid IL or missing references) //IL_01e0: Unknown result type (might be due to invalid IL or missing references) //IL_01e5: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Unknown result type (might be due to invalid IL or missing references) MovementInputSnapshot result = default(MovementInputSnapshot); Vector2 val = Vector2.zero; if (ZInput.GetButton("Forward")) { val.y += 1f; } if (ZInput.GetButton("Backward")) { val.y -= 1f; } if (ZInput.GetButton("Right")) { val.x += 1f; } if (ZInput.GetButton("Left")) { val.x -= 1f; } val.x += ZInput.GetJoyLeftStickX(true); val.y += 0f - ZInput.GetJoyLeftStickY(true); result.RawLocalInput = val; result.ForwardAmount = Mathf.Clamp01(Mathf.Max(val.y, 0f)); result.Magnitude = Mathf.Clamp01(((Vector2)(ref val)).magnitude); if (result.Magnitude < 0.1f) { return result; } val = (result.ClampedLocalInput = Vector2.ClampMagnitude(val, 1f)); Transform obj = (((Object)(object)GameCamera.instance != (Object)null) ? ((Component)GameCamera.instance).transform : ((Component)player).transform); Vector3 forward = obj.forward; forward.y = 0f; if (((Vector3)(ref forward)).sqrMagnitude < 0.01f) { forward = ((Component)player).transform.forward; forward.y = 0f; } ((Vector3)(ref forward)).Normalize(); Vector3 right = obj.right; right.y = 0f; if (((Vector3)(ref right)).sqrMagnitude < 0.01f) { right = ((Component)player).transform.right; right.y = 0f; } ((Vector3)(ref right)).Normalize(); Vector3 val2 = forward * val.y + right * val.x; if (((Vector3)(ref val2)).sqrMagnitude < 0.01f) { result.Magnitude = 0f; result.ForwardAmount = 0f; result.ClampedLocalInput = Vector2.zero; result.WorldDirection = Vector3.zero; return result; } result.WorldDirection = ((Vector3)(ref val2)).normalized; return result; } private static void LogDebug(StridePlugin.DebugLogFeature feature, string message) { if (StridePlugin.IsDebugEnabled(feature)) { StridePlugin.Log.LogInfo((object)message); } } private static void LogWallClusters(List clusters) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < clusters.Count; i++) { WallCluster wallCluster = clusters[i]; LogDebug(StridePlugin.DebugLogFeature.WallJump, $"Cluster {i + 1}: normal {FormatVector(wallCluster.Normal)}, hits {wallCluster.HitCount}, plane offset {wallCluster.PlayerPlaneOffset:0.###}, nearest {wallCluster.NearestDistance:0.###}, score {wallCluster.Score:0.###}, best facing {wallCluster.BestVelocityFacingScore:0.###}, flow {wallCluster.ForwardFlowScore:0.###}, penalty {wallCluster.FrontFacePenalty:0.###}, rejectedDistance {wallCluster.RejectedForDistance}, piece {wallCluster.PieceRootId}."); } } private static int GetPieceRootId(Piece piece, Collider collider) { if ((Object)(object)piece != (Object)null) { return ((Object)((Component)piece).gameObject).GetInstanceID(); } if ((Object)(object)collider != (Object)null) { return ((Object)((Component)((Component)collider).transform.root).gameObject).GetInstanceID(); } return 0; } internal static string FormatVector(Vector3 value) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: 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) return $"({value.x:0.###}, {value.y:0.###}, {value.z:0.###})"; } internal static string FormatVector2(Vector2 value) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) return $"({value.x:0.###}, {value.y:0.###})"; } private static string BuildWallJumpLaunchReport(StrideState.WallContactSnapshot wallContact, Vector3 away, Vector3 currentVelocity, Vector3 launchHorizontalVelocity, Vector3 jumpVelocity, MovementInputSnapshot movementInput, bool usedSnapshotApproachVelocity) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: 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_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: 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_0019: 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_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0022: 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_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003b: 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_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0045: 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_0068: 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_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) Vector3 val = Vector3.ProjectOnPlane(currentVelocity, Vector3.up); Vector3 val2 = Vector3.ProjectOnPlane(jumpVelocity, Vector3.up); float num = Vector3.Dot(val, away); float num2 = Vector3.Dot(launchHorizontalVelocity, away); float num3 = Vector3.Dot(val2, away); float num4 = CalculateTangentialSpeed(val, away); float num5 = CalculateTangentialSpeed(launchHorizontalVelocity, away); float num6 = CalculateTangentialSpeed(val2, away); float y = jumpVelocity.y; float num7 = (movementInput.HasDirectionalInput ? Vector3.Dot(movementInput.WorldDirection, away) : 0f); return string.Format("Wall jump report | wall {0} near {1:0.###} | input raw {2} clamp {3} mag {4:0.###} fwd {5:0.###} align {6:0.###} world {7} | inH {8} outDot {9:0.###} tan {10:0.###} | launchH {11} outDot {12:0.###} tan {13:0.###} mem {14} | outH {15} outDot {16:0.###} tan {17:0.###} up {18:0.###}", FormatVector(wallContact.Normal), wallContact.NearestDistance, FormatVector2(movementInput.RawLocalInput), FormatVector2(movementInput.ClampedLocalInput), movementInput.Magnitude, movementInput.ForwardAmount, num7, FormatVector(movementInput.WorldDirection), FormatVector(val), num, num4, FormatVector(launchHorizontalVelocity), num2, num5, usedSnapshotApproachVelocity ? "yes" : "no", FormatVector(val2), num3, num6, y); } private static float CalculateTangentialSpeed(Vector3 velocity, Vector3 away) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) Vector3 val = velocity - away * Vector3.Dot(velocity, away); return ((Vector3)(ref val)).magnitude; } private static string FormatDirection(Vector3 value) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) if (((Vector3)(ref value)).sqrMagnitude < 0.0001f) { return "(0, 0, 0)"; } return FormatVector(((Vector3)(ref value)).normalized); } internal static Rigidbody TryGetBody(Character character) { if (!((Object)(object)character == (Object)null)) { return CharacterBodyRef.Invoke(character); } return null; } } [HarmonyPatch(typeof(Player), "SetControls")] internal static class PlayerSetControlsJumpIntentPatch { private static Player trackedPlayer; private static bool lastResolvedJumpIntent; private static void Postfix(Player __instance, ref bool jump) { if ((Object)(object)__instance != (Object)(object)trackedPlayer) { trackedPlayer = __instance; lastResolvedJumpIntent = false; } if ((Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer || ((Character)__instance).IsDead() || ((Character)__instance).IsSwimming()) { lastResolvedJumpIntent = false; return; } bool num = !((Character)__instance).IsOnGround(); bool flag = jump; if (num && flag && !lastResolvedJumpIntent && StrideState.ShouldAcceptResolvedAirborneJumpPress()) { StrideState.RecordResolvedJumpPress(); } lastResolvedJumpIntent = flag; } } [HarmonyPatch(typeof(Player), "SetControls")] internal static class PlayerSetControlsFallRollPatch { private static readonly FieldRef CrouchToggledRef = AccessTools.FieldRefAccess("m_crouchToggled"); private static Player trackedPlayer; private static bool lastDodgeIntent; private static void Postfix(Player __instance, bool blockHold, ref bool jump, ref bool dodge) { //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance != (Object)(object)trackedPlayer) { trackedPlayer = __instance; lastDodgeIntent = false; } if ((Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer || ((Character)__instance).IsDead() || ((Character)__instance).IsSwimming()) { lastDodgeIntent = false; return; } if (StridePlugin.FallRollEnabled == null || !StridePlugin.FallRollEnabled.Value) { lastDodgeIntent = false; return; } Rigidbody val = PlayerUpdateWallJumpPatch.TryGetBody((Character)(object)__instance); string triggerSource; if ((Object)(object)val == (Object)null || ((Character)__instance).IsOnGround() || val.linearVelocity.y >= -0.1f) { lastDodgeIntent = false; } else if (!TryGetDodgeIntent(__instance, blockHold, jump, dodge, out triggerSource)) { lastDodgeIntent = false; } else if (!lastDodgeIntent) { lastDodgeIntent = true; StrideState.RecordFallRollAttempt(0f - val.linearVelocity.y); LogDebug(StridePlugin.DebugLogFeature.FallRoll, $"Buffered fall roll attempt via {triggerSource} at downward speed {0f - val.linearVelocity.y:0.###}."); } } private static bool TryGetDodgeIntent(Player player, bool blockHold, bool jump, bool dodge, out string triggerSource) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) if (dodge) { triggerSource = "dodge signal"; return true; } if ((int)ZInput.InputLayout == 0 || !ZInput.IsGamepadActive()) { bool flag = ((Character)player).IsCrouching() || CrouchToggledRef.Invoke(player); if (jump && (blockHold || flag)) { triggerSource = (blockHold ? "jump+block" : "jump+crouch"); return true; } } triggerSource = string.Empty; return false; } private static void LogDebug(StridePlugin.DebugLogFeature feature, string message) { if (StridePlugin.IsDebugEnabled(feature)) { StridePlugin.Log.LogInfo((object)message); } } } [HarmonyPatch(typeof(SEMan), "ModifyFallDamage")] internal static class SEManModifyFallDamagePatch { private static readonly FieldRef OwnerRef = AccessTools.FieldRefAccess("m_character"); private static readonly FieldRef PerfectDodgeEffectsRef = AccessTools.FieldRefAccess("m_perfectDodgeEffects"); private static void Prefix(SEMan __instance, float baseDamage, ref float damage) { if (StridePlugin.FallRollEnabled != null && StridePlugin.FallRollEnabled.Value) { Character val = ((__instance != null) ? OwnerRef.Invoke(__instance) : null); if (!((Object)(object)val == (Object)null) && !((Object)(object)val != (Object)(object)Player.m_localPlayer)) { LogFallRollResult($"HOOK: baseDamage={baseDamage:0.###} damageBeforeEffects={damage:0.###} hasBufferedAttempt={StrideState.HasPendingFallRollAttemptForDebug()}"); } } } private static void Postfix(SEMan __instance, float baseDamage, ref float damage) { //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) if (StridePlugin.FallRollEnabled == null || !StridePlugin.FallRollEnabled.Value || __instance == null) { return; } Character val = OwnerRef.Invoke(__instance); if (!((Object)(object)val == (Object)null) && !((Object)(object)val != (Object)(object)Player.m_localPlayer) && !(damage <= 0f) && StrideState.TryConsumeFallRollDamageReduction(out var reduction, out var bufferedAge, out var downwardSpeed)) { float num = Mathf.Clamp01(reduction); float num2 = damage; damage *= 1f - num; float num3 = Mathf.Max(0f, num2 - damage); Player val2 = (Player)(object)((val is Player) ? val : null); if (val2 != null) { PerfectDodgeEffectsRef.Invoke(val2).Create(((Component)val2).transform.position, Quaternion.identity, ((Component)val2).transform, 1f, -1); ((Character)val2).Message((MessageType)2, "Perfect landing", 0, (Sprite)null); } LogFallRollResult($"SUCCESS: bufferedAge={bufferedAge:0.###}s downwardSpeed={downwardSpeed:0.###} baseDamage={baseDamage:0.###} originalDamage={num2:0.###} savedDamage={num3:0.###} finalDamage={damage:0.###} reduction={num * 100f:0.#}%"); LogDebug(StridePlugin.DebugLogFeature.FallRoll, $"Applied buffered fall roll mitigation. Reduced fall damage by {num * 100f:0.#}% after {bufferedAge:0.###} s buffer at downward speed {downwardSpeed:0.###}."); } } private static void LogDebug(StridePlugin.DebugLogFeature feature, string message) { if (StridePlugin.IsDebugEnabled(feature)) { StridePlugin.Log.LogInfo((object)message); } } private static void LogFallRollResult(string message) { if (StridePlugin.IsDebugEnabled(StridePlugin.DebugLogFeature.FallRoll)) { ManualLogSource log = StridePlugin.Log; if (log != null) { log.LogInfo((object)("[FallRoll] " + message)); } } } } [HarmonyPatch(typeof(Player), "HaveStamina")] internal static class PlayerHaveStaminaDebtPatch { private static bool Prefix(Player __instance, float amount, ref bool __result) { if (!StaminaDebt.ShouldManage(__instance)) { return true; } if (StaminaDebt.IsInDebt(__instance)) { StaminaDebt.LogBlockedSpend(__instance, amount); __result = false; return false; } if (StaminaDebt.CanBorrowForChunkedUse(__instance, amount)) { __result = true; return false; } return true; } } [HarmonyPatch(typeof(Player), "RPC_UseStamina")] internal static class PlayerRpcUseStaminaDebtPatch { private static bool Prefix(Player __instance, float v) { if (!StaminaDebt.ShouldManage(__instance)) { return true; } if (!StaminaDebt.IsFinite(v) || v == 0f) { return false; } if (StaminaDebt.IsInDebt(__instance)) { StaminaDebt.LogBlockedSpend(__instance, v); return false; } if (!StaminaDebt.CanBorrowForChunkedUse(__instance, v)) { return true; } StaminaDebt.ApplyDebt(__instance, v); return false; } } [HarmonyPatch(typeof(Player), "UpdateStats", new Type[] { typeof(float) })] internal static class PlayerUpdateStatsDebtPatch { private static void Prefix(Player __instance, out float __state) { __state = StaminaDebt.GetCurrentStamina(__instance); if (StaminaDebt.ShouldManage(__instance) && StaminaDebt.IsInDebt(__instance)) { StaminaDebt.ClearRegenDelay(__instance); } } private static void Postfix(Player __instance, float __state) { StaminaDebt.LogUpdate(__instance, __state); } } [HarmonyPatch(typeof(Hud), "UpdateStamina")] internal static class HudStaminaDebtPatch { private static void Postfix(Hud __instance, Player player) { if (!((Object)(object)__instance == (Object)null) && !((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer)) { if (StaminaDebt.IsInDebt(player)) { StaminaDebt.ApplyHudDebtVisuals(__instance, player); } else { StaminaDebt.RestoreHudVisuals(__instance); } } } } [HarmonyPatch(typeof(Player), "UpdateFood", new Type[] { typeof(float), typeof(bool) })] internal static class PlayerUpdateFoodDebtPatch { private static void Prefix(Player __instance, out float __state) { __state = (StaminaDebt.ShouldManage(__instance) ? StaminaDebt.GetCurrentStamina(__instance) : 0f); } private static void Postfix(Player __instance, float __state) { if (StaminaDebt.ShouldManage(__instance) && !(__state >= 0f)) { StaminaDebt.RestoreDebtAfterFoodUpdateClamp(__instance, __state); } } } internal static class StaminaDebt { private const float MinimumChunkedDebtUse = 5f; private const float MaxDebtVisualRatio = 1.25f; private static readonly Color DebtBarColor = new Color(1f, 0.2f, 0.2f, 1f); private static readonly FieldRef CharacterNViewRef = AccessTools.FieldRefAccess("m_nview"); private static readonly FieldRef GuiBarRectRef = AccessTools.FieldRefAccess("m_bar"); private static readonly FieldRef GuiBarWidthRef = AccessTools.FieldRefAccess("m_width"); private static readonly FieldRef PlayerStaminaRef = AccessTools.FieldRefAccess("m_stamina"); private static readonly FieldRef PlayerStaminaRegenTimerRef = AccessTools.FieldRefAccess("m_staminaRegenTimer"); private static bool wasDebtActiveLastUpdate; private static bool wasDebtHudActiveLastUpdate; private static int lastLoggedDebtWholeValue = int.MinValue; private static int lastLoggedBlockedSpendWholeValue = int.MinValue; private static float lastLoggedBlockedSpendAmount = float.NaN; internal static void ResetTracking() { wasDebtActiveLastUpdate = false; wasDebtHudActiveLastUpdate = false; lastLoggedDebtWholeValue = int.MinValue; lastLoggedBlockedSpendWholeValue = int.MinValue; lastLoggedBlockedSpendAmount = float.NaN; } internal static bool IsFinite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } internal static bool ShouldManage(Player player) { if (StridePlugin.StaminaDebtEnabled == null || !StridePlugin.StaminaDebtEnabled.Value) { return false; } if ((Object)(object)player == (Object)null) { return false; } ZNetView val = CharacterNViewRef.Invoke((Character)(object)player); if ((Object)(object)player == (Object)(object)Player.m_localPlayer && (Object)(object)val != (Object)null && val.IsValid()) { return val.IsOwner(); } return false; } internal static float GetCurrentStamina(Player player) { if (!((Object)(object)player != (Object)null)) { return 0f; } return PlayerStaminaRef.Invoke(player); } internal static bool IsInDebt(Player player) { if ((Object)(object)player != (Object)null) { return PlayerStaminaRef.Invoke(player) < 0f; } return false; } internal static bool CanBorrowForChunkedUse(Player player, float amount) { if ((Object)(object)player == (Object)null || !IsFinite(amount) || amount < 5f) { return false; } float num = PlayerStaminaRef.Invoke(player); if (num > 0f) { return amount > num; } return false; } internal static void ApplyDebt(Player player, float amount) { if (!((Object)(object)player == (Object)null) && IsFinite(amount) && !(amount <= 0f)) { float num = PlayerStaminaRef.Invoke(player); PlayerStaminaRef.Invoke(player) -= amount; PlayerStaminaRegenTimerRef.Invoke(player) = 0f; Log($"Borrowed stamina debt. Cost {amount:0.###}, stamina {num:0.###} -> {PlayerStaminaRef.Invoke(player):0.###}."); } } internal static void ClearRegenDelay(Player player) { PlayerStaminaRegenTimerRef.Invoke(player) = 0f; } internal static void ApplyHudDebtVisuals(Hud hud, Player player) { float num = PlayerStaminaRef.Invoke(player); float num2 = Mathf.Max(1f, ((Character)player).GetMaxStamina()); float debtRatio = Mathf.Min(1.25f, (0f - num) / num2); hud.m_staminaText.text = StringExtensionMethods.ToFastString(Mathf.CeilToInt(num)); SetDebtBar(hud.m_staminaBar2Fast, debtRatio); SetDebtBar(hud.m_staminaBar2Slow, debtRatio); wasDebtHudActiveLastUpdate = true; } internal static void RestoreHudVisuals(Hud hud) { if (wasDebtHudActiveLastUpdate) { RestoreBar(hud.m_staminaBar2Fast); RestoreBar(hud.m_staminaBar2Slow); wasDebtHudActiveLastUpdate = false; } } private static void SetDebtBar(GuiBar bar, float debtRatio) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: 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: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)bar == (Object)null)) { if (((Behaviour)bar).enabled) { ((Behaviour)bar).enabled = false; } bar.SetColor(DebtBarColor); RectTransform val = GuiBarRectRef.Invoke(bar); if ((Object)(object)val != (Object)null) { Vector3 localScale = ((Transform)val).localScale; localScale.x = 0f - Mathf.Abs(localScale.x); ((Transform)val).localScale = localScale; val.SetSizeWithCurrentAnchors((Axis)0, GuiBarWidthRef.Invoke(bar) * debtRatio); } } } private static void RestoreBar(GuiBar bar) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: 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_0040: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)bar == (Object)null)) { bar.ResetColor(); RectTransform val = GuiBarRectRef.Invoke(bar); if ((Object)(object)val != (Object)null) { Vector3 localScale = ((Transform)val).localScale; localScale.x = Mathf.Abs(localScale.x); ((Transform)val).localScale = localScale; } if (!((Behaviour)bar).enabled) { ((Behaviour)bar).enabled = true; } } } internal static void LogUpdate(Player player, float previousStamina) { if (!ShouldManage(player)) { return; } float num = PlayerStaminaRef.Invoke(player); bool num2 = num < 0f; if (num2) { int num3 = Mathf.CeilToInt(num); if (!wasDebtActiveLastUpdate) { Log($"Debt active. Stamina crossed below zero: {previousStamina:0.###} -> {num:0.###}."); } else if (num3 != lastLoggedDebtWholeValue) { Log($"Debt repaying. Stamina {previousStamina:0.###} -> {num:0.###}, regen timer {PlayerStaminaRegenTimerRef.Invoke(player):0.###}."); } lastLoggedDebtWholeValue = num3; } else if (wasDebtActiveLastUpdate) { Log($"Debt cleared. Stamina {previousStamina:0.###} -> {num:0.###}."); lastLoggedDebtWholeValue = int.MinValue; lastLoggedBlockedSpendWholeValue = int.MinValue; lastLoggedBlockedSpendAmount = float.NaN; } wasDebtActiveLastUpdate = num2; } internal static void LogBlockedSpend(Player player, float amount) { if (!((Object)(object)player == (Object)null)) { float num = PlayerStaminaRef.Invoke(player); int num2 = Mathf.CeilToInt(num); if (num2 != lastLoggedBlockedSpendWholeValue || !(Mathf.Abs(amount - lastLoggedBlockedSpendAmount) < 0.01f)) { lastLoggedBlockedSpendWholeValue = num2; lastLoggedBlockedSpendAmount = amount; Log($"Blocked stamina spend while in debt. Cost {amount:0.###}, current stamina {num:0.###}."); } } } internal static void RestoreDebtAfterFoodUpdateClamp(Player player, float debtStamina) { float num = PlayerStaminaRef.Invoke(player); if (num == 0f) { PlayerStaminaRef.Invoke(player) = debtStamina; Log($"Restored stamina debt after food update clamp. Stamina {num:0.###} -> {debtStamina:0.###}."); } } private static void Log(string message) { if (StridePlugin.IsDebugEnabled(StridePlugin.DebugLogFeature.StaminaDebt)) { ManualLogSource log = StridePlugin.Log; if (log != null) { log.LogInfo((object)("[StaminaDebt] " + message)); } } } } internal static class StrideState { internal struct WallContactSnapshot { internal Vector3 Normal; internal Vector3 ApproachVelocity; internal float ContactTime; internal float ApproachTime; internal float NearestDistance; internal static WallContactSnapshot Invalid { get { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //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) WallContactSnapshot result = default(WallContactSnapshot); result.Normal = Vector3.zero; result.ApproachVelocity = Vector3.zero; result.ContactTime = -999f; result.ApproachTime = -999f; result.NearestDistance = float.MaxValue; return result; } } } private static float lastGroundedTime = -999f; private static float lastGroundJumpTime = -999f; private static float lastJumpPressedTime = -999f; private static float lastWallJumpTime = -999f; private static float lastFallRollAttemptTime = -999f; private static float lastFallRollDownwardSpeed; private static int lastResolvedJumpPressFrame = -1; private static int lastSuccessfulJumpFrame = -999; private static bool hasResolvedJumpPressQueued; private static bool suppressNextGroundJumpLog; private static bool usedGroundJumpSinceLastGrounded; private static bool usedWallJumpThisAirborneState; private static WallContactSnapshot lastWallContact = WallContactSnapshot.Invalid; internal static void RecordResolvedJumpPress() { if (lastResolvedJumpPressFrame != Time.frameCount) { lastResolvedJumpPressFrame = Time.frameCount; hasResolvedJumpPressQueued = true; } } internal static bool TryConsumeResolvedJumpPress() { if (!hasResolvedJumpPressQueued) { return false; } hasResolvedJumpPressQueued = false; return true; } private static void ClearResolvedJumpPress() { hasResolvedJumpPressQueued = false; } internal static void RecordJumpPressed() { if (UsesBufferedJumpTracking()) { lastJumpPressedTime = Time.time; } } internal static void RecordGrounded() { lastGroundedTime = Time.time; usedGroundJumpSinceLastGrounded = false; lastWallContact = WallContactSnapshot.Invalid; } internal static void RecordGroundJump() { ClearResolvedJumpPress(); lastSuccessfulJumpFrame = Time.frameCount; lastGroundJumpTime = Time.time; usedGroundJumpSinceLastGrounded = true; lastJumpPressedTime = -999f; usedWallJumpThisAirborneState = false; lastWallContact = WallContactSnapshot.Invalid; if (suppressNextGroundJumpLog) { suppressNextGroundJumpLog = false; } else { LogJumpTiming("Recorded regular jump."); } } internal static void RecordWallJump() { ClearResolvedJumpPress(); lastSuccessfulJumpFrame = Time.frameCount; lastWallJumpTime = Time.time; lastJumpPressedTime = -999f; usedWallJumpThisAirborneState = true; lastWallContact = WallContactSnapshot.Invalid; } internal static void RecordCoyoteJump() { ClearResolvedJumpPress(); lastSuccessfulJumpFrame = Time.frameCount; lastGroundJumpTime = Time.time; usedGroundJumpSinceLastGrounded = true; usedWallJumpThisAirborneState = false; lastWallContact = WallContactSnapshot.Invalid; } internal static bool ShouldAcceptResolvedAirborneJumpPress() { return Time.frameCount > lastSuccessfulJumpFrame + 1; } internal static void RecordWallContact(Vector3 wallNormal, Vector3 horizontalVelocity, float nearestDistance) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: 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_007e: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) if (StridePlugin.WallJumpEnabled == null || !StridePlugin.WallJumpEnabled.Value) { lastWallContact = WallContactSnapshot.Invalid; } else if (!(((Vector3)(ref wallNormal)).sqrMagnitude < 0.01f)) { float time = Time.time; Vector3 normalized = ((Vector3)(ref wallNormal)).normalized; if (!IsWallContactRecent() || !(Vector3.Dot(lastWallContact.Normal, normalized) >= 0.8f) || !(nearestDistance <= lastWallContact.NearestDistance + 0.12f)) { WallContactSnapshot wallContactSnapshot = default(WallContactSnapshot); wallContactSnapshot.Normal = normalized; wallContactSnapshot.NearestDistance = nearestDistance; wallContactSnapshot.ContactTime = time; wallContactSnapshot.ApproachTime = -999f; wallContactSnapshot.ApproachVelocity = Vector3.zero; lastWallContact = wallContactSnapshot; } else { lastWallContact.ContactTime = time; lastWallContact.Normal = normalized; lastWallContact.NearestDistance = Mathf.Min(lastWallContact.NearestDistance, nearestDistance); } float magnitude = ((Vector3)(ref horizontalVelocity)).magnitude; float num = ((magnitude > 0.01f) ? Vector3.Dot(horizontalVelocity / magnitude, -normalized) : 0f); if (magnitude >= StridePlugin.MinimumWallJumpEntrySpeed && num >= 0.05f && magnitude > ((Vector3)(ref lastWallContact.ApproachVelocity)).magnitude) { lastWallContact.ApproachTime = time; lastWallContact.ApproachVelocity = horizontalVelocity; } } } internal static void RecordFallRollAttempt(float downwardSpeed) { if (StridePlugin.FallRollEnabled != null && StridePlugin.FallRollEnabled.Value) { lastFallRollAttemptTime = Time.time; lastFallRollDownwardSpeed = Mathf.Max(0f, downwardSpeed); } } private static void ClearFallRollAttempt() { lastFallRollAttemptTime = -999f; lastFallRollDownwardSpeed = 0f; } internal static void ResetAirborneState() { usedWallJumpThisAirborneState = false; lastWallContact = WallContactSnapshot.Invalid; ClearFallRollAttempt(); } internal static void ResetAll() { lastGroundedTime = -999f; lastGroundJumpTime = -999f; lastJumpPressedTime = -999f; lastWallJumpTime = -999f; ClearFallRollAttempt(); lastResolvedJumpPressFrame = -1; lastSuccessfulJumpFrame = -999; hasResolvedJumpPressQueued = false; lastWallContact = WallContactSnapshot.Invalid; suppressNextGroundJumpLog = false; usedGroundJumpSinceLastGrounded = false; usedWallJumpThisAirborneState = false; } internal static bool TryConsumeFallRollDamageReduction(out float reduction, out float bufferedAge, out float downwardSpeed) { if (StridePlugin.FallRollEnabled == null || !StridePlugin.FallRollEnabled.Value) { reduction = 0f; bufferedAge = -1f; downwardSpeed = 0f; return false; } bufferedAge = Time.time - lastFallRollAttemptTime; downwardSpeed = lastFallRollDownwardSpeed; reduction = 0f; if (bufferedAge < 0f) { return false; } if (bufferedAge > StridePlugin.FallRollBufferSeconds) { ClearFallRollAttempt(); return false; } reduction = StridePlugin.FallRollDamageReduction; ClearFallRollAttempt(); return reduction > 0f; } private static bool HasPendingFallRollAttempt() { return lastFallRollAttemptTime > -100f; } internal static bool HasPendingFallRollAttemptForDebug() { return HasPendingFallRollAttempt(); } private static void LogFallRollResult(string message) { if (StridePlugin.IsDebugEnabled(StridePlugin.DebugLogFeature.FallRoll)) { ManualLogSource log = StridePlugin.Log; if (log != null) { log.LogInfo((object)("[FallRoll] " + message)); } } } internal static bool TryGetWallJumpContact(Player player, bool hasCurrentWall, PlayerUpdateWallJumpPatch.WallDetectionResult currentWall, out WallContactSnapshot wallContact, out bool usingRememberedWall) { //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: 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_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) wallContact = default(WallContactSnapshot); usingRememberedWall = false; if (StridePlugin.WallJumpEnabled == null || !StridePlugin.WallJumpEnabled.Value) { return false; } if (hasCurrentWall) { if (((Vector3)(ref currentWall.Normal)).sqrMagnitude < 0.01f) { LogDebug(StridePlugin.DebugLogFeature.WallJump, "Rejected current wall: no detected wall normal."); return false; } wallContact = CreateWallContactSnapshot(currentWall); if (((Vector3)(ref wallContact.Normal)).sqrMagnitude < 0.01f) { LogDebug(StridePlugin.DebugLogFeature.WallJump, "Rejected current wall: unable to create wall contact snapshot."); return false; } return true; } if (!IsWallContactRemembered()) { return false; } if (((Vector3)(ref lastWallContact.Normal)).sqrMagnitude < 0.01f) { LogDebug(StridePlugin.DebugLogFeature.WallJump, "Rejected remembered wall: no stored wall normal."); return false; } Rigidbody val = PlayerUpdateWallJumpPatch.TryGetBody((Character)(object)player); Vector3 val2 = (((Object)(object)val != (Object)null) ? Vector3.ProjectOnPlane(val.linearVelocity, Vector3.up) : Vector3.zero); if (((Vector3)(ref val2)).sqrMagnitude >= 0.0025000002f) { float num = Vector3.Dot(((Vector3)(ref val2)).normalized, -lastWallContact.Normal); if (num < 0.05f) { LogDebug(StridePlugin.DebugLogFeature.WallJump, $"Rejected remembered wall: approach alignment too low ({num:0.###})."); return false; } } wallContact = lastWallContact; usingRememberedWall = !hasCurrentWall; if (usingRememberedWall) { LogDebug(StridePlugin.DebugLogFeature.WallJump, "Using remembered wall contact " + PlayerUpdateWallJumpPatch.FormatVector(wallContact.Normal) + "."); } return true; } internal static bool TryConsumeBufferedGroundJump() { if (StridePlugin.JumpBufferingEnabled == null || !StridePlugin.JumpBufferingEnabled.Value) { return false; } if (!HasBufferedJump()) { return false; } if (usedGroundJumpSinceLastGrounded) { return false; } lastJumpPressedTime = -999f; LogDebug(StridePlugin.DebugLogFeature.JumpBuffering, "Consumed buffered ground jump."); return true; } internal static bool TryConsumeCoyoteJump() { if (StridePlugin.CoyoteTimeEnabled == null || !StridePlugin.CoyoteTimeEnabled.Value) { return false; } if (!HasBufferedJump()) { return false; } if (usedGroundJumpSinceLastGrounded) { return false; } if (Time.time - lastGroundedTime > StridePlugin.CoyoteTimeSeconds) { return false; } lastJumpPressedTime = -999f; suppressNextGroundJumpLog = true; LogDebug(StridePlugin.DebugLogFeature.CoyoteTime, "Consumed coyote jump."); return true; } internal static bool CanWallJump(Player player) { if (StridePlugin.WallJumpEnabled == null || !StridePlugin.WallJumpEnabled.Value) { return false; } if (usedWallJumpThisAirborneState) { return false; } if (Time.time - lastGroundJumpTime > StridePlugin.JumpWindowSeconds) { return false; } if (Time.time - lastWallJumpTime < StridePlugin.CooldownSeconds) { return false; } return (Object)(object)PlayerUpdateWallJumpPatch.TryGetBody((Character)(object)player) != (Object)null; } internal static void ClearWallJumpTracking() { lastWallJumpTime = -999f; usedWallJumpThisAirborneState = false; lastWallContact = WallContactSnapshot.Invalid; } internal static bool UsesBufferedJumpTracking() { if (StridePlugin.CoyoteTimeEnabled == null || !StridePlugin.CoyoteTimeEnabled.Value) { if (StridePlugin.JumpBufferingEnabled != null) { return StridePlugin.JumpBufferingEnabled.Value; } return false; } return true; } private static bool HasBufferedJump() { return Time.time - lastJumpPressedTime <= StridePlugin.JumpBufferSeconds; } private static bool IsWallContactRecent() { return Time.time - lastWallContact.ContactTime <= StridePlugin.CoyoteTimeSeconds; } private static bool IsWallContactRemembered() { if (Time.time - lastWallContact.ContactTime > StridePlugin.WallMemorySeconds) { LogDebug(StridePlugin.DebugLogFeature.WallJump, "Rejected remembered wall: memory window expired."); return false; } return true; } private static WallContactSnapshot CreateWallContactSnapshot(PlayerUpdateWallJumpPatch.WallDetectionResult wallDetection) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0026: 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_0032: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: 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_0095: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) Vector3 val = ((((Vector3)(ref wallDetection.Normal)).sqrMagnitude > 0.01f) ? ((Vector3)(ref wallDetection.Normal)).normalized : Vector3.zero); WallContactSnapshot wallContactSnapshot = default(WallContactSnapshot); wallContactSnapshot.Normal = val; wallContactSnapshot.NearestDistance = wallDetection.NearestDistance; wallContactSnapshot.ContactTime = Time.time; wallContactSnapshot.ApproachTime = -999f; wallContactSnapshot.ApproachVelocity = Vector3.zero; WallContactSnapshot result = wallContactSnapshot; if (!IsWallContactRecent()) { return result; } if (((Vector3)(ref lastWallContact.Normal)).sqrMagnitude < 0.01f) { return result; } if (!(Vector3.Dot(lastWallContact.Normal, val) >= 0.8f) || !(wallDetection.NearestDistance <= lastWallContact.NearestDistance + 0.12f)) { return result; } result.ContactTime = lastWallContact.ContactTime; result.NearestDistance = Mathf.Min(lastWallContact.NearestDistance, wallDetection.NearestDistance); result.ApproachTime = lastWallContact.ApproachTime; result.ApproachVelocity = lastWallContact.ApproachVelocity; return result; } private static void LogDebug(StridePlugin.DebugLogFeature feature, string message) { if (StridePlugin.IsDebugEnabled(feature)) { StridePlugin.Log.LogInfo((object)message); } } private static void LogJumpTiming(string message) { if (StridePlugin.IsDebugEnabled(StridePlugin.DebugLogFeature.CoyoteTime) || StridePlugin.IsDebugEnabled(StridePlugin.DebugLogFeature.JumpBuffering)) { StridePlugin.Log.LogInfo((object)message); } } }