pathing: snap yaw for sprint jump approach

This commit is contained in:
BruceChen 2026-04-15 16:29:11 +00:00
parent e271854bdb
commit 2be9b287a3
3 changed files with 318 additions and 9 deletions

View file

@ -30,6 +30,8 @@ namespace MinecraftClient.Pathing.Execution.Templates
private int _tickCount;
private Phase _phase = Phase.Approach;
private bool _leftGround;
private bool _carriedGroundEntry;
private bool _airBrakeLatched;
private const float YawToleranceDeg = 5f;
@ -55,19 +57,25 @@ namespace MinecraftClient.Pathing.Execution.Templates
float targetYaw = TemplateHelper.CalculateYaw(dx, dz);
float targetPitch = TemplateHelper.CalculatePitch(dx, dy, dz);
physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, targetYaw);
YawAlignmentMode yawMode = _phase == Phase.Approach
? YawAlignmentMode.Snap
: YawAlignmentMode.Smooth;
physics.Yaw = TemplateHelper.AlignYaw(physics.Yaw, targetYaw, yawMode);
physics.Pitch = TemplateHelper.SmoothPitch(physics.Pitch, targetPitch);
switch (_phase)
{
case Phase.Approach:
input.Forward = true;
input.Sprint = true;
if (physics.OnGround)
{
if (_tickCount == 1 && TemplateHelper.GetHorizontalSpeed(physics) > 0.02)
_carriedGroundEntry = true;
double fromStartSq = TemplateHelper.HorizontalDistanceSq(pos, ExpectedStart);
float yawDelta = YawDifference(physics.Yaw, targetYaw);
bool turnInPlace = yawDelta > 35f;
input.Forward = !turnInPlace;
input.Sprint = !turnInPlace;
// Build momentum before jumping. Sprint speed is ~5.6 m/s
// (0.28 blocks/tick). More run-up = more airtime distance.
@ -84,6 +92,14 @@ namespace MinecraftClient.Pathing.Execution.Templates
else
minApproachSq = 0.0;
if (_carriedGroundEntry
&& _segment.ExitTransition == PathTransitionType.FinalStop
&& _horizDist <= 2.5
&& GetLateralOffsetFromSegmentLine(pos) > 0.20)
{
input.Sprint = false;
}
bool yawAligned = yawDelta < YawToleranceDeg;
bool posReady = fromStartSq >= minApproachSq;
@ -93,6 +109,13 @@ namespace MinecraftClient.Pathing.Execution.Templates
_phase = Phase.Airborne;
}
}
else
{
float yawDelta = YawDifference(physics.Yaw, targetYaw);
bool turnInPlace = yawDelta > 35f;
input.Forward = !turnInPlace;
input.Sprint = !turnInPlace;
}
if (_tickCount > 40)
return TemplateState.Failed;
break;
@ -116,7 +139,14 @@ namespace MinecraftClient.Pathing.Execution.Templates
&& lookaheadAirBrake
&& !releaseInAir;
if (releaseInAir || pastTarget)
if (_segment.ExitTransition == PathTransitionType.FinalStop
&& _horizDist <= 2.5
&& (releaseInAir || pastTarget))
{
_airBrakeLatched = true;
}
if (_airBrakeLatched || releaseInAir || pastTarget)
{
input.Forward = false;
input.Sprint = false;
@ -148,6 +178,13 @@ namespace MinecraftClient.Pathing.Execution.Templates
else if (TemplateHelper.ShouldBiasTowardExitHeading(pos, _segment))
TemplateHelper.FaceExitHeading(physics, _segment);
if (_segment.ExitTransition == PathTransitionType.PrepareJump
&& physics.OnGround
&& GroundedSegmentController.ShouldComplete(_segment, pos, physics))
{
return TemplateState.Complete;
}
double horizToleranceLinear = _horizDist >= 3.5 ? 1.5 : 1.0;
double horizToleranceSq = horizToleranceLinear * horizToleranceLinear;
double vertTolerance = Math.Abs(ExpectedEnd.Y - ExpectedStart.Y) > 0.5 ? 1.5 : 1.0;
@ -189,6 +226,22 @@ namespace MinecraftClient.Pathing.Execution.Templates
return dot > 0.0;
}
private double GetLateralOffsetFromSegmentLine(Location pos)
{
double dirX = ExpectedEnd.X - ExpectedStart.X;
double dirZ = ExpectedEnd.Z - ExpectedStart.Z;
double len = Math.Sqrt(dirX * dirX + dirZ * dirZ);
if (len < 0.001)
return 0.0;
dirX /= len;
dirZ /= len;
double relX = pos.X - ExpectedStart.X;
double relZ = pos.Z - ExpectedStart.Z;
return Math.Abs((-dirZ * relX) + (dirX * relZ));
}
private bool ShouldReleaseInAir(Location pos, PlayerPhysics physics, World world)
{
if (_segment.ExitTransition == PathTransitionType.ContinueStraight || physics.OnGround)

View file

@ -4,6 +4,12 @@ using MinecraftClient.Physics;
namespace MinecraftClient.Pathing.Execution.Templates
{
internal enum YawAlignmentMode
{
Smooth,
Snap
}
internal static class TemplateHelper
{
private const double EyeHeight = 1.62;
@ -50,6 +56,14 @@ namespace MinecraftClient.Pathing.Execution.Templates
return result;
}
internal static float AlignYaw(float current, float target, YawAlignmentMode mode, float maxStep = MaxYawStepPerTick)
{
target = NormalizeYaw(target);
return mode == YawAlignmentMode.Snap
? target
: SmoothYaw(current, target, maxStep);
}
/// <summary>
/// Smoothly interpolate pitch toward a target.
/// </summary>
@ -77,16 +91,18 @@ namespace MinecraftClient.Pathing.Execution.Templates
return dx * dx + dz * dz < horizThresholdSq && Math.Abs(dy) < vertThreshold;
}
internal static void FaceSegmentHeading(PlayerPhysics physics, PathSegment segment)
internal static void FaceSegmentHeading(PlayerPhysics physics, PathSegment segment,
YawAlignmentMode mode = YawAlignmentMode.Smooth)
{
float headingYaw = CalculateYaw(segment.HeadingX, segment.HeadingZ);
physics.Yaw = SmoothYaw(physics.Yaw, headingYaw);
physics.Yaw = AlignYaw(physics.Yaw, headingYaw, mode);
}
internal static void FaceExitHeading(PlayerPhysics physics, PathSegment segment)
internal static void FaceExitHeading(PlayerPhysics physics, PathSegment segment,
YawAlignmentMode mode = YawAlignmentMode.Smooth)
{
float headingYaw = GetExitHeadingYaw(segment);
physics.Yaw = SmoothYaw(physics.Yaw, headingYaw);
physics.Yaw = AlignYaw(physics.Yaw, headingYaw, mode);
}
internal static void ApplyDecision(MovementInput input, TransitionBrakingDecision decision)
@ -187,6 +203,11 @@ namespace MinecraftClient.Pathing.Execution.Templates
return HeadingPenaltyDegrees(yaw, headingX, headingZ);
}
internal static bool ShouldTurnInPlaceBeforeAdvancing(float yaw, PathSegment segment, double maxAdvanceHeadingPenalty = 35.0)
{
return HeadingPenaltyDegrees(yaw, segment) > maxAdvanceHeadingPenalty;
}
internal static double HeadingPenaltyDegrees(float yaw, int headingX, int headingZ)
{
if (headingX == 0 && headingZ == 0)
@ -217,6 +238,13 @@ namespace MinecraftClient.Pathing.Execution.Templates
}
}
private static float NormalizeYaw(float yaw)
{
while (yaw < 0f) yaw += 360f;
while (yaw >= 360f) yaw -= 360f;
return yaw;
}
internal static PlayerPhysics ClonePhysicsForPlanning(PlayerPhysics physics)
{
return new PlayerPhysics