using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.Mapping
{
///
/// Allows moving through a Minecraft world
///
public static class Movement
{
/* ========= PATHFINDING METHODS ========= */
///
/// Handle movements due to gravity
///
/// World the player is currently located in
/// Location the player is currently at
/// Updated location after applying gravity
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;
}
///
/// Return a list of possible moves for the player
///
/// World the player is currently located in
/// Location the player is currently at
/// Allow possible but unsafe locations
/// A list of new locations the player can move to
public static IEnumerable GetAvailableMoves(World world, Location location, bool allowUnsafe = false)
{
List availableMoves = new List();
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;
}
///
/// Decompose a single move from a block to another into several steps
///
///
/// Allows moving by little steps instead or directly moving between blocks,
/// which would be rejected by anti-cheat plugins anyway.
///
/// Start location
/// Destination location
/// Amount of steps by block
/// A list of locations corresponding to the requested steps
public static Queue 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 movementSteps = new Queue();
for (int i = 1; i <= totalSteps; i++)
movementSteps.Enqueue(start + step * i);
return movementSteps;
}
else return new Queue(new[] { goal });
}
///
/// Calculate a path from the start location to the destination location
///
///
/// Based on the A* pathfinding algorithm described on Wikipedia
///
///
/// Start location
/// Destination location
/// Allow possible but unsafe locations
/// A list of locations, or null if calculation failed
public static Queue CalculatePath(World world, Location start, Location goal, bool allowUnsafe = false)
{
Queue result = null;
AutoTimeout.Perform(() =>
{
HashSet ClosedSet = new HashSet(); // The set of locations already evaluated.
HashSet OpenSet = new HashSet(new[] { start }); // The set of tentative nodes to be evaluated, initially containing the start node
Dictionary Came_From = new Dictionary(); // The map of navigated nodes.
Dictionary g_score = new Dictionary(); //:= 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 f_score = new Dictionary(); //:= 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, f_score[location])
: new KeyValuePair(location, int.MaxValue))
.OrderBy(pair => pair.Value).First().Key;
if (current == goal)
{ //reconstruct_path(Came_From, goal)
List total_path = new List(new[] { current });
while (Came_From.ContainsKey(current))
{
current = Came_From[current];
total_path.Add(current);
}
total_path.Reverse();
result = new Queue(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;
}
/* ========= LOCATION PROPERTIES ========= */
///
/// Check if the specified location is on the ground
///
/// World for performing check
/// Location to check
/// True if the specified location is on the ground
public static bool IsOnGround(World world, Location location)
{
return world.GetBlock(Move(location, Direction.Down)).Type.IsSolid();
}
///
/// Check if the specified location implies swimming
///
/// World for performing check
/// Location to check
/// True if the specified location implies swimming
public static bool IsSwimming(World world, Location location)
{
return world.GetBlock(location).Type.IsLiquid();
}
///
/// Check if the specified location is safe
///
/// World for performing check
/// Location to check
/// True if the destination location won't directly harm the player
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());
}
/* ========= SIMPLE MOVEMENTS ========= */
///
/// Check if the player can move in the specified direction
///
/// World the player is currently located in
/// Location the player is currently at
/// Direction the player is moving to
/// True if the player can move in the specified direction
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");
}
}
///
/// Get an updated location for moving in the specified direction
///
/// Current location
/// Direction to move to
/// Distance, in blocks
/// Updated location
public static Location Move(Location location, Direction direction, int length = 1)
{
return location + Move(direction) * length;
}
///
/// Get a location delta for moving in the specified direction
///
/// Direction to move to
/// A location delta for moving in that direction
public static Location Move(Direction direction)
{
switch (direction)
{
case Direction.Down:
return new Location(0, -1, 0);
case Direction.Up:
return new Location(0, 1, 0);
case Direction.East:
return new Location(1, 0, 0);
case Direction.West:
return new Location(-1, 0, 0);
case Direction.South:
return new Location(0, 0, 1);
case Direction.North:
return new Location(0, 0, -1);
default:
throw new ArgumentException("Unknown direction", "direction");
}
}
}
}