mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2025-10-14 21:22:49 +00:00
Rework of MineCube.cs and further improvements to CalculatePath() (#2014)
* Add function to determine if the client is executing a walking process
* Add comments
* Remove test bot entry
* Add funtion to approach a block as close as possible
* Add funtion to approach a block as close as possible
* Add funtion to approach a block as close as possible
* Add comment to function in McClient.cs
* Improve concurrency and reduce potential calculation power
* Apply code suggestions
* Apply code suggestions
* Improve CalculatePath() function to allow approaching
* Fix typo in MinecraftClient/ChatBot.cs
* Add comments to Chatbot fucntion
* Add break to for loop to exit quicker
* Allow to give a maxOffset to the goal
* Comment the sample bot again.
* Add parameter for calculation timeout
* Remove TestBot again
* Implement timeout in Chatbot class
* Remove test commands
* Update comment in Chatbot.cs
* Set timeout to default 5 sec
* Change order of parameters back
* Add suggested improvements
* Move task and fix missing methods in .NET 4.0
* Create switch for tool handling
* Remove unused function
* Improve movement
* Improve performance of CalculatePath()
- Replace Hashset OpenSet with a Binary Heap
- Temporary remove maxOffset / minOffset features
- Round start location for easier calculation
- Add 0.5 to each location in reconstruct path to avoid getting stuck
on edges
* Add diagonal movement
* Remove direct block movement
- causes kick for invalid packet movement if moving on the block you are
currently standing on
* Floor start in A* and improve diagonal walking check
* Add helperfunctions to McClient.cs
* Prevent client from falling into danger
* Add comment to function and remove dependencies
* Add comments
* Remove debug settings
Co-authored-by: ORelio <ORelio@users.noreply.github.com>
This commit is contained in:
parent
ea6788278d
commit
aa1f54d0d8
5 changed files with 787 additions and 502 deletions
|
|
@ -1,165 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace MinecraftClient.Mapping
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A row of blocks that will be mined
|
|
||||||
/// </summary>
|
|
||||||
public class Row
|
|
||||||
{
|
|
||||||
public readonly List<Location> BlocksInRow;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initialize a row of blocks
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="bIL"> Enter a list of blocks </param>
|
|
||||||
public Row(List<Location> blocksInRow = null)
|
|
||||||
{
|
|
||||||
BlocksInRow = blocksInRow ?? new List<Location>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Several rows are summarized in a layer
|
|
||||||
/// </summary>
|
|
||||||
public class Layer
|
|
||||||
{
|
|
||||||
public readonly List<Row> RowsInLayer;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a new row to this layer
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="givenRow"> enter a row that should be added </param>
|
|
||||||
/// <returns> Index of the last row </returns>
|
|
||||||
public void AddRow(Row givenRow = null)
|
|
||||||
{
|
|
||||||
RowsInLayer.Add(givenRow ?? new Row());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initialize a layer
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="rTM"> Enter a list of rows </param>
|
|
||||||
public Layer(List<Row> rowInLayer = null)
|
|
||||||
{
|
|
||||||
RowsInLayer = rowInLayer ?? new List<Row>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Several layers result in a cube
|
|
||||||
/// </summary>
|
|
||||||
public class Cube
|
|
||||||
{
|
|
||||||
public readonly List<Layer> LayersInCube;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a new layer to the cube
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="givenLayer"> Enter a layer that should be added </param>
|
|
||||||
/// <returns> Index of the last layer </returns>
|
|
||||||
public void AddLayer(Layer givenLayer = null)
|
|
||||||
{
|
|
||||||
LayersInCube.Add(givenLayer ?? new Layer());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initialize a cube
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="lTM"> Enter a list of layers </param>
|
|
||||||
public Cube(List<Layer> layerInCube = null)
|
|
||||||
{
|
|
||||||
LayersInCube = layerInCube ?? new List<Layer>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class CubeFromWorld
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a cube of blocks out of two coordinates.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="startBlock">Start Location</param>
|
|
||||||
/// <param name="stopBlock">Stop Location</param>
|
|
||||||
/// <returns>A cube of blocks consisting of Layers, Rows and single blocks</returns>
|
|
||||||
public static Cube GetBlocksAsCube(World currentWorld, Location startBlock, Location stopBlock, List<Material> 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get all numbers between from and to.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="start">Number to start</param>
|
|
||||||
/// <param name="end">Number to stop</param>
|
|
||||||
/// <returns>All numbers between the start and stop number, including the stop number</returns>
|
|
||||||
private static List<int> GetNumbersFromTo(int start, int stop)
|
|
||||||
{
|
|
||||||
List<int> tempList = new List<int>();
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -15,7 +15,13 @@ namespace MinecraftClient.Mapping
|
||||||
West = 1,
|
West = 1,
|
||||||
North = 2,
|
North = 2,
|
||||||
East = 3,
|
East = 3,
|
||||||
|
|
||||||
Up = 4,
|
Up = 4,
|
||||||
Down = 5
|
Down = 5,
|
||||||
|
|
||||||
|
NorthEast = 6,
|
||||||
|
SouthEast = 7,
|
||||||
|
SouthWest = 8,
|
||||||
|
NorthWest = 9,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
|
@ -164,72 +163,77 @@ namespace MinecraftClient.Mapping
|
||||||
/// <returns>A list of locations, or null if calculation failed</returns>
|
/// <returns>A list of locations, or null if calculation failed</returns>
|
||||||
public static Queue<Location> CalculatePath(World world, Location start, Location goal, bool allowUnsafe, int maxOffset, int minOffset, CancellationToken ct)
|
public static Queue<Location> CalculatePath(World world, Location start, Location goal, bool allowUnsafe, int maxOffset, int minOffset, CancellationToken ct)
|
||||||
{
|
{
|
||||||
|
// This is a bad configuration
|
||||||
if (minOffset > maxOffset)
|
if (minOffset > maxOffset)
|
||||||
throw new ArgumentException("minOffset must be lower or equal to maxOffset", "minOffset");
|
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.
|
// We always use distance squared so our limits must also be squared.
|
||||||
minOffset *= minOffset;
|
minOffset *= minOffset;
|
||||||
maxOffset *= maxOffset;
|
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.
|
// Prepare variables and datastructures for A*
|
||||||
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
|
// Dictionary that contains the relation between all coordinates and resolves the final path
|
||||||
g_score[start] = 0; // Cost from start along best known path.
|
Dictionary<Location, Location> CameFrom = new Dictionary<Location, Location>();
|
||||||
// Estimated total cost from start to goal through y.
|
// Create a Binary Heap for all open positions => Allows fast access to Nodes with lowest scores
|
||||||
Dictionary<Location, int> f_score = new Dictionary<Location, int>(); //:= map with default value of Infinity
|
BinaryHeap openSet = new BinaryHeap();
|
||||||
f_score[start] = (int)start.DistanceSquared(goal); //heuristic_cost_estimate(start, goal)
|
// Dictionary to keep track of the G-Score of every location
|
||||||
|
Dictionary<Location, int> gScoreDict = new Dictionary<Location, int>();
|
||||||
|
|
||||||
while (OpenSet.Count > 0)
|
// Set start values for variables
|
||||||
|
openSet.Insert(0, (int)start.DistanceSquared(goal), start);
|
||||||
|
gScoreDict[start] = 0;
|
||||||
|
BinaryHeap.Node current = null;
|
||||||
|
|
||||||
|
///---///
|
||||||
|
// 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
|
// Get the root node of the Binary Heap
|
||||||
OpenSet.Select(location => f_score.ContainsKey(location)
|
// Node with the lowest F-Score or lowest H-Score on tie
|
||||||
? new KeyValuePair<Location, int>(location, f_score[location])
|
current = openSet.GetRootLocation();
|
||||||
: new KeyValuePair<Location, int>(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
|
// Return if goal found and no maxOffset was given OR current node is between minOffset and maxOffset
|
||||||
if (maxOffset > 0 && ClosedSet.Count > 0)
|
if ((current.Location == goal && maxOffset <= 0) || (maxOffset > 0 && current.H_score >= minOffset && current.H_score <= maxOffset))
|
||||||
// Get the block that currently is closest to the goal
|
|
||||||
closestGoal = ClosedSet.OrderBy(checkedLocation => checkedLocation.DistanceSquared(goal)).First();
|
|
||||||
|
|
||||||
// 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 ReconstructPath(CameFrom, current.Location);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discover neighbored blocks
|
||||||
|
foreach (Location neighbor in GetAvailableMoves(world, current.Location, allowUnsafe))
|
||||||
|
{
|
||||||
|
// If we are cancelled: break
|
||||||
if (ct.IsCancellationRequested)
|
if (ct.IsCancellationRequested)
|
||||||
break; // Stop searching for blocks if we are cancelled.
|
break;
|
||||||
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!
|
// tentative_gScore is the distance from start to the neighbor through current
|
||||||
Came_From[neighbor] = current;
|
int tentativeGScore = current.G_score + (int)current.Location.DistanceSquared(neighbor);
|
||||||
g_score[neighbor] = tentative_g_score;
|
|
||||||
f_score[neighbor] = g_score[neighbor] + (int)neighbor.DistanceSquared(goal); //heuristic_cost_estimate(neighbor, goal)
|
// 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
|
//// Goal could not be reached. Set the path to the closest location if close enough
|
||||||
if (maxOffset == int.MaxValue || goal.DistanceSquared(closestGoal) <= maxOffset)
|
if (current != null && (maxOffset == int.MaxValue || openSet.MinH_ScoreNode.H_score <= maxOffset))
|
||||||
return ReconstructPath(Came_From, closestGoal);
|
return ReconstructPath(CameFrom, openSet.MinH_ScoreNode.Location);
|
||||||
else
|
else
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -242,16 +246,196 @@ namespace MinecraftClient.Mapping
|
||||||
/// <returns>the path that leads to current from the start position</returns>
|
/// <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)
|
||||||
{
|
{
|
||||||
List<Location> total_path = new List<Location>(new[] { current });
|
// 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) });
|
||||||
while (Came_From.ContainsKey(current))
|
while (Came_From.ContainsKey(current))
|
||||||
{
|
{
|
||||||
current = Came_From[current];
|
current = Came_From[current];
|
||||||
total_path.Add(current);
|
total_path.Add(current + new Location(0.5, 0, 0.5));
|
||||||
}
|
}
|
||||||
total_path.Reverse();
|
total_path.Reverse();
|
||||||
return new Queue<Location>(total_path);
|
return new Queue<Location>(total_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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
|
||||||
|
/// !!!
|
||||||
|
/// </summary>
|
||||||
|
public class BinaryHeap
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a location and its attributes
|
||||||
|
/// </summary>
|
||||||
|
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<Node> heapList;
|
||||||
|
// Hashset for quick checks of locations included in the heap
|
||||||
|
private HashSet<Location> locationList;
|
||||||
|
public Node MinH_ScoreNode;
|
||||||
|
|
||||||
|
public BinaryHeap()
|
||||||
|
{
|
||||||
|
heapList = new List<Node>();
|
||||||
|
locationList = new HashSet<Location>();
|
||||||
|
MinH_ScoreNode = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Insert a new location in the heap
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newG_Score">G-Score of the location</param>
|
||||||
|
/// <param name="newH_Score">H-Score of the location</param>
|
||||||
|
/// <param name="loc">The location</param>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtain the root which represents the node the the best attributes currently
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>node with the best attributes currently</returns>
|
||||||
|
/// <exception cref="InvalidOperationException"></exception>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares two nodes and evaluates their position to the goal.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="firstNode">First node to compare</param>
|
||||||
|
/// <param name="secondNode">Second node to compare</param>
|
||||||
|
/// <returns>True if the first node has a more promissing position to the goal than the second</returns>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the size of the heap
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>size of the heap</returns>
|
||||||
|
public int Count()
|
||||||
|
{
|
||||||
|
return heapList.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the heap contains a node with a certain location
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="loc">Location to check</param>
|
||||||
|
/// <returns>true if a node with the given location is in the heap</returns>
|
||||||
|
public bool ContainsLocation(Location loc)
|
||||||
|
{
|
||||||
|
return locationList.Contains(loc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ========= LOCATION PROPERTIES ========= */
|
/* ========= LOCATION PROPERTIES ========= */
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -313,22 +497,47 @@ namespace MinecraftClient.Mapping
|
||||||
{
|
{
|
||||||
switch (direction)
|
switch (direction)
|
||||||
{
|
{
|
||||||
|
// Move vertical
|
||||||
case Direction.Down:
|
case Direction.Down:
|
||||||
return !IsOnGround(world, location);
|
return !IsOnGround(world, location);
|
||||||
case Direction.Up:
|
case Direction.Up:
|
||||||
return (IsOnGround(world, location) || IsSwimming(world, location))
|
return (IsOnGround(world, location) || IsSwimming(world, location))
|
||||||
&& !world.GetBlock(Move(Move(location, Direction.Up), Direction.Up)).Type.IsSolid();
|
&& !world.GetBlock(Move(Move(location, Direction.Up), Direction.Up)).Type.IsSolid();
|
||||||
|
|
||||||
|
// Move horizontal
|
||||||
case Direction.East:
|
case Direction.East:
|
||||||
case Direction.West:
|
case Direction.West:
|
||||||
case Direction.South:
|
case Direction.South:
|
||||||
case Direction.North:
|
case Direction.North:
|
||||||
return !world.GetBlock(Move(location, direction)).Type.IsSolid()
|
return PlayerFitsHere(world, Move(location, direction));
|
||||||
&& !world.GetBlock(Move(Move(location, direction), Direction.Up)).Type.IsSolid();
|
|
||||||
|
// 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:
|
default:
|
||||||
throw new ArgumentException("Unknown direction", "direction");
|
throw new ArgumentException("Unknown direction", "direction");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evaluates if a player fits in this location
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="world">Current world</param>
|
||||||
|
/// <param name="location">Location to check</param>
|
||||||
|
/// <returns>True if a player is able to stand in this location</returns>
|
||||||
|
public static bool PlayerFitsHere(World world, Location location)
|
||||||
|
{
|
||||||
|
return !world.GetBlock(location).Type.IsSolid()
|
||||||
|
&& !world.GetBlock(Move(location, Direction.Up)).Type.IsSolid();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get an updated location for moving in the specified direction
|
/// Get an updated location for moving in the specified direction
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -350,10 +559,13 @@ namespace MinecraftClient.Mapping
|
||||||
{
|
{
|
||||||
switch (direction)
|
switch (direction)
|
||||||
{
|
{
|
||||||
|
// Move vertical
|
||||||
case Direction.Down:
|
case Direction.Down:
|
||||||
return new Location(0, -1, 0);
|
return new Location(0, -1, 0);
|
||||||
case Direction.Up:
|
case Direction.Up:
|
||||||
return new Location(0, 1, 0);
|
return new Location(0, 1, 0);
|
||||||
|
|
||||||
|
// Move horizontal straight
|
||||||
case Direction.East:
|
case Direction.East:
|
||||||
return new Location(1, 0, 0);
|
return new Location(1, 0, 0);
|
||||||
case Direction.West:
|
case Direction.West:
|
||||||
|
|
@ -362,6 +574,17 @@ namespace MinecraftClient.Mapping
|
||||||
return new Location(0, 0, 1);
|
return new Location(0, 0, 1);
|
||||||
case Direction.North:
|
case Direction.North:
|
||||||
return new Location(0, 0, -1);
|
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:
|
default:
|
||||||
throw new ArgumentException("Unknown direction", "direction");
|
throw new ArgumentException("Unknown direction", "direction");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,8 @@ namespace MinecraftClient
|
||||||
private float playerYaw;
|
private float playerYaw;
|
||||||
private float playerPitch;
|
private float playerPitch;
|
||||||
private double motionY;
|
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 int sequenceId; // User for player block synchronization (Aka. digging, placing blocks, etc..)
|
||||||
|
|
||||||
private string host;
|
private string host;
|
||||||
|
|
@ -344,7 +346,7 @@ namespace MinecraftClient
|
||||||
{
|
{
|
||||||
lock (locationLock)
|
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)
|
if (_yaw == null || _pitch == null)
|
||||||
{
|
{
|
||||||
|
|
@ -1125,9 +1127,7 @@ namespace MinecraftClient
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Calculate path through pathfinding. Path contains a list of 1-block movement that will be divided into steps
|
// 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 = Movement.CalculatePath(world, this.location, location, allowUnsafe, maxOffset, minOffset, timeout ?? TimeSpan.FromSeconds(5));
|
||||||
path = new Queue<Location>(new[] { location });
|
|
||||||
else path = Movement.CalculatePath(world, this.location, location, allowUnsafe, maxOffset, minOffset, timeout ?? TimeSpan.FromSeconds(5));
|
|
||||||
return path != null;
|
return path != null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1905,6 +1905,49 @@ namespace MinecraftClient
|
||||||
return terrainAndMovementsEnabled && locationReceived && ((steps != null && steps.Count > 0) || (path != null && path.Count > 0));
|
return terrainAndMovementsEnabled && locationReceived && ((steps != null && steps.Count > 0) || (path != null && path.Count > 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the current goal
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Current goal of movement. Location.Zero if not set.</returns>
|
||||||
|
public Location GetCurrentMovementGoal()
|
||||||
|
{
|
||||||
|
return ClientIsMoving() ? Location.Zero : path.Last();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cancels the current movement
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if there was an active path</returns>
|
||||||
|
public bool CancelMovement()
|
||||||
|
{
|
||||||
|
bool success = ClientIsMoving();
|
||||||
|
path = null;
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Change the amount of sent movement packets per time
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newSpeed">Set a new walking type</param>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when the server sends a new player location,
|
/// Called when the server sends a new player location,
|
||||||
/// or if a ChatBot whishes to update the player's location.
|
/// or if a ChatBot whishes to update the player's location.
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,16 @@ MCC.LoadBot(new MineCube());
|
||||||
|
|
||||||
//MCCScript Extensions
|
//MCCScript Extensions
|
||||||
|
|
||||||
|
//using System.Threading.Tasks;
|
||||||
|
|
||||||
class MineCube : ChatBot
|
class MineCube : ChatBot
|
||||||
{
|
{
|
||||||
|
private CancellationTokenSource cts;
|
||||||
|
private Task currentMiningTask;
|
||||||
|
private TimeSpan breakTimeout;
|
||||||
|
private bool toolHandling;
|
||||||
|
private int cacheSize;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
if (!GetTerrainEnabled())
|
if (!GetTerrainEnabled())
|
||||||
|
|
@ -14,69 +22,84 @@ class MineCube : ChatBot
|
||||||
UnloadBot();
|
UnloadBot();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentMiningTask = null;
|
||||||
|
breakTimeout = TimeSpan.FromSeconds(15);
|
||||||
|
cacheSize = 10;
|
||||||
|
toolHandling = true;
|
||||||
|
|
||||||
RegisterChatBotCommand("mine", "Mine a cube from a to b", "/mine x y z OR /mine x1 y1 z1 x2 y2 z2", EvaluateMineCommand);
|
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);
|
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.");
|
LogToConsole("Mining bot created by Daenges.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dig out a 2 Block high cube and let the bot walk through it
|
/// Walks in a 2 high area under an area of blocks and mines anything above its head.
|
||||||
/// mining all blocks above it that it can reach.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="walkingArea">Area that the bot should walk through. (The lower Y coordinate of the 2 high cube.)</param>
|
/// <param name="currentWorld">The current world</param>
|
||||||
public void MineUp(Cube walkingArea)
|
/// <param name="startBlock">The start corner of walking</param>
|
||||||
|
/// <param name="stopBlock">The stop corner of walking</param>
|
||||||
|
/// <param name="ct">CancellationToken to stop the task on cancel</param>
|
||||||
|
public void MineUp(World currentWorld, Location startBlock, Location stopBlock, CancellationToken ct)
|
||||||
{
|
{
|
||||||
foreach (Layer lay in walkingArea.LayersInCube)
|
if (startBlock.Y != stopBlock.Y)
|
||||||
{
|
{
|
||||||
foreach (Row r in lay.RowsInLayer)
|
LogToConsole("Command FAILED. Both coordinates must be on the same y level.");
|
||||||
{
|
|
||||||
foreach (Location loc in r.BlocksInRow)
|
|
||||||
{
|
|
||||||
Location currentLoc = GetCurrentLocation();
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int height = Convert.ToInt32(Math.Round(currentLoc.Y)) + 2; height < Convert.ToInt32(Math.Round(currentLoc.Y)) + 7; height++)
|
IEnumerable<int> xLocationRange = GetNumbersFromTo(Convert.ToInt32(Math.Round(startBlock.X)), Convert.ToInt32(Math.Round(stopBlock.X)));
|
||||||
|
IEnumerable<int> zLocationRange = GetNumbersFromTo(Convert.ToInt32(Math.Round(startBlock.Z)), Convert.ToInt32(Math.Round(stopBlock.Z)));
|
||||||
|
|
||||||
|
foreach (int currentXLoc in xLocationRange)
|
||||||
{
|
{
|
||||||
Location mineLocation = new Location(loc.X, height, loc.Z);
|
foreach (int currentZLoc in zLocationRange)
|
||||||
Material mineLocationMaterial = GetWorld().GetBlock(mineLocation).Type;
|
{
|
||||||
|
Location standLocation = new Location(currentXLoc, startBlock.Y, currentZLoc);
|
||||||
|
|
||||||
|
// Walk to the new location.
|
||||||
|
waitForMoveToLocation(standLocation, maxOffset: 1);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
Location mineLocation = new Location(currentXLoc, height, currentZLoc);
|
||||||
|
Material mineLocationMaterial = currentWorld.GetBlock(mineLocation).Type;
|
||||||
|
|
||||||
// Stop mining process if breaking the next block could endager the bot
|
// Stop mining process if breaking the next block could endager the bot
|
||||||
// through falling blocks or liquids.
|
// through falling blocks or liquids.
|
||||||
if (IsGravityBlockAbove(mineLocation) || IsSorroundedByLiquid(mineLocation)) { break; }
|
if (!IsGravitySave(currentWorld, mineLocation) || IsSorroundedByLiquid(currentWorld, mineLocation)) { break; }
|
||||||
// Skip this block if it can not be mined.
|
// Skip this block if it can not be mined.
|
||||||
if (Material2Tool.IsUnbreakable(mineLocationMaterial)) { continue; }
|
if (Material2Tool.IsUnbreakable(mineLocationMaterial)) { continue; }
|
||||||
|
|
||||||
//DateTime start = DateTime.Now;
|
if (Settings.InventoryHandling && toolHandling)
|
||||||
|
{
|
||||||
// Search this tool in hotbar and select the correct slot
|
// Search this tool in hotbar and select the correct slot
|
||||||
SelectCorrectSlotInHotbar(
|
SelectCorrectSlotInHotbar(
|
||||||
// Returns the correct tool for this type
|
// Returns the correct tool for this type
|
||||||
Material2Tool.GetCorrectToolForBlock(
|
Material2Tool.GetCorrectToolForBlock(
|
||||||
// returns the type of the current block
|
// returns the type of the current block
|
||||||
mineLocationMaterial));
|
mineLocationMaterial));
|
||||||
|
}
|
||||||
|
|
||||||
// Unable to check when breaking is over.
|
// If we are able to reach the block && break sucessfully sent
|
||||||
if (DigBlock(mineLocation))
|
if (GetCurrentLocation().EyesLocation().DistanceSquared(mineLocation) <= 25 && DigBlock(mineLocation))
|
||||||
{
|
{
|
||||||
short i = 0; // Maximum wait time of 10 sec.
|
AutoTimeout.Perform(() =>
|
||||||
while (GetWorld().GetBlock(mineLocation).Type != Material.Air && i <= 100)
|
{
|
||||||
|
while (GetWorld().GetBlock(mineLocation).Type != Material.Air)
|
||||||
{
|
{
|
||||||
Thread.Sleep(100);
|
Thread.Sleep(100);
|
||||||
i++;
|
|
||||||
|
if (ct.IsCancellationRequested)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
}, breakTimeout);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -85,73 +108,201 @@ class MineCube : ChatBot
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
LogToConsole("Finished mining up.");
|
LogToConsole("Finished mining up.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Mines out a cube of blocks from top to bottom.
|
/// Mine a cube of blocks from top to bottom between start and stop location
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="cubeToMine">The cube that should be mined.</param>
|
/// <param name="currentWorld">The current world</param>
|
||||||
public void Mine(Cube cubeToMine)
|
/// <param name="startBlock">The upper corner of the cube to mine</param>
|
||||||
|
/// <param name="stopBlock">The lower corner of the cube to mine</param>
|
||||||
|
/// <param name="ct">CancellationToken to stop the task on cancel</param>
|
||||||
|
public void Mine(World currentWorld, Location startBlock, Location stopBlock, CancellationToken ct)
|
||||||
{
|
{
|
||||||
foreach (Layer lay in cubeToMine.LayersInCube)
|
// Turn the cube around, so the bot always starts from the top.
|
||||||
|
if (stopBlock.Y > startBlock.Y)
|
||||||
{
|
{
|
||||||
foreach (Row r in lay.RowsInLayer)
|
Location temp = stopBlock;
|
||||||
{
|
stopBlock = startBlock;
|
||||||
foreach (Location loc in r.BlocksInRow)
|
startBlock = temp;
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
IEnumerable<int> xLocationRange = GetNumbersFromTo(Convert.ToInt32(Math.Round(startBlock.X)), Convert.ToInt32(Math.Round(stopBlock.X)));
|
||||||
else
|
IEnumerable<int> yLocationRange = GetNumbersFromTo(Convert.ToInt32(Math.Round(startBlock.Y)), Convert.ToInt32(Math.Round(stopBlock.Y)));
|
||||||
|
IEnumerable<int> zLocationRange = GetNumbersFromTo(Convert.ToInt32(Math.Round(startBlock.Z)), Convert.ToInt32(Math.Round(stopBlock.Z)));
|
||||||
|
|
||||||
|
foreach (int currentYLoc in yLocationRange)
|
||||||
{
|
{
|
||||||
LogDebugToConsole("Unable to walk to: " + loc.X.ToString() + " " + (loc.Y + 1).ToString() + " " + loc.Z.ToString());
|
foreach (int currentXLoc in xLocationRange)
|
||||||
}
|
{
|
||||||
|
|
||||||
|
if (ct.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
currentMiningTask = null;
|
||||||
|
LogToConsole("Cancellation requested. STOP MINING.");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//DateTime start = DateTime.Now;
|
List<Location> blocksToMine = null;
|
||||||
|
|
||||||
|
// If the end of the new row is closer than the start, reverse the line and start here
|
||||||
|
Location currentStandingLoc = GetCurrentLocation();
|
||||||
|
Queue<int> currentZLocationRangeQueue = new Queue<int>(currentStandingLoc.DistanceSquared(new Location(currentXLoc, currentYLoc, zLocationRange.Last())) < currentStandingLoc.DistanceSquared(new Location(currentXLoc, currentYLoc, zLocationRange.First())) ?
|
||||||
|
zLocationRange.Reverse() :
|
||||||
|
zLocationRange);
|
||||||
|
|
||||||
|
while (!ct.IsCancellationRequested && (currentZLocationRangeQueue.Count > 0 || blocksToMine.Count > 0))
|
||||||
|
{
|
||||||
|
// Evaluate the next blocks to mine, while mining
|
||||||
|
Task<List<Location>> cacheEval = Task<List<Location>>.Factory.StartNew(() => // Get a new chunk of blocks that can be mined
|
||||||
|
EvaluateBlocks(currentWorld, currentXLoc, currentYLoc, currentZLocationRangeQueue, ct, cacheSize));
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
Location currentLoc = GetCurrentLocation();
|
||||||
|
Location currentBlockUnderFeet = new Location(Math.Floor(currentLoc.X), Math.Floor(currentLoc.Y) - 1, Math.Floor(currentLoc.Z));
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent falling into danger
|
||||||
|
if (mineLocation == currentBlockUnderFeet && !Movement.IsSafe(currentWorld, currentBlockUnderFeet))
|
||||||
|
waitForMoveToLocation(mineLocation, maxOffset: 4, minOffset:3);
|
||||||
|
|
||||||
|
// Is inventoryhandling activated?
|
||||||
|
if (Settings.InventoryHandling && toolHandling)
|
||||||
|
{
|
||||||
// Search this tool in hotbar and select the correct slot
|
// Search this tool in hotbar and select the correct slot
|
||||||
SelectCorrectSlotInHotbar(
|
SelectCorrectSlotInHotbar(
|
||||||
// Returns the correct tool for this type
|
// Returns the correct tool for this type
|
||||||
Material2Tool.GetCorrectToolForBlock(
|
Material2Tool.GetCorrectToolForBlock(
|
||||||
// returns the type of the current block
|
// returns the type of the current block
|
||||||
GetWorld().GetBlock(loc).Type));
|
currentWorld.GetBlock(mineLocation).Type));
|
||||||
|
}
|
||||||
|
|
||||||
// Unable to check when breaking is over.
|
// If we are able to reach the block && break sucessfully sent
|
||||||
if (DigBlock(loc))
|
if (GetCurrentLocation().EyesLocation().DistanceSquared(mineLocation) <= 25 && DigBlock(mineLocation))
|
||||||
{
|
{
|
||||||
short i = 0; // Maximum wait time of 10 sec.
|
// Wait until the block is broken (== Air)
|
||||||
while (GetWorld().GetBlock(loc).Type != Material.Air && i <= 100)
|
AutoTimeout.Perform(() =>
|
||||||
|
{
|
||||||
|
while (GetWorld().GetBlock(mineLocation).Type != Material.Air)
|
||||||
{
|
{
|
||||||
Thread.Sleep(100);
|
Thread.Sleep(100);
|
||||||
|
|
||||||
|
if (ct.IsCancellationRequested)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}, breakTimeout);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogDebugToConsole("Unable to break this block: " + mineLocation.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This function selects a certain amount of minable blocks in a row
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="currentWorld">The current world</param>
|
||||||
|
/// <param name="xLoc">The current x location of the row</param>
|
||||||
|
/// <param name="yLoc">The current y location of the row</param>
|
||||||
|
/// <param name="zLocationQueue">All Z blocks that will be mined</param>
|
||||||
|
/// <param name="ct">CancellationToken to stop the task on cancel</param>
|
||||||
|
/// <param name="cacheSize">Maximum amount of blocks to return</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private List<Location> EvaluateBlocks(World currentWorld, int xLoc, int yLoc, Queue<int> zLocationQueue, CancellationToken ct, int cacheSize = 10)
|
||||||
|
{
|
||||||
|
List<Location> blockMiningCache = new List<Location>();
|
||||||
|
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());
|
||||||
|
|
||||||
|
// 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++;
|
i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return blockMiningCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a sequence of numbers between a start and a stop number, including both
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="start">Number to start from</param>
|
||||||
|
/// <param name="stop">Number to end with</param>
|
||||||
|
/// <returns>a sequence of numbers between a start and a stop number, including both</returns>
|
||||||
|
private static IEnumerable<int> GetNumbersFromTo(int start, int stop)
|
||||||
|
{
|
||||||
|
return start <= stop ? Enumerable.Range(start, stop - start + 1) : Enumerable.Range(stop, start - stop + 1).Reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts walk and waits until the client arrives
|
||||||
|
/// </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="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>
|
||||||
|
/// <param name="minOffset">Do not get closer of destination than specified distance</param>
|
||||||
|
/// <param name="timeout">How long to wait before stopping computation (default: 5 seconds)</param>
|
||||||
|
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
|
else
|
||||||
{
|
{
|
||||||
LogDebugToConsole("Unable to break this block: " + loc.ToString());
|
LogDebugToConsole("Unable to walk to: " + goal.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LogToConsole("Mining finished.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Func<Location, Location> GetHeadLocation = locFeet => new Location(locFeet.X, locFeet.Y + 1, locFeet.Z);
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks all slots of the hotbar for an Item and selects it if found
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tools">List of items that may be selected, from worst to best</param>
|
||||||
private void SelectCorrectSlotInHotbar(ItemType[] tools)
|
private void SelectCorrectSlotInHotbar(ItemType[] tools)
|
||||||
{
|
{
|
||||||
if (GetInventoryEnabled())
|
if (GetInventoryEnabled())
|
||||||
|
|
@ -175,61 +326,97 @@ class MineCube : ChatBot
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsGravityBlockAbove(Location block)
|
/// <summary>
|
||||||
|
/// Check if mining the current block would update others
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="currentWorld">Current World</param>
|
||||||
|
/// <param name="blockToMine">The block to be checked</param>
|
||||||
|
/// <returns>true if mining the current block would not update others</returns>
|
||||||
|
public bool IsGravitySave(World currentWorld, Location blockToMine)
|
||||||
{
|
{
|
||||||
World world = GetWorld();
|
Location currentLoc = GetCurrentLocation();
|
||||||
double blockX = Math.Round(block.X);
|
Location block = new Location(Math.Round(blockToMine.X), Math.Round(blockToMine.Y), Math.Round(blockToMine.Z));
|
||||||
double blockY = Math.Round(block.Y);
|
|
||||||
double blockZ = Math.Round(block.Z);
|
|
||||||
|
|
||||||
List<Material> gravityBlockList = new List<Material>(new Material[] { Material.Gravel, Material.Sand, Material.RedSand, Material.Scaffolding, Material.Anvil, });
|
List<Material> gravityBlockList = new List<Material>(new Material[] { Material.Gravel, Material.Sand, Material.RedSand, Material.Scaffolding, Material.Anvil, });
|
||||||
|
Func<Location, bool> isGravityBlock = (Location blockToCheck) => gravityBlockList.Contains(currentWorld.GetBlock(blockToCheck).Type);
|
||||||
|
Func<Location, bool> isBlockSolid = (Location blockToCheck) => currentWorld.GetBlock(blockToCheck).Type.IsSolid();
|
||||||
var temptype = world.GetBlock(new Location(blockX, blockY + 1, blockZ)).Type;
|
|
||||||
var tempLoc = gravityBlockList.Contains(world.GetBlock(new Location(blockX, blockY + 1, blockZ)).Type);
|
|
||||||
|
|
||||||
return
|
return
|
||||||
// Block can not fall down on player e.g. Sand, Gravel etc.
|
// Block can not fall down on player e.g. Sand, Gravel etc.
|
||||||
gravityBlockList.Contains(world.GetBlock(new Location(blockX, blockY + 1, blockZ)).Type);
|
!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));
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsSorroundedByLiquid(Location block)
|
/// <summary>
|
||||||
|
/// Checks if the current block is sorrounded by liquids
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="currentWorld">Current World</param>
|
||||||
|
/// <param name="blockToMine">The block to be checked</param>
|
||||||
|
/// <returns>true if mining the current block results in liquid flow change</returns>
|
||||||
|
public bool IsSorroundedByLiquid(World currentWorld, Location blockToMine)
|
||||||
{
|
{
|
||||||
World world = GetWorld();
|
Location block = new Location(Math.Round(blockToMine.X), Math.Round(blockToMine.Y), Math.Round(blockToMine.Z));
|
||||||
double blockX = Math.Round(block.X);
|
Func<Location, bool> isLiquid = (Location blockToCheck) => currentWorld.GetBlock(blockToCheck).Type.IsLiquid();
|
||||||
double blockY = Math.Round(block.Y);
|
|
||||||
double blockZ = Math.Round(block.Z);
|
|
||||||
|
|
||||||
List<Material> liquidBlockList = new List<Material>(new Material[] { Material.Water, Material.Lava, });
|
|
||||||
|
|
||||||
return // Liquid can not flow down the hole. Liquid is unable to flow diagonally.
|
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) ||
|
isLiquid(block) ||
|
||||||
liquidBlockList.Contains(world.GetBlock(new Location(blockX - 1, blockY, blockZ)).Type) ||
|
isLiquid(Movement.Move(block, Direction.Up)) ||
|
||||||
liquidBlockList.Contains(world.GetBlock(new Location(blockX + 1, blockY, blockZ)).Type) ||
|
isLiquid(Movement.Move(block, Direction.North)) ||
|
||||||
liquidBlockList.Contains(world.GetBlock(new Location(blockX, blockY, blockZ - 1)).Type) ||
|
isLiquid(Movement.Move(block, Direction.South)) ||
|
||||||
liquidBlockList.Contains(world.GetBlock(new Location(blockX, blockY, blockZ + 1)).Type);
|
isLiquid(Movement.Move(block, Direction.East)) ||
|
||||||
|
isLiquid(Movement.Move(block, Direction.West));
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
/// <summary>
|
||||||
|
/// The Help page for this command.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>a help page</returns>
|
||||||
private string getHelpPage()
|
private string getHelpPage()
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
"Usage of the mine bot:\n" +
|
"Usage of the mine bot:\n" +
|
||||||
"/mine <x1> <y1> <z1> <x2> <y2> <z2> OR /mine <x> <y> <z>\n" +
|
"/mine <x1> <y1> <z1> <x2> <y2> <z2> OR /mine <x> <y> <z>\n" +
|
||||||
"to excavate a cube of blocks from top to bottom. (2 high area above the cube must be dug free by hand.)\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 <x1> <y1> <z1> <x2> <y1> <z2> OR /mineup <x> <y> <z>\n" +
|
"/mineup <x1> <y1> <z1> <x2> <y1> <z2> OR /mineup <x> <y> <z>\n" +
|
||||||
"to walk over a quadratic field of blocks and simultaniously mine everything above the head. \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";
|
"(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."; ;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Evaluates the given command
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="command"></param>
|
|
||||||
/// <param name="args"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private string EvaluateMineCommand(string command, string[] args)
|
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)
|
if (args.Length > 2)
|
||||||
{
|
{
|
||||||
Location startBlock;
|
Location startBlock;
|
||||||
|
|
@ -280,35 +467,26 @@ class MineCube : ChatBot
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentMiningTask == null)
|
||||||
|
{
|
||||||
if (command.Contains("mineup"))
|
if (command.Contains("mineup"))
|
||||||
{
|
{
|
||||||
if (Math.Round(startBlock.Y) != Math.Round(stopBlock.Y))
|
cts = new CancellationTokenSource();
|
||||||
{
|
|
||||||
return "Both blocks must have the same Y value!\n" + getHelpPage();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Material> materialWhitelist = new List<Material>() { Material.Air };
|
currentMiningTask = Task.Factory.StartNew(() => MineUp(GetWorld(), startBlock, stopBlock, cts.Token));
|
||||||
Thread tempThread = new Thread(() => MineUp(CubeFromWorld.GetBlocksAsCube(GetWorld(), startBlock, stopBlock, materialWhitelist, isBlacklist: false)));
|
|
||||||
tempThread.Start();
|
|
||||||
return "Start mining up.";
|
return "Start mining up.";
|
||||||
}
|
}
|
||||||
else
|
else if (command.Contains("mine"))
|
||||||
{
|
{
|
||||||
// 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<Material> blacklistedMaterials = new List<Material>() { Material.Air, Material.Water, Material.Lava };
|
cts = new CancellationTokenSource();
|
||||||
Thread tempThread = new Thread(() => Mine(CubeFromWorld.GetBlocksAsCube(GetWorld(), startBlock, stopBlock, blacklistedMaterials)));
|
|
||||||
tempThread.Start();
|
|
||||||
|
|
||||||
|
currentMiningTask = Task.Factory.StartNew(() => Mine(GetWorld(), startBlock, stopBlock, cts.Token));
|
||||||
return "Start mining cube.";
|
return "Start mining cube.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else return "You are already mining. Cancel it with '/minecancel'";
|
||||||
|
}
|
||||||
|
|
||||||
return "Invalid command syntax.\n" + getHelpPage();
|
return "Invalid command syntax.\n" + getHelpPage();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue