mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
260 lines
11 KiB
C#
260 lines
11 KiB
C#
using System;
|
|
using MinecraftClient.Mapping;
|
|
using MinecraftClient.Physics;
|
|
|
|
namespace MinecraftClient.Pathing.Execution.Templates
|
|
{
|
|
/// <summary>
|
|
/// Jump across a gap. Uses a phase-based state machine:
|
|
/// Approach -> Jump -> Airborne -> Landing.
|
|
///
|
|
/// All parkour jumps use sprint-jumping (vanilla optimal horizontal distance).
|
|
/// The key to landing on small platforms is releasing forward/sprint input mid-air
|
|
/// once the player is close to or past the target, letting drag decelerate them
|
|
/// onto the block.
|
|
///
|
|
/// During Approach, the template waits for the yaw to be within 5 degrees of
|
|
/// the target direction before jumping. For medium/long jumps, it also builds
|
|
/// momentum by sprinting toward the block edge.
|
|
/// </summary>
|
|
public sealed class SprintJumpTemplate : IActionTemplate
|
|
{
|
|
private enum Phase { Approach, Airborne, Landing }
|
|
|
|
public Location ExpectedStart { get; }
|
|
public Location ExpectedEnd { get; }
|
|
|
|
private readonly PathSegment _segment;
|
|
private readonly PathSegment? _nextSegment;
|
|
private readonly double _horizDist;
|
|
private int _tickCount;
|
|
private Phase _phase = Phase.Approach;
|
|
private bool _airReleaseCommitted;
|
|
private bool _leftGround;
|
|
|
|
private const float YawToleranceDeg = 5f;
|
|
|
|
public SprintJumpTemplate(PathSegment segment, PathSegment? nextSegment)
|
|
{
|
|
_segment = segment;
|
|
_nextSegment = nextSegment;
|
|
ExpectedStart = segment.Start;
|
|
ExpectedEnd = segment.End;
|
|
double dx = segment.End.X - segment.Start.X;
|
|
double dz = segment.End.Z - segment.Start.Z;
|
|
_horizDist = Math.Sqrt(dx * dx + dz * dz);
|
|
}
|
|
|
|
public TemplateState Tick(Location pos, PlayerPhysics physics, MovementInput input, World world)
|
|
{
|
|
_tickCount++;
|
|
|
|
double dx = ExpectedEnd.X - pos.X;
|
|
double dz = ExpectedEnd.Z - pos.Z;
|
|
double dy = ExpectedEnd.Y - pos.Y;
|
|
double horizDistSq = dx * dx + dz * 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);
|
|
|
|
switch (_phase)
|
|
{
|
|
case Phase.Approach:
|
|
input.Forward = true;
|
|
input.Sprint = true;
|
|
|
|
if (physics.OnGround)
|
|
{
|
|
double fromStartSq = TemplateHelper.HorizontalDistanceSq(pos, ExpectedStart);
|
|
float yawDelta = YawDifference(physics.Yaw, targetYaw);
|
|
|
|
// Build momentum before jumping. Sprint speed is ~5.6 m/s
|
|
// (0.28 blocks/tick). More run-up = more airtime distance.
|
|
// Standing sprint jump (0t): ~3.6 blocks horizontal
|
|
// 2-tick sprint (0.56m): ~4.3 blocks horizontal
|
|
// 4-tick sprint (1.1m): ~5.0 blocks horizontal
|
|
double minApproachSq;
|
|
if (_horizDist >= 5.0)
|
|
minApproachSq = 0.64; // 0.8 blocks - 3+ ticks of sprint
|
|
else if (_horizDist >= 4.0)
|
|
minApproachSq = 0.36; // 0.6 blocks - 2-3 ticks of sprint
|
|
else if (_horizDist > 2.5)
|
|
minApproachSq = 0.09; // 0.3 blocks - 1-2 ticks of sprint
|
|
else
|
|
minApproachSq = 0.0;
|
|
|
|
bool yawAligned = yawDelta < YawToleranceDeg;
|
|
bool posReady = fromStartSq >= minApproachSq;
|
|
|
|
if (yawAligned && posReady)
|
|
{
|
|
input.Jump = true;
|
|
_phase = Phase.Airborne;
|
|
}
|
|
}
|
|
if (_tickCount > 40)
|
|
return TemplateState.Failed;
|
|
break;
|
|
|
|
case Phase.Airborne:
|
|
{
|
|
if (!physics.OnGround)
|
|
_leftGround = true;
|
|
|
|
bool pastTarget = IsPastTarget(pos);
|
|
bool releaseInAir = ShouldReleaseInAir(pos, physics, world);
|
|
if (_segment.ExitTransition == PathTransitionType.LandingRecovery && releaseInAir)
|
|
_airReleaseCommitted = true;
|
|
if (_airReleaseCommitted)
|
|
releaseInAir = true;
|
|
|
|
if (releaseInAir || pastTarget)
|
|
{
|
|
input.Forward = false;
|
|
input.Sprint = false;
|
|
}
|
|
else
|
|
{
|
|
input.Forward = true;
|
|
input.Sprint = true;
|
|
}
|
|
|
|
if (_leftGround && physics.OnGround)
|
|
{
|
|
_phase = Phase.Landing;
|
|
goto case Phase.Landing;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Phase.Landing:
|
|
TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(_segment, _nextSegment, pos, physics, world);
|
|
TemplateHelper.ApplyDecision(input, decision);
|
|
if (decision.HoldBack)
|
|
TemplateHelper.FaceSegmentHeading(physics, _segment);
|
|
|
|
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;
|
|
if (_segment.ExitTransition == PathTransitionType.ContinueStraight
|
|
&& horizDistSq < horizToleranceSq && Math.Abs(dy) < vertTolerance)
|
|
return TemplateState.Complete;
|
|
|
|
if (_segment.ExitTransition != PathTransitionType.ContinueStraight
|
|
&& physics.OnGround
|
|
&& TemplateHelper.IsSettledOnTargetBlock(pos, ExpectedEnd, physics))
|
|
{
|
|
return TemplateState.Complete;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (pos.Y < ExpectedEnd.Y - 4.0)
|
|
return TemplateState.Failed;
|
|
|
|
if (_tickCount > 60)
|
|
return TemplateState.Failed;
|
|
|
|
return TemplateState.InProgress;
|
|
}
|
|
|
|
private bool IsPastTarget(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 false;
|
|
dirX /= len;
|
|
dirZ /= len;
|
|
|
|
double relX = pos.X - ExpectedEnd.X;
|
|
double relZ = pos.Z - ExpectedEnd.Z;
|
|
double dot = relX * dirX + relZ * dirZ;
|
|
return dot > 0.0;
|
|
}
|
|
|
|
private bool ShouldReleaseInAir(Location pos, PlayerPhysics physics, World world)
|
|
{
|
|
if (TransitionBrakingPlanner.ShouldReleaseForwardInAir(_segment, _nextSegment, pos, physics))
|
|
return true;
|
|
|
|
if (_segment.ExitTransition == PathTransitionType.ContinueStraight || physics.OnGround)
|
|
return false;
|
|
|
|
Location? landingIfHolding = PredictLandingPosition(physics, world, holdForward: true, holdSprint: true);
|
|
Location? landingIfReleased = PredictLandingPosition(physics, world, holdForward: false, holdSprint: false);
|
|
if (landingIfHolding is null || landingIfReleased is null)
|
|
return false;
|
|
|
|
bool holdingStaysInside = TemplateFootingHelper.IsFootprintInsideTargetBlock(landingIfHolding.Value, ExpectedEnd);
|
|
bool releasingStaysInside = TemplateFootingHelper.IsFootprintInsideTargetBlock(landingIfReleased.Value, ExpectedEnd);
|
|
|
|
if (_segment.ExitTransition == PathTransitionType.LandingRecovery && !holdingStaysInside)
|
|
return true;
|
|
|
|
return !holdingStaysInside && releasingStaysInside;
|
|
}
|
|
|
|
private Location? PredictLandingPosition(PlayerPhysics physics, World world, bool holdForward, bool holdSprint)
|
|
{
|
|
PlayerPhysics sim = ClonePhysics(physics);
|
|
var input = new MovementInput
|
|
{
|
|
Forward = holdForward,
|
|
Sprint = holdSprint
|
|
};
|
|
|
|
for (int tick = 0; tick < 16; tick++)
|
|
{
|
|
sim.ApplyInput(input);
|
|
sim.Tick(world);
|
|
if (sim.OnGround)
|
|
return new Location(sim.Position.X, sim.Position.Y, sim.Position.Z);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static PlayerPhysics ClonePhysics(PlayerPhysics physics)
|
|
{
|
|
return new PlayerPhysics
|
|
{
|
|
Position = physics.Position,
|
|
DeltaMovement = physics.DeltaMovement,
|
|
Yaw = physics.Yaw,
|
|
Pitch = physics.Pitch,
|
|
OnGround = physics.OnGround,
|
|
HorizontalCollision = physics.HorizontalCollision,
|
|
VerticalCollision = physics.VerticalCollision,
|
|
VerticalCollisionBelow = physics.VerticalCollisionBelow,
|
|
FallDistance = physics.FallDistance,
|
|
StuckSpeedMultiplier = physics.StuckSpeedMultiplier,
|
|
Xxa = physics.Xxa,
|
|
Zza = physics.Zza,
|
|
Yya = physics.Yya,
|
|
Jumping = physics.Jumping,
|
|
Sprinting = physics.Sprinting,
|
|
Sneaking = physics.Sneaking,
|
|
CreativeFlying = physics.CreativeFlying,
|
|
InWater = physics.InWater,
|
|
IsUnderWater = physics.IsUnderWater,
|
|
InLava = physics.InLava,
|
|
OnClimbable = physics.OnClimbable,
|
|
HasSlowFalling = physics.HasSlowFalling,
|
|
HasLevitation = physics.HasLevitation,
|
|
LevitationAmplifier = physics.LevitationAmplifier,
|
|
MovementSpeed = physics.MovementSpeed
|
|
};
|
|
}
|
|
|
|
private static float YawDifference(float current, float target)
|
|
{
|
|
float delta = target - current;
|
|
while (delta > 180f) delta -= 360f;
|
|
while (delta < -180f) delta += 360f;
|
|
return Math.Abs(delta);
|
|
}
|
|
}
|
|
}
|