feat: add diagonal ascend/descend moves, fix pitch, smooth look angles

- Add MoveDiagonalAscend and MoveDiagonalDescend for "corner" moves:
  step diagonally around a wall edge while ascending/descending 1 block.
  Requires at least one intermediate cardinal direction to be passable.

- Fix pitch calculation: look toward target's eye level (same height
  delta as feet delta) instead of subtracting eye height, which caused
  the player to stare at the ground during flat walks.

- Add Yaw/Pitch smoothing via SmoothYaw/SmoothPitch in TemplateHelper.
  Max 35 deg/tick for yaw, 25 deg/tick for pitch. Prevents instant
  camera snaps between path segments while still being responsive
  enough for sprint-jumps and tight maneuvers.

- Apply smoothing to all five action templates (Walk, Ascend, Descend,
  Climb, SprintJump).

Made-with: Cursor
This commit is contained in:
BruceChen 2026-04-12 01:15:17 +08:00
parent 399c8cdc79
commit 285c3000c3
9 changed files with 222 additions and 16 deletions

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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
}
/// <summary>
/// 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.
/// </summary>
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);
}
/// <summary>
/// Smoothly interpolate yaw toward a target, respecting wrap-around at 0/360.
/// </summary>
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;
}
/// <summary>
/// Smoothly interpolate pitch toward a target.
/// </summary>
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;

View file

@ -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;