Support for using relative coordinates in /move

This commit is contained in:
BruceChen 2022-09-05 22:03:47 +08:00
parent 8f6b962607
commit 0eb8d9998c
4 changed files with 58 additions and 27 deletions

View file

@ -90,21 +90,27 @@ namespace MinecraftClient.Commands
{ {
try try
{ {
int x = int.Parse(args[0]); Location current = handler.GetCurrentLocation(), currentCenter = new Location(current).ConvertToCenter();
int y = int.Parse(args[1]);
int z = int.Parse(args[2]); double x = args[0].StartsWith('~') ? current.X + (args[0].Length > 1 ? double.Parse(args[0][1..]) : 0) : double.Parse(args[0]);
Location goal = new Location(x, y, z); double y = args[1].StartsWith('~') ? current.Y + (args[1].Length > 1 ? double.Parse(args[1][1..]) : 0) : double.Parse(args[1]);
double z = args[2].StartsWith('~') ? current.Z + (args[2].Length > 1 ? double.Parse(args[2][1..]) : 0) : double.Parse(args[2]);
Location goal = new(x, y, z);
ChunkColumn? chunkColumn = handler.GetWorld().GetChunkColumn(goal); ChunkColumn? chunkColumn = handler.GetWorld().GetChunkColumn(goal);
if (chunkColumn == null || chunkColumn.FullyLoaded == false) if (chunkColumn == null || chunkColumn.FullyLoaded == false)
return Translations.Get("cmd.move.chunk_not_loaded"); return Translations.Get("cmd.move.chunk_not_loaded");
Location current = handler.GetCurrentLocation(); if (takeRisk || Movement.PlayerFitsHere(handler.GetWorld(), goal))
handler.MoveTo(current.ToCenter(), allowDirectTeleport: true); {
if (current.DistanceSquared(goal) <= 1.5)
if (handler.MoveTo(goal, allowUnsafe: takeRisk)) handler.MoveTo(goal, allowDirectTeleport: true);
else if (!handler.MoveTo(goal, allowUnsafe: takeRisk))
return takeRisk ? Translations.Get("cmd.move.fail", goal) : Translations.Get("cmd.move.suggestforce", goal);
return Translations.Get("cmd.move.walk", goal, current); return Translations.Get("cmd.move.walk", goal, current);
else return takeRisk ? Translations.Get("cmd.move.fail", goal) : Translations.Get("cmd.move.suggestforce", goal); }
else
return Translations.Get("cmd.move.suggestforce", goal);
} }
catch (FormatException) { return GetCmdDescTranslated(); } catch (FormatException) { return GetCmdDescTranslated(); }
} }

View file

@ -47,6 +47,16 @@ namespace MinecraftClient.Mapping
Z = z; Z = z;
} }
/// <summary>
/// Create a new location
/// </summary>
public Location(Location loc)
{
X = loc.X;
Y = loc.Y;
Z = loc.Z;
}
/// <summary> /// <summary>
/// Create a new location /// Create a new location
/// </summary> /// </summary>
@ -67,7 +77,7 @@ namespace MinecraftClient.Mapping
/// Round coordinates /// Round coordinates
/// </summary> /// </summary>
/// <returns>itself</returns> /// <returns>itself</returns>
public Location ToFloor() public Location ConvertToFloor()
{ {
this.X = Math.Floor(this.X); this.X = Math.Floor(this.X);
this.Y = Math.Floor(this.Y); this.Y = Math.Floor(this.Y);
@ -79,7 +89,7 @@ namespace MinecraftClient.Mapping
/// Get the center coordinates /// Get the center coordinates
/// </summary> /// </summary>
/// <returns>itself</returns> /// <returns>itself</returns>
public Location ToCenter() public Location ConvertToCenter()
{ {
this.X = Math.Floor(this.X) + 0.5; this.X = Math.Floor(this.X) + 0.5;
this.Z = Math.Floor(this.Z) + 0.5; this.Z = Math.Floor(this.Z) + 0.5;

View file

@ -168,8 +168,9 @@ namespace MinecraftClient.Mapping
throw new ArgumentException("minOffset must be lower or equal to maxOffset", "minOffset"); throw new ArgumentException("minOffset must be lower or equal to maxOffset", "minOffset");
// Round start coordinates for easier calculation // Round start coordinates for easier calculation
start.ToFloor(); Location startCenter = new Location(start).ConvertToCenter();
goal.ToFloor(); Location startLower = new Location(start).ConvertToFloor();
Location goalLower = new Location(goal).ConvertToFloor();
// We always use distance squared so our limits must also be squared. // We always use distance squared so our limits must also be squared.
minOffset *= minOffset; minOffset *= minOffset;
@ -187,8 +188,8 @@ namespace MinecraftClient.Mapping
Dictionary<Location, int> gScoreDict = new Dictionary<Location, int>(); Dictionary<Location, int> gScoreDict = new Dictionary<Location, int>();
// Set start values for variables // Set start values for variables
openSet.Insert(0, (int)start.DistanceSquared(goal), start); openSet.Insert(0, (int)startLower.DistanceSquared(goalLower), startLower);
gScoreDict[start] = 0; gScoreDict[startLower] = 0;
BinaryHeap.Node current = null; BinaryHeap.Node current = null;
///---/// ///---///
@ -203,9 +204,9 @@ namespace MinecraftClient.Mapping
current = openSet.GetRootLocation(); current = openSet.GetRootLocation();
// Return if goal found and no maxOffset was given OR current node is between minOffset and maxOffset // Return if goal found and no maxOffset was given OR current node is between minOffset and maxOffset
if ((current.Location == goal && maxOffset <= 0) || (maxOffset > 0 && current.H_score >= minOffset && current.H_score <= maxOffset)) if ((current.Location == goalLower && maxOffset <= 0) || (maxOffset > 0 && current.H_score >= minOffset && current.H_score <= maxOffset))
{ {
return ReconstructPath(CameFrom, current.Location); return ReconstructPath(CameFrom, current.Location, startCenter, goal);
} }
// Discover neighbored blocks // Discover neighbored blocks
@ -227,14 +228,14 @@ namespace MinecraftClient.Mapping
// If this location is not already included in the Binary Heap: save it // If this location is not already included in the Binary Heap: save it
if (!openSet.ContainsLocation(neighbor)) if (!openSet.ContainsLocation(neighbor))
openSet.Insert(tentativeGScore, (int)neighbor.DistanceSquared(goal), neighbor); openSet.Insert(tentativeGScore, (int)neighbor.DistanceSquared(goalLower), neighbor);
} }
} }
} }
//// Goal could not be reached. Set the path to the closest location if close enough //// Goal could not be reached. Set the path to the closest location if close enough
if (current != null && (maxOffset == int.MaxValue || openSet.MinH_ScoreNode.H_score <= maxOffset)) if (current != null && (maxOffset == int.MaxValue || openSet.MinH_ScoreNode.H_score <= maxOffset))
return ReconstructPath(CameFrom, openSet.MinH_ScoreNode.Location); return ReconstructPath(CameFrom, openSet.MinH_ScoreNode.Location, startCenter, goal);
else else
return null; return null;
} }
@ -245,15 +246,29 @@ namespace MinecraftClient.Mapping
/// <param name="Came_From">The collection of Locations that leads back to the start</param> /// <param name="Came_From">The collection of Locations that leads back to the start</param>
/// <param name="current">Endpoint of our later walk</param> /// <param name="current">Endpoint of our later walk</param>
/// <returns>the path that leads to current from the start position</returns> /// <returns>the path that leads to current from the start position</returns>
private static Queue<Location> ReconstructPath(Dictionary<Location, Location> Came_From, Location current) private static Queue<Location> ReconstructPath(Dictionary<Location, Location> Came_From, Location current, Location startCenter, Location end)
{ {
// Add 0.5 to walk over the middle of a block and avoid collisions // Add 0.5 to walk over the middle of a block and avoid collisions
List<Location> total_path = new List<Location>(new[] { current + new Location(0.5, 0, 0.5) }); Location center = new(0.5, 0, 0.5);
List<Location> total_path = new();
// Move from the center of the block to the final position
if (current != end && current.DistanceSquared(end) <= 1.5)
total_path.Add(end);
// Generate intermediate paths
total_path.Add(current + center);
while (Came_From.ContainsKey(current)) while (Came_From.ContainsKey(current))
{ {
current = Came_From[current]; current = Came_From[current];
total_path.Add(current + new Location(0.5, 0, 0.5)); total_path.Add(current + center);
} }
// Move to the center of the block first
if (current != startCenter && current.DistanceSquared(startCenter) <= 1.5)
total_path.Add(startCenter);
total_path.Reverse(); total_path.Reverse();
return new Queue<Location>(total_path); return new Queue<Location>(total_path);
} }

View file

@ -1102,7 +1102,7 @@ namespace MinecraftClient
/// <summary> /// <summary>
/// Move to the specified location /// Move to the specified location
/// </summary> /// </summary>
/// <param name="location">Location to reach</param> /// <param name="goal">Location to reach</param>
/// <param name="allowUnsafe">Allow possible but unsafe locations thay may hurt the player: lava, cactus...</param> /// <param name="allowUnsafe">Allow possible but unsafe locations thay may hurt the player: lava, cactus...</param>
/// <param name="allowDirectTeleport">Allow non-vanilla direct teleport instead of computing path, but may cause invalid moves and/or trigger anti-cheat plugins</param> /// <param name="allowDirectTeleport">Allow non-vanilla direct teleport instead of computing path, but may cause invalid moves and/or trigger anti-cheat plugins</param>
/// <param name="maxOffset">If no valid path can be found, also allow locations within specified distance of destination</param> /// <param name="maxOffset">If no valid path can be found, also allow locations within specified distance of destination</param>
@ -1110,21 +1110,21 @@ namespace MinecraftClient
/// <param name="timeout">How long to wait until the path is evaluated (default: 5 seconds)</param> /// <param name="timeout">How long to wait until the path is evaluated (default: 5 seconds)</param>
/// <remarks>When location is unreachable, computation will reach timeout, then optionally fallback to a close location within maxOffset</remarks> /// <remarks>When location is unreachable, computation will reach timeout, then optionally fallback to a close location within maxOffset</remarks>
/// <returns>True if a path has been found</returns> /// <returns>True if a path has been found</returns>
public bool MoveTo(Location location, bool allowUnsafe = false, bool allowDirectTeleport = false, int maxOffset = 0, int minOffset = 0, TimeSpan? timeout = null) public bool MoveTo(Location goal, bool allowUnsafe = false, bool allowDirectTeleport = false, int maxOffset = 0, int minOffset = 0, TimeSpan? timeout = null)
{ {
lock (locationLock) lock (locationLock)
{ {
if (allowDirectTeleport) if (allowDirectTeleport)
{ {
// 1-step path to the desired location without checking anything // 1-step path to the desired location without checking anything
UpdateLocation(location, location); // Update yaw and pitch to look at next step UpdateLocation(goal, goal); // Update yaw and pitch to look at next step
handler.SendLocationUpdate(location, Movement.IsOnGround(world, location), _yaw, _pitch); handler.SendLocationUpdate(goal, Movement.IsOnGround(world, goal), _yaw, _pitch);
return true; return true;
} }
else else
{ {
// Calculate path through pathfinding. Path contains a list of 1-block movement that will be divided into steps // Calculate path through pathfinding. Path contains a list of 1-block movement that will be divided into steps
path = Movement.CalculatePath(world, this.location, location, allowUnsafe, maxOffset, minOffset, timeout ?? TimeSpan.FromSeconds(5)); path = Movement.CalculatePath(world, this.location, goal, allowUnsafe, maxOffset, minOffset, timeout ?? TimeSpan.FromSeconds(5));
return path != null; return path != null;
} }
} }