From 95378dca8eafb63060e1faee1dff76f258a0de41 Mon Sep 17 00:00:00 2001
From: Daenges <57369924+Daenges@users.noreply.github.com>
Date: Tue, 4 Jan 2022 15:34:09 +0100
Subject: [PATCH] Automatic sugar cane farming (#1882)
* Automatic sugar cane farming
Inspired by #1871 I wrote a script to farm a field of sugar cane fully autonomous.
* Implement suggestions
* Remove unused comment
* Remove comment and improve return value
---
.../config/ChatBots/SugarCaneFarmer.cs | 236 ++++++++++++++++++
1 file changed, 236 insertions(+)
create mode 100644 MinecraftClient/config/ChatBots/SugarCaneFarmer.cs
diff --git a/MinecraftClient/config/ChatBots/SugarCaneFarmer.cs b/MinecraftClient/config/ChatBots/SugarCaneFarmer.cs
new file mode 100644
index 00000000..6ccda760
--- /dev/null
+++ b/MinecraftClient/config/ChatBots/SugarCaneFarmer.cs
@@ -0,0 +1,236 @@
+//MCCScript 1.0
+
+MCC.LoadBot(new SugarCaneFarmer());
+
+//MCCScript Extensions
+
+class SugarCaneFarmerBase : ChatBot
+{
+ ///
+ /// MoveToLocation() + waiting until the bot is near the location.
+ ///
+ /// Location to walk to
+ /// Distance of blocks from the goal that is accepted as "arrived"
+ /// True if walking was successful
+ public bool WaitForMoveToLocation(Location pos, float tolerance = 2f)
+ {
+ if (MoveToLocation(new Location(pos.X, pos.Y, pos.Z)))
+ {
+ while (GetCurrentLocation().Distance(pos) > tolerance)
+ {
+ Thread.Sleep(200);
+ }
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// DigBlock + waiting until the block is broken
+ ///
+ /// Location of the block that should be broken
+ /// Switch to the correct tool, if it is in hotbar
+ /// Minimum time until the client gives up
+ /// True if mining was successful
+ public bool WaitForDigBlock(Location block, bool useCorrectTool = false, int digTimeout = 1000)
+ {
+ if (Material2Tool.IsUnbreakable(GetWorld().GetBlock(block).Type))
+ return false;
+
+ if (useCorrectTool && GetInventoryEnabled())
+ {
+ // 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(block).Type));
+ }
+
+ // Unable to check when breaking is over.
+ if (DigBlock(block))
+ {
+ short i = 0; // Maximum wait time of 10 sec.
+ while (GetWorld().GetBlock(block).Type != Material.Air && i <= digTimeout)
+ {
+ Thread.Sleep(100);
+ i++;
+ }
+ return i <= digTimeout;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Selects the corect tool in the hotbar
+ ///
+ /// List of tools that can be applied to mine a block
+ public 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.");
+ }
+ }
+
+ ///
+ /// Returns the head position from the current location
+ ///
+ public static Func GetHeadLocation = locFeet => new Location(locFeet.X, locFeet.Y + 1, locFeet.Z);
+}
+
+class SugarCaneFarmer : SugarCaneFarmerBase
+{
+ public enum CoordinateType { X, Y, Z };
+
+ ///
+ /// Used to stop the farming process on demand.
+ ///
+ private bool farming = true;
+
+ ///
+ /// Returns all sugar canes that are above a solid block and another sugar cane
+ ///
+ /// Radius of the search
+ /// Order of the returned sugar canes
+ /// list of sugar canes, sorted after given parameter
+ private List collectSugarCaneBlocks(int radius, CoordinateType coordinateOrder = CoordinateType.X)
+ {
+ IEnumerable breakingCoordinates = GetWorld().FindBlock(
+ new Location(Math.Floor(GetCurrentLocation().X), Math.Floor(GetCurrentLocation().Y), Math.Floor(GetCurrentLocation().Z)),
+ Material.SugarCane, radius).Where(block =>
+ GetWorld().GetBlock(new Location(block.X, block.Y - 1, block.Z)).Type == Material.SugarCane &&
+ GetWorld().GetBlock(new Location(block.X, block.Y - 2, block.Z)).Type.IsSolid());
+
+ switch (coordinateOrder)
+ {
+ case CoordinateType.Z:
+ return breakingCoordinates.OrderBy(coordinate => coordinate.Z).ToList();
+ case CoordinateType.Y:
+ return breakingCoordinates.OrderBy(coordinate => coordinate.Y).ToList();
+ default:
+ return breakingCoordinates.OrderBy(coordinate => coordinate.X).ToList();
+ }
+ }
+
+ public override void Initialize()
+ {
+ LogToConsole("Sugar Cane farming bot created by Daenges.");
+ RegisterChatBotCommand("sugarcane", "Farm sugar cane automatically", "/sugarcane [range x|y|z]/[stop]", commandHandler);
+ }
+
+ ///
+ /// Start the farming process
+ ///
+ /// Range of farming
+ /// In which order the plants should be farmed
+ private void startBot(int range, CoordinateType sortOrder)
+ {
+ List sugarCanePositions;
+
+ // create a loop so the bot keeps farming if nothing else is stated
+ // uses the same range as initially given
+ do
+ {
+ sugarCanePositions = collectSugarCaneBlocks(range, sortOrder);
+
+ LogToConsole(string.Format("Found: {0} Sugar Cane", sugarCanePositions.Count));
+
+ if (sugarCanePositions.Count > 0)
+ {
+ foreach (Location loc in sugarCanePositions)
+ {
+ if (!farming)
+ break;
+
+ if (GetHeadLocation(GetCurrentLocation()).Distance(loc) > 5)
+ {
+ if (!WaitForMoveToLocation(new Location(loc.X, loc.Y - 1, loc.Z)))
+ {
+ LogToConsole("Moving failed.");
+ continue;
+ }
+ }
+
+ if (!WaitForDigBlock(new Location(loc.X, loc.Y, loc.Z)))
+ LogDebugToConsole("Unable to break this block: " + loc.ToString());
+ }
+ }
+ else { LogToConsole("Nothing found. Stop farming..."); }
+
+ if (farming && sugarCanePositions.Count > 0)
+ LogToConsole("Finished farming. Searching for further work... Use '/sugarcane stop' to stop farming.");
+
+ } while (farming && sugarCanePositions.Count > 0);
+
+ farming = true;
+ LogToConsole("[FARMING STOPPED]");
+ }
+
+ private string commandHandler(string command, string[] args)
+ {
+ if (args.Length == 1)
+ {
+ if (args[0] == "stop")
+ {
+ farming = false;
+ return "Stop farming...";
+ }
+ else
+ {
+ int range;
+
+ try { range = Convert.ToInt32(args[0]); }
+ catch (Exception) { return "Range must be a number"; }
+
+ Thread workThread = new Thread(() => startBot(range, CoordinateType.X));
+ workThread.Start();
+
+ return "Start farming.";
+ }
+ }
+ else if (args.Length == 2)
+ {
+ int range;
+
+ try { range = Convert.ToInt32(args[0]); }
+ catch (Exception) { return "Range must be a number"; }
+
+ Thread workThread;
+
+ if (args[1].ToLower() == "x")
+ workThread = new Thread(() => startBot(range, CoordinateType.X));
+ else if (args[1].ToLower() == "y")
+ workThread = new Thread(() => startBot(range, CoordinateType.Y));
+ else
+ workThread = new Thread(() => startBot(range, CoordinateType.Z));
+
+ workThread.Start();
+
+ return "Start farming.";
+ }
+ else
+ return "Usage: /sugarcane [range x|y|z]/[stop]";
+ }
+}