Minecraft-Console-Client/MinecraftClient/Pathing/Execution/TransitionLookaheadEvaluator.cs

196 lines
7.6 KiB
C#

using System;
using MinecraftClient.Mapping;
using MinecraftClient.Pathing.Execution.Templates;
using MinecraftClient.Physics;
namespace MinecraftClient.Pathing.Execution
{
public static class TransitionLookaheadEvaluator
{
public static TransitionInputProfile ChooseGroundProfile(PathSegment segment, Location pos, PlayerPhysics physics, World world)
{
double remaining = TemplateHelper.RemainingDistanceAlongSegment(pos, segment);
double forwardSpeed = Math.Max(0.0,
TemplateHelper.ProjectHorizontalSpeedAlongHeading(physics, segment.HeadingX, segment.HeadingZ));
bool requiresJumpEntry = segment.ExitHints.RequireJumpReady
|| segment.ExitTransition == PathTransitionType.PrepareJump;
if (segment.ExitTransition == PathTransitionType.ContinueStraight && !requiresJumpEntry)
return TransitionInputProfile.Carry;
if (requiresJumpEntry)
return TransitionInputProfile.Carry;
bool requiresSlowEntry = segment.ExitHints.RequireStableFooting
|| segment.ExitTransition is PathTransitionType.FinalStop or PathTransitionType.Turn
|| (segment.ExitTransition == PathTransitionType.LandingRecovery
&& (segment.ExitHints.AllowAirBrake || IsFiniteSpeedCap(segment)));
if (!requiresSlowEntry)
return TransitionInputProfile.Carry;
double maxExitSpeed = GetTargetMaxExitSpeed(segment);
double hardBrakeDistance = TransitionBrakingPlanner.EstimateGroundStopDistance(
physics, world, segment.HeadingX, segment.HeadingZ, applyBackBrake: true);
double coastStopDistance = TransitionBrakingPlanner.EstimateGroundStopDistance(
physics, world, segment.HeadingX, segment.HeadingZ, applyBackBrake: false);
if (remaining < 0.0)
return TransitionInputProfile.Brake;
if (forwardSpeed > maxExitSpeed && remaining <= hardBrakeDistance + 0.10)
return TransitionInputProfile.Brake;
if (forwardSpeed <= maxExitSpeed && remaining > 0.0)
return TransitionInputProfile.Carry;
if (remaining <= coastStopDistance + 0.06)
return TransitionInputProfile.Coast;
return TransitionInputProfile.Carry;
}
public static TransitionInputProfile ChooseAirProfile(PathSegment segment, Location pos, PlayerPhysics physics, World world)
{
if (!segment.ExitHints.AllowAirBrake)
return TransitionInputProfile.AirHoldForward;
TransitionInputProfile[] candidates =
[
TransitionInputProfile.AirHoldForward,
TransitionInputProfile.AirRelease,
TransitionInputProfile.AirBrake
];
return ChooseBest(segment, pos, physics, world, candidates);
}
private static TransitionInputProfile ChooseBest(PathSegment segment, Location pos, PlayerPhysics physics, World world,
TransitionInputProfile[] candidates)
{
TransitionInputProfile best = candidates[0];
double bestScore = double.PositiveInfinity;
foreach (TransitionInputProfile candidate in candidates)
{
double score = Score(segment, pos, physics, world, candidate);
if (score < bestScore)
{
best = candidate;
bestScore = score;
}
}
return best;
}
private static double Score(PathSegment segment, Location pos, PlayerPhysics physics, World world, TransitionInputProfile candidate)
{
PlayerPhysics sim = TemplateHelper.ClonePhysicsForPlanning(physics);
sim.Position = new Vec3d(pos.X, pos.Y, pos.Z);
var input = new MovementInput();
Location simPos = pos;
for (int tick = 0; tick < segment.ExitHints.HorizonTicks; tick++)
{
if (TemplateHelper.ShouldBiasTowardExitHeading(simPos, segment))
TemplateHelper.FaceExitHeading(sim, segment);
input.Reset();
ApplyCandidateInput(input, candidate, segment);
sim.ApplyInput(input);
sim.Tick(world);
simPos = new Location(sim.Position.X, sim.Position.Y, sim.Position.Z);
}
double score = 0.0;
double exitSpeed = TemplateHelper.ProjectHorizontalSpeedAlongHint(sim, segment);
double horizontalSpeed = TemplateHelper.GetHorizontalSpeed(sim);
if (segment.ExitHints.RequireGrounded && !sim.OnGround)
score += 1000.0;
if (segment.ExitHints.RequireStableFooting
&& !TemplateHelper.IsSettledOnTargetBlock(simPos, segment.End, sim))
{
score += 1000.0;
}
if (segment.ExitHints.RequireStableFooting && !sim.OnGround)
{
double remaining = TemplateHelper.RemainingDistanceAlongSegment(simPos, segment);
if (remaining > 0.0)
score += 2000.0 + remaining * 500.0;
if (simPos.Y < segment.End.Y)
score += 2000.0 + (segment.End.Y - simPos.Y) * 500.0;
}
if (exitSpeed < segment.ExitHints.MinExitSpeed)
score += (segment.ExitHints.MinExitSpeed - exitSpeed) * 200.0;
if (exitSpeed > segment.ExitHints.MaxExitSpeed)
score += (exitSpeed - segment.ExitHints.MaxExitSpeed) * 200.0;
score += TemplateHelper.HeadingPenaltyDegrees(sim.Yaw, segment);
if (segment.ExitHints.RequireStableFooting)
{
double dx = segment.End.X - simPos.X;
double dz = segment.End.Z - simPos.Z;
score += (dx * dx + dz * dz) * 20.0;
}
else
{
score += Math.Abs(TemplateHelper.RemainingDistanceAlongSegment(simPos, segment)) * 10.0;
}
if (segment.ExitHints.RequireJumpReady && horizontalSpeed < segment.ExitHints.MinExitSpeed)
score += 250.0;
return score;
}
private static void ApplyCandidateInput(MovementInput input, TransitionInputProfile candidate, PathSegment segment)
{
switch (candidate)
{
case TransitionInputProfile.Carry:
case TransitionInputProfile.AirHoldForward:
input.Forward = true;
input.Sprint = segment.PreserveSprint || segment.ExitHints.RequireJumpReady;
break;
case TransitionInputProfile.Brake:
case TransitionInputProfile.AirBrake:
input.Back = true;
break;
case TransitionInputProfile.Coast:
case TransitionInputProfile.AirRelease:
default:
break;
}
}
private static bool IsFiniteSpeedCap(PathSegment segment)
{
return !double.IsPositiveInfinity(segment.ExitHints.MaxExitSpeed);
}
private static double GetTargetMaxExitSpeed(PathSegment segment)
{
if (IsFiniteSpeedCap(segment))
return segment.ExitHints.MaxExitSpeed;
return segment.ExitTransition switch
{
PathTransitionType.FinalStop => 0.03,
PathTransitionType.Turn or PathTransitionType.LandingRecovery => 0.035,
_ => double.PositiveInfinity
};
}
}
}