//MCCScript 1.0 MCC.LoadBot(new MineCube()); //MCCScript Extensions //using System.Threading.Tasks; class MineCube : ChatBot { private CancellationTokenSource cts; private Task currentMiningTask; private TimeSpan breakTimeout; private bool toolHandling; private int cacheSize; public override void Initialize() { if (!GetTerrainEnabled()) { LogToConsole(Translations.Get("extra.terrainandmovement_required")); UnloadBot(); 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("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."); } /// /// 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."); } 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))); foreach (int currentXLoc in xLocationRange) { foreach (int currentZLoc in zLocationRange) { 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 // 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; } 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)); } // 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); if (ct.IsCancellationRequested) break; } }, breakTimeout); } else { LogDebugToConsole("Unable to break this block: " + mineLocation.ToString()); } } } } LogToConsole("Finished mining up."); } /// /// 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))); foreach (int currentYLoc in yLocationRange) { foreach (int currentXLoc in xLocationRange) { if (ct.IsCancellationRequested) { currentMiningTask = null; LogToConsole("Cancellation requested. STOP MINING."); return; } List blocksToMine = null; // 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); 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)); // 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 SelectCorrectSlotInHotbar( // Returns the correct tool for this type Material2Tool.GetCorrectToolForBlock( // returns the type of the current block currentWorld.GetBlock(mineLocation).Type)); } // 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 (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."); } /// /// 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()); // 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(); } }