mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-29 13:04:59 +00:00
pathing: tighten linear parkour execution
This commit is contained in:
parent
891763602a
commit
d919bff91f
22 changed files with 2072 additions and 79 deletions
|
|
@ -69,34 +69,31 @@ namespace MinecraftClient.Pathing.Core
|
|||
foreach (int dz in offsets)
|
||||
moves.Add(new MoveSprintDescend(0, dz * 2));
|
||||
|
||||
// Cardinal parkour: 2-4 block sprint jumps along +-X and +-Z
|
||||
// Cardinal parkour: long sprint jumps along +-X and +-Z.
|
||||
// Longer distances remain gated by MoveParkour feasibility and available runway/carry.
|
||||
foreach (int dx in offsets)
|
||||
{
|
||||
for (int dist = 2; dist <= 4; dist++)
|
||||
for (int dist = 2; dist <= 5; dist++)
|
||||
moves.Add(new MoveParkour(dx * dist, 0));
|
||||
// Ascending: +1Y, dist 2-3 (dist 4 ascend not physically reliable)
|
||||
// Ascending cardinal parkour tops out at offset 3.
|
||||
for (int dist = 2; dist <= 3; dist++)
|
||||
moves.Add(new MoveParkour(dx * dist, 0, yDelta: 1));
|
||||
// Descending parkour: sprint-jump, land 1-2 blocks lower
|
||||
for (int dist = 2; dist <= 4; dist++)
|
||||
{
|
||||
// Descending cardinal parkour tops out at offset 5.
|
||||
for (int dist = 2; dist <= 5; dist++)
|
||||
moves.Add(new MoveParkour(dx * dist, 0, yDelta: -1));
|
||||
if (dist <= 3)
|
||||
moves.Add(new MoveParkour(dx * dist, 0, yDelta: -2));
|
||||
}
|
||||
for (int dist = 2; dist <= 5; dist++)
|
||||
moves.Add(new MoveParkour(dx * dist, 0, yDelta: -2));
|
||||
}
|
||||
foreach (int dz in offsets)
|
||||
{
|
||||
for (int dist = 2; dist <= 4; dist++)
|
||||
for (int dist = 2; dist <= 5; dist++)
|
||||
moves.Add(new MoveParkour(0, dz * dist));
|
||||
for (int dist = 2; dist <= 3; dist++)
|
||||
moves.Add(new MoveParkour(0, dz * dist, yDelta: 1));
|
||||
for (int dist = 2; dist <= 4; dist++)
|
||||
{
|
||||
for (int dist = 2; dist <= 5; dist++)
|
||||
moves.Add(new MoveParkour(0, dz * dist, yDelta: -1));
|
||||
if (dist <= 3)
|
||||
moves.Add(new MoveParkour(0, dz * dist, yDelta: -2));
|
||||
}
|
||||
for (int dist = 2; dist <= 5; dist++)
|
||||
moves.Add(new MoveParkour(0, dz * dist, yDelta: -2));
|
||||
}
|
||||
|
||||
// Diagonal parkour: sprint jumps at angles.
|
||||
|
|
@ -162,6 +159,7 @@ namespace MinecraftClient.Pathing.Core
|
|||
|
||||
int nodesExplored = 0;
|
||||
int unloadedChunkHits = 0;
|
||||
bool searchAborted = false;
|
||||
PathNode? bestPartialNode = startNode;
|
||||
double bestPartialScore = startNode.HCost + startNode.GCost * 0.5;
|
||||
MoveResult moveResult = default;
|
||||
|
|
@ -172,12 +170,14 @@ namespace MinecraftClient.Pathing.Core
|
|||
{
|
||||
if (ct.IsCancellationRequested)
|
||||
{
|
||||
searchAborted = true;
|
||||
DebugLog?.Invoke($"[A*] Cancelled after {nodesExplored} nodes, {sw.ElapsedMilliseconds}ms");
|
||||
break;
|
||||
}
|
||||
|
||||
if (sw.ElapsedMilliseconds > timeoutMs)
|
||||
{
|
||||
searchAborted = true;
|
||||
DebugLog?.Invoke($"[A*] Timeout ({timeoutMs}ms) after {nodesExplored} nodes");
|
||||
break;
|
||||
}
|
||||
|
|
@ -195,6 +195,7 @@ namespace MinecraftClient.Pathing.Core
|
|||
|
||||
foreach (var move in _allMoves)
|
||||
{
|
||||
ctx.PreviousMoveType = current.MoveUsed;
|
||||
moveResult.Cost = 0;
|
||||
move.Calculate(ctx, current.X, current.Y, current.Z, ref moveResult);
|
||||
|
||||
|
|
@ -251,7 +252,9 @@ namespace MinecraftClient.Pathing.Core
|
|||
}
|
||||
}
|
||||
|
||||
if (bestPartialNode is not null && bestPartialNode != startNode)
|
||||
if (bestPartialNode is not null
|
||||
&& bestPartialNode != startNode
|
||||
&& (searchAborted || unloadedChunkHits > 0))
|
||||
{
|
||||
DebugLog?.Invoke($"[A*] Partial path to ({bestPartialNode.X},{bestPartialNode.Y},{bestPartialNode.Z}), " +
|
||||
$"{nodesExplored} nodes, {sw.ElapsedMilliseconds}ms");
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ namespace MinecraftClient.Pathing.Core
|
|||
public double WalkCost { get; }
|
||||
public double SprintCost { get; }
|
||||
public double SneakCost { get; }
|
||||
public MoveType PreviousMoveType { get; internal set; }
|
||||
|
||||
public CalculationContext(
|
||||
World world,
|
||||
|
|
|
|||
|
|
@ -52,39 +52,57 @@ namespace MinecraftClient.Pathing.Execution
|
|||
return PathExecutorState.Complete;
|
||||
}
|
||||
|
||||
_segmentTicks++;
|
||||
_totalTicks++;
|
||||
var state = _currentTemplate.Tick(pos, physics, input, world);
|
||||
|
||||
switch (state)
|
||||
int sameTickAdvanceCount = 0;
|
||||
while (_currentTemplate is not null)
|
||||
{
|
||||
case TemplateState.Complete:
|
||||
input.Reset();
|
||||
_observer?.OnSegmentCompleted(_currentIndex, _segments.Count, _segments[_currentIndex], _segmentTicks, pos);
|
||||
_debugLog?.Invoke($"[PathExec] Segment {_currentIndex} complete " +
|
||||
$"({_segments[_currentIndex].MoveType}) at ({pos.X:F2},{pos.Y:F2},{pos.Z:F2})");
|
||||
_currentIndex++;
|
||||
_segmentTicks = 0;
|
||||
if (_currentIndex >= _segments.Count)
|
||||
{
|
||||
_currentTemplate = null;
|
||||
_debugLog?.Invoke("[PathExec] All segments complete!");
|
||||
return PathExecutorState.Complete;
|
||||
}
|
||||
AdvanceToNextSegment();
|
||||
return PathExecutorState.InProgress;
|
||||
_segmentTicks++;
|
||||
var state = _currentTemplate.Tick(pos, physics, input, world);
|
||||
|
||||
case TemplateState.Failed:
|
||||
input.Reset();
|
||||
_observer?.OnSegmentFailed(_currentIndex, _segments.Count, _segments[_currentIndex], _segmentTicks, pos);
|
||||
_debugLog?.Invoke($"[PathExec] Segment {_currentIndex} FAILED " +
|
||||
$"({_segments[_currentIndex].MoveType}) at ({pos.X:F2},{pos.Y:F2},{pos.Z:F2}), " +
|
||||
$"target was ({_currentTemplate.ExpectedEnd.X:F2},{_currentTemplate.ExpectedEnd.Y:F2},{_currentTemplate.ExpectedEnd.Z:F2})");
|
||||
return PathExecutorState.Failed;
|
||||
switch (state)
|
||||
{
|
||||
case TemplateState.Complete:
|
||||
_observer?.OnSegmentCompleted(_currentIndex, _segments.Count, _segments[_currentIndex], _segmentTicks, pos);
|
||||
_debugLog?.Invoke($"[PathExec] Segment {_currentIndex} complete " +
|
||||
$"({_segments[_currentIndex].MoveType}) at ({pos.X:F2},{pos.Y:F2},{pos.Z:F2})");
|
||||
_currentIndex++;
|
||||
_segmentTicks = 0;
|
||||
if (_currentIndex >= _segments.Count)
|
||||
{
|
||||
input.Reset();
|
||||
_currentTemplate = null;
|
||||
_debugLog?.Invoke("[PathExec] All segments complete!");
|
||||
return PathExecutorState.Complete;
|
||||
}
|
||||
|
||||
default:
|
||||
return PathExecutorState.InProgress;
|
||||
AdvanceToNextSegment();
|
||||
|
||||
// Do not waste the handoff tick when the next segment needs to issue
|
||||
// a jump or braking input immediately.
|
||||
sameTickAdvanceCount++;
|
||||
if (sameTickAdvanceCount > _segments.Count)
|
||||
{
|
||||
input.Reset();
|
||||
_debugLog?.Invoke("[PathExec] Excessive same-tick segment advances; aborting.");
|
||||
return PathExecutorState.Failed;
|
||||
}
|
||||
continue;
|
||||
|
||||
case TemplateState.Failed:
|
||||
input.Reset();
|
||||
_observer?.OnSegmentFailed(_currentIndex, _segments.Count, _segments[_currentIndex], _segmentTicks, pos);
|
||||
_debugLog?.Invoke($"[PathExec] Segment {_currentIndex} FAILED " +
|
||||
$"({_segments[_currentIndex].MoveType}) at ({pos.X:F2},{pos.Y:F2},{pos.Z:F2}), " +
|
||||
$"target was ({_currentTemplate.ExpectedEnd.X:F2},{_currentTemplate.ExpectedEnd.Y:F2},{_currentTemplate.ExpectedEnd.Z:F2})");
|
||||
return PathExecutorState.Failed;
|
||||
|
||||
default:
|
||||
return PathExecutorState.InProgress;
|
||||
}
|
||||
}
|
||||
|
||||
input.Reset();
|
||||
return PathExecutorState.Complete;
|
||||
}
|
||||
|
||||
private void AdvanceToNextSegment()
|
||||
|
|
|
|||
|
|
@ -38,6 +38,14 @@ namespace MinecraftClient.Pathing.Execution
|
|||
{
|
||||
_goal = goal;
|
||||
_replanCount = 0;
|
||||
if (result.Status == PathStatus.Failed || result.Path.Count < 2)
|
||||
{
|
||||
_infoLog?.Invoke("[PathMgr] Navigation rejected -- no path found.");
|
||||
_executor = null;
|
||||
_goal = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var segments = PathSegmentBuilder.FromPath(result.Path);
|
||||
_executor = new PathExecutor(segments, _debugLog, _observer);
|
||||
_infoLog?.Invoke($"[PathMgr] Navigation started: {segments.Count} segments");
|
||||
|
|
@ -53,6 +61,19 @@ namespace MinecraftClient.Pathing.Execution
|
|||
switch (state)
|
||||
{
|
||||
case PathExecutorState.Complete:
|
||||
if (_goal is not null)
|
||||
{
|
||||
int px = (int)Math.Floor(pos.X);
|
||||
int py = (int)Math.Floor(pos.Y);
|
||||
int pz = (int)Math.Floor(pos.Z);
|
||||
if (!_goal.IsInGoal(px, py, pz))
|
||||
{
|
||||
_infoLog?.Invoke("[PathMgr] Planned route ended before reaching goal, replanning...");
|
||||
Replan(pos, world);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_observer?.OnNavigationCompleted(_executor.TotalTicks);
|
||||
_infoLog?.Invoke("[PathMgr] Navigation complete!");
|
||||
_executor = null;
|
||||
|
|
|
|||
|
|
@ -67,7 +67,11 @@ namespace MinecraftClient.Pathing.Execution.Templates
|
|||
TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(_segment, _nextSegment, pos, physics, world);
|
||||
if (horizDistSq > 0.01 && !decision.HoldBack)
|
||||
{
|
||||
float groundedYaw = TemplateHelper.ShouldBiasTowardExitHeading(pos, _segment)
|
||||
bool onOrPastTarget = TemplateFootingHelper.IsFootprintInsideTargetBlock(pos, ExpectedEnd)
|
||||
|| TemplateHelper.HasReachedSegmentEndPlane(pos, _segment);
|
||||
float groundedYaw = onOrPastTarget
|
||||
? TemplateHelper.GetExitHeadingYaw(_segment)
|
||||
: TemplateHelper.ShouldBiasTowardExitHeading(pos, _segment)
|
||||
? TemplateHelper.GetExitHeadingYaw(_segment)
|
||||
: targetYaw;
|
||||
physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, groundedYaw);
|
||||
|
|
@ -90,8 +94,16 @@ namespace MinecraftClient.Pathing.Execution.Templates
|
|||
}
|
||||
else if (horizDistSq > 0.01)
|
||||
{
|
||||
physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, targetYaw);
|
||||
if (_hasFallen || YawDifference(physics.Yaw, targetYaw) <= PreDropYawToleranceDeg)
|
||||
bool onOrPastTarget = TemplateFootingHelper.IsFootprintInsideTargetBlock(pos, ExpectedEnd)
|
||||
|| TemplateHelper.HasReachedSegmentEndPlane(pos, _segment);
|
||||
bool biasTowardExitInAir = onOrPastTarget
|
||||
|| (_hasFallen && TemplateHelper.ShouldBiasTowardExitHeading(pos, _segment, distanceThreshold: 1.5));
|
||||
float airborneYaw = biasTowardExitInAir
|
||||
? TemplateHelper.GetExitHeadingYaw(_segment)
|
||||
: targetYaw;
|
||||
|
||||
physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, airborneYaw);
|
||||
if (_hasFallen || YawDifference(physics.Yaw, airborneYaw) <= PreDropYawToleranceDeg)
|
||||
{
|
||||
if (!_hasFallen && !_needsSprint && ShouldCoastOffLedge(pos))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using MinecraftClient.Mapping;
|
||||
using MinecraftClient.Pathing.Core;
|
||||
using MinecraftClient.Physics;
|
||||
|
||||
namespace MinecraftClient.Pathing.Execution.Templates
|
||||
|
|
@ -31,6 +32,7 @@ namespace MinecraftClient.Pathing.Execution.Templates
|
|||
private Phase _phase = Phase.Approach;
|
||||
private bool _leftGround;
|
||||
private bool _carriedGroundEntry;
|
||||
private bool _releaseForwardLatched;
|
||||
|
||||
private const float YawToleranceDeg = 5f;
|
||||
|
||||
|
|
@ -53,10 +55,19 @@ namespace MinecraftClient.Pathing.Execution.Templates
|
|||
double dz = ExpectedEnd.Z - pos.Z;
|
||||
double dy = ExpectedEnd.Y - pos.Y;
|
||||
double horizDistSq = dx * dx + dz * dz;
|
||||
bool prepareJumpTouchdown = _phase == Phase.Airborne && _leftGround && physics.OnGround;
|
||||
bool groundedPrepareJumpHandoff = (_phase == Phase.Landing || prepareJumpTouchdown)
|
||||
&& physics.OnGround
|
||||
&& _segment.ExitTransition == PathTransitionType.PrepareJump
|
||||
&& _segment.ExitHints.RequireJumpReady
|
||||
&& (TemplateFootingHelper.IsCenterInsideTargetBlock(pos, _segment.End)
|
||||
|| TemplateHelper.HasReachedSegmentEndPlane(pos, _segment));
|
||||
|
||||
float targetYaw = TemplateHelper.CalculateYaw(dx, dz);
|
||||
float targetPitch = TemplateHelper.CalculatePitch(dx, dy, dz);
|
||||
physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, targetYaw);
|
||||
physics.Yaw = groundedPrepareJumpHandoff
|
||||
? TemplateHelper.SmoothYaw(physics.Yaw, TemplateHelper.GetExitHeadingYaw(_segment))
|
||||
: TemplateHelper.SmoothYaw(physics.Yaw, targetYaw);
|
||||
physics.Pitch = TemplateHelper.SmoothPitch(physics.Pitch, targetPitch);
|
||||
|
||||
switch (_phase)
|
||||
|
|
@ -67,38 +78,49 @@ namespace MinecraftClient.Pathing.Execution.Templates
|
|||
if (_tickCount == 1 && TemplateHelper.GetHorizontalSpeed(physics) > 0.02)
|
||||
_carriedGroundEntry = true;
|
||||
|
||||
double fromStartSq = TemplateHelper.HorizontalDistanceSq(pos, ExpectedStart);
|
||||
double approachProgress = ((pos.X - ExpectedStart.X) * _segment.HeadingX)
|
||||
+ ((pos.Z - ExpectedStart.Z) * _segment.HeadingZ);
|
||||
float yawDelta = YawDifference(physics.Yaw, targetYaw);
|
||||
bool turnInPlace = yawDelta > 35f;
|
||||
input.Forward = !turnInPlace;
|
||||
input.Sprint = !turnInPlace;
|
||||
|
||||
bool carriedShortFinalStopJump = _carriedGroundEntry
|
||||
&& _segment.ExitTransition == PathTransitionType.FinalStop
|
||||
&& _horizDist <= 2.5;
|
||||
bool carriedDescendingFinalStopJump = _carriedGroundEntry
|
||||
&& _segment.ExitTransition == PathTransitionType.FinalStop
|
||||
&& ExpectedEnd.Y < ExpectedStart.Y
|
||||
&& _horizDist <= 3.5;
|
||||
bool carriedDescendingParkourJump = _carriedGroundEntry
|
||||
&& _segment.ExitTransition == PathTransitionType.PrepareJump
|
||||
&& ExpectedEnd.Y < ExpectedStart.Y
|
||||
&& _horizDist <= 3.5;
|
||||
if (carriedShortFinalStopJump || carriedDescendingFinalStopJump || carriedDescendingParkourJump)
|
||||
input.Sprint = false;
|
||||
|
||||
// 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
|
||||
double minApproachDistance;
|
||||
bool carriedLongDescendingJump = _carriedGroundEntry
|
||||
&& ExpectedEnd.Y < ExpectedStart.Y
|
||||
&& _horizDist >= 5.0;
|
||||
if (carriedLongDescendingJump)
|
||||
minApproachDistance = 0.8; // use nearly the full landing block to preserve long-jump carry
|
||||
else if (_horizDist >= 5.0)
|
||||
minApproachDistance = 0.8; // 3+ ticks of sprint
|
||||
else if (_horizDist >= 4.0)
|
||||
minApproachSq = 0.36; // 0.6 blocks - 2-3 ticks of sprint
|
||||
minApproachDistance = 0.6; // 2-3 ticks of sprint
|
||||
else if (_horizDist > 3.5)
|
||||
minApproachSq = 0.09; // 0.3 blocks - 1-2 ticks of sprint
|
||||
minApproachDistance = 0.3; // 1-2 ticks of sprint
|
||||
else
|
||||
minApproachSq = 0.0;
|
||||
|
||||
if (_carriedGroundEntry
|
||||
&& _segment.ExitTransition == PathTransitionType.FinalStop
|
||||
&& _horizDist <= 2.5
|
||||
&& GetLateralOffsetFromSegmentLine(pos) > 0.20)
|
||||
{
|
||||
input.Sprint = false;
|
||||
}
|
||||
minApproachDistance = 0.0;
|
||||
|
||||
bool yawAligned = yawDelta < YawToleranceDeg;
|
||||
bool posReady = fromStartSq >= minApproachSq;
|
||||
|
||||
bool posReady = approachProgress >= minApproachDistance;
|
||||
if (yawAligned && posReady)
|
||||
{
|
||||
input.Jump = true;
|
||||
|
|
@ -122,20 +144,25 @@ namespace MinecraftClient.Pathing.Execution.Templates
|
|||
_leftGround = true;
|
||||
|
||||
bool pastTarget = IsPastTarget(pos);
|
||||
bool parkourOnOrPastTarget = _segment.MoveType == MoveType.Parkour
|
||||
&& (TemplateFootingHelper.IsFootprintInsideTargetBlock(pos, ExpectedEnd)
|
||||
|| TemplateHelper.HasReachedSegmentEndPlane(pos, _segment)
|
||||
|| pastTarget);
|
||||
bool biasTowardExitInAir = _segment.ExitTransition == PathTransitionType.LandingRecovery
|
||||
? TemplateHelper.ShouldBiasTowardExitHeading(pos, _segment, distanceThreshold: 1.5)
|
||||
: TemplateHelper.ShouldBiasTowardExitHeading(pos, _segment);
|
||||
if (biasTowardExitInAir)
|
||||
if (parkourOnOrPastTarget || biasTowardExitInAir)
|
||||
TemplateHelper.FaceExitHeading(physics, _segment);
|
||||
|
||||
bool lookaheadAirBrake = TransitionBrakingPlanner.ShouldReleaseForwardInAir(
|
||||
_segment, _nextSegment, pos, physics, world);
|
||||
bool releaseInAir = ShouldReleaseInAir(pos, physics, world);
|
||||
_releaseForwardLatched |= releaseInAir;
|
||||
bool earlySoftBrake = _segment.ExitTransition == PathTransitionType.LandingRecovery
|
||||
&& lookaheadAirBrake
|
||||
&& !releaseInAir;
|
||||
|
||||
if (releaseInAir || pastTarget)
|
||||
if (_releaseForwardLatched || pastTarget)
|
||||
{
|
||||
input.Forward = false;
|
||||
input.Sprint = false;
|
||||
|
|
@ -148,7 +175,8 @@ namespace MinecraftClient.Pathing.Execution.Templates
|
|||
else
|
||||
{
|
||||
input.Forward = true;
|
||||
input.Sprint = true;
|
||||
input.Sprint = !(_segment.ExitTransition == PathTransitionType.FinalStop
|
||||
&& ExpectedEnd.Y < ExpectedStart.Y);
|
||||
}
|
||||
|
||||
if (_leftGround && physics.OnGround)
|
||||
|
|
@ -160,15 +188,36 @@ namespace MinecraftClient.Pathing.Execution.Templates
|
|||
}
|
||||
|
||||
case Phase.Landing:
|
||||
TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(_segment, _nextSegment, pos, physics, world);
|
||||
TemplateHelper.ApplyDecision(input, decision);
|
||||
if (decision.HoldBack)
|
||||
TemplateHelper.FaceSegmentHeading(physics, _segment);
|
||||
else if (TemplateHelper.ShouldBiasTowardExitHeading(pos, _segment))
|
||||
bool descendingPrepareJump = _segment.ExitTransition == PathTransitionType.PrepareJump
|
||||
&& ExpectedEnd.Y < ExpectedStart.Y;
|
||||
bool descendingPrepareJumpOnSupport = descendingPrepareJump
|
||||
&& physics.OnGround
|
||||
&& TemplateFootingHelper.IsFootprintInsideTargetBlock(pos, _segment.End);
|
||||
bool descendingPrepareJumpPastSupport = descendingPrepareJump
|
||||
&& physics.OnGround
|
||||
&& TemplateHelper.HasReachedSegmentEndPlane(pos, _segment)
|
||||
&& !descendingPrepareJumpOnSupport;
|
||||
|
||||
if (descendingPrepareJumpPastSupport)
|
||||
{
|
||||
input.Forward = false;
|
||||
input.Sprint = false;
|
||||
input.Back = true;
|
||||
TemplateHelper.FaceExitHeading(physics, _segment);
|
||||
}
|
||||
else
|
||||
{
|
||||
TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(_segment, _nextSegment, pos, physics, world);
|
||||
TemplateHelper.ApplyDecision(input, decision);
|
||||
if (decision.HoldBack)
|
||||
TemplateHelper.FaceSegmentHeading(physics, _segment);
|
||||
else if (TemplateHelper.ShouldBiasTowardExitHeading(pos, _segment))
|
||||
TemplateHelper.FaceExitHeading(physics, _segment);
|
||||
}
|
||||
|
||||
if (_segment.ExitTransition == PathTransitionType.PrepareJump
|
||||
&& physics.OnGround
|
||||
&& (!descendingPrepareJump || descendingPrepareJumpOnSupport)
|
||||
&& GroundedSegmentController.ShouldComplete(_segment, pos, physics))
|
||||
{
|
||||
return TemplateState.Complete;
|
||||
|
|
@ -236,6 +285,17 @@ namespace MinecraftClient.Pathing.Execution.Templates
|
|||
if (_segment.ExitTransition == PathTransitionType.ContinueStraight || physics.OnGround)
|
||||
return false;
|
||||
|
||||
bool heuristicFinalStopRelease = _segment.ExitTransition == PathTransitionType.FinalStop
|
||||
&& ShouldReleaseByRemainingLead(pos, physics);
|
||||
if (heuristicFinalStopRelease)
|
||||
return true;
|
||||
|
||||
bool heuristicDescendingPrepareJumpRelease = _segment.ExitTransition == PathTransitionType.PrepareJump
|
||||
&& ExpectedEnd.Y < ExpectedStart.Y
|
||||
&& ShouldReleaseByRemainingLead(pos, physics);
|
||||
if (heuristicDescendingPrepareJumpRelease)
|
||||
return true;
|
||||
|
||||
bool plannerWantsRelease = TransitionBrakingPlanner.ShouldReleaseForwardInAir(
|
||||
_segment, _nextSegment, pos, physics, world);
|
||||
double remaining = TemplateHelper.RemainingDistanceAlongSegment(pos, _segment);
|
||||
|
|
@ -259,6 +319,16 @@ namespace MinecraftClient.Pathing.Execution.Templates
|
|||
return !holdingStaysInside && releasingStaysInside;
|
||||
}
|
||||
|
||||
private bool ShouldReleaseByRemainingLead(Location pos, PlayerPhysics physics)
|
||||
{
|
||||
double remaining = TemplateHelper.RemainingDistanceAlongSegment(pos, _segment);
|
||||
double forwardSpeed = Math.Max(0.0,
|
||||
TemplateHelper.ProjectHorizontalSpeedAlongHeading(physics, _segment.HeadingX, _segment.HeadingZ));
|
||||
double dropHeight = Math.Max(0.0, ExpectedStart.Y - ExpectedEnd.Y);
|
||||
double releaseLead = 0.14 + (Math.Max(0.0, dropHeight - 1.0) * 0.20);
|
||||
return remaining <= forwardSpeed + releaseLead;
|
||||
}
|
||||
|
||||
private Location? PredictLandingPosition(PlayerPhysics physics, World world, bool holdForward, bool holdSprint)
|
||||
{
|
||||
PlayerPhysics sim = TemplateHelper.ClonePhysicsForPlanning(physics);
|
||||
|
|
|
|||
|
|
@ -137,6 +137,17 @@ namespace MinecraftClient.Pathing.Execution.Templates
|
|||
return dx * segment.HeadingX + dz * segment.HeadingZ;
|
||||
}
|
||||
|
||||
internal static double LateralOffsetFromSegmentLine(Location pos, PathSegment segment)
|
||||
{
|
||||
GetNormalizedSegmentDirection(segment, out double dirX, out double dirZ);
|
||||
if (dirX == 0.0 && dirZ == 0.0)
|
||||
return 0.0;
|
||||
|
||||
double relX = pos.X - segment.Start.X;
|
||||
double relZ = pos.Z - segment.Start.Z;
|
||||
return Math.Abs((-dirZ * relX) + (dirX * relZ));
|
||||
}
|
||||
|
||||
internal static bool ShouldBiasTowardExitHeading(Location pos, PathSegment segment, double distanceThreshold = 0.35)
|
||||
{
|
||||
GetExitHeading(segment, out int headingX, out int headingZ);
|
||||
|
|
|
|||
|
|
@ -58,6 +58,24 @@ namespace MinecraftClient.Pathing.Moves.Impl
|
|||
return;
|
||||
}
|
||||
|
||||
bool cardinal = (XOffset == 0) != (ZOffset == 0);
|
||||
if (cardinal)
|
||||
{
|
||||
int distance = Math.Max(Math.Abs(XOffset), Math.Abs(ZOffset));
|
||||
int maxDistance = _yDelta switch
|
||||
{
|
||||
> 0 => 3,
|
||||
< 0 => 5,
|
||||
_ => 5,
|
||||
};
|
||||
|
||||
if (distance > maxDistance)
|
||||
{
|
||||
result.SetImpossible();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't parkour from climbable blocks (unreliable jump)
|
||||
Material standingOn = ctx.GetMaterial(x, y - 1, z);
|
||||
if (standingOn.CanBeClimbedOn())
|
||||
|
|
@ -105,6 +123,12 @@ namespace MinecraftClient.Pathing.Moves.Impl
|
|||
return;
|
||||
}
|
||||
|
||||
if (ParkourFeasibility.HasIntermediateLandingConflict(ctx, x, y, z, XOffset, ZOffset, _yDelta))
|
||||
{
|
||||
result.SetImpossible();
|
||||
return;
|
||||
}
|
||||
|
||||
int xSign = Math.Sign(XOffset);
|
||||
int zSign = Math.Sign(ZOffset);
|
||||
int xAbs = Math.Abs(XOffset);
|
||||
|
|
|
|||
|
|
@ -15,10 +15,22 @@ internal static class ParkourFeasibility
|
|||
int yDelta)
|
||||
{
|
||||
double horiz = Math.Sqrt(xOffset * xOffset + zOffset * zOffset);
|
||||
double threshold = yDelta > 0 ? 2.5 : 3.5;
|
||||
bool carriedEntry = ctx.PreviousMoveType is MoveType.Parkour or MoveType.Descend;
|
||||
double threshold = yDelta switch
|
||||
{
|
||||
> 0 when carriedEntry => 4.5,
|
||||
> 0 => 2.5,
|
||||
< 0 when carriedEntry => 5.5,
|
||||
< 0 => 3.5,
|
||||
_ when carriedEntry => 5.5,
|
||||
_ => 3.5,
|
||||
};
|
||||
if (horiz < threshold)
|
||||
return true;
|
||||
|
||||
if (carriedEntry && yDelta < 0)
|
||||
return true;
|
||||
|
||||
int backX = x - Math.Sign(xOffset);
|
||||
int backZ = z - Math.Sign(zOffset);
|
||||
if (!ctx.CanWalkOn(backX, y - 1, backZ))
|
||||
|
|
@ -96,6 +108,42 @@ internal static class ParkourFeasibility
|
|||
return true;
|
||||
}
|
||||
|
||||
public static bool HasIntermediateLandingConflict(
|
||||
CalculationContext ctx,
|
||||
int x,
|
||||
int y,
|
||||
int z,
|
||||
int xOffset,
|
||||
int zOffset,
|
||||
int yDelta)
|
||||
{
|
||||
if (yDelta >= 0)
|
||||
return false;
|
||||
|
||||
bool cardinal = (xOffset == 0) != (zOffset == 0);
|
||||
int distance = Math.Max(Math.Abs(xOffset), Math.Abs(zOffset));
|
||||
if (!cardinal || distance < 6)
|
||||
return false;
|
||||
|
||||
int destY = y + yDelta;
|
||||
int xSign = Math.Sign(xOffset);
|
||||
int zSign = Math.Sign(zOffset);
|
||||
|
||||
for (int step = 1; step < distance; step++)
|
||||
{
|
||||
int gx = x + (xOffset != 0 ? xSign * step : 0);
|
||||
int gz = z + (zOffset != 0 ? zSign * step : 0);
|
||||
|
||||
for (int candidateY = y - 1; candidateY >= destY; candidateY--)
|
||||
{
|
||||
if (ctx.CanWalkOn(gx, candidateY - 1, gz) && IsColumnPassable(ctx, gx, candidateY, gz))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsColumnPassable(CalculationContext ctx, int x, int y, int z)
|
||||
{
|
||||
return ctx.CanWalkThrough(x, y, z)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue