Merge pull request #2154 from BruceChenQAQ/master

Support for using relative coordinates in /move
This commit is contained in:
BruceChen 2022-09-05 23:24:27 +08:00 committed by GitHub
commit 7a33e65c61
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 59 additions and 28 deletions

View file

@ -80,7 +80,7 @@ namespace MinecraftClient
/// <returns>Argument array or empty array if no arguments</returns>
public static string[] getArgs(string command)
{
string[] args = getArg(command).Split(' ');
string[] args = getArg(command).Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (args.Length == 1 && args[0] == "")
{
return new string[] { };

View file

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

View file

@ -47,6 +47,16 @@ namespace MinecraftClient.Mapping
Z = z;
}
/// <summary>
/// Create a new location
/// </summary>
public Location(Location loc)
{
X = loc.X;
Y = loc.Y;
Z = loc.Z;
}
/// <summary>
/// Create a new location
/// </summary>
@ -67,7 +77,7 @@ namespace MinecraftClient.Mapping
/// Round coordinates
/// </summary>
/// <returns>itself</returns>
public Location ToFloor()
public Location ConvertToFloor()
{
this.X = Math.Floor(this.X);
this.Y = Math.Floor(this.Y);
@ -79,7 +89,7 @@ namespace MinecraftClient.Mapping
/// Get the center coordinates
/// </summary>
/// <returns>itself</returns>
public Location ToCenter()
public Location ConvertToCenter()
{
this.X = Math.Floor(this.X) + 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");
// Round start coordinates for easier calculation
start.ToFloor();
goal.ToFloor();
Location startCenter = new Location(start).ConvertToCenter();
Location startLower = new Location(start).ConvertToFloor();
Location goalLower = new Location(goal).ConvertToFloor();
// We always use distance squared so our limits must also be squared.
minOffset *= minOffset;
@ -187,8 +188,8 @@ namespace MinecraftClient.Mapping
Dictionary<Location, int> gScoreDict = new Dictionary<Location, int>();
// Set start values for variables
openSet.Insert(0, (int)start.DistanceSquared(goal), start);
gScoreDict[start] = 0;
openSet.Insert(0, (int)startLower.DistanceSquared(goalLower), startLower);
gScoreDict[startLower] = 0;
BinaryHeap.Node current = null;
///---///
@ -203,9 +204,9 @@ namespace MinecraftClient.Mapping
current = openSet.GetRootLocation();
// 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
@ -227,14 +228,14 @@ namespace MinecraftClient.Mapping
// If this location is not already included in the Binary Heap: save it
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
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
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="current">Endpoint of our later walk</param>
/// <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
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))
{
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();
return new Queue<Location>(total_path);
}

View file

@ -1102,7 +1102,7 @@ namespace MinecraftClient
/// <summary>
/// Move to the specified location
/// </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="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>
@ -1110,21 +1110,21 @@ namespace MinecraftClient
/// <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>
/// <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)
{
if (allowDirectTeleport)
{
// 1-step path to the desired location without checking anything
UpdateLocation(location, location); // Update yaw and pitch to look at next step
handler.SendLocationUpdate(location, Movement.IsOnGround(world, location), _yaw, _pitch);
UpdateLocation(goal, goal); // Update yaw and pitch to look at next step
handler.SendLocationUpdate(goal, Movement.IsOnGround(world, goal), _yaw, _pitch);
return true;
}
else
{
// 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;
}
}