From 761adfa44e703b62eb3b59c4dfde0cecf0f77f6d Mon Sep 17 00:00:00 2001 From: ThisTestUser Date: Sat, 24 Jun 2023 11:37:02 -0400 Subject: [PATCH 1/2] AutoFarm enchancements -Harvest first option -Check line of sight option -Choose fortune, silk touch tool -Harvest exclusions --- .../net/wurstclient/hacks/AutoFarmHack.java | 98 ++++++++++++++++++- .../net/wurstclient/util/BlockBreaker.java | 7 +- .../net/wurstclient/util/InventoryUtils.java | 28 ++++++ 3 files changed, 127 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/wurstclient/hacks/AutoFarmHack.java b/src/main/java/net/wurstclient/hacks/AutoFarmHack.java index 6de6c02133..8578884f23 100644 --- a/src/main/java/net/wurstclient/hacks/AutoFarmHack.java +++ b/src/main/java/net/wurstclient/hacks/AutoFarmHack.java @@ -13,7 +13,11 @@ import net.minecraft.block.*; import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.enchantment.EnchantmentHelper; +import net.minecraft.enchantment.Enchantments; +import net.minecraft.item.AxeItem; import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; import net.minecraft.item.Items; import net.minecraft.network.packet.c2s.play.HandSwingC2SPacket; import net.minecraft.registry.tag.BlockTags; @@ -27,6 +31,7 @@ import net.wurstclient.events.UpdateListener; import net.wurstclient.hack.Hack; import net.wurstclient.hacks.autofarm.AutoFarmRenderer; +import net.wurstclient.settings.BlockListSetting; import net.wurstclient.settings.CheckboxSetting; import net.wurstclient.settings.SliderSetting; import net.wurstclient.settings.SliderSetting.ValueDisplay; @@ -48,6 +53,28 @@ public final class AutoFarmHack extends Hack private final CheckboxSetting replant = new CheckboxSetting("Replant", true); + private final CheckboxSetting harvestFirst = new CheckboxSetting( + "Harvest first", "Harvest all crops first before replanting.", + false); + + private final CheckboxSetting checkLOS = new CheckboxSetting( + "Check line of sight", + "Makes sure that you don't reach through walls when breaking or replanting.", + false); + + private final CheckboxSetting fortune = new CheckboxSetting( + "Choose fortune tool", + "Chooses a fortune tool to harvest crops.", + false); + + private final CheckboxSetting silkTouch = new CheckboxSetting( + "Choose silk touch tool", + "Chooses a silk touch tool to harvest melons. Axes will be prioritized.", + false); + + private final BlockListSetting excluded = new BlockListSetting("Excluded Crops", + "List of crops that will not be harvested."); + private final HashMap seeds = new HashMap<>(); { seeds.put(Blocks.WHEAT, Items.WHEAT_SEEDS); @@ -60,6 +87,16 @@ public final class AutoFarmHack extends Hack seeds.put(Blocks.COCOA, Items.COCOA_BEANS); } + private final HashSet fortuneBlocks = new HashSet<>(); + { + fortuneBlocks.add(Blocks.WHEAT); + fortuneBlocks.add(Blocks.CARROTS); + fortuneBlocks.add(Blocks.POTATOES); + fortuneBlocks.add(Blocks.BEETROOTS); + fortuneBlocks.add(Blocks.NETHER_WART); + fortuneBlocks.add(Blocks.MELON); + } + private final HashMap plants = new HashMap<>(); private final ArrayDeque> prevBlocks = new ArrayDeque<>(); private BlockPos currentlyHarvesting; @@ -76,6 +113,11 @@ public AutoFarmHack() setCategory(Category.BLOCKS); addSetting(range); addSetting(replant); + addSetting(harvestFirst); + addSetting(checkLOS); + addSetting(fortune); + addSetting(silkTouch); + addSetting(excluded); } @Override @@ -141,11 +183,15 @@ public void onUpdate() getBlocksToReplant(eyesVec, eyesBlock, rangeSq, blockRange); } - // first, try to replant - boolean replanting = replant(blocksToReplant); + // replant and harvest + if(harvestFirst.isChecked()) + harvest(blocksToHarvest); + + boolean replanting = false; + if(currentlyHarvesting == null) + replanting = replant(blocksToReplant); - // if we can't replant, harvest instead - if(!replanting) + if(!harvestFirst.isChecked() && !replanting) harvest(blocksToHarvest); // upate busy state @@ -197,6 +243,9 @@ private boolean shouldBeHarvested(BlockPos pos) Block block = BlockUtils.getBlock(pos); BlockState state = BlockUtils.getState(pos); + if(Collections.binarySearch(excluded.getBlockNames(), BlockUtils.getName(pos)) >= 0) + return false; + if(block instanceof CropBlock) return ((CropBlock)block).isMature(state); @@ -294,6 +343,9 @@ private boolean replant(List blocksToReplant) if(params == null || params.distanceSq() > range.getValueSq()) continue; + if(checkLOS.isChecked() && !params.lineOfSight()) + continue; + // face block WURST.getRotationFaker().faceVectorPacket(params.hitVec()); @@ -353,11 +405,47 @@ private void harvest(List blocksToHarvest) } for(BlockPos pos : blocksToHarvest) - if(BlockBreaker.breakOneBlock(pos)) + { + boolean findSilkTouch = silkTouch.isChecked() && BlockUtils.getBlock(pos) == Blocks.MELON; + boolean findFortune = fortune.isChecked() && fortuneBlocks.contains(BlockUtils.getBlock(pos)); + ItemStack held = MC.player.getMainHandStack(); + if(findSilkTouch) + { + if(EnchantmentHelper.getLevel(Enchantments.SILK_TOUCH, held) == 0 + || !(held.getItem() instanceof AxeItem)) + { + int slot = InventoryUtils.indexOf(stack -> stack.getItem() instanceof AxeItem + && EnchantmentHelper.getLevel(Enchantments.SILK_TOUCH, stack) > 0); + if(slot == -1) + slot = InventoryUtils.indexOf(stack -> EnchantmentHelper.getLevel(Enchantments.SILK_TOUCH, stack) > 0); + InventoryUtils.selectItem(slot); + } + }else if(findFortune) + { + int[] slots = InventoryUtils.indicesOf(stack -> EnchantmentHelper.getLevel(Enchantments.SILK_TOUCH, stack) == 0 + && EnchantmentHelper.getLevel(Enchantments.FORTUNE, stack) > 0, 36, false); + + int selected = -1; + int level = EnchantmentHelper.getLevel(Enchantments.SILK_TOUCH, held) > 0 ? 0 + : EnchantmentHelper.getLevel(Enchantments.FORTUNE, held); + for(int slot : slots) + { + int curLevel = EnchantmentHelper.getLevel(Enchantments.FORTUNE, + MC.player.getInventory().getStack(slot)); + if(curLevel > level) + { + selected = slot; + level = curLevel; + } + } + InventoryUtils.selectItem(selected); + } + if(BlockBreaker.breakOneBlock(pos, checkLOS.isChecked())) { currentlyHarvesting = pos; break; } + } if(currentlyHarvesting == null) MC.interactionManager.cancelBlockBreaking(); diff --git a/src/main/java/net/wurstclient/util/BlockBreaker.java b/src/main/java/net/wurstclient/util/BlockBreaker.java index 16398f6f4f..9d57e5ef83 100644 --- a/src/main/java/net/wurstclient/util/BlockBreaker.java +++ b/src/main/java/net/wurstclient/util/BlockBreaker.java @@ -32,9 +32,14 @@ public enum BlockBreaker private static final MinecraftClient MC = WurstClient.MC; public static boolean breakOneBlock(BlockPos pos) + { + return breakOneBlock(pos, false); + } + + public static boolean breakOneBlock(BlockPos pos, boolean checkLOS) { BlockBreakingParams params = getBlockBreakingParams(pos); - if(params == null) + if(params == null || (checkLOS && !params.lineOfSight)) return false; // face block diff --git a/src/main/java/net/wurstclient/util/InventoryUtils.java b/src/main/java/net/wurstclient/util/InventoryUtils.java index de41474633..38dc5c9e87 100644 --- a/src/main/java/net/wurstclient/util/InventoryUtils.java +++ b/src/main/java/net/wurstclient/util/InventoryUtils.java @@ -82,6 +82,34 @@ public static int indexOf(Predicate predicate, int maxInvSlot, return slot; } + /** + * Searches the player's inventory from slot 0 to {@code maxInvSlot-1} for + * all items that matches the given predicate. + * + * @param predicate + * checks if an item is the one you want + * @param maxInvSlot + * the maximum slot to search (exclusive), usually 9 for the + * hotbar or 36 for the whole inventory + * @param includeOffhand + * also search the offhand (slot 40), even if maxInvSlot is lower + * @return + * all the slots the item was found on as an array + */ + public static int[] indicesOf(Predicate predicate, int maxInvSlot, + boolean includeOffhand) + { + PlayerInventory inventory = MC.player.getInventory(); + + // create a stream of all slots that we want to search + IntStream stream = IntStream.range(0, maxInvSlot); + if(includeOffhand) + stream = IntStream.concat(stream, IntStream.of(40)); + + // find the slots of the item we want + return stream.filter(i -> predicate.test(inventory.getStack(i))).toArray(); + } + public static boolean selectItem(Item item) { return selectItem(stack -> stack.isOf(item), 36, false); From ac7ea6d27cd2ba61351e21fce3e221f2107427f8 Mon Sep 17 00:00:00 2001 From: ThisTestUser Date: Tue, 2 Jan 2024 18:11:21 -0500 Subject: [PATCH 2/2] Cleanup --- .../net/wurstclient/hacks/AutoFarmHack.java | 932 +++++++++--------- .../net/wurstclient/util/InventoryUtils.java | 7 +- 2 files changed, 476 insertions(+), 463 deletions(-) diff --git a/src/main/java/net/wurstclient/hacks/AutoFarmHack.java b/src/main/java/net/wurstclient/hacks/AutoFarmHack.java index b677f7cdee..d44d8757f8 100644 --- a/src/main/java/net/wurstclient/hacks/AutoFarmHack.java +++ b/src/main/java/net/wurstclient/hacks/AutoFarmHack.java @@ -1,460 +1,472 @@ -/* - * Copyright (c) 2014-2024 Wurst-Imperium and contributors. - * - * This source code is subject to the terms of the GNU General Public - * License, version 3. If a copy of the GPL was not distributed with this - * file, You can obtain one at: https://www.gnu.org/licenses/gpl-3.0.txt - */ -package net.wurstclient.hacks; - -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import net.minecraft.block.*; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.enchantment.EnchantmentHelper; -import net.minecraft.enchantment.Enchantments; -import net.minecraft.item.AxeItem; -import net.minecraft.item.Item; -import net.minecraft.item.ItemStack; -import net.minecraft.item.Items; -import net.minecraft.network.packet.c2s.play.HandSwingC2SPacket; -import net.minecraft.registry.tag.BlockTags; -import net.minecraft.util.ActionResult; -import net.minecraft.util.Hand; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Vec3d; -import net.wurstclient.Category; -import net.wurstclient.SearchTags; -import net.wurstclient.events.RenderListener; -import net.wurstclient.events.UpdateListener; -import net.wurstclient.hack.Hack; -import net.wurstclient.hacks.autofarm.AutoFarmRenderer; -import net.wurstclient.settings.BlockListSetting; -import net.wurstclient.settings.CheckboxSetting; -import net.wurstclient.settings.SliderSetting; -import net.wurstclient.settings.SliderSetting.ValueDisplay; -import net.wurstclient.util.BlockBreaker; -import net.wurstclient.util.BlockPlacer; -import net.wurstclient.util.BlockPlacer.BlockPlacingParams; -import net.wurstclient.util.BlockUtils; -import net.wurstclient.util.InventoryUtils; -import net.wurstclient.util.OverlayRenderer; -import net.wurstclient.util.RotationUtils; - -@SearchTags({"auto farm", "AutoHarvest", "auto harvest"}) -public final class AutoFarmHack extends Hack - implements UpdateListener, RenderListener -{ - private final SliderSetting range = - new SliderSetting("Range", 5, 1, 6, 0.05, ValueDisplay.DECIMAL); - - private final CheckboxSetting replant = - new CheckboxSetting("Replant", true); - - private final CheckboxSetting harvestFirst = new CheckboxSetting( - "Harvest first", "Harvest all crops first before replanting.", - false); - - private final CheckboxSetting checkLOS = new CheckboxSetting( - "Check line of sight", - "Makes sure that you don't reach through walls when breaking or replanting.", - false); - - private final CheckboxSetting fortune = new CheckboxSetting( - "Choose fortune tool", - "Chooses a fortune tool to harvest crops.", - false); - - private final CheckboxSetting silkTouch = new CheckboxSetting( - "Choose silk touch tool", - "Chooses a silk touch tool to harvest melons. Axes will be prioritized.", - false); - - private final BlockListSetting excluded = new BlockListSetting("Excluded Crops", - "List of crops that will not be harvested."); - - private final HashMap seeds = new HashMap<>(); - { - seeds.put(Blocks.WHEAT, Items.WHEAT_SEEDS); - seeds.put(Blocks.CARROTS, Items.CARROT); - seeds.put(Blocks.POTATOES, Items.POTATO); - seeds.put(Blocks.BEETROOTS, Items.BEETROOT_SEEDS); - seeds.put(Blocks.PUMPKIN_STEM, Items.PUMPKIN_SEEDS); - seeds.put(Blocks.MELON_STEM, Items.MELON_SEEDS); - seeds.put(Blocks.NETHER_WART, Items.NETHER_WART); - seeds.put(Blocks.COCOA, Items.COCOA_BEANS); - } - - private final HashSet fortuneBlocks = new HashSet<>(); - { - fortuneBlocks.add(Blocks.WHEAT); - fortuneBlocks.add(Blocks.CARROTS); - fortuneBlocks.add(Blocks.POTATOES); - fortuneBlocks.add(Blocks.BEETROOTS); - fortuneBlocks.add(Blocks.NETHER_WART); - fortuneBlocks.add(Blocks.MELON); - } - - - private final HashMap plants = new HashMap<>(); - private final ArrayDeque> prevBlocks = new ArrayDeque<>(); - private BlockPos currentlyHarvesting; - - private final AutoFarmRenderer renderer = new AutoFarmRenderer(); - private final OverlayRenderer overlay = new OverlayRenderer(); - - private boolean busy; - - public AutoFarmHack() - { - super("AutoFarm"); - - setCategory(Category.BLOCKS); - addSetting(range); - addSetting(replant); - addSetting(harvestFirst); - addSetting(checkLOS); - addSetting(fortune); - addSetting(silkTouch); - addSetting(excluded); - } - - @Override - public void onEnable() - { - plants.clear(); - - EVENTS.add(UpdateListener.class, this); - EVENTS.add(RenderListener.class, this); - } - - @Override - public void onDisable() - { - EVENTS.remove(UpdateListener.class, this); - EVENTS.remove(RenderListener.class, this); - - if(currentlyHarvesting != null) - { - MC.interactionManager.breakingBlock = true; - MC.interactionManager.cancelBlockBreaking(); - currentlyHarvesting = null; - } - - prevBlocks.clear(); - overlay.resetProgress(); - busy = false; - - renderer.reset(); - } - - @Override - public void onUpdate() - { - currentlyHarvesting = null; - Vec3d eyesVec = RotationUtils.getEyesPos().subtract(0.5, 0.5, 0.5); - BlockPos eyesBlock = BlockPos.ofFloored(RotationUtils.getEyesPos()); - double rangeSq = range.getValueSq(); - int blockRange = range.getValueCeil(); - - // get nearby, non-empty blocks - ArrayList blocks = BlockUtils - .getAllInBoxStream(eyesBlock, blockRange) - .filter(pos -> eyesVec.squaredDistanceTo(Vec3d.of(pos)) <= rangeSq) - .filter(BlockUtils::canBeClicked) - .collect(Collectors.toCollection(ArrayList::new)); - - // check for any new plants and add them to the map - updatePlants(blocks); - - ArrayList blocksToHarvest = new ArrayList<>(); - ArrayList blocksToReplant = new ArrayList<>(); - - // don't place or break any blocks while Freecam is enabled - if(!WURST.getHax().freecamHack.isEnabled()) - { - // check which of the nearby blocks can be harvested - blocksToHarvest = getBlocksToHarvest(eyesVec, blocks); - - // do a new search to find empty blocks that can be replanted - if(replant.isChecked()) - blocksToReplant = - getBlocksToReplant(eyesVec, eyesBlock, rangeSq, blockRange); - } - - // replant and harvest - if(harvestFirst.isChecked()) - harvest(blocksToHarvest); - - boolean replanting = false; - if(currentlyHarvesting == null) - replanting = replant(blocksToReplant); - - if(!harvestFirst.isChecked() && !replanting) - harvest(blocksToHarvest); - - // upate busy state - busy = replanting || currentlyHarvesting != null; - - // update renderer - renderer.updateVertexBuffers(blocksToHarvest, plants.keySet(), - blocksToReplant); - } - - @Override - public void onRender(MatrixStack matrixStack, float partialTicks) - { - renderer.render(matrixStack); - overlay.render(matrixStack, partialTicks, currentlyHarvesting); - } - - /** - * Returns true if AutoFarm is currently harvesting or replanting something. - */ - public boolean isBusy() - { - return busy; - } - - private void updatePlants(List blocks) - { - for(BlockPos pos : blocks) - { - Item seed = seeds.get(BlockUtils.getBlock(pos)); - if(seed == null) - continue; - - plants.put(pos, seed); - } - } - - private ArrayList getBlocksToHarvest(Vec3d eyesVec, - ArrayList blocks) - { - return blocks.parallelStream().filter(this::shouldBeHarvested) - .sorted(Comparator.comparingDouble( - pos -> eyesVec.squaredDistanceTo(Vec3d.of(pos)))) - .collect(Collectors.toCollection(ArrayList::new)); - } - - private boolean shouldBeHarvested(BlockPos pos) - { - Block block = BlockUtils.getBlock(pos); - BlockState state = BlockUtils.getState(pos); - - if(Collections.binarySearch(excluded.getBlockNames(), BlockUtils.getName(pos)) >= 0) - return false; - - if(block instanceof CropBlock) - return ((CropBlock)block).isMature(state); - - if(block instanceof NetherWartBlock) - return state.get(NetherWartBlock.AGE) >= 3; - - if(block instanceof CocoaBlock) - return state.get(CocoaBlock.AGE) >= 2; - - if(block == Blocks.PUMPKIN || block == Blocks.MELON) - return true; - - if(block instanceof SugarCaneBlock) - return BlockUtils.getBlock(pos.down()) instanceof SugarCaneBlock - && !(BlockUtils - .getBlock(pos.down(2)) instanceof SugarCaneBlock); - - if(block instanceof CactusBlock) - return BlockUtils.getBlock(pos.down()) instanceof CactusBlock - && !(BlockUtils.getBlock(pos.down(2)) instanceof CactusBlock); - - if(block instanceof KelpPlantBlock) - return BlockUtils.getBlock(pos.down()) instanceof KelpPlantBlock - && !(BlockUtils - .getBlock(pos.down(2)) instanceof KelpPlantBlock); - - if(block instanceof BambooBlock) - return BlockUtils.getBlock(pos.down()) instanceof BambooBlock - && !(BlockUtils.getBlock(pos.down(2)) instanceof BambooBlock); - - return false; - } - - private ArrayList getBlocksToReplant(Vec3d eyesVec, - BlockPos eyesBlock, double rangeSq, int blockRange) - { - return BlockUtils.getAllInBoxStream(eyesBlock, blockRange) - .filter(pos -> eyesVec.squaredDistanceTo(Vec3d.of(pos)) <= rangeSq) - .filter(pos -> BlockUtils.getState(pos).isReplaceable()) - .filter(pos -> plants.containsKey(pos)).filter(this::canBeReplanted) - .sorted(Comparator.comparingDouble( - pos -> eyesVec.squaredDistanceTo(Vec3d.of(pos)))) - .collect(Collectors.toCollection(ArrayList::new)); - } - - private boolean canBeReplanted(BlockPos pos) - { - Item item = plants.get(pos); - - if(item == Items.WHEAT_SEEDS || item == Items.CARROT - || item == Items.POTATO || item == Items.BEETROOT_SEEDS - || item == Items.PUMPKIN_SEEDS || item == Items.MELON_SEEDS) - return BlockUtils.getBlock(pos.down()) instanceof FarmlandBlock; - - if(item == Items.NETHER_WART) - return BlockUtils.getBlock(pos.down()) instanceof SoulSandBlock; - - if(item == Items.COCOA_BEANS) - return BlockUtils.getState(pos.north()).isIn(BlockTags.JUNGLE_LOGS) - || BlockUtils.getState(pos.east()).isIn(BlockTags.JUNGLE_LOGS) - || BlockUtils.getState(pos.south()).isIn(BlockTags.JUNGLE_LOGS) - || BlockUtils.getState(pos.west()).isIn(BlockTags.JUNGLE_LOGS); - - return false; - } - - private boolean replant(List blocksToReplant) - { - // check cooldown - if(MC.itemUseCooldown > 0) - return false; - - // check if already holding one of the seeds needed for blocksToReplant - Optional heldSeed = blocksToReplant.stream().map(plants::get) - .distinct().filter(item -> MC.player.isHolding(item)).findFirst(); - - // if so, try to replant the blocks that need that seed - if(heldSeed.isPresent()) - { - // get the seed and the hand that is holding it - Item item = heldSeed.get(); - Hand hand = MC.player.getMainHandStack().isOf(item) ? Hand.MAIN_HAND - : Hand.OFF_HAND; - - // filter out blocks that need a different seed - ArrayList blocksToReplantWithHeldSeed = - blocksToReplant.stream().filter(pos -> plants.get(pos) == item) - .collect(Collectors.toCollection(ArrayList::new)); - - for(BlockPos pos : blocksToReplantWithHeldSeed) - { - // skip over blocks that we can't reach - BlockPlacingParams params = - BlockPlacer.getBlockPlacingParams(pos); - if(params == null || params.distanceSq() > range.getValueSq()) - continue; - - if(checkLOS.isChecked() && !params.lineOfSight()) - continue; - - // face block - WURST.getRotationFaker().faceVectorPacket(params.hitVec()); - - // place seed - ActionResult result = MC.interactionManager - .interactBlock(MC.player, hand, params.toHitResult()); - - // swing arm - if(result.isAccepted() && result.shouldSwingHand()) - MC.player.networkHandler - .sendPacket(new HandSwingC2SPacket(hand)); - - // reset cooldown - MC.itemUseCooldown = 4; - return true; - } - } - - // otherwise, find a block that we can reach and have seeds for - for(BlockPos pos : blocksToReplant) - { - // skip over blocks that we can't reach - BlockPlacingParams params = BlockPlacer.getBlockPlacingParams(pos); - if(params == null || params.distanceSq() > range.getValueSq()) - continue; - - // try to select the seed (returns false if we don't have it) - Item item = plants.get(pos); - if(InventoryUtils.selectItem(item)) - return true; - } - - // if we couldn't replant anything, return false - return false; - } - - private void harvest(List blocksToHarvest) - { - if(MC.player.getAbilities().creativeMode) - { - Stream stream = blocksToHarvest.parallelStream(); - for(Set set : prevBlocks) - stream = stream.filter(pos -> !set.contains(pos)); - List filteredBlocks = stream.collect(Collectors.toList()); - - prevBlocks.addLast(new HashSet<>(filteredBlocks)); - while(prevBlocks.size() > 5) - prevBlocks.removeFirst(); - - if(!filteredBlocks.isEmpty()) - currentlyHarvesting = filteredBlocks.get(0); - - MC.interactionManager.cancelBlockBreaking(); - overlay.resetProgress(); - BlockBreaker.breakBlocksWithPacketSpam(filteredBlocks); - return; - } - - for(BlockPos pos : blocksToHarvest) - { - boolean findSilkTouch = silkTouch.isChecked() && BlockUtils.getBlock(pos) == Blocks.MELON; - boolean findFortune = fortune.isChecked() && fortuneBlocks.contains(BlockUtils.getBlock(pos)); - ItemStack held = MC.player.getMainHandStack(); - if(findSilkTouch) - { - if(EnchantmentHelper.getLevel(Enchantments.SILK_TOUCH, held) == 0 - || !(held.getItem() instanceof AxeItem)) - { - int slot = InventoryUtils.indexOf(stack -> stack.getItem() instanceof AxeItem - && EnchantmentHelper.getLevel(Enchantments.SILK_TOUCH, stack) > 0); - if(slot == -1) - slot = InventoryUtils.indexOf(stack -> EnchantmentHelper.getLevel(Enchantments.SILK_TOUCH, stack) > 0); - InventoryUtils.selectItem(slot); - } - }else if(findFortune) - { - int[] slots = InventoryUtils.indicesOf(stack -> EnchantmentHelper.getLevel(Enchantments.SILK_TOUCH, stack) == 0 - && EnchantmentHelper.getLevel(Enchantments.FORTUNE, stack) > 0, 36, false); - - int selected = -1; - int level = EnchantmentHelper.getLevel(Enchantments.SILK_TOUCH, held) > 0 ? 0 - : EnchantmentHelper.getLevel(Enchantments.FORTUNE, held); - for(int slot : slots) - { - int curLevel = EnchantmentHelper.getLevel(Enchantments.FORTUNE, - MC.player.getInventory().getStack(slot)); - if(curLevel > level) - { - selected = slot; - level = curLevel; - } - } - InventoryUtils.selectItem(selected); - } - if(BlockBreaker.breakOneBlock(pos, checkLOS.isChecked())) - { - currentlyHarvesting = pos; - break; - } - } - - if(currentlyHarvesting == null) - MC.interactionManager.cancelBlockBreaking(); - - if(currentlyHarvesting != null - && BlockUtils.getHardness(currentlyHarvesting) < 1) - overlay.updateProgress(); - else - overlay.resetProgress(); - } -} +/* + * Copyright (c) 2014-2024 Wurst-Imperium and contributors. + * + * This source code is subject to the terms of the GNU General Public + * License, version 3. If a copy of the GPL was not distributed with this + * file, You can obtain one at: https://www.gnu.org/licenses/gpl-3.0.txt + */ +package net.wurstclient.hacks; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import net.minecraft.block.*; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.enchantment.EnchantmentHelper; +import net.minecraft.enchantment.Enchantments; +import net.minecraft.item.AxeItem; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.network.packet.c2s.play.HandSwingC2SPacket; +import net.minecraft.registry.tag.BlockTags; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.wurstclient.Category; +import net.wurstclient.SearchTags; +import net.wurstclient.events.RenderListener; +import net.wurstclient.events.UpdateListener; +import net.wurstclient.hack.Hack; +import net.wurstclient.hacks.autofarm.AutoFarmRenderer; +import net.wurstclient.settings.BlockListSetting; +import net.wurstclient.settings.CheckboxSetting; +import net.wurstclient.settings.SliderSetting; +import net.wurstclient.settings.SliderSetting.ValueDisplay; +import net.wurstclient.util.BlockBreaker; +import net.wurstclient.util.BlockPlacer; +import net.wurstclient.util.BlockPlacer.BlockPlacingParams; +import net.wurstclient.util.BlockUtils; +import net.wurstclient.util.InventoryUtils; +import net.wurstclient.util.OverlayRenderer; +import net.wurstclient.util.RotationUtils; + +@SearchTags({"auto farm", "AutoHarvest", "auto harvest"}) +public final class AutoFarmHack extends Hack + implements UpdateListener, RenderListener +{ + private final SliderSetting range = + new SliderSetting("Range", 5, 1, 6, 0.05, ValueDisplay.DECIMAL); + + private final CheckboxSetting replant = + new CheckboxSetting("Replant", true); + + private final CheckboxSetting harvestFirst = new CheckboxSetting( + "Harvest first", "Harvest all crops first before replanting.", false); + + private final CheckboxSetting checkLOS = new CheckboxSetting( + "Check line of sight", + "Makes sure that you don't reach through walls when breaking or replanting.", + false); + + private final CheckboxSetting fortune = + new CheckboxSetting("Choose fortune tool", + "Chooses a fortune tool to harvest crops.", false); + + private final CheckboxSetting silkTouch = new CheckboxSetting( + "Choose silk touch tool", + "Chooses a silk touch tool to harvest melons. Axes will be prioritized.", + false); + + private final BlockListSetting excluded = new BlockListSetting( + "Excluded Crops", "List of crops that will not be harvested."); + + private final HashMap seeds = new HashMap<>(); + { + seeds.put(Blocks.WHEAT, Items.WHEAT_SEEDS); + seeds.put(Blocks.CARROTS, Items.CARROT); + seeds.put(Blocks.POTATOES, Items.POTATO); + seeds.put(Blocks.BEETROOTS, Items.BEETROOT_SEEDS); + seeds.put(Blocks.PUMPKIN_STEM, Items.PUMPKIN_SEEDS); + seeds.put(Blocks.MELON_STEM, Items.MELON_SEEDS); + seeds.put(Blocks.NETHER_WART, Items.NETHER_WART); + seeds.put(Blocks.COCOA, Items.COCOA_BEANS); + } + + private final HashSet fortuneBlocks = new HashSet<>(); + { + fortuneBlocks.add(Blocks.WHEAT); + fortuneBlocks.add(Blocks.CARROTS); + fortuneBlocks.add(Blocks.POTATOES); + fortuneBlocks.add(Blocks.BEETROOTS); + fortuneBlocks.add(Blocks.NETHER_WART); + fortuneBlocks.add(Blocks.MELON); + } + + private final HashMap plants = new HashMap<>(); + private final ArrayDeque> prevBlocks = new ArrayDeque<>(); + private BlockPos currentlyHarvesting; + + private final AutoFarmRenderer renderer = new AutoFarmRenderer(); + private final OverlayRenderer overlay = new OverlayRenderer(); + + private boolean busy; + + public AutoFarmHack() + { + super("AutoFarm"); + + setCategory(Category.BLOCKS); + addSetting(range); + addSetting(replant); + addSetting(harvestFirst); + addSetting(checkLOS); + addSetting(fortune); + addSetting(silkTouch); + addSetting(excluded); + } + + @Override + public void onEnable() + { + plants.clear(); + + EVENTS.add(UpdateListener.class, this); + EVENTS.add(RenderListener.class, this); + } + + @Override + public void onDisable() + { + EVENTS.remove(UpdateListener.class, this); + EVENTS.remove(RenderListener.class, this); + + if(currentlyHarvesting != null) + { + MC.interactionManager.breakingBlock = true; + MC.interactionManager.cancelBlockBreaking(); + currentlyHarvesting = null; + } + + prevBlocks.clear(); + overlay.resetProgress(); + busy = false; + + renderer.reset(); + } + + @Override + public void onUpdate() + { + currentlyHarvesting = null; + Vec3d eyesVec = RotationUtils.getEyesPos().subtract(0.5, 0.5, 0.5); + BlockPos eyesBlock = BlockPos.ofFloored(RotationUtils.getEyesPos()); + double rangeSq = range.getValueSq(); + int blockRange = range.getValueCeil(); + + // get nearby, non-empty blocks + ArrayList blocks = BlockUtils + .getAllInBoxStream(eyesBlock, blockRange) + .filter(pos -> eyesVec.squaredDistanceTo(Vec3d.of(pos)) <= rangeSq) + .filter(BlockUtils::canBeClicked) + .collect(Collectors.toCollection(ArrayList::new)); + + // check for any new plants and add them to the map + updatePlants(blocks); + + ArrayList blocksToHarvest = new ArrayList<>(); + ArrayList blocksToReplant = new ArrayList<>(); + + // don't place or break any blocks while Freecam is enabled + if(!WURST.getHax().freecamHack.isEnabled()) + { + // check which of the nearby blocks can be harvested + blocksToHarvest = getBlocksToHarvest(eyesVec, blocks); + + // do a new search to find empty blocks that can be replanted + if(replant.isChecked()) + blocksToReplant = + getBlocksToReplant(eyesVec, eyesBlock, rangeSq, blockRange); + } + + // replant and harvest + if(harvestFirst.isChecked()) + harvest(blocksToHarvest); + + boolean replanting = false; + if(currentlyHarvesting == null) + replanting = replant(blocksToReplant); + + if(!harvestFirst.isChecked() && !replanting) + harvest(blocksToHarvest); + + // upate busy state + busy = replanting || currentlyHarvesting != null; + + // update renderer + renderer.updateVertexBuffers(blocksToHarvest, plants.keySet(), + blocksToReplant); + } + + @Override + public void onRender(MatrixStack matrixStack, float partialTicks) + { + renderer.render(matrixStack); + overlay.render(matrixStack, partialTicks, currentlyHarvesting); + } + + /** + * Returns true if AutoFarm is currently harvesting or replanting something. + */ + public boolean isBusy() + { + return busy; + } + + private void updatePlants(List blocks) + { + for(BlockPos pos : blocks) + { + Item seed = seeds.get(BlockUtils.getBlock(pos)); + if(seed == null) + continue; + + plants.put(pos, seed); + } + } + + private ArrayList getBlocksToHarvest(Vec3d eyesVec, + ArrayList blocks) + { + return blocks.parallelStream().filter(this::shouldBeHarvested) + .sorted(Comparator.comparingDouble( + pos -> eyesVec.squaredDistanceTo(Vec3d.of(pos)))) + .collect(Collectors.toCollection(ArrayList::new)); + } + + private boolean shouldBeHarvested(BlockPos pos) + { + Block block = BlockUtils.getBlock(pos); + BlockState state = BlockUtils.getState(pos); + + if(Collections.binarySearch(excluded.getBlockNames(), + BlockUtils.getName(pos)) >= 0) + return false; + + if(block instanceof CropBlock) + return ((CropBlock)block).isMature(state); + + if(block instanceof NetherWartBlock) + return state.get(NetherWartBlock.AGE) >= 3; + + if(block instanceof CocoaBlock) + return state.get(CocoaBlock.AGE) >= 2; + + if(block == Blocks.PUMPKIN || block == Blocks.MELON) + return true; + + if(block instanceof SugarCaneBlock) + return BlockUtils.getBlock(pos.down()) instanceof SugarCaneBlock + && !(BlockUtils + .getBlock(pos.down(2)) instanceof SugarCaneBlock); + + if(block instanceof CactusBlock) + return BlockUtils.getBlock(pos.down()) instanceof CactusBlock + && !(BlockUtils.getBlock(pos.down(2)) instanceof CactusBlock); + + if(block instanceof KelpPlantBlock) + return BlockUtils.getBlock(pos.down()) instanceof KelpPlantBlock + && !(BlockUtils + .getBlock(pos.down(2)) instanceof KelpPlantBlock); + + if(block instanceof BambooBlock) + return BlockUtils.getBlock(pos.down()) instanceof BambooBlock + && !(BlockUtils.getBlock(pos.down(2)) instanceof BambooBlock); + + return false; + } + + private ArrayList getBlocksToReplant(Vec3d eyesVec, + BlockPos eyesBlock, double rangeSq, int blockRange) + { + return BlockUtils.getAllInBoxStream(eyesBlock, blockRange) + .filter(pos -> eyesVec.squaredDistanceTo(Vec3d.of(pos)) <= rangeSq) + .filter(pos -> BlockUtils.getState(pos).isReplaceable()) + .filter(pos -> plants.containsKey(pos)).filter(this::canBeReplanted) + .sorted(Comparator.comparingDouble( + pos -> eyesVec.squaredDistanceTo(Vec3d.of(pos)))) + .collect(Collectors.toCollection(ArrayList::new)); + } + + private boolean canBeReplanted(BlockPos pos) + { + Item item = plants.get(pos); + + if(item == Items.WHEAT_SEEDS || item == Items.CARROT + || item == Items.POTATO || item == Items.BEETROOT_SEEDS + || item == Items.PUMPKIN_SEEDS || item == Items.MELON_SEEDS) + return BlockUtils.getBlock(pos.down()) instanceof FarmlandBlock; + + if(item == Items.NETHER_WART) + return BlockUtils.getBlock(pos.down()) instanceof SoulSandBlock; + + if(item == Items.COCOA_BEANS) + return BlockUtils.getState(pos.north()).isIn(BlockTags.JUNGLE_LOGS) + || BlockUtils.getState(pos.east()).isIn(BlockTags.JUNGLE_LOGS) + || BlockUtils.getState(pos.south()).isIn(BlockTags.JUNGLE_LOGS) + || BlockUtils.getState(pos.west()).isIn(BlockTags.JUNGLE_LOGS); + + return false; + } + + private boolean replant(List blocksToReplant) + { + // check cooldown + if(MC.itemUseCooldown > 0) + return false; + + // check if already holding one of the seeds needed for blocksToReplant + Optional heldSeed = blocksToReplant.stream().map(plants::get) + .distinct().filter(item -> MC.player.isHolding(item)).findFirst(); + + // if so, try to replant the blocks that need that seed + if(heldSeed.isPresent()) + { + // get the seed and the hand that is holding it + Item item = heldSeed.get(); + Hand hand = MC.player.getMainHandStack().isOf(item) ? Hand.MAIN_HAND + : Hand.OFF_HAND; + + // filter out blocks that need a different seed + ArrayList blocksToReplantWithHeldSeed = + blocksToReplant.stream().filter(pos -> plants.get(pos) == item) + .collect(Collectors.toCollection(ArrayList::new)); + + for(BlockPos pos : blocksToReplantWithHeldSeed) + { + // skip over blocks that we can't reach + BlockPlacingParams params = + BlockPlacer.getBlockPlacingParams(pos); + if(params == null || params.distanceSq() > range.getValueSq()) + continue; + + if(checkLOS.isChecked() && !params.lineOfSight()) + continue; + + // face block + WURST.getRotationFaker().faceVectorPacket(params.hitVec()); + + // place seed + ActionResult result = MC.interactionManager + .interactBlock(MC.player, hand, params.toHitResult()); + + // swing arm + if(result.isAccepted() && result.shouldSwingHand()) + MC.player.networkHandler + .sendPacket(new HandSwingC2SPacket(hand)); + + // reset cooldown + MC.itemUseCooldown = 4; + return true; + } + } + + // otherwise, find a block that we can reach and have seeds for + for(BlockPos pos : blocksToReplant) + { + // skip over blocks that we can't reach + BlockPlacingParams params = BlockPlacer.getBlockPlacingParams(pos); + if(params == null || params.distanceSq() > range.getValueSq()) + continue; + + // try to select the seed (returns false if we don't have it) + Item item = plants.get(pos); + if(InventoryUtils.selectItem(item)) + return true; + } + + // if we couldn't replant anything, return false + return false; + } + + private void harvest(List blocksToHarvest) + { + if(MC.player.getAbilities().creativeMode) + { + Stream stream = blocksToHarvest.parallelStream(); + for(Set set : prevBlocks) + stream = stream.filter(pos -> !set.contains(pos)); + List filteredBlocks = stream.collect(Collectors.toList()); + + prevBlocks.addLast(new HashSet<>(filteredBlocks)); + while(prevBlocks.size() > 5) + prevBlocks.removeFirst(); + + if(!filteredBlocks.isEmpty()) + currentlyHarvesting = filteredBlocks.get(0); + + MC.interactionManager.cancelBlockBreaking(); + overlay.resetProgress(); + BlockBreaker.breakBlocksWithPacketSpam(filteredBlocks); + return; + } + + for(BlockPos pos : blocksToHarvest) + { + boolean findSilkTouch = silkTouch.isChecked() + && BlockUtils.getBlock(pos) == Blocks.MELON; + boolean findFortune = fortune.isChecked() + && fortuneBlocks.contains(BlockUtils.getBlock(pos)); + ItemStack held = MC.player.getMainHandStack(); + if(findSilkTouch) + { + if(EnchantmentHelper.getLevel(Enchantments.SILK_TOUCH, + held) == 0 || !(held.getItem() instanceof AxeItem)) + { + int slot = InventoryUtils + .indexOf(stack -> stack.getItem() instanceof AxeItem + && EnchantmentHelper + .getLevel(Enchantments.SILK_TOUCH, stack) > 0); + if(slot == -1) + slot = InventoryUtils.indexOf(stack -> EnchantmentHelper + .getLevel(Enchantments.SILK_TOUCH, stack) > 0); + InventoryUtils.selectItem(slot); + } + }else if(findFortune) + { + int[] slots = + InventoryUtils + .indicesOf( + stack -> EnchantmentHelper + .getLevel(Enchantments.SILK_TOUCH, stack) == 0 + && EnchantmentHelper + .getLevel(Enchantments.FORTUNE, stack) > 0, + 36, false); + + int selected = -1; + int level = EnchantmentHelper.getLevel(Enchantments.SILK_TOUCH, + held) > 0 ? 0 + : EnchantmentHelper.getLevel(Enchantments.FORTUNE, + held); + for(int slot : slots) + { + int curLevel = + EnchantmentHelper.getLevel(Enchantments.FORTUNE, + MC.player.getInventory().getStack(slot)); + if(curLevel > level) + { + selected = slot; + level = curLevel; + } + } + InventoryUtils.selectItem(selected); + } + if(BlockBreaker.breakOneBlock(pos, checkLOS.isChecked())) + { + currentlyHarvesting = pos; + break; + } + } + + if(currentlyHarvesting == null) + MC.interactionManager.cancelBlockBreaking(); + + if(currentlyHarvesting != null + && BlockUtils.getHardness(currentlyHarvesting) < 1) + overlay.updateProgress(); + else + overlay.resetProgress(); + } +} diff --git a/src/main/java/net/wurstclient/util/InventoryUtils.java b/src/main/java/net/wurstclient/util/InventoryUtils.java index d32aca0bce..05d4bda5e0 100644 --- a/src/main/java/net/wurstclient/util/InventoryUtils.java +++ b/src/main/java/net/wurstclient/util/InventoryUtils.java @@ -96,8 +96,8 @@ public static int indexOf(Predicate predicate, int maxInvSlot, * @return * all the slots the item was found on as an array */ - public static int[] indicesOf(Predicate predicate, int maxInvSlot, - boolean includeOffhand) + public static int[] indicesOf(Predicate predicate, + int maxInvSlot, boolean includeOffhand) { PlayerInventory inventory = MC.player.getInventory(); @@ -107,7 +107,8 @@ public static int[] indicesOf(Predicate predicate, int maxInvSlot, stream = IntStream.concat(stream, IntStream.of(40)); // find the slots of the item we want - return stream.filter(i -> predicate.test(inventory.getStack(i))).toArray(); + return stream.filter(i -> predicate.test(inventory.getStack(i))) + .toArray(); } public static boolean selectItem(Item item)