/// <summary> /// Calculate a path from the start location to the destination location /// </summary> /// <remarks> /// Based on the A* pathfinding algorithm described on Wikipedia /// </remarks> /// <see href="https://en.wikipedia.org/wiki/A*_search_algorithm#Pseudocode"/> /// <param name="start">Start location</param> /// <param name="goal">Destination location</param> /// <param name="allowUnsafe">Allow possible but unsafe locations</param> /// <returns>A list of locations, or null if calculation failed</returns> public static Queue<Location> CalculatePath(World world, Location start, Location goal, bool allowUnsafe = false) { Queue<Location> result = null; AutoTimeout.Perform(() => { HashSet<Location> ClosedSet = new HashSet<Location>(); // The set of locations already evaluated. HashSet<Location> OpenSet = new HashSet<Location>(new[] { start }); // The set of tentative nodes to be evaluated, initially containing the start node Dictionary<Location, Location> Came_From = new Dictionary<Location, Location>(); // The map of navigated nodes. Dictionary<Location, int> g_score = new Dictionary<Location, int>(); //:= map with default value of Infinity g_score[start] = 0; // Cost from start along best known path. // Estimated total cost from start to goal through y. Dictionary<Location, int> f_score = new Dictionary<Location, int>(); //:= map with default value of Infinity f_score[start] = (int)start.DistanceSquared(goal); //heuristic_cost_estimate(start, goal) while (OpenSet.Count > 0) { Location current = //the node in OpenSet having the lowest f_score[] value OpenSet.Select(location => f_score.ContainsKey(location) ? new KeyValuePair<Location, int>(location, f_score[location]) : new KeyValuePair<Location, int>(location, int.MaxValue)) .OrderBy(pair => pair.Value).First().Key; if (current == goal) { //reconstruct_path(Came_From, goal) List<Location> total_path = new List<Location>(new[] { current }); while (Came_From.ContainsKey(current)) { current = Came_From[current]; total_path.Add(current); } total_path.Reverse(); result = new Queue<Location>(total_path); } OpenSet.Remove(current); ClosedSet.Add(current); foreach (Location neighbor in GetAvailableMoves(world, current, allowUnsafe)) { if (ClosedSet.Contains(neighbor)) continue; // Ignore the neighbor which is already evaluated. int tentative_g_score = g_score[current] + (int)current.DistanceSquared(neighbor); //dist_between(current,neighbor) // length of this path. if (!OpenSet.Contains(neighbor)) // Discover a new node OpenSet.Add(neighbor); else if (tentative_g_score >= g_score[neighbor]) continue; // This is not a better path. // This path is the best until now. Record it! Came_From[neighbor] = current; g_score[neighbor] = tentative_g_score; f_score[neighbor] = g_score[neighbor] + (int)neighbor.DistanceSquared(goal); //heuristic_cost_estimate(neighbor, goal) } } }, TimeSpan.FromSeconds(5)); return result; }
/// <summary> /// Get block at the specified location /// </summary> /// <param name="location">Location to retrieve block from</param> /// <returns>Block at specified location or Air if the location is not loaded</returns> public Block GetBlock(Location location) { ChunkColumn column = GetChunkColumn(location); if (column != null) { Chunk chunk = column.GetChunk(location); if (chunk != null) return chunk.GetBlock(location); } return new Block(Material.Air); }
public override string Run(McTcpClient handler, string command) { if (Settings.TerrainAndMovements) { string[] args = getArgs(command); if (args.Length == 1) { string dirStr = getArg(command).Trim().ToLower(); Direction direction; switch (dirStr) { case "up": direction = Direction.Up; break; case "down": direction = Direction.Down; break; case "east": direction = Direction.East; break; case "west": direction = Direction.West; break; case "north": direction = Direction.North; break; case "south": direction = Direction.South; break; case "get": return handler.GetCurrentLocation().ToString(); default: return "Unknown direction '" + dirStr + "'."; } if (Movement.CanMove(handler.GetWorld(), handler.GetCurrentLocation(), direction)) { handler.MoveTo(Movement.Move(handler.GetCurrentLocation(), direction)); return "Moving " + dirStr + '.'; } else return "Cannot move in that direction."; } else if (args.Length == 3) { 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); if (handler.MoveTo(goal)) return "Walking to " + goal; return "Failed to compute path to " + goal; } catch (FormatException) { return CMDDesc; } } else return CMDDesc; } else return "Please enable terrainandmovements in config to use this command."; }
/// <summary> /// Set block at the specified location /// </summary> /// <param name="location">Location to set block to</param> /// <param name="block">Block to set</param> public void SetBlock(Location location, Block block) { ChunkColumn column = this[location.ChunkX, location.ChunkZ]; if (column != null) { Chunk chunk = column[location.ChunkY]; if (chunk == null) column[location.ChunkY] = chunk = new Chunk(); chunk[location.ChunkBlockX, location.ChunkBlockY, location.ChunkBlockZ] = block; } }
/* ========= LOCATION PROPERTIES ========= */ /// <summary> /// Check if the specified location is on the ground /// </summary> /// <param name="world">World for performing check</param> /// <param name="location">Location to check</param> /// <returns>True if the specified location is on the ground</returns> public static bool IsOnGround(World world, Location location) { return world.GetBlock(Move(location, Direction.Down)).Type.IsSolid(); }
/// <summary> /// Get a squared distance to the specified location /// </summary> /// <param name="location">Other location for computing distance</param> /// <returns>Distance to the specified location, without using a square root</returns> public double DistanceSquared(Location location) { return ((X - location.X) * (X - location.X)) + ((Y - location.Y) * (Y - location.Y)) + ((Z - location.Z) * (Z - location.Z)); }
/* ========= SIMPLE MOVEMENTS ========= */ /// <summary> /// Check if the player can move in the specified direction /// </summary> /// <param name="world">World the player is currently located in</param> /// <param name="location">Location the player is currently at</param> /// <param name="direction">Direction the player is moving to</param> /// <returns>True if the player can move in the specified direction</returns> public static bool CanMove(World world, Location location, Direction direction) { switch (direction) { case Direction.Down: return !IsOnGround(world, location); case Direction.Up: return (IsOnGround(world, location) || IsSwimming(world, location)) && !world.GetBlock(Move(Move(location, Direction.Up), Direction.Up)).Type.IsSolid(); case Direction.East: case Direction.West: case Direction.South: case Direction.North: return !world.GetBlock(Move(location, direction)).Type.IsSolid() && !world.GetBlock(Move(Move(location, direction), Direction.Up)).Type.IsSolid(); default: throw new ArgumentException("Unknown direction", "direction"); } }
/// <summary> /// Look at the specified location /// </summary> /// <param name="location">Location to look at</param> protected void LookAtLocation(Mapping.Location location) { Handler.UpdateLocation(Handler.GetCurrentLocation(), location); }
/// <summary> /// Get exact distance to the specified location /// </summary> /// <param name="location">Other location for computing distance</param> /// <returns>Distance to the specified location, with square root so lower performances</returns> public double Distance(Location location) { return Math.Sqrt(DistanceSquared(location)); }
public bool SendLocationUpdate(Location location, bool onGround) { return false; //Currently not implemented }
/// <summary> /// Move to the specified location /// </summary> /// <param name="location">Location to reach</param> /// <param name="allowUnsafe">Allow possible but unsafe locations thay may hurt the player: lava, cactus...</param> /// <param name="allowSmallTeleport">Allow non-vanilla small teleport instead of computing path, but may cause invalid moves and/or trigger anti-cheat plugins</param> /// <returns>True if a path has been found</returns> protected bool MoveToLocation(Mapping.Location location, bool allowUnsafe = false, bool allowSmallTeleport = false) { return(Handler.MoveTo(location, allowUnsafe, allowSmallTeleport)); }
/* ========= PATHFINDING METHODS ========= */ /// <summary> /// Handle movements due to gravity /// </summary> /// <param name="world">World the player is currently located in</param> /// <param name="location">Location the player is currently at</param> /// <returns>Updated location after applying gravity</returns> public static Location HandleGravity(World world, Location location) { Location onFoots = new Location(location.X, Math.Floor(location.Y), location.Z); Location belowFoots = Move(location, Direction.Down); if (!IsOnGround(world, location) && !IsSwimming(world, location)) location = Move2Steps(location, belowFoots).Dequeue(); else if (!(world.GetBlock(onFoots).Type.IsSolid())) location = Move2Steps(location, onFoots).Dequeue(); return location; }
/// <summary> /// Get chunk column at the specified location /// </summary> /// <param name="location">Location to retrieve chunk column</param> /// <returns>The chunk column</returns> public ChunkColumn GetChunkColumn(Location location) { return this[location.ChunkX, location.ChunkZ]; }
/// <summary> /// Return a list of possible moves for the player /// </summary> /// <param name="world">World the player is currently located in</param> /// <param name="location">Location the player is currently at</param> /// <param name="allowUnsafe">Allow possible but unsafe locations</param> /// <returns>A list of new locations the player can move to</returns> public static IEnumerable<Location> GetAvailableMoves(World world, Location location, bool allowUnsafe = false) { List<Location> availableMoves = new List<Location>(); if (IsOnGround(world, location) || IsSwimming(world, location)) { foreach (Direction dir in Enum.GetValues(typeof(Direction))) if (CanMove(world, location, dir) && (allowUnsafe || IsSafe(world, Move(location, dir)))) availableMoves.Add(Move(location, dir)); } else { foreach (Direction dir in new []{ Direction.East, Direction.West, Direction.North, Direction.South }) if (CanMove(world, location, dir) && IsOnGround(world, Move(location, dir)) && (allowUnsafe || IsSafe(world, Move(location, dir)))) availableMoves.Add(Move(location, dir)); availableMoves.Add(Move(location, Direction.Down)); } return availableMoves; }
/// <summary> /// Called when the server sends a new player location, /// or if a ChatBot whishes to update the player's location. /// </summary> /// <param name="location">The new location</param> /// <param name="relative">If true, the location is relative to the current location</param> public void UpdateLocation(Location location) { UpdateLocation(location, false); }
/// <summary> /// Called when the server sends a new player location, /// or if a ChatBot whishes to update the player's location. /// </summary> /// <param name="location">The new location</param> /// <param name="relative">If true, the location is relative to the current location</param> public void UpdateLocation(Location location, bool relative) { lock (locationLock) { if (relative) { this.location += location; } else this.location = location; locationReceived = true; } }
/// <summary> /// Called ~10 times per second by the protocol handler /// </summary> public void OnUpdate() { foreach (var bot in bots.ToArray()) { try { bot.Update(); bot.ProcessQueuedText(); } catch (Exception e) { if (!(e is ThreadAbortException)) { ConsoleIO.WriteLineFormatted("§8Update: Got error from " + bot.ToString() + ": " + e.ToString()); } else throw; //ThreadAbortException should not be caught } } if (Settings.TerrainAndMovements && locationReceived) { lock (locationLock) { for (int i = 0; i < 2; i++) //Needs to run at 20 tps; MCC runs at 10 tps { if (steps != null && steps.Count > 0) location = steps.Dequeue(); else if (path != null && path.Count > 0) steps = Movement.Move2Steps(location, path.Dequeue()); else location = Movement.HandleGravity(world, location); handler.SendLocationUpdate(location, Movement.IsOnGround(world, location)); } } } }
/// <summary> /// Move to the specified location /// </summary> /// <param name="location">Location to reach</param> /// <param name="allowUnsafe">Allow possible but unsafe locations</param> /// <returns>True if a path has been found</returns> public bool MoveTo(Location location, bool allowUnsafe = false) { lock (locationLock) { if (Movement.GetAvailableMoves(world, this.location, allowUnsafe).Contains(location)) path = new Queue<Location>(new[] { location }); else path = Movement.CalculatePath(world, this.location, location, allowUnsafe); return path != null; } }
/// <summary> /// Get chunk at the specified location /// </summary> /// <param name="location">Location, a modulo will be applied</param> /// <returns>The chunk, or null if not loaded</returns> public Chunk GetChunk(Location location) { return this[location.ChunkY]; }
/// <summary> /// Check if the specified location is safe /// </summary> /// <param name="world">World for performing check</param> /// <param name="location">Location to check</param> /// <returns>True if the destination location won't directly harm the player</returns> public static bool IsSafe(World world, Location location) { return //No block that can harm the player !world.GetBlock(location).Type.CanHarmPlayers() && !world.GetBlock(Move(location, Direction.Up)).Type.CanHarmPlayers() && !world.GetBlock(Move(location, Direction.Down)).Type.CanHarmPlayers() //No fall from a too high place && (world.GetBlock(Move(location, Direction.Down)).Type.IsSolid() || world.GetBlock(Move(location, Direction.Down, 2)).Type.IsSolid() || world.GetBlock(Move(location, Direction.Down, 3)).Type.IsSolid()) //Not an underwater location && !(world.GetBlock(Move(location, Direction.Up)).Type.IsLiquid()); }
/// <summary> /// Get block at the specified location /// </summary> /// <param name="location">Location, a modulo will be applied</param> /// <returns>The block</returns> public Block GetBlock(Location location) { return this[location.ChunkBlockX, location.ChunkBlockY, location.ChunkBlockZ]; }
/// <summary> /// Check if the specified location implies swimming /// </summary> /// <param name="world">World for performing check</param> /// <param name="location">Location to check</param> /// <returns>True if the specified location implies swimming</returns> public static bool IsSwimming(World world, Location location) { return world.GetBlock(location).Type.IsLiquid(); }
/// <summary> /// Send a location update to the server /// </summary> /// <param name="location">The new location of the player</param> /// <param name="onGround">True if the player is on the ground</param> /// <returns>True if the location update was successfully sent</returns> public bool SendLocationUpdate(Location location, bool onGround) { if (Settings.TerrainAndMovements) { try { SendPacket(protocolversion >= MC19Version ? 0x0C : 0x04, concatBytes( getDouble(location.X), getDouble(location.Y), getDouble(location.Z), new byte[] { onGround ? (byte)1 : (byte)0 })); return true; } catch (SocketException) { return false; } } else return false; }
/// <summary> /// Get an updated location for moving in the specified direction /// </summary> /// <param name="location">Current location</param> /// <param name="direction">Direction to move to</param> /// <param name="length">Distance, in blocks</param> /// <returns>Updated location</returns> public static Location Move(Location location, Direction direction, int length = 1) { return location + Move(direction) * length; }
/// <summary> /// Move to the specified location /// </summary> /// <param name="location">Location to reach</param> /// <param name="allowUnsafe">Allow possible but unsafe locations</param> /// <returns>True if a path has been found</returns> protected bool MoveToLocation(Mapping.Location location, bool allowUnsafe = false) { return(Handler.MoveTo(location, allowUnsafe)); }
/// <summary> /// Decompose a single move from a block to another into several steps /// </summary> /// <remarks> /// Allows moving by little steps instead or directly moving between blocks, /// which would be rejected by anti-cheat plugins anyway. /// </remarks> /// <param name="start">Start location</param> /// <param name="goal">Destination location</param> /// <param name="stepsByBlock">Amount of steps by block</param> /// <returns>A list of locations corresponding to the requested steps</returns> public static Queue<Location> Move2Steps(Location start, Location goal, int stepsByBlock = 8) { if (stepsByBlock <= 0) stepsByBlock = 1; double totalStepsDouble = start.Distance(goal) * stepsByBlock; int totalSteps = (int)Math.Ceiling(totalStepsDouble); Location step = (goal - start) / totalSteps; if (totalStepsDouble >= 1) { Queue<Location> movementSteps = new Queue<Location>(); for (int i = 1; i <= totalSteps; i++) movementSteps.Enqueue(start + step * i); return movementSteps; } else return new Queue<Location>(new[] { goal }); }