From 9e6b689dd6170079a68057150a5b8eed084eeed5 Mon Sep 17 00:00:00 2001 From: BruceChen Date: Sun, 12 Apr 2026 01:38:17 +0800 Subject: [PATCH] feat: add corner walk, sprint descend, and parkour descend moves - MoveDiagonal: allow single-side-blocked diagonals (corner walk) so the bot can hug an open side to cut around a wall; both-sides-blocked remains impossible. Walk-speed cost when one side is blocked. - MoveSprintDescend: sprint off a ledge covering 2 horizontal blocks while dropping 1-3 blocks. Registered for cardinal and diagonal offsets. - MoveParkour: support negative yDelta (-1, -2) for descending parkour where the bot sprint-jumps across a gap and lands on a lower platform. Registered cardinal (dist 2-4, y-1/-2) and diagonal variants. - DescendTemplate: sprint when horizontal distance > 1.5 blocks. - SprintJumpTemplate: increase vertical landing tolerance for descend. Made-with: Cursor --- .../Pathing/Core/AStarPathFinder.cs | 28 ++++ .../Execution/Templates/DescendTemplate.cs | 7 + .../Execution/Templates/SprintJumpTemplate.cs | 4 +- .../Pathing/Moves/Impl/MoveDiagonal.cs | 21 +-- .../Pathing/Moves/Impl/MoveParkour.cs | 12 +- .../Pathing/Moves/Impl/MoveSprintDescend.cs | 122 ++++++++++++++++++ 6 files changed, 183 insertions(+), 11 deletions(-) create mode 100644 MinecraftClient/Pathing/Moves/Impl/MoveSprintDescend.cs diff --git a/MinecraftClient/Pathing/Core/AStarPathFinder.cs b/MinecraftClient/Pathing/Core/AStarPathFinder.cs index 14c4ca08..dd2d7a4e 100644 --- a/MinecraftClient/Pathing/Core/AStarPathFinder.cs +++ b/MinecraftClient/Pathing/Core/AStarPathFinder.cs @@ -59,6 +59,16 @@ namespace MinecraftClient.Pathing.Core moves.Add(new MoveFall()); + // Sprint descend: sprint off ledge, 2 blocks horizontal + 1-3 drop + foreach (int dx in offsets) + { + moves.Add(new MoveSprintDescend(dx * 2, 0)); + moves.Add(new MoveSprintDescend(dx, dx)); + moves.Add(new MoveSprintDescend(dx, -dx)); + } + foreach (int dz in offsets) + moves.Add(new MoveSprintDescend(0, dz * 2)); + // Cardinal parkour: 2-4 block sprint jumps along +-X and +-Z foreach (int dx in offsets) { @@ -67,6 +77,13 @@ namespace MinecraftClient.Pathing.Core // Ascending: +1Y, dist 2-3 (dist 4 ascend not physically reliable) for (int dist = 2; dist <= 3; dist++) moves.Add(new MoveParkour(dx * dist, 0, yDelta: 1)); + // Descending parkour: sprint-jump, land 1-2 blocks lower + for (int dist = 2; dist <= 4; dist++) + { + moves.Add(new MoveParkour(dx * dist, 0, yDelta: -1)); + if (dist <= 3) + moves.Add(new MoveParkour(dx * dist, 0, yDelta: -2)); + } } foreach (int dz in offsets) { @@ -74,6 +91,12 @@ namespace MinecraftClient.Pathing.Core moves.Add(new MoveParkour(0, dz * dist)); for (int dist = 2; dist <= 3; dist++) moves.Add(new MoveParkour(0, dz * dist, yDelta: 1)); + for (int dist = 2; dist <= 4; dist++) + { + moves.Add(new MoveParkour(0, dz * dist, yDelta: -1)); + if (dist <= 3) + moves.Add(new MoveParkour(0, dz * dist, yDelta: -2)); + } } // Diagonal parkour: sprint jumps at angles. @@ -90,6 +113,11 @@ namespace MinecraftClient.Pathing.Core // (3,1)/(1,3): sqrt(10) ~ 3.16 blocks moves.Add(new MoveParkour(dx * 3, dz * 1)); moves.Add(new MoveParkour(dx * 1, dz * 3)); + + // Diagonal descending parkour + moves.Add(new MoveParkour(dx * 2, dz * 1, yDelta: -1)); + moves.Add(new MoveParkour(dx * 1, dz * 2, yDelta: -1)); + moves.Add(new MoveParkour(dx * 2, dz * 2, yDelta: -1)); } } diff --git a/MinecraftClient/Pathing/Execution/Templates/DescendTemplate.cs b/MinecraftClient/Pathing/Execution/Templates/DescendTemplate.cs index 21185816..9cec3f78 100644 --- a/MinecraftClient/Pathing/Execution/Templates/DescendTemplate.cs +++ b/MinecraftClient/Pathing/Execution/Templates/DescendTemplate.cs @@ -7,6 +7,7 @@ namespace MinecraftClient.Pathing.Execution.Templates /// /// Walk off a ledge and drop 1-N blocks to a landing spot. /// Walks toward the destination; gravity handles the fall. + /// Sprints when the horizontal distance is large (> 1.5 blocks). /// Supports solid landings, water landings, and mid-fall vine/ladder grabs. /// public sealed class DescendTemplate : IActionTemplate @@ -16,11 +17,15 @@ namespace MinecraftClient.Pathing.Execution.Templates private int _tickCount; private bool _hasFallen; + private readonly bool _needsSprint; public DescendTemplate(Location start, Location end) { ExpectedStart = start; ExpectedEnd = end; + double hdx = end.X - start.X; + double hdz = end.Z - start.Z; + _needsSprint = (hdx * hdx + hdz * hdz) > 2.25; } public TemplateState Tick(Location pos, PlayerPhysics physics, MovementInput input) @@ -70,6 +75,8 @@ namespace MinecraftClient.Pathing.Execution.Templates { physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, targetYaw); input.Forward = true; + if (_needsSprint) + input.Sprint = true; } return TemplateState.InProgress; diff --git a/MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs b/MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs index 7483b083..dc70940f 100644 --- a/MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs +++ b/MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs @@ -82,9 +82,9 @@ namespace MinecraftClient.Pathing.Execution.Templates goto case Phase.Landing; case Phase.Landing: - // Tolerance scales with jump distance double horizTolerance = _horizDist >= 3.5 ? 3.0 : 2.0; - if (horizDistSq < horizTolerance && Math.Abs(dy) < 1.0) + double vertTolerance = Math.Abs(ExpectedEnd.Y - ExpectedStart.Y) > 0.5 ? 1.5 : 1.0; + if (horizDistSq < horizTolerance && Math.Abs(dy) < vertTolerance) return TemplateState.Complete; return TemplateState.Failed; } diff --git a/MinecraftClient/Pathing/Moves/Impl/MoveDiagonal.cs b/MinecraftClient/Pathing/Moves/Impl/MoveDiagonal.cs index 70e78d77..8b43a2bd 100644 --- a/MinecraftClient/Pathing/Moves/Impl/MoveDiagonal.cs +++ b/MinecraftClient/Pathing/Moves/Impl/MoveDiagonal.cs @@ -4,7 +4,9 @@ namespace MinecraftClient.Pathing.Moves.Impl { /// /// Diagonal walk (1 block in both X and Z, same Y). - /// Checks both intermediate cardinal columns for clearance. + /// Allows corner walks: if one intermediate cardinal is blocked by a wall + /// but the other is clear, the player can hug the open side to cut the + /// corner. Both sides blocked is impossible (player AABB too wide). /// public sealed class MoveDiagonal : IMove { @@ -36,19 +38,22 @@ namespace MinecraftClient.Pathing.Moves.Impl return; } - if (!ctx.CanWalkThrough(x + XOffset, y, z) || !ctx.CanWalkThrough(x + XOffset, y + 1, z)) + bool sideX = ctx.CanWalkThrough(x + XOffset, y, z) && + ctx.CanWalkThrough(x + XOffset, y + 1, z); + bool sideZ = ctx.CanWalkThrough(x, y, z + ZOffset) && + ctx.CanWalkThrough(x, y + 1, z + ZOffset); + + if (!sideX && !sideZ) { result.SetImpossible(); return; } - if (!ctx.CanWalkThrough(x, y, z + ZOffset) || !ctx.CanWalkThrough(x, y + 1, z + ZOffset)) - { - result.SetImpossible(); - return; - } + double cost = ctx.SprintCost * ActionCosts.DiagonalMultiplier; + if (!sideX || !sideZ) + cost = ctx.WalkCost * ActionCosts.DiagonalMultiplier; - result.Set(destX, y, destZ, ctx.SprintCost * ActionCosts.DiagonalMultiplier); + result.Set(destX, y, destZ, cost); } } } diff --git a/MinecraftClient/Pathing/Moves/Impl/MoveParkour.cs b/MinecraftClient/Pathing/Moves/Impl/MoveParkour.cs index 278ffe06..fc442b9c 100644 --- a/MinecraftClient/Pathing/Moves/Impl/MoveParkour.cs +++ b/MinecraftClient/Pathing/Moves/Impl/MoveParkour.cs @@ -6,7 +6,8 @@ namespace MinecraftClient.Pathing.Moves.Impl { /// /// Sprint jump across a gap in cardinal or diagonal direction. - /// Supports horizontal distances of 2-4 blocks and optional +1Y ascent. + /// Supports horizontal distances of 2-4 blocks, optional +1Y ascent, + /// and -1/-2Y descent (land on a lower platform after the jump). /// Based on Baritone's MovementParkour design with diagonal extensions. /// public sealed class MoveParkour : IMove @@ -44,6 +45,12 @@ namespace MinecraftClient.Pathing.Moves.Impl return; } + if (_yDelta < 0 && -_yDelta > ctx.MaxFallHeight) + { + result.SetImpossible(); + return; + } + if (!ctx.CanSprint) { result.SetImpossible(); @@ -172,6 +179,9 @@ namespace MinecraftClient.Pathing.Moves.Impl double cost; if (_yDelta > 0) cost = horizDist * ctx.SprintCost + ctx.JumpPenalty * 2; + else if (_yDelta < 0) + cost = horizDist * ctx.SprintCost + ctx.JumpPenalty + + ActionCosts.FallCost(-_yDelta); else if (horizDist >= 3.5) cost = horizDist * ctx.SprintCost + ctx.JumpPenalty; else diff --git a/MinecraftClient/Pathing/Moves/Impl/MoveSprintDescend.cs b/MinecraftClient/Pathing/Moves/Impl/MoveSprintDescend.cs new file mode 100644 index 00000000..6acd9813 --- /dev/null +++ b/MinecraftClient/Pathing/Moves/Impl/MoveSprintDescend.cs @@ -0,0 +1,122 @@ +using System; +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; + +namespace MinecraftClient.Pathing.Moves.Impl +{ + /// + /// Sprint off a ledge and land 2 blocks away horizontally while dropping 1-3 blocks. + /// At sprint speed (~5.6 blocks/s), falling 1-3 blocks gives enough airtime to + /// cover 2 horizontal blocks without needing a jump. + /// Supports cardinal (2,0)/(0,2) and diagonal (1,1) offsets. + /// + public sealed class MoveSprintDescend : IMove + { + public MoveType Type => MoveType.Descend; + public int XOffset { get; } + public int ZOffset { get; } + public bool DynamicY => true; + + public MoveSprintDescend(int xOffset, int zOffset) + { + XOffset = xOffset; + ZOffset = zOffset; + } + + public void Calculate(CalculationContext ctx, int x, int y, int z, ref MoveResult result) + { + if (!ctx.CanSprint) + { + result.SetImpossible(); + return; + } + + int destX = x + XOffset; + int destZ = z + ZOffset; + + Material fromDown = ctx.GetMaterial(x, y - 1, z); + if (fromDown.CanBeClimbedOn()) + { + result.SetImpossible(); + return; + } + + int xSign = Math.Sign(XOffset); + int zSign = Math.Sign(ZOffset); + int xAbs = Math.Abs(XOffset); + int zAbs = Math.Abs(ZOffset); + + // The flight path sweeps through intermediate blocks at current Y. + // Check body clearance for all intermediate and destination columns. + for (int i = 0; i <= xAbs; i++) + { + for (int j = 0; j <= zAbs; j++) + { + if (i == 0 && j == 0) continue; + int gx = x + xSign * i; + int gz = z + zSign * j; + if (!ctx.CanWalkThrough(gx, y, gz) || !ctx.CanWalkThrough(gx, y + 1, gz)) + { + result.SetImpossible(); + return; + } + } + } + + // The first step in the primary direction must lack ground (this IS a drop). + if (xAbs > 0 && zAbs == 0) + { + if (ctx.CanWalkOn(x + xSign, y - 1, z)) + { + result.SetImpossible(); + return; + } + } + else if (xAbs == 0 && zAbs > 0) + { + if (ctx.CanWalkOn(x, y - 1, z + zSign)) + { + result.SetImpossible(); + return; + } + } + else + { + if (ctx.CanWalkOn(x + xSign, y - 1, z + zSign)) + { + result.SetImpossible(); + return; + } + } + + // Scan downward from destination column for a landing spot. + double horizDist = Math.Sqrt((double)(XOffset * XOffset + ZOffset * ZOffset)); + for (int drop = 1; drop <= ctx.MaxFallHeight; drop++) + { + int landY = y - drop - 1; + if (landY < -64) break; + + if (!ctx.CanWalkOn(destX, landY, destZ)) + continue; + + Material landMat = ctx.GetMaterial(destX, landY, destZ); + if (MoveHelper.IsHazardous(landMat)) + { + result.SetImpossible(); + return; + } + + // Body space at landing + if (!ctx.CanWalkThrough(destX, landY + 1, destZ) || + !ctx.CanWalkThrough(destX, landY + 2, destZ)) + continue; + + double cost = horizDist * ctx.SprintCost + ActionCosts.FallCost(drop); + result.Set(destX, landY + 1, destZ, cost); + return; + } + + result.SetImpossible(); + } + } +}