mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
feat: converge grounded path segment completion
This commit is contained in:
parent
3b4e552d70
commit
6b449cc72a
12 changed files with 489 additions and 43 deletions
|
|
@ -0,0 +1,99 @@
|
|||
using System;
|
||||
using MinecraftClient.Mapping;
|
||||
using MinecraftClient.Pathing.Core;
|
||||
using MinecraftClient.Pathing.Execution;
|
||||
using MinecraftClient.Pathing.Execution.Templates;
|
||||
using MinecraftClient.Physics;
|
||||
using Xunit;
|
||||
|
||||
namespace MinecraftClient.Tests.Pathing.Execution;
|
||||
|
||||
public sealed class ClimbFallTemplateTests
|
||||
{
|
||||
[Fact]
|
||||
public void ClimbTemplate_AscendsLadderColumn_CompletesOverTarget()
|
||||
{
|
||||
World world = FlatWorldTestBuilder.CreateStoneFloor(min: -2, max: 2);
|
||||
BuildLadder(world, x: 0, z: 0, bottomY: 80, topY: 84);
|
||||
|
||||
var segment = new PathSegment
|
||||
{
|
||||
Start = new Location(0.5, 80, 0.5),
|
||||
End = new Location(0.5, 84, 0.5),
|
||||
MoveType = MoveType.Climb,
|
||||
ExitTransition = PathTransitionType.FinalStop
|
||||
};
|
||||
|
||||
var template = new ClimbTemplate(segment, null);
|
||||
var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 0f);
|
||||
physics.OnClimbable = true;
|
||||
|
||||
TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 220, out Location finalPos);
|
||||
|
||||
Assert.Equal(TemplateState.Complete, state);
|
||||
AssertNearTargetBlock(finalPos, segment.End);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClimbTemplate_DescendsLadderColumn_CompletesOverTarget()
|
||||
{
|
||||
World world = FlatWorldTestBuilder.CreateStoneFloor(min: -2, max: 2);
|
||||
BuildLadder(world, x: 0, z: 0, bottomY: 80, topY: 84);
|
||||
|
||||
var segment = new PathSegment
|
||||
{
|
||||
Start = new Location(0.5, 84, 0.5),
|
||||
End = new Location(0.5, 80, 0.5),
|
||||
MoveType = MoveType.Climb,
|
||||
ExitTransition = PathTransitionType.FinalStop
|
||||
};
|
||||
|
||||
var template = new ClimbTemplate(segment, null);
|
||||
var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 180f);
|
||||
physics.OnClimbable = true;
|
||||
|
||||
TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 220, out Location finalPos);
|
||||
|
||||
Assert.Equal(TemplateState.Complete, state);
|
||||
AssertNearTargetBlock(finalPos, segment.End);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FallTemplate_DropsStraightDown_CompletesOnFloor()
|
||||
{
|
||||
World world = FlatWorldTestBuilder.CreateStoneFloor(min: 4, max: 8);
|
||||
|
||||
var segment = new PathSegment
|
||||
{
|
||||
Start = new Location(5.5, 85, 5.5),
|
||||
End = new Location(5.5, 80, 5.5),
|
||||
MoveType = MoveType.Fall,
|
||||
ExitTransition = PathTransitionType.FinalStop
|
||||
};
|
||||
|
||||
var template = new FallTemplate(segment, null);
|
||||
var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 90f);
|
||||
physics.OnGround = false;
|
||||
physics.DeltaMovement = new Vec3d(0, -0.15, 0);
|
||||
|
||||
TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 260, out Location finalPos);
|
||||
|
||||
Assert.Equal(TemplateState.Complete, state);
|
||||
AssertNearTargetBlock(finalPos, segment.End);
|
||||
}
|
||||
|
||||
private static void AssertNearTargetBlock(Location actual, Location target)
|
||||
{
|
||||
Assert.True(Math.Abs(actual.Y - target.Y) < 0.6, $"Expected final Y near {target.Y:F2}, got {actual.Y:F2}");
|
||||
Assert.True(TemplateFootingHelper.IsFootprintInsideTargetBlock(actual, target),
|
||||
$"Expected the final footprint to stay within {target}, got {actual}");
|
||||
}
|
||||
|
||||
private static void BuildLadder(World world, int x, int z, int bottomY, int topY)
|
||||
{
|
||||
for (int y = bottomY; y <= topY; y++)
|
||||
{
|
||||
FlatWorldTestBuilder.SetClimbable(world, x, y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using MinecraftClient.Mapping;
|
||||
using MinecraftClient.Mapping.BlockPalettes;
|
||||
using MinecraftClient.Physics;
|
||||
|
||||
namespace MinecraftClient.Tests.Pathing.Execution;
|
||||
|
||||
|
|
@ -8,6 +11,7 @@ internal static class FlatWorldTestBuilder
|
|||
{
|
||||
private static readonly Lock InitLock = new();
|
||||
private static bool _defaultsLoaded;
|
||||
private static readonly Dictionary<Material, ushort> MaterialIds = new();
|
||||
|
||||
public static World CreateStoneFloor(int floorY = 79, int min = -32, int max = 32)
|
||||
{
|
||||
|
|
@ -30,13 +34,56 @@ internal static class FlatWorldTestBuilder
|
|||
{
|
||||
for (int z = min; z <= max; z++)
|
||||
{
|
||||
world.SetBlock(new Location(x, floorY, z), new Block(1));
|
||||
SetSolid(world, x, floorY, z);
|
||||
}
|
||||
}
|
||||
|
||||
return world;
|
||||
}
|
||||
|
||||
public static void SetSolid(World world, int x, int y, int z)
|
||||
{
|
||||
SetMaterial(world, x, y, z, Material.Stone);
|
||||
}
|
||||
|
||||
public static void FillSolid(World world, int x1, int y1, int z1, int x2, int y2, int z2)
|
||||
{
|
||||
for (int x = Math.Min(x1, x2); x <= Math.Max(x1, x2); x++)
|
||||
{
|
||||
for (int y = Math.Min(y1, y2); y <= Math.Max(y1, y2); y++)
|
||||
{
|
||||
for (int z = Math.Min(z1, z2); z <= Math.Max(z1, z2); z++)
|
||||
{
|
||||
SetSolid(world, x, y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void ClearBox(World world, int x1, int y1, int z1, int x2, int y2, int z2)
|
||||
{
|
||||
for (int x = Math.Min(x1, x2); x <= Math.Max(x1, x2); x++)
|
||||
{
|
||||
for (int y = Math.Min(y1, y2); y <= Math.Max(y1, y2); y++)
|
||||
{
|
||||
for (int z = Math.Min(z1, z2); z <= Math.Max(z1, z2); z++)
|
||||
{
|
||||
world.SetBlock(new Location(x, y, z), Block.Air);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetMaterial(World world, int x, int y, int z, Material material)
|
||||
{
|
||||
world.SetBlock(new Location(x, y, z), new Block(ResolveMaterialId(material)));
|
||||
}
|
||||
|
||||
public static void SetClimbable(World world, int x, int y, int z)
|
||||
{
|
||||
SetMaterial(world, x, y, z, Material.Ladder);
|
||||
}
|
||||
|
||||
private static void EnsureDefaultDimensionsLoaded()
|
||||
{
|
||||
lock (InitLock)
|
||||
|
|
@ -44,8 +91,31 @@ internal static class FlatWorldTestBuilder
|
|||
if (_defaultsLoaded)
|
||||
return;
|
||||
|
||||
Block.Palette = new Palette1219();
|
||||
World.LoadDefaultDimensions1206Plus();
|
||||
BlockShapes.Initialize();
|
||||
_defaultsLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static ushort ResolveMaterialId(Material material)
|
||||
{
|
||||
lock (InitLock)
|
||||
{
|
||||
if (MaterialIds.TryGetValue(material, out ushort id))
|
||||
return id;
|
||||
|
||||
for (int candidate = 0; candidate <= ushort.MaxValue; candidate++)
|
||||
{
|
||||
if (Block.Palette.FromId(candidate) == material)
|
||||
{
|
||||
ushort resolved = (ushort)candidate;
|
||||
MaterialIds[material] = resolved;
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Could not resolve a block id for material {material}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
using MinecraftClient.Mapping;
|
||||
using MinecraftClient.Pathing.Core;
|
||||
using MinecraftClient.Pathing.Execution;
|
||||
using MinecraftClient.Pathing.Execution.Templates;
|
||||
using Xunit;
|
||||
|
||||
namespace MinecraftClient.Tests.Pathing.Execution;
|
||||
|
||||
public sealed class GroundedTemplateConvergenceTests
|
||||
{
|
||||
[Fact]
|
||||
public void WalkTemplate_FinalStop_Completes_WhenFootprintStaysInsideTargetBlock()
|
||||
{
|
||||
World world = FlatWorldTestBuilder.CreateStoneFloor();
|
||||
var segment = new PathSegment
|
||||
{
|
||||
Start = new Location(0.5, 80, 0.5),
|
||||
End = new Location(1.5, 80, 0.5),
|
||||
MoveType = MoveType.Traverse,
|
||||
ExitTransition = PathTransitionType.FinalStop
|
||||
};
|
||||
|
||||
var template = new WalkTemplate(segment, null);
|
||||
var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 270f);
|
||||
|
||||
TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 160, out Location finalPos);
|
||||
|
||||
Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement}");
|
||||
Assert.True(TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segment.End));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WalkTemplate_PrepareJump_CompletesWithoutSettlingOnRunUpBlock()
|
||||
{
|
||||
World world = FlatWorldTestBuilder.CreateStoneFloor();
|
||||
var current = new PathSegment
|
||||
{
|
||||
Start = new Location(0.5, 80, 0.5),
|
||||
End = new Location(1.5, 80, 0.5),
|
||||
MoveType = MoveType.Traverse,
|
||||
ExitTransition = PathTransitionType.PrepareJump,
|
||||
PreserveSprint = true
|
||||
};
|
||||
var next = new PathSegment
|
||||
{
|
||||
Start = new Location(1.5, 80, 0.5),
|
||||
End = new Location(3.5, 80, 0.5),
|
||||
MoveType = MoveType.Parkour,
|
||||
ExitTransition = PathTransitionType.FinalStop
|
||||
};
|
||||
|
||||
var template = new WalkTemplate(current, next);
|
||||
var physics = TemplateSimulationRunner.CreateGroundedPhysics(current.Start, yaw: 270f);
|
||||
|
||||
TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 60, out _);
|
||||
|
||||
Assert.Equal(TemplateState.Complete, state);
|
||||
Assert.True(physics.DeltaMovement.X > 0.02);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DescendTemplate_LandingRecovery_CompletesOnLandingBlock()
|
||||
{
|
||||
World world = FlatWorldTestBuilder.CreateStoneFloor();
|
||||
FlatWorldTestBuilder.ClearBox(world, 1, 79, 0, 1, 79, 0);
|
||||
FlatWorldTestBuilder.SetSolid(world, 1, 78, 0);
|
||||
|
||||
var segment = new PathSegment
|
||||
{
|
||||
Start = new Location(0.5, 80, 0.5),
|
||||
End = new Location(1.5, 79, 0.5),
|
||||
MoveType = MoveType.Descend,
|
||||
ExitTransition = PathTransitionType.LandingRecovery
|
||||
};
|
||||
|
||||
var template = new DescendTemplate(segment, null);
|
||||
var physics = TemplateSimulationRunner.CreateGroundedPhysics(segment.Start, yaw: 270f);
|
||||
|
||||
TemplateState state = TemplateSimulationRunner.Run(template, physics, world, maxTicks: 240, out Location finalPos);
|
||||
|
||||
Assert.True(state == TemplateState.Complete, $"state={state} finalPos={finalPos} vel={physics.DeltaMovement}");
|
||||
Assert.True(TemplateFootingHelper.IsFootprintInsideTargetBlock(finalPos, segment.End));
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,8 @@ public sealed class PathExecutorCompletionTests
|
|||
var physics = new PlayerPhysics
|
||||
{
|
||||
Yaw = 270f,
|
||||
Pitch = 0f
|
||||
Pitch = 0f,
|
||||
OnGround = true
|
||||
};
|
||||
var input = new MovementInput();
|
||||
var pos = new Location(1.48, 80, 0.5);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
using MinecraftClient.Mapping;
|
||||
using MinecraftClient.Pathing.Execution.Templates;
|
||||
using MinecraftClient.Physics;
|
||||
using Xunit;
|
||||
|
||||
namespace MinecraftClient.Tests.Pathing.Execution;
|
||||
|
||||
public sealed class TemplateFootingTests
|
||||
{
|
||||
[Fact]
|
||||
public void IsFootprintInsideTargetBlock_ReturnsTrue_WhenPlayerIsNearEdgeButStillInside()
|
||||
{
|
||||
bool inside = TemplateFootingHelper.IsFootprintInsideTargetBlock(
|
||||
new Location(10.69, 80.0, 4.50),
|
||||
new Location(10.50, 80.0, 4.50));
|
||||
|
||||
Assert.True(inside);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsFootprintInsideTargetBlock_ReturnsFalse_WhenPlayerCrossesBlockEdge()
|
||||
{
|
||||
bool inside = TemplateFootingHelper.IsFootprintInsideTargetBlock(
|
||||
new Location(10.81, 80.0, 4.50),
|
||||
new Location(10.50, 80.0, 4.50));
|
||||
|
||||
Assert.False(inside);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WillLeaveTargetBlockNextTick_ReturnsTrue_WhenVelocityWouldCarryPastEdge()
|
||||
{
|
||||
var physics = new PlayerPhysics
|
||||
{
|
||||
Position = new Vec3d(10.67, 80.0, 4.50),
|
||||
DeltaMovement = new Vec3d(0.060, 0.0, 0.0),
|
||||
OnGround = true
|
||||
};
|
||||
|
||||
bool exitsNextTick = TemplateFootingHelper.WillLeaveTargetBlockNextTick(
|
||||
new Location(10.67, 80.0, 4.50),
|
||||
physics,
|
||||
new Location(10.50, 80.0, 4.50));
|
||||
|
||||
Assert.True(exitsNextTick);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
using MinecraftClient.Mapping;
|
||||
using MinecraftClient.Pathing.Execution;
|
||||
using MinecraftClient.Physics;
|
||||
|
||||
namespace MinecraftClient.Tests.Pathing.Execution;
|
||||
|
||||
internal static class TemplateSimulationRunner
|
||||
{
|
||||
internal static PlayerPhysics CreateGroundedPhysics(Location start, float yaw)
|
||||
{
|
||||
return new PlayerPhysics
|
||||
{
|
||||
Position = new Vec3d(start.X, start.Y, start.Z),
|
||||
DeltaMovement = Vec3d.Zero,
|
||||
OnGround = true,
|
||||
MovementSpeed = 0.1f,
|
||||
Yaw = yaw,
|
||||
Pitch = 0f
|
||||
};
|
||||
}
|
||||
|
||||
internal static TemplateState Run(IActionTemplate template, PlayerPhysics physics, World world, int maxTicks, out Location finalPos)
|
||||
{
|
||||
var input = new MovementInput();
|
||||
TemplateState state = TemplateState.InProgress;
|
||||
|
||||
for (int tick = 0; tick < maxTicks; tick++)
|
||||
{
|
||||
input.Reset();
|
||||
Location pos = new(physics.Position.X, physics.Position.Y, physics.Position.Z);
|
||||
state = template.Tick(pos, physics, input, world);
|
||||
if (state != TemplateState.InProgress)
|
||||
break;
|
||||
|
||||
physics.ApplyInput(input);
|
||||
physics.Tick(world);
|
||||
}
|
||||
|
||||
finalPos = new Location(physics.Position.X, physics.Position.Y, physics.Position.Z);
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
@ -47,25 +47,11 @@ namespace MinecraftClient.Pathing.Execution.Templates
|
|||
if (physics.OnGround && dy > 0.1)
|
||||
input.Jump = true;
|
||||
|
||||
if (physics.OnGround && Math.Abs(dy) < 0.15)
|
||||
if (physics.OnGround && Math.Abs(dy) < 0.2)
|
||||
{
|
||||
TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(_segment, _nextSegment, pos, physics, world);
|
||||
TemplateHelper.ApplyDecision(input, decision);
|
||||
if (decision.HoldBack)
|
||||
TemplateHelper.FaceSegmentHeading(physics, _segment);
|
||||
|
||||
if (_segment.ExitTransition == PathTransitionType.ContinueStraight && horizDistSq < 0.25)
|
||||
GroundedSegmentController.Apply(_segment, _nextSegment, pos, physics, input, world);
|
||||
if (GroundedSegmentController.ShouldComplete(_segment, pos, physics))
|
||||
return TemplateState.Complete;
|
||||
|
||||
if (_segment.ExitTransition != PathTransitionType.ContinueStraight
|
||||
&& TemplateHelper.IsSettledAtEnd(pos, ExpectedEnd, physics, horizThresholdSq: 0.0025))
|
||||
{
|
||||
return TemplateState.Complete;
|
||||
}
|
||||
}
|
||||
else if (horizDistSq < 0.25 && Math.Abs(dy) < 0.8)
|
||||
{
|
||||
return TemplateState.Complete;
|
||||
}
|
||||
|
||||
double movedSq = TemplateHelper.HorizontalDistanceSq(pos, _lastPos);
|
||||
|
|
|
|||
|
|
@ -59,26 +59,14 @@ namespace MinecraftClient.Pathing.Execution.Templates
|
|||
float targetPitch = TemplateHelper.CalculatePitch(dx, dy, dz);
|
||||
physics.Pitch = TemplateHelper.SmoothPitch(physics.Pitch, targetPitch);
|
||||
|
||||
if (physics.OnGround && Math.Abs(dy) < (_hasFallen ? 0.8 : 0.5))
|
||||
if (physics.OnGround && Math.Abs(dy) < (_hasFallen ? 1.0 : 0.6))
|
||||
{
|
||||
if (horizDistSq > 0.01)
|
||||
physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, targetYaw);
|
||||
|
||||
TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(_segment, _nextSegment, pos, physics, world);
|
||||
TemplateHelper.ApplyDecision(input, decision);
|
||||
if (decision.HoldBack)
|
||||
TemplateHelper.FaceSegmentHeading(physics, _segment);
|
||||
|
||||
if (_segment.ExitTransition == PathTransitionType.ContinueStraight)
|
||||
{
|
||||
double completionThreshold = _hasFallen ? 0.5 : 0.25;
|
||||
if (horizDistSq < completionThreshold)
|
||||
return TemplateState.Complete;
|
||||
}
|
||||
else if (TemplateHelper.IsSettledAtEnd(pos, ExpectedEnd, physics, horizThresholdSq: 0.0025))
|
||||
{
|
||||
GroundedSegmentController.Apply(_segment, _nextSegment, pos, physics, input, world);
|
||||
if (GroundedSegmentController.ShouldComplete(_segment, pos, physics))
|
||||
return TemplateState.Complete;
|
||||
}
|
||||
}
|
||||
else if (physics.OnClimbable)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
using MinecraftClient.Mapping;
|
||||
using MinecraftClient.Physics;
|
||||
|
||||
namespace MinecraftClient.Pathing.Execution.Templates
|
||||
{
|
||||
internal static class GroundedSegmentController
|
||||
{
|
||||
internal static void Apply(PathSegment segment, PathSegment? nextSegment, Location pos, PlayerPhysics physics, MovementInput input, World world)
|
||||
{
|
||||
TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(segment, nextSegment, pos, physics, world);
|
||||
TemplateHelper.ApplyDecision(input, decision);
|
||||
if (decision.HoldBack)
|
||||
TemplateHelper.FaceSegmentHeading(physics, segment);
|
||||
}
|
||||
|
||||
internal static bool ShouldComplete(PathSegment segment, Location pos, PlayerPhysics physics)
|
||||
{
|
||||
return segment.ExitTransition switch
|
||||
{
|
||||
PathTransitionType.ContinueStraight => TemplateHelper.IsNear(pos, segment.End, horizThresholdSq: 0.09),
|
||||
PathTransitionType.PrepareJump => TemplateHelper.HasReachedSegmentEndPlane(pos, segment)
|
||||
&& TemplateHelper.ProjectHorizontalSpeedAlongSegment(physics, segment) > 0.02,
|
||||
_ => physics.OnGround && TemplateHelper.IsSettledOnTargetBlock(pos, segment.End, physics)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
using System;
|
||||
using MinecraftClient.Mapping;
|
||||
using MinecraftClient.Physics;
|
||||
|
||||
namespace MinecraftClient.Pathing.Execution.Templates
|
||||
{
|
||||
public static class TemplateFootingHelper
|
||||
{
|
||||
private const double HalfWidth = PhysicsConsts.PlayerWidth / 2.0;
|
||||
|
||||
public static bool IsFootprintInsideTargetBlock(Location pos, Location target, double epsilon = 1.0E-4)
|
||||
{
|
||||
double minX = pos.X - HalfWidth;
|
||||
double maxX = pos.X + HalfWidth;
|
||||
double minZ = pos.Z - HalfWidth;
|
||||
double maxZ = pos.Z + HalfWidth;
|
||||
|
||||
double blockMinX = Math.Floor(target.X);
|
||||
double blockMaxX = blockMinX + 1.0;
|
||||
double blockMinZ = Math.Floor(target.Z);
|
||||
double blockMaxZ = blockMinZ + 1.0;
|
||||
|
||||
return minX >= blockMinX - epsilon
|
||||
&& maxX <= blockMaxX + epsilon
|
||||
&& minZ >= blockMinZ - epsilon
|
||||
&& maxZ <= blockMaxZ + epsilon;
|
||||
}
|
||||
|
||||
public static bool WillLeaveTargetBlockNextTick(Location pos, PlayerPhysics physics, Location target, double epsilon = 1.0E-4)
|
||||
{
|
||||
Location nextPos = new(
|
||||
pos.X + physics.DeltaMovement.X,
|
||||
pos.Y,
|
||||
pos.Z + physics.DeltaMovement.Z);
|
||||
return !IsFootprintInsideTargetBlock(nextPos, target, epsilon);
|
||||
}
|
||||
|
||||
public static bool WillCrossSupportExitNextTick(Location pos, PlayerPhysics physics, PathSegment segment, double epsilon = 1.0E-4)
|
||||
{
|
||||
double nextX = pos.X + physics.DeltaMovement.X;
|
||||
double nextZ = pos.Z + physics.DeltaMovement.Z;
|
||||
|
||||
double blockMinX = Math.Floor(segment.End.X);
|
||||
double blockMaxX = blockMinX + 1.0;
|
||||
double blockMinZ = Math.Floor(segment.End.Z);
|
||||
double blockMaxZ = blockMinZ + 1.0;
|
||||
|
||||
if (segment.HeadingX > 0 && nextX > blockMaxX - HalfWidth + epsilon)
|
||||
return true;
|
||||
if (segment.HeadingX < 0 && nextX < blockMinX + HalfWidth - epsilon)
|
||||
return true;
|
||||
if (segment.HeadingZ > 0 && nextZ > blockMaxZ - HalfWidth + epsilon)
|
||||
return true;
|
||||
if (segment.HeadingZ < 0 && nextZ < blockMinZ + HalfWidth - epsilon)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -90,14 +90,57 @@ namespace MinecraftClient.Pathing.Execution.Templates
|
|||
input.Back = decision.HoldBack;
|
||||
}
|
||||
|
||||
internal static bool HasReachedSegmentEndPlane(Location pos, PathSegment segment, double tolerance = 0.05)
|
||||
{
|
||||
GetNormalizedSegmentDirection(segment, out double dirX, out double dirZ);
|
||||
double relX = pos.X - segment.End.X;
|
||||
double relZ = pos.Z - segment.End.Z;
|
||||
return relX * dirX + relZ * dirZ >= -tolerance;
|
||||
}
|
||||
|
||||
internal static double ProjectHorizontalSpeedAlongSegment(PlayerPhysics physics, PathSegment segment)
|
||||
{
|
||||
GetNormalizedSegmentDirection(segment, out double dirX, out double dirZ);
|
||||
return physics.DeltaMovement.X * dirX + physics.DeltaMovement.Z * dirZ;
|
||||
}
|
||||
|
||||
internal static bool IsSettledOnTargetBlock(Location pos, Location target, PlayerPhysics physics,
|
||||
double speedThresholdSq = 0.0016)
|
||||
{
|
||||
double horizontalSpeedSq = physics.DeltaMovement.X * physics.DeltaMovement.X
|
||||
+ physics.DeltaMovement.Z * physics.DeltaMovement.Z;
|
||||
return TemplateFootingHelper.IsFootprintInsideTargetBlock(pos, target)
|
||||
&& !TemplateFootingHelper.WillLeaveTargetBlockNextTick(pos, physics, target)
|
||||
&& horizontalSpeedSq <= speedThresholdSq;
|
||||
}
|
||||
|
||||
internal static bool IsSettledAtEnd(Location pos, Location target, PlayerPhysics physics,
|
||||
double horizThresholdSq = 0.0025, double speedThresholdSq = 0.0016)
|
||||
{
|
||||
if (IsSettledOnTargetBlock(pos, target, physics, speedThresholdSq))
|
||||
return true;
|
||||
|
||||
double dx = target.X - pos.X;
|
||||
double dz = target.Z - pos.Z;
|
||||
double horizontalSpeedSq = physics.DeltaMovement.X * physics.DeltaMovement.X
|
||||
+ physics.DeltaMovement.Z * physics.DeltaMovement.Z;
|
||||
return dx * dx + dz * dz <= horizThresholdSq && horizontalSpeedSq <= speedThresholdSq;
|
||||
}
|
||||
|
||||
private static void GetNormalizedSegmentDirection(PathSegment segment, out double dirX, out double dirZ)
|
||||
{
|
||||
dirX = segment.End.X - segment.Start.X;
|
||||
dirZ = segment.End.Z - segment.Start.Z;
|
||||
double len = Math.Sqrt(dirX * dirX + dirZ * dirZ);
|
||||
if (len < 1.0E-6)
|
||||
{
|
||||
dirX = 0.0;
|
||||
dirZ = 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
dirX /= len;
|
||||
dirZ /= len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,22 +40,21 @@ namespace MinecraftClient.Pathing.Execution.Templates
|
|||
physics.Yaw = TemplateHelper.SmoothYaw(physics.Yaw, targetYaw);
|
||||
physics.Pitch = TemplateHelper.SmoothPitch(physics.Pitch, targetPitch);
|
||||
|
||||
TransitionBrakingDecision decision = TransitionBrakingPlanner.Plan(_segment, _nextSegment, pos, physics, world);
|
||||
TemplateHelper.ApplyDecision(input, decision);
|
||||
if (decision.HoldBack)
|
||||
TemplateHelper.FaceSegmentHeading(physics, _segment);
|
||||
GroundedSegmentController.Apply(_segment, _nextSegment, pos, physics, input, world);
|
||||
|
||||
if (_segment.ExitTransition == PathTransitionType.ContinueStraight && TemplateHelper.IsNear(pos, ExpectedEnd, horizThresholdSq: 0.09))
|
||||
return TemplateState.Complete;
|
||||
|
||||
if (_segment.ExitTransition != PathTransitionType.ContinueStraight && TemplateHelper.IsSettledAtEnd(pos, ExpectedEnd, physics))
|
||||
if (GroundedSegmentController.ShouldComplete(_segment, pos, physics))
|
||||
return TemplateState.Complete;
|
||||
|
||||
double movedSq = TemplateHelper.HorizontalDistanceSq(pos, _lastPos);
|
||||
_stuckTicks = movedSq < 0.0005 ? _stuckTicks + 1 : 0;
|
||||
_lastPos = pos;
|
||||
|
||||
int maxTicks = _segment.ExitTransition == PathTransitionType.ContinueStraight ? 100 : 140;
|
||||
int maxTicks = _segment.ExitTransition switch
|
||||
{
|
||||
PathTransitionType.ContinueStraight => 100,
|
||||
PathTransitionType.PrepareJump => 80,
|
||||
_ => 140
|
||||
};
|
||||
if (_stuckTicks > 40 || _tickCount > maxTicks)
|
||||
return TemplateState.Failed;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue