diff --git a/MinecraftClient/Pathing/Core/AStarPathFinder.cs b/MinecraftClient/Pathing/Core/AStarPathFinder.cs index 70bc0254..14c4ca08 100644 --- a/MinecraftClient/Pathing/Core/AStarPathFinder.cs +++ b/MinecraftClient/Pathing/Core/AStarPathFinder.cs @@ -44,6 +44,16 @@ namespace MinecraftClient.Pathing.Core moves.Add(new MoveDiagonal(-1, 1)); moves.Add(new MoveDiagonal(-1, -1)); + // Diagonal ascend/descend: corner jumps and drops + foreach (int dx in offsets) + { + foreach (int dz in offsets) + { + moves.Add(new MoveDiagonalAscend(dx, dz)); + moves.Add(new MoveDiagonalDescend(dx, dz)); + } + } + moves.Add(new MoveClimb(true)); moves.Add(new MoveClimb(false)); diff --git a/MinecraftClient/Pathing/Execution/Templates/AscendTemplate.cs b/MinecraftClient/Pathing/Execution/Templates/AscendTemplate.cs index 16ad6b55..3ad3c41e 100644 --- a/MinecraftClient/Pathing/Execution/Templates/AscendTemplate.cs +++ b/MinecraftClient/Pathing/Execution/Templates/AscendTemplate.cs @@ -33,8 +33,10 @@ namespace MinecraftClient.Pathing.Execution.Templates double dy = ExpectedEnd.Y - pos.Y; double horizDistSq = dx * dx + dz * dz; - physics.Yaw = TemplateHelper.CalculateYaw(dx, dz); - physics.Pitch = TemplateHelper.CalculatePitch(dx, dy - 1.62, dz); + float targetYaw = TemplateHelper.CalculateYaw(dx, dz); + float targetPitch = TemplateHelper.CalculatePitch(dx, dy, dz); + physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, targetYaw); + physics.Pitch = TemplateHelper.SmoothPitch(physics.Pitch, targetPitch); input.Forward = true; input.Sprint = true; diff --git a/MinecraftClient/Pathing/Execution/Templates/ClimbTemplate.cs b/MinecraftClient/Pathing/Execution/Templates/ClimbTemplate.cs index 0f562bbe..0ffea56d 100644 --- a/MinecraftClient/Pathing/Execution/Templates/ClimbTemplate.cs +++ b/MinecraftClient/Pathing/Execution/Templates/ClimbTemplate.cs @@ -39,7 +39,8 @@ namespace MinecraftClient.Pathing.Execution.Templates if (_tickCount > 120) return TemplateState.Failed; - physics.Pitch = _goingUp ? -70f : 70f; + float targetPitch = _goingUp ? -70f : 70f; + physics.Pitch = TemplateHelper.SmoothPitch(physics.Pitch, targetPitch); if (physics.OnClimbable) { @@ -48,7 +49,10 @@ namespace MinecraftClient.Pathing.Execution.Templates input.Jump = true; input.Forward = true; if (horizDistSq > 0.01) - physics.Yaw = TemplateHelper.CalculateYaw(dx, dz); + { + float targetYaw = TemplateHelper.CalculateYaw(dx, dz); + physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, targetYaw); + } } else { @@ -58,7 +62,8 @@ namespace MinecraftClient.Pathing.Execution.Templates // Keep centered horizontally by gently steering if drifting. if (horizDistSq > 0.15) { - physics.Yaw = TemplateHelper.CalculateYaw(dx, dz); + float targetYaw = TemplateHelper.CalculateYaw(dx, dz); + physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, targetYaw); input.Forward = true; } } @@ -67,7 +72,8 @@ namespace MinecraftClient.Pathing.Execution.Templates { if (horizDistSq > 0.01) { - physics.Yaw = TemplateHelper.CalculateYaw(dx, dz); + float targetYaw = TemplateHelper.CalculateYaw(dx, dz); + physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, targetYaw); input.Forward = true; } } diff --git a/MinecraftClient/Pathing/Execution/Templates/DescendTemplate.cs b/MinecraftClient/Pathing/Execution/Templates/DescendTemplate.cs index 31df4abd..21185816 100644 --- a/MinecraftClient/Pathing/Execution/Templates/DescendTemplate.cs +++ b/MinecraftClient/Pathing/Execution/Templates/DescendTemplate.cs @@ -54,19 +54,21 @@ namespace MinecraftClient.Pathing.Execution.Templates if (_tickCount > 200) return TemplateState.Failed; - physics.Pitch = TemplateHelper.CalculatePitch(dx, dy - 1.62, dz); + float targetYaw = TemplateHelper.CalculateYaw(dx, dz); + float targetPitch = TemplateHelper.CalculatePitch(dx, dy, dz); + physics.Pitch = TemplateHelper.SmoothPitch(physics.Pitch, targetPitch); if (physics.OnClimbable) { if (horizDistSq > 0.25) { - physics.Yaw = TemplateHelper.CalculateYaw(dx, dz); + physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, targetYaw); input.Forward = true; } } else if (horizDistSq > 0.01) { - physics.Yaw = TemplateHelper.CalculateYaw(dx, dz); + physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, targetYaw); input.Forward = true; } diff --git a/MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs b/MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs index 96971656..7483b083 100644 --- a/MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs +++ b/MinecraftClient/Pathing/Execution/Templates/SprintJumpTemplate.cs @@ -39,8 +39,10 @@ namespace MinecraftClient.Pathing.Execution.Templates double dy = ExpectedEnd.Y - pos.Y; double horizDistSq = dx * dx + dz * dz; - physics.Yaw = TemplateHelper.CalculateYaw(dx, dz); - physics.Pitch = TemplateHelper.CalculatePitch(dx, dy - 1.62, dz); + float targetYaw = TemplateHelper.CalculateYaw(dx, dz); + float targetPitch = TemplateHelper.CalculatePitch(dx, dy, dz); + physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, targetYaw); + physics.Pitch = TemplateHelper.SmoothPitch(physics.Pitch, targetPitch); input.Forward = true; input.Sprint = true; diff --git a/MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs b/MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs index 0e888821..f724906c 100644 --- a/MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs +++ b/MinecraftClient/Pathing/Execution/Templates/TemplateHelper.cs @@ -5,6 +5,10 @@ namespace MinecraftClient.Pathing.Execution.Templates { internal static class TemplateHelper { + private const double EyeHeight = 1.62; + private const float MaxYawStepPerTick = 35f; + private const float MaxPitchStepPerTick = 25f; + internal static float CalculateYaw(double dx, double dz) { float yaw = (float)(-Math.Atan2(dx, dz) / Math.PI * 180.0); @@ -13,17 +17,49 @@ namespace MinecraftClient.Pathing.Execution.Templates } /// - /// Calculate the pitch angle (in degrees) to look toward a 3D offset. - /// Negative = look up, positive = look down. Clamped to [-90, 90]. - /// The dy is relative to eye height (~1.62 blocks above feet). + /// Calculate the pitch angle to look from current eye position toward + /// the target's feet-level Y. dy = targetFeetY - playerFeetY. /// internal static float CalculatePitch(double dx, double dy, double dz) { double horizDist = Math.Sqrt(dx * dx + dz * dz); + // Look toward the target's eye level, not feet. + // Both player and target are at feet+EyeHeight, so the vertical + // difference is just dy (target feet Y - player feet Y). float pitch = (float)(-Math.Atan2(dy, horizDist) / Math.PI * 180.0); return Math.Clamp(pitch, -90f, 90f); } + /// + /// Smoothly interpolate yaw toward a target, respecting wrap-around at 0/360. + /// + internal static float SmoothYaw(float current, float target, float maxStep = MaxYawStepPerTick) + { + float delta = target - current; + // Normalize to [-180, 180] + while (delta > 180f) delta -= 360f; + while (delta < -180f) delta += 360f; + + if (Math.Abs(delta) <= maxStep) + return target; + + float result = current + Math.Sign(delta) * maxStep; + if (result < 0) result += 360f; + if (result >= 360f) result -= 360f; + return result; + } + + /// + /// Smoothly interpolate pitch toward a target. + /// + internal static float SmoothPitch(float current, float target, float maxStep = MaxPitchStepPerTick) + { + float delta = target - current; + if (Math.Abs(delta) <= maxStep) + return target; + return current + Math.Sign(delta) * maxStep; + } + internal static double HorizontalDistanceSq(Location a, Location b) { double dx = a.X - b.X; diff --git a/MinecraftClient/Pathing/Execution/Templates/WalkTemplate.cs b/MinecraftClient/Pathing/Execution/Templates/WalkTemplate.cs index 55e0f73f..0cdbf96e 100644 --- a/MinecraftClient/Pathing/Execution/Templates/WalkTemplate.cs +++ b/MinecraftClient/Pathing/Execution/Templates/WalkTemplate.cs @@ -31,8 +31,10 @@ namespace MinecraftClient.Pathing.Execution.Templates double dx = ExpectedEnd.X - pos.X; double dz = ExpectedEnd.Z - pos.Z; double dy = ExpectedEnd.Y - pos.Y; - physics.Yaw = TemplateHelper.CalculateYaw(dx, dz); - physics.Pitch = TemplateHelper.CalculatePitch(dx, dy - 1.62, dz); + float targetYaw = TemplateHelper.CalculateYaw(dx, dz); + float targetPitch = TemplateHelper.CalculatePitch(dx, dy, dz); + physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, targetYaw); + physics.Pitch = TemplateHelper.SmoothPitch(physics.Pitch, targetPitch); input.Forward = true; input.Sprint = true; diff --git a/MinecraftClient/Pathing/Moves/Impl/MoveDiagonalAscend.cs b/MinecraftClient/Pathing/Moves/Impl/MoveDiagonalAscend.cs new file mode 100644 index 00000000..b0bde700 --- /dev/null +++ b/MinecraftClient/Pathing/Moves/Impl/MoveDiagonalAscend.cs @@ -0,0 +1,69 @@ +using MinecraftClient.Pathing.Core; + +namespace MinecraftClient.Pathing.Moves.Impl +{ + /// + /// Jump diagonally (1 block in X and Z) and land 1 block higher. + /// Handles the "corner jump" pattern: jump around a wall edge and land + /// one block higher on a platform that is diagonally adjacent. + /// + public sealed class MoveDiagonalAscend : IMove + { + public MoveType Type => MoveType.Ascend; + public int XOffset { get; } + public int ZOffset { get; } + public bool DynamicY => false; + + public MoveDiagonalAscend(int xOffset, int zOffset) + { + XOffset = xOffset; + ZOffset = zOffset; + } + + public void Calculate(CalculationContext ctx, int x, int y, int z, ref MoveResult result) + { + int destX = x + XOffset; + int destZ = z + ZOffset; + int destY = y + 1; + + // Need headroom to jump (y+2 at start) + if (!ctx.CanWalkThrough(x, y + 2, z)) + { + result.SetImpossible(); + return; + } + + // Destination: solid ground, body passable, head passable + if (!ctx.CanWalkOn(destX, y, destZ)) + { + result.SetImpossible(); + return; + } + + if (!ctx.CanWalkThrough(destX, destY, destZ) || + !ctx.CanWalkThrough(destX, destY + 1, destZ)) + { + result.SetImpossible(); + return; + } + + // At least one of the two intermediate cardinal directions must be passable + // at both the current and destination height (player sweeps through). + bool pathViaX = ctx.CanWalkThrough(x + XOffset, y, z) && + ctx.CanWalkThrough(x + XOffset, y + 1, z) && + ctx.CanWalkThrough(x + XOffset, y + 2, z); + bool pathViaZ = ctx.CanWalkThrough(x, y, z + ZOffset) && + ctx.CanWalkThrough(x, y + 1, z + ZOffset) && + ctx.CanWalkThrough(x, y + 2, z + ZOffset); + + if (!pathViaX && !pathViaZ) + { + result.SetImpossible(); + return; + } + + double cost = ctx.SprintCost * ActionCosts.DiagonalMultiplier + ctx.JumpPenalty; + result.Set(destX, destY, destZ, cost); + } + } +} diff --git a/MinecraftClient/Pathing/Moves/Impl/MoveDiagonalDescend.cs b/MinecraftClient/Pathing/Moves/Impl/MoveDiagonalDescend.cs new file mode 100644 index 00000000..2f9e2578 --- /dev/null +++ b/MinecraftClient/Pathing/Moves/Impl/MoveDiagonalDescend.cs @@ -0,0 +1,77 @@ +using MinecraftClient.Mapping; +using MinecraftClient.Pathing.Core; + +namespace MinecraftClient.Pathing.Moves.Impl +{ + /// + /// Walk diagonally (1 block in X and Z) and drop 1 block. + /// Handles the "corner drop" pattern: step around a wall edge and land + /// one block lower on a platform that is diagonally adjacent. + /// + public sealed class MoveDiagonalDescend : IMove + { + public MoveType Type => MoveType.Descend; + public int XOffset { get; } + public int ZOffset { get; } + public bool DynamicY => false; + + public MoveDiagonalDescend(int xOffset, int zOffset) + { + XOffset = xOffset; + ZOffset = zOffset; + } + + public void Calculate(CalculationContext ctx, int x, int y, int z, ref MoveResult result) + { + int destX = x + XOffset; + int destZ = z + ZOffset; + int destY = y - 1; + + // Destination must have ground, body space, and head space + if (!ctx.CanWalkOn(destX, destY - 1, destZ)) + { + result.SetImpossible(); + return; + } + + if (!ctx.CanWalkThrough(destX, destY, destZ) || + !ctx.CanWalkThrough(destX, destY + 1, destZ)) + { + result.SetImpossible(); + return; + } + + Material landOn = ctx.GetMaterial(destX, destY - 1, destZ); + if (MoveHelper.IsHazardous(landOn)) + { + result.SetImpossible(); + return; + } + + // Don't descend from climbable blocks + Material fromDown = ctx.GetMaterial(x, y - 1, z); + if (fromDown.CanBeClimbedOn()) + { + result.SetImpossible(); + return; + } + + // At least one of the two intermediate cardinal directions must be passable + // (player needs clearance to cut the corner). + bool pathViaX = ctx.CanWalkThrough(x + XOffset, y, z) && + ctx.CanWalkThrough(x + XOffset, y + 1, z); + bool pathViaZ = ctx.CanWalkThrough(x, y, z + ZOffset) && + ctx.CanWalkThrough(x, y + 1, z + ZOffset); + + if (!pathViaX && !pathViaZ) + { + result.SetImpossible(); + return; + } + + double cost = ActionCosts.WalkOffBlock * ActionCosts.DiagonalMultiplier + + ActionCosts.FallCost(1); + result.Set(destX, destY, destZ, cost); + } + } +}