diff --git a/MinecraftClient/Mapping/CubeFromWorld.cs b/MinecraftClient/Mapping/CubeFromWorld.cs
deleted file mode 100644
index 94ee39ee..00000000
--- a/MinecraftClient/Mapping/CubeFromWorld.cs
+++ /dev/null
@@ -1,165 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace MinecraftClient.Mapping
-{
- ///
- /// A row of blocks that will be mined
- ///
- public class Row
- {
- public readonly List BlocksInRow;
-
- ///
- /// Initialize a row of blocks
- ///
- /// Enter a list of blocks
- public Row(List blocksInRow = null)
- {
- BlocksInRow = blocksInRow ?? new List();
- }
- }
-
- ///
- /// Several rows are summarized in a layer
- ///
- public class Layer
- {
- public readonly List RowsInLayer;
-
- ///
- /// Add a new row to this layer
- ///
- /// enter a row that should be added
- /// Index of the last row
- public void AddRow(Row givenRow = null)
- {
- RowsInLayer.Add(givenRow ?? new Row());
- }
-
- ///
- /// Initialize a layer
- ///
- /// Enter a list of rows
- public Layer(List rowInLayer = null)
- {
- RowsInLayer = rowInLayer ?? new List();
- }
- }
-
- ///
- /// Several layers result in a cube
- ///
- public class Cube
- {
- public readonly List LayersInCube;
-
- ///
- /// Add a new layer to the cube
- ///
- /// Enter a layer that should be added
- /// Index of the last layer
- public void AddLayer(Layer givenLayer = null)
- {
- LayersInCube.Add(givenLayer ?? new Layer());
- }
-
- ///
- /// Initialize a cube
- ///
- /// Enter a list of layers
- public Cube(List layerInCube = null)
- {
- LayersInCube = layerInCube ?? new List();
- }
- }
-
- public static class CubeFromWorld
- {
- ///
- /// Creates a cube of blocks out of two coordinates.
- ///
- /// Start Location
- /// Stop Location
- /// A cube of blocks consisting of Layers, Rows and single blocks
- public static Cube GetBlocksAsCube(World currentWorld, Location startBlock, Location stopBlock, List materialList = null, bool isBlacklist = true)
- {
- // Initialize cube to mine.
- Cube cubeToMine = new Cube();
-
- // Get the distance between start and finish as Vector.
- Location vectorToStopPosition = stopBlock - startBlock;
-
- // Initialize Iteration process
- int[] iterateX = GetNumbersFromTo(0, Convert.ToInt32(Math.Round(vectorToStopPosition.X))).ToArray();
- int[] iterateY = GetNumbersFromTo(0, Convert.ToInt32(Math.Round(vectorToStopPosition.Y))).ToArray();
- int[] iterateZ = GetNumbersFromTo(0, Convert.ToInt32(Math.Round(vectorToStopPosition.Z))).ToArray();
-
- // Iterate through all coordinates relative to the start block.
- foreach (int y in iterateY)
- {
- Layer tempLayer = new Layer();
- foreach (int x in iterateX)
- {
- Row tempRow = new Row();
- foreach (int z in iterateZ)
- {
- if (materialList != null && materialList.Count > 0)
- {
- Location tempLocation = new Location(Math.Round(startBlock.X + x), Math.Round(startBlock.Y + y), Math.Round(startBlock.Z + z));
- Material tempLocationMaterial = currentWorld.GetBlock(tempLocation).Type;
-
- // XOR
- // If blacklist == true and it does not contain the material (false); Add it.
- // If blacklist == false (whitelist) and it contains the item (true); Add it.
- if (isBlacklist ^ materialList.Contains(tempLocationMaterial))
- {
- tempRow.BlocksInRow.Add(tempLocation);
- }
- }
- else
- {
- tempRow.BlocksInRow.Add(new Location(Math.Round(startBlock.X + x), Math.Round(startBlock.Y + y), Math.Round(startBlock.Z + z)));
- }
- }
- if (tempRow.BlocksInRow.Count > 0)
- {
- tempLayer.AddRow(tempRow);
- }
- }
- if (tempLayer.RowsInLayer.Count > 0)
- {
- cubeToMine.AddLayer(tempLayer);
- }
- }
-
- return cubeToMine;
- }
-
- ///
- /// Get all numbers between from and to.
- ///
- /// Number to start
- /// Number to stop
- /// All numbers between the start and stop number, including the stop number
- private static List GetNumbersFromTo(int start, int stop)
- {
- List tempList = new List();
- if (start <= stop)
- {
- for (int i = start; i <= stop; i++)
- {
- tempList.Add(i);
- }
- }
- else
- {
- for (int i = start; i >= stop; i--)
- {
- tempList.Add(i);
- }
- }
- return tempList;
- }
- }
-}
diff --git a/MinecraftClient/Mapping/Direction.cs b/MinecraftClient/Mapping/Direction.cs
index 5092e7ff..209d97e2 100644
--- a/MinecraftClient/Mapping/Direction.cs
+++ b/MinecraftClient/Mapping/Direction.cs
@@ -15,7 +15,13 @@ namespace MinecraftClient.Mapping
West = 1,
North = 2,
East = 3,
+
Up = 4,
- Down = 5
+ Down = 5,
+
+ NorthEast = 6,
+ SouthEast = 7,
+ SouthWest = 8,
+ NorthWest = 9,
}
}
diff --git a/MinecraftClient/Mapping/Movement.cs b/MinecraftClient/Mapping/Movement.cs
index fc66ca7a..ec63abe0 100644
--- a/MinecraftClient/Mapping/Movement.cs
+++ b/MinecraftClient/Mapping/Movement.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -61,7 +60,7 @@ namespace MinecraftClient.Mapping
}
else
{
- foreach (Direction dir in new []{ Direction.East, Direction.West, Direction.North, Direction.South })
+ 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));
@@ -164,72 +163,77 @@ namespace MinecraftClient.Mapping
/// A list of locations, or null if calculation failed
public static Queue CalculatePath(World world, Location start, Location goal, bool allowUnsafe, int maxOffset, int minOffset, CancellationToken ct)
{
-
+ // This is a bad configuration
if (minOffset > maxOffset)
throw new ArgumentException("minOffset must be lower or equal to maxOffset", "minOffset");
-
+
+ // Round start coordinates for easier calculation
+ start = new Location(Math.Floor(start.X), Math.Floor(start.Y), Math.Floor(start.Z));
+
// We always use distance squared so our limits must also be squared.
minOffset *= minOffset;
maxOffset *= maxOffset;
- Location current = new Location(); // Location that is currently processed
- Location closestGoal = new Location(); // Closest Location to the goal. Used for approaching if goal can not be reached or was not found.
- 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.
+ ///---///
+ // Prepare variables and datastructures for A*
+ ///---///
+
+ // Dictionary that contains the relation between all coordinates and resolves the final path
+ Dictionary CameFrom = new Dictionary();
+ // Create a Binary Heap for all open positions => Allows fast access to Nodes with lowest scores
+ BinaryHeap openSet = new BinaryHeap();
+ // Dictionary to keep track of the G-Score of every location
+ Dictionary gScoreDict = new Dictionary();
- 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)
+ // Set start values for variables
+ openSet.Insert(0, (int)start.DistanceSquared(goal), start);
+ gScoreDict[start] = 0;
+ BinaryHeap.Node current = null;
- while (OpenSet.Count > 0)
+ ///---///
+ // Start of A*
+ ///---///
+
+ // Execute while we have nodes to process and we are not cancelled
+ while (openSet.Count() > 0 && !ct.IsCancellationRequested)
{
- 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).
- // Sort for h-score (f-score - g-score) to get smallest distance to goal if f-scores are equal
- ThenBy(pair => f_score[pair.Key]-g_score[pair.Key]).First().Key;
-
- // Only assert a value if it is of actual use later
- if (maxOffset > 0 && ClosedSet.Count > 0)
- // Get the block that currently is closest to the goal
- closestGoal = ClosedSet.OrderBy(checkedLocation => checkedLocation.DistanceSquared(goal)).First();
+ // Get the root node of the Binary Heap
+ // Node with the lowest F-Score or lowest H-Score on tie
+ current = openSet.GetRootLocation();
- // Stop when goal is reached or we are close enough
- if (current == goal || (minOffset > 0 && current.DistanceSquared(goal) <= minOffset))
- return ReconstructPath(Came_From, current);
- else if (ct.IsCancellationRequested)
- break; // Return if we are cancelled
-
- OpenSet.Remove(current);
- ClosedSet.Add(current);
-
- foreach (Location neighbor in GetAvailableMoves(world, current, allowUnsafe))
+ // 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 (ct.IsCancellationRequested)
- break; // Stop searching for blocks if we are cancelled.
- 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.
+ return ReconstructPath(CameFrom, current.Location);
+ }
- // 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)
+ // Discover neighbored blocks
+ foreach (Location neighbor in GetAvailableMoves(world, current.Location, allowUnsafe))
+ {
+ // If we are cancelled: break
+ if (ct.IsCancellationRequested)
+ break;
+
+ // tentative_gScore is the distance from start to the neighbor through current
+ int tentativeGScore = current.G_score + (int)current.Location.DistanceSquared(neighbor);
+
+ // If the neighbor is not in the gScoreDict OR its current tentativeGScore is lower than the previously saved one:
+ if (!gScoreDict.ContainsKey(neighbor) || (gScoreDict.ContainsKey(neighbor) && tentativeGScore < gScoreDict[neighbor]))
+ {
+ // Save the new relation between the neighbored block and the current one
+ CameFrom[neighbor] = current.Location;
+ gScoreDict[neighbor] = tentativeGScore;
+
+ // 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);
+ }
}
}
- // Goal could not be reached. Set the path to the closest location if close enough
- if (maxOffset == int.MaxValue || goal.DistanceSquared(closestGoal) <= maxOffset)
- return ReconstructPath(Came_From, closestGoal);
+ //// 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);
else
return null;
}
@@ -242,16 +246,196 @@ namespace MinecraftClient.Mapping
/// the path that leads to current from the start position
private static Queue ReconstructPath(Dictionary Came_From, Location current)
{
- List total_path = new List(new[] { current });
+ // 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) });
while (Came_From.ContainsKey(current))
{
current = Came_From[current];
- total_path.Add(current);
+ total_path.Add(current + new Location(0.5, 0, 0.5));
}
total_path.Reverse();
return new Queue(total_path);
}
+ ///
+ /// A datastructure to store Locations as Nodes and provide them in sorted and queued order.
+ /// !!!
+ /// CAN BE REPLACED WITH PriorityQueue IN .NET-6
+ /// https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.priorityqueue-2?view=net-6.0
+ /// !!!
+ ///
+ public class BinaryHeap
+ {
+ ///
+ /// Represents a location and its attributes
+ ///
+ public class Node
+ {
+ // Distance to start
+ public int G_score;
+ // Distance to Goal
+ public int H_score;
+ public int F_score { get { return H_score + G_score; } }
+
+ public Location Location;
+
+ public Node(int g_score, int h_score, Location loc)
+ {
+ this.G_score = g_score;
+ this.H_score = h_score;
+ Location = loc;
+ }
+ }
+
+ // List which contains all nodes in form of a Binary Heap
+ private List heapList;
+ // Hashset for quick checks of locations included in the heap
+ private HashSet locationList;
+ public Node MinH_ScoreNode;
+
+ public BinaryHeap()
+ {
+ heapList = new List();
+ locationList = new HashSet();
+ MinH_ScoreNode = null;
+ }
+
+ ///
+ /// Insert a new location in the heap
+ ///
+ /// G-Score of the location
+ /// H-Score of the location
+ /// The location
+ public void Insert(int newG_Score, int newH_Score, Location loc)
+ {
+ // Begin at the end of the list
+ int i = heapList.Count;
+
+ // Temporarily save the node created with the parameters to allow comparisons
+ Node newNode = new Node(newG_Score, newH_Score, loc);
+
+ // Add new note to the end of the list
+ heapList.Add(newNode);
+ locationList.Add(loc);
+
+ // Save node with the smallest H-Score => Distance to goal
+ if (MinH_ScoreNode == null || newNode.H_score < MinH_ScoreNode.H_score)
+ MinH_ScoreNode = newNode;
+
+ // There is no need of sorting for one node.
+ if (i > 0)
+ {
+ /// Go up the heap from child to parent and move parent down...
+ // while we are not looking at the root node AND the new node has better attributes than the parent node ((i - 1) / 2)
+ while (i > 0 && FirstNodeBetter(newNode /* Current Child */, heapList[(i - 1) / 2] /* Coresponding Parent */))
+ {
+ // Move parent down and replace current child -> New free space is created
+ heapList[i] = heapList[(i - 1) / 2];
+ // Select the next parent to check
+ i = (i - 1) / 2;
+ }
+
+ /// Nodes were moved down at position I there is now a free space at the correct position for our new node:
+ // Insert new node in position
+ heapList[i] = newNode;
+ }
+ }
+
+ ///
+ /// Obtain the root which represents the node the the best attributes currently
+ ///
+ /// node with the best attributes currently
+ ///
+ public Node GetRootLocation()
+ {
+ // The heap is empty. There is nothing to return.
+ if (heapList.Count == 0)
+ {
+ throw new InvalidOperationException("The heap is empty.");
+ }
+
+ // Save the root node
+ Node rootNode = heapList[0];
+ locationList.Remove(rootNode.Location);
+
+ // Temporarirly store the last item's value.
+ Node lastNode = heapList[heapList.Count - 1];
+
+ // Remove the last value.
+ heapList.RemoveAt(heapList.Count - 1);
+
+ if (heapList.Count > 0)
+ {
+ // Start at the first index.
+ int currentParentPos = 0;
+
+ /// Go through the heap from root to bottom...
+ // Continue until the halfway point of the heap.
+ while (currentParentPos < heapList.Count / 2)
+ {
+ // Select the left child of the current parent
+ int currentChildPos = (2 * currentParentPos) + 1;
+
+ // If the currently selected child is not the last entry of the list AND right child has better attributes
+ if ((currentChildPos < heapList.Count - 1) && FirstNodeBetter(heapList[currentChildPos + 1], heapList[currentChildPos]))
+ {
+ // Select the right child
+ currentChildPos++;
+ }
+
+ // If the last item is smaller than both siblings at the
+ // current height, break.
+ if (FirstNodeBetter(lastNode, heapList[currentChildPos]))
+ {
+ break;
+ }
+
+ // Move the item at index j up one level.
+ heapList[currentParentPos] = heapList[currentChildPos];
+ // Move index i to the appropriate branch.
+ currentParentPos = currentChildPos;
+ }
+ // Insert the last node into the currently free position
+ heapList[currentParentPos] = lastNode;
+ }
+
+ return rootNode;
+ }
+
+ ///
+ /// Compares two nodes and evaluates their position to the goal.
+ ///
+ /// First node to compare
+ /// Second node to compare
+ /// True if the first node has a more promissing position to the goal than the second
+ private static bool FirstNodeBetter(Node firstNode, Node secondNode)
+ {
+ // Is the F_score smaller?
+ return (firstNode.F_score < secondNode.F_score) ||
+ // If F_score is equal, evaluate the h-score
+ (firstNode.F_score == secondNode.F_score && firstNode.H_score < secondNode.H_score);
+ }
+
+ ///
+ /// Get the size of the heap
+ ///
+ /// size of the heap
+ public int Count()
+ {
+ return heapList.Count;
+ }
+
+ ///
+ /// Check if the heap contains a node with a certain location
+ ///
+ /// Location to check
+ /// true if a node with the given location is in the heap
+ public bool ContainsLocation(Location loc)
+ {
+ return locationList.Contains(loc);
+ }
+ }
+
/* ========= LOCATION PROPERTIES ========= */
///
@@ -313,22 +497,47 @@ namespace MinecraftClient.Mapping
{
switch (direction)
{
+ // Move vertical
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();
+
+ // Move horizontal
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();
+ return PlayerFitsHere(world, Move(location, direction));
+
+ // Move diagonal
+ case Direction.NorthEast:
+ return PlayerFitsHere(world, Move(location, Direction.North)) && PlayerFitsHere(world, Move(location, Direction.East)) && PlayerFitsHere(world, Move(location, direction));
+ case Direction.SouthEast:
+ return PlayerFitsHere(world, Move(location, Direction.South)) && PlayerFitsHere(world, Move(location, Direction.East)) && PlayerFitsHere(world, Move(location, direction));
+ case Direction.SouthWest:
+ return PlayerFitsHere(world, Move(location, Direction.South)) && PlayerFitsHere(world, Move(location, Direction.West)) && PlayerFitsHere(world, Move(location, direction));
+ case Direction.NorthWest:
+ return PlayerFitsHere(world, Move(location, Direction.North)) && PlayerFitsHere(world, Move(location, Direction.West)) && PlayerFitsHere(world, Move(location, direction));
+
default:
throw new ArgumentException("Unknown direction", "direction");
}
}
+ ///
+ /// Evaluates if a player fits in this location
+ ///
+ /// Current world
+ /// Location to check
+ /// True if a player is able to stand in this location
+ public static bool PlayerFitsHere(World world, Location location)
+ {
+ return !world.GetBlock(location).Type.IsSolid()
+ && !world.GetBlock(Move(location, Direction.Up)).Type.IsSolid();
+ }
+
///
/// Get an updated location for moving in the specified direction
///
@@ -350,10 +559,13 @@ namespace MinecraftClient.Mapping
{
switch (direction)
{
+ // Move vertical
case Direction.Down:
return new Location(0, -1, 0);
case Direction.Up:
return new Location(0, 1, 0);
+
+ // Move horizontal straight
case Direction.East:
return new Location(1, 0, 0);
case Direction.West:
@@ -362,6 +574,17 @@ namespace MinecraftClient.Mapping
return new Location(0, 0, 1);
case Direction.North:
return new Location(0, 0, -1);
+
+ // Move horizontal diagonal
+ case Direction.NorthEast:
+ return Move(Direction.North) + Move(Direction.East);
+ case Direction.SouthEast:
+ return Move(Direction.South) + Move(Direction.East);
+ case Direction.SouthWest:
+ return Move(Direction.South) + Move(Direction.West);
+ case Direction.NorthWest:
+ return Move(Direction.North) + Move(Direction.West);
+
default:
throw new ArgumentException("Unknown direction", "direction");
}
diff --git a/MinecraftClient/McClient.cs b/MinecraftClient/McClient.cs
index 1aa68340..82276f25 100644
--- a/MinecraftClient/McClient.cs
+++ b/MinecraftClient/McClient.cs
@@ -60,6 +60,8 @@ namespace MinecraftClient
private float playerYaw;
private float playerPitch;
private double motionY;
+ public enum MovementType { Sneak, Walk, Sprint}
+ public int currentMovementSpeed = 4;
private int sequenceId; // User for player block synchronization (Aka. digging, placing blocks, etc..)
private string host;
@@ -344,7 +346,7 @@ namespace MinecraftClient
{
lock (locationLock)
{
- for (int i = 0; i < 2; i++) //Needs to run at 20 tps; MCC runs at 10 tps
+ for (int i = 0; i < currentMovementSpeed; i++) //Needs to run at 20 tps; MCC runs at 10 tps
{
if (_yaw == null || _pitch == null)
{
@@ -1125,9 +1127,7 @@ namespace MinecraftClient
else
{
// Calculate path through pathfinding. Path contains a list of 1-block movement that will be divided into steps
- if (Movement.GetAvailableMoves(world, this.location, allowUnsafe).Contains(location))
- path = new Queue(new[] { location });
- else path = Movement.CalculatePath(world, this.location, location, allowUnsafe, maxOffset, minOffset, timeout ?? TimeSpan.FromSeconds(5));
+ path = Movement.CalculatePath(world, this.location, location, allowUnsafe, maxOffset, minOffset, timeout ?? TimeSpan.FromSeconds(5));
return path != null;
}
}
@@ -1905,6 +1905,49 @@ namespace MinecraftClient
return terrainAndMovementsEnabled && locationReceived && ((steps != null && steps.Count > 0) || (path != null && path.Count > 0));
}
+ ///
+ /// Get the current goal
+ ///
+ /// Current goal of movement. Location.Zero if not set.
+ public Location GetCurrentMovementGoal()
+ {
+ return ClientIsMoving() ? Location.Zero : path.Last();
+ }
+
+ ///
+ /// Cancels the current movement
+ ///
+ /// True if there was an active path
+ public bool CancelMovement()
+ {
+ bool success = ClientIsMoving();
+ path = null;
+ return success;
+ }
+
+ ///
+ /// Change the amount of sent movement packets per time
+ ///
+ /// Set a new walking type
+ public void SetMovementSpeed(MovementType newSpeed)
+ {
+ switch (newSpeed)
+ {
+ case MovementType.Sneak:
+ // https://minecraft.fandom.com/wiki/Sneaking#Effects - Sneaking 1.31m/s
+ currentMovementSpeed = 2;
+ break;
+ case MovementType.Walk:
+ // https://minecraft.fandom.com/wiki/Walking#Usage - Walking 4.317 m/s
+ currentMovementSpeed = 4;
+ break;
+ case MovementType.Sprint:
+ // https://minecraft.fandom.com/wiki/Sprinting#Usage - Sprinting 5.612 m/s
+ currentMovementSpeed = 5;
+ break;
+ }
+ }
+
///
/// Called when the server sends a new player location,
/// or if a ChatBot whishes to update the player's location.
diff --git a/MinecraftClient/config/ChatBots/MineCube.cs b/MinecraftClient/config/ChatBots/MineCube.cs
index fffb56ec..f2099bdf 100644
--- a/MinecraftClient/config/ChatBots/MineCube.cs
+++ b/MinecraftClient/config/ChatBots/MineCube.cs
@@ -4,312 +4,490 @@ MCC.LoadBot(new MineCube());
//MCCScript Extensions
+//using System.Threading.Tasks;
+
class MineCube : ChatBot
{
- public override void Initialize()
- {
- if (!GetTerrainEnabled())
- {
- LogToConsole(Translations.Get("extra.terrainandmovement_required"));
- UnloadBot();
- return;
- }
- RegisterChatBotCommand("mine", "Mine a cube from a to b", "/mine x y z OR /mine x1 y1 z1 x2 y2 z2", EvaluateMineCommand);
- RegisterChatBotCommand("mineup", "Walk over a flat cubic platform of blocks and mine everything above you", "/mine x1 y1 z1 x2 y2 z2 (y1 = y2)", EvaluateMineCommand);
- LogToConsole("Mining bot created by Daenges.");
- }
+ private CancellationTokenSource cts;
+ private Task currentMiningTask;
+ private TimeSpan breakTimeout;
+ private bool toolHandling;
+ private int cacheSize;
- ///
- /// Dig out a 2 Block high cube and let the bot walk through it
- /// mining all blocks above it that it can reach.
- ///
- /// Area that the bot should walk through. (The lower Y coordinate of the 2 high cube.)
- public void MineUp(Cube walkingArea)
- {
- foreach (Layer lay in walkingArea.LayersInCube)
- {
- foreach (Row r in lay.RowsInLayer)
- {
- foreach (Location loc in r.BlocksInRow)
- {
- Location currentLoc = GetCurrentLocation();
+ public override void Initialize()
+ {
+ if (!GetTerrainEnabled())
+ {
+ LogToConsole(Translations.Get("extra.terrainandmovement_required"));
+ UnloadBot();
+ return;
+ }
- if (MoveToLocation(new Location(loc.X, loc.Y, loc.Z)))
- {
- while (Math.Round(GetCurrentLocation().Distance(loc)) > 1)
- {
- Thread.Sleep(200);
- }
- }
- else
- {
- // This block is not reachable for some reason.
- // Keep on going with the next collumn.
- LogDebugToConsole("Unable to walk to: " + loc.X.ToString() + " " + (loc.Y).ToString() + " " + loc.Z.ToString());
- continue;
- }
+ currentMiningTask = null;
+ breakTimeout = TimeSpan.FromSeconds(15);
+ cacheSize = 10;
+ toolHandling = true;
- for (int height = Convert.ToInt32(Math.Round(currentLoc.Y)) + 2; height < Convert.ToInt32(Math.Round(currentLoc.Y)) + 7; height++)
- {
- Location mineLocation = new Location(loc.X, height, loc.Z);
- Material mineLocationMaterial = GetWorld().GetBlock(mineLocation).Type;
+ RegisterChatBotCommand("mine", "Mine a cube from a to b", "/mine x y z OR /mine x1 y1 z1 x2 y2 z2", EvaluateMineCommand);
+ RegisterChatBotCommand("mineup", "Walk over a flat cubic platform of blocks and mine everything above you", "/mine x1 y1 z1 x2 y2 z2 (y1 = y2)", EvaluateMineCommand);
+ LogToConsole("Mining bot created by Daenges.");
+ }
- // Stop mining process if breaking the next block could endager the bot
- // through falling blocks or liquids.
- if (IsGravityBlockAbove(mineLocation) || IsSorroundedByLiquid(mineLocation)) { break; }
- // Skip this block if it can not be mined.
- if (Material2Tool.IsUnbreakable(mineLocationMaterial)) { continue; }
+ ///
+ /// Walks in a 2 high area under an area of blocks and mines anything above its head.
+ ///
+ /// The current world
+ /// The start corner of walking
+ /// The stop corner of walking
+ /// CancellationToken to stop the task on cancel
+ public void MineUp(World currentWorld, Location startBlock, Location stopBlock, CancellationToken ct)
+ {
+ if (startBlock.Y != stopBlock.Y)
+ {
+ LogToConsole("Command FAILED. Both coordinates must be on the same y level.");
+ }
- //DateTime start = DateTime.Now;
- // Search this tool in hotbar and select the correct slot
- SelectCorrectSlotInHotbar(
- // Returns the correct tool for this type
- Material2Tool.GetCorrectToolForBlock(
- // returns the type of the current block
- mineLocationMaterial));
+ IEnumerable xLocationRange = GetNumbersFromTo(Convert.ToInt32(Math.Round(startBlock.X)), Convert.ToInt32(Math.Round(stopBlock.X)));
+ IEnumerable zLocationRange = GetNumbersFromTo(Convert.ToInt32(Math.Round(startBlock.Z)), Convert.ToInt32(Math.Round(stopBlock.Z)));
- // Unable to check when breaking is over.
- if (DigBlock(mineLocation))
- {
- short i = 0; // Maximum wait time of 10 sec.
- while (GetWorld().GetBlock(mineLocation).Type != Material.Air && i <= 100)
- {
- Thread.Sleep(100);
- i++;
- }
- }
- else
- {
- LogDebugToConsole("Unable to break this block: " + mineLocation.ToString());
- }
- }
- }
- }
- }
- LogToConsole("Finished mining up.");
- }
+ foreach (int currentXLoc in xLocationRange)
+ {
+ foreach (int currentZLoc in zLocationRange)
+ {
+ Location standLocation = new Location(currentXLoc, startBlock.Y, currentZLoc);
- ///
- /// Mines out a cube of blocks from top to bottom.
- ///
- /// The cube that should be mined.
- public void Mine(Cube cubeToMine)
- {
- foreach (Layer lay in cubeToMine.LayersInCube)
- {
- foreach (Row r in lay.RowsInLayer)
- {
- foreach (Location loc in r.BlocksInRow)
- {
- Material locMaterial = GetWorld().GetBlock(loc).Type;
- if (!Material2Tool.IsUnbreakable(locMaterial) && !IsSorroundedByLiquid(loc))
- {
- if (GetHeadLocation(GetCurrentLocation()).Distance(loc) > 5)
- {
- // Unable to detect when walking is over and goal is reached.
- if (MoveToLocation(new Location(loc.X, loc.Y + 1, loc.Z)))
- {
- while (GetCurrentLocation().Distance(loc) > 2)
- {
- Thread.Sleep(200);
- }
+ // Walk to the new location.
+ waitForMoveToLocation(standLocation, maxOffset: 1);
- }
- else
- {
- LogDebugToConsole("Unable to walk to: " + loc.X.ToString() + " " + (loc.Y + 1).ToString() + " " + loc.Z.ToString());
- }
- }
+ for (int height = Convert.ToInt32(startBlock.Y) + 2; height < Convert.ToInt32(startBlock.Y) + 7; height++)
+ {
+ if (ct.IsCancellationRequested)
+ {
+ currentMiningTask = null;
+ LogToConsole("Cancellation requested. STOP MINING.");
+ return;
+ }
- //DateTime start = DateTime.Now;
- // Search this tool in hotbar and select the correct slot
- SelectCorrectSlotInHotbar(
- // Returns the correct tool for this type
- Material2Tool.GetCorrectToolForBlock(
- // returns the type of the current block
- GetWorld().GetBlock(loc).Type));
+ Location mineLocation = new Location(currentXLoc, height, currentZLoc);
+ Material mineLocationMaterial = currentWorld.GetBlock(mineLocation).Type;
- // Unable to check when breaking is over.
- if (DigBlock(loc))
- {
- short i = 0; // Maximum wait time of 10 sec.
- while (GetWorld().GetBlock(loc).Type != Material.Air && i <= 100)
- {
- Thread.Sleep(100);
- i++;
- }
- }
- else
- {
- LogDebugToConsole("Unable to break this block: " + loc.ToString());
- }
- }
- }
- }
- }
- LogToConsole("Mining finished.");
- }
+ // Stop mining process if breaking the next block could endager the bot
+ // through falling blocks or liquids.
+ if (!IsGravitySave(currentWorld, mineLocation) || IsSorroundedByLiquid(currentWorld, mineLocation)) { break; }
+ // Skip this block if it can not be mined.
+ if (Material2Tool.IsUnbreakable(mineLocationMaterial)) { continue; }
- public Func GetHeadLocation = locFeet => new Location(locFeet.X, locFeet.Y + 1, locFeet.Z);
+ if (Settings.InventoryHandling && toolHandling)
+ {
+ // Search this tool in hotbar and select the correct slot
+ SelectCorrectSlotInHotbar(
+ // Returns the correct tool for this type
+ Material2Tool.GetCorrectToolForBlock(
+ // returns the type of the current block
+ mineLocationMaterial));
+ }
- private void SelectCorrectSlotInHotbar(ItemType[] tools)
- {
- if (GetInventoryEnabled())
- {
- foreach (ItemType tool in tools)
- {
- int[] tempArray = GetPlayerInventory().SearchItem(tool);
- // Check whether an item could be found and make sure that it is in
- // a hotbar slot (36-44).
- if (tempArray.Length > 0 && tempArray[0] > 35)
- {
- // Changeslot takes numbers from 0-8
- ChangeSlot(Convert.ToInt16(tempArray[0] - 36));
- break;
- }
- }
- }
- else
- {
- LogToConsole("Activate Inventory Handling.");
- }
- }
+ // If we are able to reach the block && break sucessfully sent
+ if (GetCurrentLocation().EyesLocation().DistanceSquared(mineLocation) <= 25 && DigBlock(mineLocation))
+ {
+ AutoTimeout.Perform(() =>
+ {
+ while (GetWorld().GetBlock(mineLocation).Type != Material.Air)
+ {
+ Thread.Sleep(100);
- public bool IsGravityBlockAbove(Location block)
- {
- World world = GetWorld();
- double blockX = Math.Round(block.X);
- double blockY = Math.Round(block.Y);
- double blockZ = Math.Round(block.Z);
+ if (ct.IsCancellationRequested)
+ break;
+ }
+ }, breakTimeout);
+ }
+ else
+ {
+ LogDebugToConsole("Unable to break this block: " + mineLocation.ToString());
+ }
+ }
+ }
+ }
+ LogToConsole("Finished mining up.");
+ }
- List gravityBlockList = new List(new Material[] { Material.Gravel, Material.Sand, Material.RedSand, Material.Scaffolding, Material.Anvil, });
+ ///
+ /// Mine a cube of blocks from top to bottom between start and stop location
+ ///
+ /// The current world
+ /// The upper corner of the cube to mine
+ /// The lower corner of the cube to mine
+ /// CancellationToken to stop the task on cancel
+ public void Mine(World currentWorld, Location startBlock, Location stopBlock, CancellationToken ct)
+ {
+ // Turn the cube around, so the bot always starts from the top.
+ if (stopBlock.Y > startBlock.Y)
+ {
+ Location temp = stopBlock;
+ stopBlock = startBlock;
+ startBlock = temp;
+ }
+ IEnumerable xLocationRange = GetNumbersFromTo(Convert.ToInt32(Math.Round(startBlock.X)), Convert.ToInt32(Math.Round(stopBlock.X)));
+ IEnumerable yLocationRange = GetNumbersFromTo(Convert.ToInt32(Math.Round(startBlock.Y)), Convert.ToInt32(Math.Round(stopBlock.Y)));
+ IEnumerable zLocationRange = GetNumbersFromTo(Convert.ToInt32(Math.Round(startBlock.Z)), Convert.ToInt32(Math.Round(stopBlock.Z)));
- var temptype = world.GetBlock(new Location(blockX, blockY + 1, blockZ)).Type;
- var tempLoc = gravityBlockList.Contains(world.GetBlock(new Location(blockX, blockY + 1, blockZ)).Type);
+ foreach (int currentYLoc in yLocationRange)
+ {
+ foreach (int currentXLoc in xLocationRange)
+ {
- return
- // Block can not fall down on player e.g. Sand, Gravel etc.
- gravityBlockList.Contains(world.GetBlock(new Location(blockX, blockY + 1, blockZ)).Type);
- }
+ if (ct.IsCancellationRequested)
+ {
+ currentMiningTask = null;
+ LogToConsole("Cancellation requested. STOP MINING.");
+ return;
+ }
- public bool IsSorroundedByLiquid(Location block)
- {
- World world = GetWorld();
- double blockX = Math.Round(block.X);
- double blockY = Math.Round(block.Y);
- double blockZ = Math.Round(block.Z);
+ List blocksToMine = null;
- List liquidBlockList = new List(new Material[] { Material.Water, Material.Lava, });
+ // If the end of the new row is closer than the start, reverse the line and start here
+ Location currentStandingLoc = GetCurrentLocation();
+ Queue currentZLocationRangeQueue = new Queue(currentStandingLoc.DistanceSquared(new Location(currentXLoc, currentYLoc, zLocationRange.Last())) < currentStandingLoc.DistanceSquared(new Location(currentXLoc, currentYLoc, zLocationRange.First())) ?
+ zLocationRange.Reverse() :
+ zLocationRange);
- return // Liquid can not flow down the hole. Liquid is unable to flow diagonally.
- liquidBlockList.Contains(world.GetBlock(new Location(blockX, blockY + 1, blockZ)).Type) ||
- liquidBlockList.Contains(world.GetBlock(new Location(blockX - 1, blockY, blockZ)).Type) ||
- liquidBlockList.Contains(world.GetBlock(new Location(blockX + 1, blockY, blockZ)).Type) ||
- liquidBlockList.Contains(world.GetBlock(new Location(blockX, blockY, blockZ - 1)).Type) ||
- liquidBlockList.Contains(world.GetBlock(new Location(blockX, blockY, blockZ + 1)).Type);
- }
+ while (!ct.IsCancellationRequested && (currentZLocationRangeQueue.Count > 0 || blocksToMine.Count > 0))
+ {
+ // Evaluate the next blocks to mine, while mining
+ Task> cacheEval = Task>.Factory.StartNew(() => // Get a new chunk of blocks that can be mined
+ EvaluateBlocks(currentWorld, currentXLoc, currentYLoc, currentZLocationRangeQueue, ct, cacheSize));
- ///
- private string getHelpPage()
- {
- return
- "Usage of the mine bot:\n" +
- "/mine OR /mine \n" +
- "to excavate a cube of blocks from top to bottom. (2 high area above the cube must be dug free by hand.)\n" +
- "/mineup OR /mineup \n" +
- "to walk over a quadratic field of blocks and simultaniously mine everything above the head. \n" +
- "(Mines up to 5 Blocks, stops if gravel or lava would fall. 2 High area below this must be dug fee by hand.)\n";
- }
+ // On the first run, we need the task to finish, otherwise we would not have any results
+ if (blocksToMine != null)
+ {
+ // For all blocks in this block chunk
+ foreach (Location mineLocation in blocksToMine)
+ {
+ if (ct.IsCancellationRequested)
+ break;
- ///
- /// Evaluates the given command
- ///
- ///
- ///
- ///
- private string EvaluateMineCommand(string command, string[] args)
- {
- if (args.Length > 2)
- {
- Location startBlock;
- Location stopBlock;
+ Location currentLoc = GetCurrentLocation();
+ Location currentBlockUnderFeet = new Location(Math.Floor(currentLoc.X), Math.Floor(currentLoc.Y) - 1, Math.Floor(currentLoc.Z));
- if (args.Length > 5)
- {
- try
- {
- startBlock = new Location(
- double.Parse(args[0]),
- double.Parse(args[1]),
- double.Parse(args[2])
- );
+ // If we are too far away from the mining location
+ if (currentLoc.EyesLocation().DistanceSquared(mineLocation) > 25)
+ {
+ // Walk to the new location
+ waitForMoveToLocation(mineLocation, maxOffset:3);
+ }
- stopBlock = new Location(
- double.Parse(args[3]),
- double.Parse(args[4]),
- double.Parse(args[5])
- );
+ // Prevent falling into danger
+ if (mineLocation == currentBlockUnderFeet && !Movement.IsSafe(currentWorld, currentBlockUnderFeet))
+ waitForMoveToLocation(mineLocation, maxOffset: 4, minOffset:3);
- }
- catch (Exception e)
- {
- LogDebugToConsole(e.ToString());
- return "Please enter correct coordinates as numbers.\n" + getHelpPage();
- }
- }
- else
- {
- Location tempLoc = GetCurrentLocation();
- startBlock = new Location(Math.Round(tempLoc.X),
- Math.Round(tempLoc.Y),
- Math.Round(tempLoc.Z));
+ // Is inventoryhandling activated?
+ if (Settings.InventoryHandling && toolHandling)
+ {
+ // Search this tool in hotbar and select the correct slot
+ SelectCorrectSlotInHotbar(
+ // Returns the correct tool for this type
+ Material2Tool.GetCorrectToolForBlock(
+ // returns the type of the current block
+ currentWorld.GetBlock(mineLocation).Type));
+ }
- try
- {
- stopBlock = new Location(
- double.Parse(args[0]),
- double.Parse(args[1]),
- double.Parse(args[2])
- );
- }
- catch (Exception e)
- {
- LogDebugToConsole(e.ToString());
- return "Please enter correct coordinates as numbers.\n" + getHelpPage();
- }
- }
+ // If we are able to reach the block && break sucessfully sent
+ if (GetCurrentLocation().EyesLocation().DistanceSquared(mineLocation) <= 25 && DigBlock(mineLocation))
+ {
+ // Wait until the block is broken (== Air)
+ AutoTimeout.Perform(() =>
+ {
+ while (GetWorld().GetBlock(mineLocation).Type != Material.Air)
+ {
+ Thread.Sleep(100);
- if (command.Contains("mineup"))
- {
- if (Math.Round(startBlock.Y) != Math.Round(stopBlock.Y))
- {
- return "Both blocks must have the same Y value!\n" + getHelpPage();
- }
+ if (ct.IsCancellationRequested)
+ break;
+ }
+ }, breakTimeout);
+ }
+ else
+ {
+ LogDebugToConsole("Unable to break this block: " + mineLocation.ToString());
+ }
- List materialWhitelist = new List() { Material.Air };
- Thread tempThread = new Thread(() => MineUp(CubeFromWorld.GetBlocksAsCube(GetWorld(), startBlock, stopBlock, materialWhitelist, isBlacklist: false)));
- tempThread.Start();
- return "Start mining up.";
- }
- else
- {
- // Turn the cube around, so the bot always starts from the top.
- if (stopBlock.Y > startBlock.Y)
- {
- Location temp = stopBlock;
- stopBlock = startBlock;
- startBlock = temp;
- }
+ }
+ }
- List blacklistedMaterials = new List() { Material.Air, Material.Water, Material.Lava };
- Thread tempThread = new Thread(() => Mine(CubeFromWorld.GetBlocksAsCube(GetWorld(), startBlock, stopBlock, blacklistedMaterials)));
- tempThread.Start();
+ if (!ct.IsCancellationRequested)
+ {
+ // Wait for the block evaluation task to finish (if not already) and save the result
+ if (!cacheEval.IsCompleted)
+ {
+ cacheEval.Wait();
+ }
+ blocksToMine = cacheEval.Result;
+ }
+ }
+ }
+ }
+ currentMiningTask = null;
+ LogToConsole("MINING FINISHED.");
+ }
- return "Start mining cube.";
- }
- }
+ ///
+ /// This function selects a certain amount of minable blocks in a row
+ ///
+ /// The current world
+ /// The current x location of the row
+ /// The current y location of the row
+ /// All Z blocks that will be mined
+ /// CancellationToken to stop the task on cancel
+ /// Maximum amount of blocks to return
+ ///
+ private List EvaluateBlocks(World currentWorld, int xLoc, int yLoc, Queue zLocationQueue, CancellationToken ct, int cacheSize = 10)
+ {
+ List blockMiningCache = new List();
+ int i = 0;
+ while (zLocationQueue.Count > 0 && i < cacheSize && !ct.IsCancellationRequested)
+ {
+ // Get the block to mine, relative to the startblock of the row
+ Location mineLocation = new Location(xLoc, yLoc, zLocationQueue.Dequeue());
- return "Invalid command syntax.\n" + getHelpPage();
- }
+ // Add the current location to the mining cache if it is safe to mine
+ if (currentWorld.GetBlock(mineLocation).Type != Material.Air &&
+ IsGravitySave(currentWorld, mineLocation) &&
+ !IsSorroundedByLiquid(currentWorld, mineLocation) &&
+ !Material2Tool.IsUnbreakable(currentWorld.GetBlock(mineLocation).Type))
+ {
+ blockMiningCache.Add(mineLocation);
+ i++;
+ }
+ }
+ return blockMiningCache;
+ }
+
+ ///
+ /// Generates a sequence of numbers between a start and a stop number, including both
+ ///
+ /// Number to start from
+ /// Number to end with
+ /// a sequence of numbers between a start and a stop number, including both
+ private static IEnumerable GetNumbersFromTo(int start, int stop)
+ {
+ return start <= stop ? Enumerable.Range(start, stop - start + 1) : Enumerable.Range(stop, start - stop + 1).Reverse();
+ }
+
+ ///
+ /// Starts walk and waits until the client arrives
+ ///
+ /// 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
+ /// Do not get closer of destination than specified distance
+ /// How long to wait before stopping computation (default: 5 seconds)
+ private void waitForMoveToLocation(Location goal, bool allowUnsafe = false, bool allowDirectTeleport = false, int maxOffset = 0, int minOffset = 0, TimeSpan? timeout = null)
+ {
+ if (MoveToLocation(goal, allowUnsafe, allowDirectTeleport, maxOffset, minOffset, timeout))
+ {
+ // Wait till the client stops moving
+ while (ClientIsMoving())
+ {
+ Thread.Sleep(200);
+ }
+ }
+ else
+ {
+ LogDebugToConsole("Unable to walk to: " + goal.ToString());
+ }
+ }
+
+ ///
+ /// Checks all slots of the hotbar for an Item and selects it if found
+ ///
+ /// List of items that may be selected, from worst to best
+ private void SelectCorrectSlotInHotbar(ItemType[] tools)
+ {
+ if (GetInventoryEnabled())
+ {
+ foreach (ItemType tool in tools)
+ {
+ int[] tempArray = GetPlayerInventory().SearchItem(tool);
+ // Check whether an item could be found and make sure that it is in
+ // a hotbar slot (36-44).
+ if (tempArray.Length > 0 && tempArray[0] > 35)
+ {
+ // Changeslot takes numbers from 0-8
+ ChangeSlot(Convert.ToInt16(tempArray[0] - 36));
+ break;
+ }
+ }
+ }
+ else
+ {
+ LogToConsole("Activate Inventory Handling.");
+ }
+ }
+
+ ///
+ /// Check if mining the current block would update others
+ ///
+ /// Current World
+ /// The block to be checked
+ /// true if mining the current block would not update others
+ public bool IsGravitySave(World currentWorld, Location blockToMine)
+ {
+ Location currentLoc = GetCurrentLocation();
+ Location block = new Location(Math.Round(blockToMine.X), Math.Round(blockToMine.Y), Math.Round(blockToMine.Z));
+ List gravityBlockList = new List(new Material[] { Material.Gravel, Material.Sand, Material.RedSand, Material.Scaffolding, Material.Anvil, });
+ Func isGravityBlock = (Location blockToCheck) => gravityBlockList.Contains(currentWorld.GetBlock(blockToCheck).Type);
+ Func isBlockSolid = (Location blockToCheck) => currentWorld.GetBlock(blockToCheck).Type.IsSolid();
+
+ return
+ // Block can not fall down on player e.g. Sand, Gravel etc.
+ !isGravityBlock(Movement.Move(block, Direction.Up)) &&
+ (Movement.Move(currentLoc, Direction.Down) != blockToMine || currentWorld.GetBlock(Movement.Move(currentLoc, Direction.Down, 2)).Type.IsSolid()) &&
+ // Prevent updating flying sand/gravel under player
+ !isGravityBlock(Movement.Move(block, Direction.Down)) || isBlockSolid(Movement.Move(block, Direction.Down, 2));
+ }
+
+ ///
+ /// Checks if the current block is sorrounded by liquids
+ ///
+ /// Current World
+ /// The block to be checked
+ /// true if mining the current block results in liquid flow change
+ public bool IsSorroundedByLiquid(World currentWorld, Location blockToMine)
+ {
+ Location block = new Location(Math.Round(blockToMine.X), Math.Round(blockToMine.Y), Math.Round(blockToMine.Z));
+ Func isLiquid = (Location blockToCheck) => currentWorld.GetBlock(blockToCheck).Type.IsLiquid();
+
+ return // Liquid can not flow down the hole. Liquid is unable to flow diagonally.
+ isLiquid(block) ||
+ isLiquid(Movement.Move(block, Direction.Up)) ||
+ isLiquid(Movement.Move(block, Direction.North)) ||
+ isLiquid(Movement.Move(block, Direction.South)) ||
+ isLiquid(Movement.Move(block, Direction.East)) ||
+ isLiquid(Movement.Move(block, Direction.West));
+ }
+
+ ///
+ /// The Help page for this command.
+ ///
+ /// a help page
+ private string getHelpPage()
+ {
+ return
+ "Usage of the mine bot:\n" +
+ "/mine OR /mine \n" +
+ "to excavate a cube of blocks from top to bottom. (There must be a 2 high area of air above the cube you want to mine.)\n" +
+ "/mineup OR /mineup \n" +
+ "to walk over a quadratic field of blocks and simultaniously mine everything above the head. \n" +
+ "(Mines up to 5 Blocks, stops if gravel or lava would fall. There must be a 2 high area of air below the cube you want to mine.)\n" +
+ "/mine OR /mineup cancel\n" +
+ "to cancel the current mining process.\n" +
+ "/mine OR /mineup cachesize\n" +
+ "to set the current cache size\n" +
+ "/mine OR /mineup breaktimeout\n" +
+ "to set the time to wait until a block is broken."; ;
+
+ }
+
+ private string EvaluateMineCommand(string command, string[] args)
+ {
+ for (int i = 0; i < args.Length; i++)
+ {
+ switch (args[i])
+ {
+ case "breaktimeout":
+ int temp;
+ if (int.TryParse(args[i + 1], out temp))
+ breakTimeout = TimeSpan.FromMilliseconds(temp);
+ else return "Please enter a valid number.";
+ return string.Format("Set the break timout to {0} ms.", breakTimeout);
+
+ case "cachesize":
+ return int.TryParse(args[i + 1], out cacheSize) ? string.Format("Set cache size to {0} blocks.", cacheSize) : "Please enter a valid number";
+
+ case "cancel":
+ cts.Cancel();
+ currentMiningTask = null;
+ return "Cancelled current mining process.";
+
+ case "toolHandling":
+ toolHandling = !toolHandling;
+ return string.Format("Tool handling was set to: {0}", toolHandling.ToString());
+ }
+ }
+
+ if (args.Length > 2)
+ {
+ Location startBlock;
+ Location stopBlock;
+
+ if (args.Length > 5)
+ {
+ try
+ {
+ startBlock = new Location(
+ double.Parse(args[0]),
+ double.Parse(args[1]),
+ double.Parse(args[2])
+ );
+
+ stopBlock = new Location(
+ double.Parse(args[3]),
+ double.Parse(args[4]),
+ double.Parse(args[5])
+ );
+
+ }
+ catch (Exception e)
+ {
+ LogDebugToConsole(e.ToString());
+ return "Please enter correct coordinates as numbers.\n" + getHelpPage();
+ }
+ }
+ else
+ {
+ Location tempLoc = GetCurrentLocation();
+ startBlock = new Location(Math.Round(tempLoc.X),
+ Math.Round(tempLoc.Y),
+ Math.Round(tempLoc.Z));
+
+ try
+ {
+ stopBlock = new Location(
+ double.Parse(args[0]),
+ double.Parse(args[1]),
+ double.Parse(args[2])
+ );
+ }
+ catch (Exception e)
+ {
+ LogDebugToConsole(e.ToString());
+ return "Please enter correct coordinates as numbers.\n" + getHelpPage();
+ }
+ }
+
+ if (currentMiningTask == null)
+ {
+ if (command.Contains("mineup"))
+ {
+ cts = new CancellationTokenSource();
+
+ currentMiningTask = Task.Factory.StartNew(() => MineUp(GetWorld(), startBlock, stopBlock, cts.Token));
+ return "Start mining up.";
+ }
+ else if (command.Contains("mine"))
+ {
+
+ cts = new CancellationTokenSource();
+
+ currentMiningTask = Task.Factory.StartNew(() => Mine(GetWorld(), startBlock, stopBlock, cts.Token));
+ return "Start mining cube.";
+ }
+ }
+ else return "You are already mining. Cancel it with '/minecancel'";
+ }
+
+ return "Invalid command syntax.\n" + getHelpPage();
+ }
}
\ No newline at end of file