From a6261e40198eb1ea93aa7fb563736851e8667fff Mon Sep 17 00:00:00 2001 From: BruceChen Date: Sat, 11 Apr 2026 02:47:31 +0800 Subject: [PATCH] fix: improve pathfinding execution for climbing and block classification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix MoveHelper.CanWalkThrough to treat climbable blocks (ladders, vines) as passable, not solid -- MCC's IsSolid() incorrectly classifies them - Fix MoveHelper.CanWalkOn to exclude climbable blocks from ground check - Add fence gate passability in MoveHelper - Fix start position calculation in MoveToAStar to handle solid-block floor rounding (player at y=79.9 → floor y=79 inside solid) - Fix ReachedWaypoint to require vertical proximity for climb waypoints, preventing premature waypoint consumption during ladder ascent - Fix SetInputToward to handle ladder climbing with Jump input and proper wall-facing when OnClimbable Made-with: Cursor --- MinecraftClient/McClient.cs | 57 +++++++++++++++++++-- MinecraftClient/Pathing/Moves/MoveHelper.cs | 22 ++++++++ 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/MinecraftClient/McClient.cs b/MinecraftClient/McClient.cs index 9c3f9751..c343f16e 100644 --- a/MinecraftClient/McClient.cs +++ b/MinecraftClient/McClient.cs @@ -1730,11 +1730,20 @@ namespace MinecraftClient int sx = (int)Math.Floor(location.X); int sy = (int)Math.Floor(location.Y); int sz = (int)Math.Floor(location.Z); + + // If floored Y lands inside a solid block (e.g. player on top of it), step up + if (!ctx.CanWalkThrough(sx, sy, sz) && ctx.CanWalkThrough(sx, sy + 1, sz)) + sy++; + int gx = (int)Math.Floor(goal.X); int gy = (int)Math.Floor(goal.Y); int gz = (int)Math.Floor(goal.Z); - Log.Info($"[Goto] A* search from ({sx},{sy},{sz}) to ({gx},{gy},{gz})..."); + if (!ctx.CanWalkThrough(gx, gy, gz) && ctx.CanWalkThrough(gx, gy + 1, gz)) + gy++; + + Log.Info($"[Goto] A* search from ({sx},{sy},{sz}) to ({gx},{gy},{gz}) " + + $"[raw pos=({location.X:F2},{location.Y:F2},{location.Z:F2})]"); using var cts = new CancellationTokenSource(); var result = finder.Calculate(ctx, sx, sy, sz, @@ -3293,12 +3302,20 @@ namespace MinecraftClient /// /// Check if the player has approximately reached a waypoint. + /// Uses both horizontal and vertical distance for climb/descend waypoints. /// private bool ReachedWaypoint(Location target) { double dx = target.X - location.X; double dz = target.Z - location.Z; - return dx * dx + dz * dz < 0.25; // within ~0.5 blocks horizontally + double dy = target.Y - location.Y; + double horizDistSq = dx * dx + dz * dz; + + // Vertical waypoint (climbing/falling): require reaching target Y level + if (horizDistSq < 0.5 && Math.Abs(dy) > 0.8) + return false; + + return horizDistSq < 0.25 && Math.Abs(dy) < 0.8; } /// @@ -3312,7 +3329,41 @@ namespace MinecraftClient double dy = target.Y - location.Y; double distSqr = dx * dx + dz * dz; - if (distSqr < 0.01) return; // Close enough horizontally + // Climbing: target is above/below with small horizontal offset + if (playerPhysics.OnClimbable && Math.Abs(dy) > 0.5 && distSqr < 1.0) + { + if (dy > 0) + { + physicsInput.Jump = true; + // Push against the wall for HorizontalCollision-triggered climbing + if (distSqr > 0.01) + { + float yaw = (float)(-Math.Atan2(dx, dz) / Math.PI * 180.0); + if (yaw < 0) yaw += 360; + playerPhysics.Yaw = yaw; + playerYaw = yaw; + physicsInput.Forward = true; + } + else + { + physicsInput.Forward = true; + } + } + else + { + physicsInput.Sneak = false; + } + return; + } + + // Non-climbing vertical jump + if (distSqr < 0.1 && dy > 0.5 && playerPhysics.OnGround) + { + physicsInput.Jump = true; + return; + } + + if (distSqr < 0.01) return; // Calculate yaw to face target float targetYaw = (float)(-Math.Atan2(dx, dz) / Math.PI * 180.0); diff --git a/MinecraftClient/Pathing/Moves/MoveHelper.cs b/MinecraftClient/Pathing/Moves/MoveHelper.cs index 8bb6108f..e925bc6f 100644 --- a/MinecraftClient/Pathing/Moves/MoveHelper.cs +++ b/MinecraftClient/Pathing/Moves/MoveHelper.cs @@ -19,6 +19,10 @@ namespace MinecraftClient.Pathing.Moves return true; if (mat.IsLiquid()) return false; + if (mat.CanBeClimbedOn()) + return true; + if (IsOpenGate(mat)) + return true; if (mat.IsSolid()) return false; if (mat.CanHarmPlayers()) @@ -38,6 +42,10 @@ namespace MinecraftClient.Pathing.Moves return false; if (mat.CanHarmPlayers()) return false; + if (mat.CanBeClimbedOn()) + return false; + if (IsOpenGate(mat)) + return false; return mat.IsSolid(); } @@ -65,5 +73,19 @@ namespace MinecraftClient.Pathing.Moves { return mat == Material.Water; } + + /// + /// Conservative check for gate-type blocks. Since we cannot read block state + /// (open/closed) during planning, treat all fence gates as passable. + /// + private static bool IsOpenGate(Material mat) + { + return mat is Material.AcaciaFenceGate or Material.BirchFenceGate + or Material.CrimsonFenceGate or Material.DarkOakFenceGate + or Material.JungleFenceGate or Material.MangroveWood + or Material.OakFenceGate or Material.SpruceFenceGate + or Material.WarpedFenceGate or Material.CherryFenceGate + or Material.BambooFenceGate or Material.PaleOakFenceGate; + } } }