diff --git a/MinecraftClient/Command.cs b/MinecraftClient/Command.cs
index b97c4374..62909f62 100644
--- a/MinecraftClient/Command.cs
+++ b/MinecraftClient/Command.cs
@@ -80,7 +80,7 @@ namespace MinecraftClient
/// Argument array or empty array if no arguments
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[] { };
diff --git a/MinecraftClient/Commands/Move.cs b/MinecraftClient/Commands/Move.cs
index c7a2c61c..7fa718b0 100644
--- a/MinecraftClient/Commands/Move.cs
+++ b/MinecraftClient/Commands/Move.cs
@@ -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(); }
}
diff --git a/MinecraftClient/Mapping/Location.cs b/MinecraftClient/Mapping/Location.cs
index c5232330..d7972b43 100644
--- a/MinecraftClient/Mapping/Location.cs
+++ b/MinecraftClient/Mapping/Location.cs
@@ -47,6 +47,16 @@ namespace MinecraftClient.Mapping
Z = z;
}
+ ///
+ /// Create a new location
+ ///
+ public Location(Location loc)
+ {
+ X = loc.X;
+ Y = loc.Y;
+ Z = loc.Z;
+ }
+
///
/// Create a new location
///
@@ -67,7 +77,7 @@ namespace MinecraftClient.Mapping
/// Round coordinates
///
/// itself
- 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
///
/// itself
- public Location ToCenter()
+ public Location ConvertToCenter()
{
this.X = Math.Floor(this.X) + 0.5;
this.Z = Math.Floor(this.Z) + 0.5;
diff --git a/MinecraftClient/Mapping/Movement.cs b/MinecraftClient/Mapping/Movement.cs
index be573c9e..97af6359 100644
--- a/MinecraftClient/Mapping/Movement.cs
+++ b/MinecraftClient/Mapping/Movement.cs
@@ -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 gScoreDict = new Dictionary();
// 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
/// The collection of Locations that leads back to the start
/// Endpoint of our later walk
/// the path that leads to current from the start position
- private static Queue ReconstructPath(Dictionary Came_From, Location current)
+ private static Queue ReconstructPath(Dictionary Came_From, Location current, Location startCenter, Location end)
{
// Add 0.5 to walk over the middle of a block and avoid collisions
- List total_path = new List(new[] { current + new Location(0.5, 0, 0.5) });
+ Location center = new(0.5, 0, 0.5);
+
+ List 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(total_path);
}
diff --git a/MinecraftClient/McClient.cs b/MinecraftClient/McClient.cs
index a0bb0069..d126f61d 100644
--- a/MinecraftClient/McClient.cs
+++ b/MinecraftClient/McClient.cs
@@ -1102,7 +1102,7 @@ namespace MinecraftClient
///
/// Move to the specified location
///
- /// Location to reach
+ /// Location to reach
/// Allow possible but unsafe locations thay may hurt the player: lava, cactus...
/// Allow non-vanilla direct teleport instead of computing path, but may cause invalid moves and/or trigger anti-cheat plugins
/// If no valid path can be found, also allow locations within specified distance of destination
@@ -1110,21 +1110,21 @@ namespace MinecraftClient
/// How long to wait until the path is evaluated (default: 5 seconds)
/// When location is unreachable, computation will reach timeout, then optionally fallback to a close location within maxOffset
/// True if a path has been found
- 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;
}
}