From d2d447091eda3f3da71984c8e9f4a039be0766a4 Mon Sep 17 00:00:00 2001 From: Jakob <28766618+Altidias@users.noreply.github.com> Date: Thu, 9 Jul 2026 10:51:40 +1000 Subject: [PATCH 1/6] Add AreaNuker: instantly break every in reach block inside a selected box while moving through it --- .../java/net/wurstclient/hack/HackList.java | 1 + .../net/wurstclient/hacks/AreaNukerHack.java | 386 ++++++++++++++++++ .../assets/wurst/translations/en_us.json | 1 + 3 files changed, 388 insertions(+) create mode 100644 src/main/java/net/wurstclient/hacks/AreaNukerHack.java diff --git a/src/main/java/net/wurstclient/hack/HackList.java b/src/main/java/net/wurstclient/hack/HackList.java index e6ff2c716a..fc92247b41 100644 --- a/src/main/java/net/wurstclient/hack/HackList.java +++ b/src/main/java/net/wurstclient/hack/HackList.java @@ -144,6 +144,7 @@ public final class HackList implements UpdateListener public final EntityCountHack entityCountHack = new EntityCountHack(); public final EntityControlHack entityControlHack = new EntityControlHack(); public final ExcavatorHack excavatorHack = new ExcavatorHack(); + public final AreaNukerHack areaNukerHack = new AreaNukerHack(); public final ExtraElytraHack extraElytraHack = new ExtraElytraHack(); public final FancyChatHack fancyChatHack = new FancyChatHack(); public final FakeLagHack fakeLagHack = new FakeLagHack(); diff --git a/src/main/java/net/wurstclient/hacks/AreaNukerHack.java b/src/main/java/net/wurstclient/hacks/AreaNukerHack.java new file mode 100644 index 0000000000..03841c517e --- /dev/null +++ b/src/main/java/net/wurstclient/hacks/AreaNukerHack.java @@ -0,0 +1,386 @@ +/* + * Copyright (c) 2014-2026 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 com.mojang.blaze3d.platform.InputConstants; +import com.mojang.blaze3d.vertex.PoseStack; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.lwjgl.glfw.GLFW; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.core.BlockPos; +import net.minecraft.util.CommonColors; +import net.minecraft.util.Mth; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; +import net.wurstclient.Category; +import net.wurstclient.SearchTags; +import net.wurstclient.events.GUIRenderListener; +import net.wurstclient.events.RenderListener; +import net.wurstclient.events.UpdateListener; +import net.wurstclient.hack.DontSaveState; +import net.wurstclient.hack.Hack; +import net.wurstclient.settings.CheckboxSetting; +import net.wurstclient.settings.SliderSetting; +import net.wurstclient.settings.SliderSetting.ValueDisplay; +import net.wurstclient.settings.SwingHandSetting; +import net.wurstclient.settings.SwingHandSetting.SwingHand; +import net.wurstclient.util.BlockBreaker; +import net.wurstclient.util.BlockUtils; +import net.wurstclient.util.RenderUtils; +import net.wurstclient.util.RotationUtils; + +@SearchTags({"area nuker", "AreaNuker", "speed excavator", "SpeedExcavator", + "area speed nuker"}) +@DontSaveState +public final class AreaNukerHack extends Hack + implements UpdateListener, RenderListener, GUIRenderListener +{ + private final SliderSetting range = + new SliderSetting("Range", 5, 1, 6, 0.05, ValueDisplay.DECIMAL); + + private final CheckboxSetting autoSwitchTool = new CheckboxSetting( + "Auto switch tool", + "Automatically switch to the best tool in your hotbar for the current" + + " block even if the AutoTool hack is disabled.", + false); + + private final SwingHandSetting swingHand = new SwingHandSetting( + SwingHandSetting.genericMiningDescription(this), SwingHand.OFF); + + private Step step; + private BlockPos posStart; + private BlockPos posEnd; + private BlockPos posLookingAt; + private Area area; + private BlockPos currentBlock; + + private boolean prevAutoToolEnabled; + + public AreaNukerHack() + { + super("AreaNuker"); + setCategory(Category.BLOCKS); + addSetting(range); + addSetting(autoSwitchTool); + addSetting(swingHand); + } + + @Override + public String getRenderName() + { + if(step == Step.NUKE && area != null && area.blocksList.size() > 0) + { + double broken = area.blocksList.size() - area.remainingBlocks; + int pct = (int)(broken / area.blocksList.size() * 100); + return getName() + " " + pct + "%"; + } + return getName(); + } + + @Override + protected void onEnable() + { + // disable conflicting block-breaking hacks + WURST.getHax().autoMineHack.setEnabled(false); + WURST.getHax().excavatorHack.setEnabled(false); + WURST.getHax().nukerHack.setEnabled(false); + WURST.getHax().nukerLegitHack.setEnabled(false); + WURST.getHax().speedNukerHack.setEnabled(false); + WURST.getHax().tunnellerHack.setEnabled(false); + WURST.getHax().veinMinerHack.setEnabled(false); + + prevAutoToolEnabled = WURST.getHax().autoToolHack.isEnabled(); + + step = Step.START_POS; + posStart = null; + posEnd = null; + posLookingAt = null; + area = null; + currentBlock = null; + + EVENTS.add(UpdateListener.class, this); + EVENTS.add(RenderListener.class, this); + EVENTS.add(GUIRenderListener.class, this); + } + + @Override + protected void onDisable() + { + EVENTS.remove(UpdateListener.class, this); + EVENTS.remove(RenderListener.class, this); + EVENTS.remove(GUIRenderListener.class, this); + + MC.gameMode.stopDestroyBlock(); + + posStart = null; + posEnd = null; + posLookingAt = null; + area = null; + currentBlock = null; + + if(!prevAutoToolEnabled && WURST.getHax().autoToolHack.isEnabled()) + WURST.getHax().autoToolHack.setEnabled(false); + } + + @Override + public void onUpdate() + { + if(step.selectPos) + handlePositionSelection(); + else if(step == Step.SCAN_AREA) + scanArea(); + else if(step == Step.NUKE) + nuke(); + } + + private void handlePositionSelection() + { + if(getStepPos() != null + && InputConstants.isKeyDown(MC.getWindow(), GLFW.GLFW_KEY_ENTER)) + { + step = Step.values()[step.ordinal() + 1]; + if(!step.selectPos) + posLookingAt = null; + return; + } + + if(MC.hitResult instanceof BlockHitResult bhr) + { + posLookingAt = bhr.getBlockPos(); + + if(MC.options.keyShift.isDown()) + posLookingAt = posLookingAt.relative(bhr.getDirection()); + }else + posLookingAt = null; + + if(posLookingAt != null && MC.options.keyUse.isDown()) + setStepPos(posLookingAt); + } + + private BlockPos getStepPos() + { + return step == Step.START_POS ? posStart : posEnd; + } + + private void setStepPos(BlockPos pos) + { + if(step == Step.START_POS) + posStart = pos; + else + posEnd = pos; + } + + private void scanArea() + { + if(area == null) + area = new Area(posStart, posEnd); + + // scan in chunks per tick so huge areas don't freeze the game + for(int i = 0; i < area.scanSpeed && area.iterator.hasNext(); i++) + { + area.scannedBlocks++; + BlockPos pos = area.iterator.next(); + if(BlockUtils.canBeClicked(pos)) + { + area.blocksList.add(pos); + area.blocksSet.add(pos); + } + } + + area.progress = (float)area.scannedBlocks / area.totalBlocks; + + if(!area.iterator.hasNext()) + { + area.remainingBlocks = area.blocksList.size(); + step = Step.NUKE; + } + } + + private void nuke() + { + // wait for AutoEat to finish eating + if(WURST.getHax().autoEatHack.isEating()) + return; + + ArrayList blocks = getValidBlocksInRange(); + currentBlock = blocks.isEmpty() ? null : blocks.get(0); + + if(!blocks.isEmpty()) + { + // equip best tool for the closest block + if(WURST.getHax().autoToolHack.isEnabled()) + WURST.getHax().autoToolHack.equipIfEnabled(blocks.get(0)); + else if(autoSwitchTool.isChecked()) + WURST.getHax().autoToolHack.equipBestTool(blocks.get(0), true, + true, 0); + + // the speed: break every in-range block at once via packet spam + BlockBreaker.breakBlocksWithPacketSpam(blocks); + swingHand.swing(InteractionHand.MAIN_HAND); + }else + MC.gameMode.stopDestroyBlock(); + + // recount remaining blocks; finish when the box is clear + Predicate breakable = MC.player.getAbilities().instabuild + ? BlockUtils::canBeClicked : pos -> BlockUtils.canBeClicked(pos) + && !BlockUtils.isUnbreakable(pos); + area.remainingBlocks = + (int)area.blocksList.parallelStream().filter(breakable).count(); + + if(area.remainingBlocks == 0) + setEnabled(false); + } + + private ArrayList getValidBlocksInRange() + { + Vec3 eyesVec = RotationUtils.getEyesPos(); + BlockPos eyesBlock = BlockPos.containing(eyesVec); + double rangeSq = range.getValueSq(); + int blockRange = range.getValueCeil(); + + return BlockUtils.getAllInBoxStream(eyesBlock, blockRange) + .filter(pos -> pos.distToCenterSqr(eyesVec) <= rangeSq) + .filter(area.blocksSet::contains).filter(BlockUtils::canBeClicked) + .filter(pos -> !BlockUtils.isUnbreakable(pos)) + .sorted( + Comparator.comparingDouble(pos -> pos.distToCenterSqr(eyesVec))) + .collect(Collectors.toCollection(ArrayList::new)); + } + + @Override + public void onRender(PoseStack matrixStack, float partialTicks) + { + int black = 0x80000000; + int gray = 0x26404040; + int green1 = 0x2600FF00; + int green2 = 0x4D00FF00; + + // confirmed area box + if(area != null) + { + AABB areaBox = new AABB(area.minX, area.minY, area.minZ, + area.minX + area.sizeX + 1, area.minY + area.sizeY + 1, + area.minZ + area.sizeZ + 1).deflate(1 / 16.0); + RenderUtils.drawOutlinedBox(matrixStack, areaBox, black, true); + + // scanning sweep highlight + if(area.progress < 1) + { + double scanX = + Mth.lerp(area.progress, areaBox.minX, areaBox.maxX); + AABB scanner = areaBox.setMinX(scanX).setMaxX(scanX); + RenderUtils.drawOutlinedBox(matrixStack, scanner, black, true); + RenderUtils.drawSolidBox(matrixStack, scanner, green2, true); + } + } + + // live preview between start and the position being chosen for end + if(area == null && step == Step.END_POS && posStart != null + && getStepPos() != null) + { + AABB preview = AABB.encapsulatingFullBlocks(posStart, getStepPos()) + .deflate(1 / 16.0); + RenderUtils.drawOutlinedBox(matrixStack, preview, black, true); + } + + // already-selected corner positions + ArrayList selectedBoxes = new ArrayList<>(); + if(posStart != null) + selectedBoxes.add(new AABB(posStart).deflate(1 / 16.0)); + if(posEnd != null) + selectedBoxes.add(new AABB(posEnd).deflate(1 / 16.0)); + RenderUtils.drawOutlinedBoxes(matrixStack, selectedBoxes, black, false); + RenderUtils.drawSolidBoxes(matrixStack, selectedBoxes, green1, false); + + // block currently under the crosshair during selection + if(posLookingAt != null) + { + AABB box = new AABB(posLookingAt).deflate(1 / 16.0); + RenderUtils.drawOutlinedBox(matrixStack, box, black, false); + RenderUtils.drawSolidBox(matrixStack, box, gray, false); + } + } + + @Override + public void onRenderGUI(GuiGraphicsExtractor context, float partialTicks) + { + String message; + if(step.selectPos && getStepPos() != null) + message = "Press enter to confirm, or select a different position."; + else + message = step.message; + + Font tr = MC.font; + int msgWidth = tr.width(message); + + int msgX1 = context.guiWidth() / 2 - msgWidth / 2; + int msgX2 = msgX1 + msgWidth + 2; + int msgY1 = context.guiHeight() / 2 + 1; + int msgY2 = msgY1 + 10; + + context.fill(msgX1, msgY1, msgX2, msgY2, 0x80000000); + context.text(tr, message, msgX1 + 2, msgY1 + 1, CommonColors.WHITE, + false); + } + + private enum Step + { + START_POS("Select start position.", true), + END_POS("Select end position.", true), + SCAN_AREA("Scanning area...", false), + NUKE("Nuking area...", false); + + private final String message; + private final boolean selectPos; + + Step(String message, boolean selectPos) + { + this.message = message; + this.selectPos = selectPos; + } + } + + private static final class Area + { + private final int minX, minY, minZ; + private final int sizeX, sizeY, sizeZ; + + private final int totalBlocks, scanSpeed; + private final Iterator iterator; + + private int scannedBlocks, remainingBlocks; + private float progress; + + private final ArrayList blocksList = new ArrayList<>(); + private final HashSet blocksSet = new HashSet<>(); + + private Area(BlockPos start, BlockPos end) + { + minX = Math.min(start.getX(), end.getX()); + minY = Math.min(start.getY(), end.getY()); + minZ = Math.min(start.getZ(), end.getZ()); + + sizeX = Math.abs(start.getX() - end.getX()); + sizeY = Math.abs(start.getY() - end.getY()); + sizeZ = Math.abs(start.getZ() - end.getZ()); + + totalBlocks = (sizeX + 1) * (sizeY + 1) * (sizeZ + 1); + scanSpeed = Mth.clamp(totalBlocks / 30, 1, 16384); + iterator = BlockUtils.getAllInBox(start, end).iterator(); + } + } +} diff --git a/src/main/resources/assets/wurst/translations/en_us.json b/src/main/resources/assets/wurst/translations/en_us.json index c1306112b5..f505eda708 100644 --- a/src/main/resources/assets/wurst/translations/en_us.json +++ b/src/main/resources/assets/wurst/translations/en_us.json @@ -31,6 +31,7 @@ "description.wurst.hack.antiwaterpush": "Prevents you from getting pushed by water.", "description.wurst.hack.antiwobble": "Disables the wobble effect caused by nausea and portals.", "description.wurst.hack.arrowdmg": "Massively increases arrow damage, but also consumes a lot of hunger and reduces accuracy.\n\nDoes not work with crossbows and seems to be patched on Paper servers.", + "description.wurst.hack.areanuker": "Combines Excavator's area selection with SpeedNuker's speed.", "description.wurst.setting.arrowdmg.strength": "Strength of the effect. 10 is the highest possible as of Minecraft 1.21.", "description.wurst.setting.arrowdmg.trident_yeet_mode": "When enabled, tridents fly much further. Doesn't seem to affect damage or Riptide.\n\nWARNING: You can easily lose your trident by enabling this option!", "description.wurst.hack.attributeswap": "Swaps main-hand item attributes with the target slot.", From a11e642259bd0fa1e92371d3b56893cea0e9f5c7 Mon Sep 17 00:00:00 2001 From: Jakob <28766618+Altidias@users.noreply.github.com> Date: Thu, 9 Jul 2026 10:55:19 +1000 Subject: [PATCH 2/6] Add AutoSpawnProofer: automatically places torches on nearby spawnable tiles --- .../java/net/wurstclient/hack/HackList.java | 2 + .../hacks/AutoSpawnProoferHack.java | 227 ++++++++++++++++++ .../assets/wurst/translations/en_us.json | 1 + 3 files changed, 230 insertions(+) create mode 100644 src/main/java/net/wurstclient/hacks/AutoSpawnProoferHack.java diff --git a/src/main/java/net/wurstclient/hack/HackList.java b/src/main/java/net/wurstclient/hack/HackList.java index fc92247b41..c32e3aa42a 100644 --- a/src/main/java/net/wurstclient/hack/HackList.java +++ b/src/main/java/net/wurstclient/hack/HackList.java @@ -86,6 +86,8 @@ public final class HackList implements UpdateListener public final AutoSoupHack autoSoupHack = new AutoSoupHack(); public final AutoSprintHack autoSprintHack = new AutoSprintHack(); public final AutoStealHack autoStealHack = new AutoStealHack(); + public final AutoSpawnProoferHack autoSpawnProoferHack = + new AutoSpawnProoferHack(); public final AutoSwimHack autoSwimHack = new AutoSwimHack(); public final AutoSwitchHack autoSwitchHack = new AutoSwitchHack(); public final AutoSwordHack autoSwordHack = new AutoSwordHack(); diff --git a/src/main/java/net/wurstclient/hacks/AutoSpawnProoferHack.java b/src/main/java/net/wurstclient/hacks/AutoSpawnProoferHack.java new file mode 100644 index 0000000000..996f33c3f1 --- /dev/null +++ b/src/main/java/net/wurstclient/hacks/AutoSpawnProoferHack.java @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2014-2026 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 com.mojang.blaze3d.vertex.PoseStack; +import java.awt.Color; +import java.util.Comparator; +import java.util.List; +import net.minecraft.core.BlockPos; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.EntityTypes; +import net.minecraft.world.entity.SpawnPlacements; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.LightLayer; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +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.settings.BlockListSetting; +import net.wurstclient.settings.CheckboxSetting; +import net.wurstclient.settings.ColorSetting; +import net.wurstclient.settings.EspStyleSetting; +import net.wurstclient.settings.SliderSetting; +import net.wurstclient.settings.SliderSetting.ValueDisplay; +import net.wurstclient.settings.SwingHandSetting; +import net.wurstclient.settings.SwingHandSetting.SwingHand; +import net.wurstclient.util.BlockPlacer; +import net.wurstclient.util.BlockPlacer.BlockPlacingParams; +import net.wurstclient.util.BlockUtils; +import net.wurstclient.util.InteractionSimulator; +import net.wurstclient.util.InventoryUtils; +import net.wurstclient.util.RenderUtils; +import net.wurstclient.util.RotationUtils; + +@SearchTags({"auto spawn proofer", "AutoTorch", "auto torch", "torch placer", + "spawn proof", "spawnproof", "anti spawn"}) +public final class AutoSpawnProoferHack extends Hack + implements UpdateListener, RenderListener +{ + private final SliderSetting range = + new SliderSetting("Range", 5, 1, 7, 0.05, ValueDisplay.DECIMAL); + + private final CheckboxSetting autoPlace = new CheckboxSetting("Auto place", + "Automatically places light sources on highlighted spawn spots.\n" + + "When off, AutoSpawnProofer only highlights where mobs can spawn.", + true); + + private final SliderSetting lightLevel = new SliderSetting("Light level", + "Maximum block light at which a tile still counts as spawnable.\n" + + "Hostile mobs spawn at block light 0, so the default of 1 proofs" + + " exactly the tiles where they can appear.", + 1, 0, 7, 1, ValueDisplay.INTEGER); + + private final BlockListSetting lightBlocks = new BlockListSetting( + "Light blocks", "The light sources AutoSpawnProofer is allowed to use.", + "minecraft:torch", "minecraft:soul_torch", "minecraft:jack_o_lantern", + "minecraft:shroomlight", "minecraft:glowstone", + "minecraft:sea_lantern"); + + private final EspStyleSetting style = new EspStyleSetting(); + private final ColorSetting color = new ColorSetting("Color", + "Spawnable tiles are highlighted in this color.", new Color(0xFF0000)); + private final SliderSetting fillAlpha = new SliderSetting("Fill opacity", + "Opacity of the highlighted spawn tiles.", 64, 0, 255, 1, + ValueDisplay.INTEGER); + private final SliderSetting outlineAlpha = new SliderSetting( + "Outline opacity", "Opacity of the highlight outlines.", 255, 0, 255, 1, + ValueDisplay.INTEGER); + + private final SwingHandSetting swingHand = + new SwingHandSetting(this, SwingHand.SERVER); + + private List targets = List.of(); + private BlockPos currentTarget; + + public AutoSpawnProoferHack() + { + super("AutoSpawnProofer"); + setCategory(Category.BLOCKS); + addSetting(range); + addSetting(autoPlace); + addSetting(lightLevel); + addSetting(lightBlocks); + addSetting(style); + addSetting(color); + addSetting(fillAlpha); + addSetting(outlineAlpha); + addSetting(swingHand); + } + + @Override + protected void onEnable() + { + EVENTS.add(UpdateListener.class, this); + EVENTS.add(RenderListener.class, this); + } + + @Override + protected void onDisable() + { + EVENTS.remove(UpdateListener.class, this); + EVENTS.remove(RenderListener.class, this); + + targets = List.of(); + currentTarget = null; + } + + @Override + public void onUpdate() + { + currentTarget = null; + + Vec3 eyesVec = RotationUtils.getEyesPos(); + BlockPos eyesBlock = BlockPos.containing(eyesVec); + double rangeSq = range.getValueSq(); + int blockRange = range.getValueCeil(); + + targets = BlockUtils.getAllInBoxStream(eyesBlock, blockRange) + .filter(pos -> pos.distToCenterSqr(eyesVec) <= rangeSq) + .filter(this::isSpawnable) + .sorted( + Comparator.comparingDouble(pos -> pos.distToCenterSqr(eyesVec))) + .toList(); + + if(autoPlace.isChecked()) + placeLight(rangeSq); + } + + @Override + public void onRender(PoseStack matrixStack, float partialTicks) + { + if(targets.isEmpty()) + return; + + List boxes = targets.stream().map(AABB::new).toList(); + + if(style.getSelected().hasBoxes()) + { + RenderUtils.drawSolidBoxes(matrixStack, boxes, + color.getColorI(fillAlpha.getValueI()), false); + RenderUtils.drawOutlinedBoxes(matrixStack, boxes, + color.getColorI(outlineAlpha.getValueI()), false); + } + + if(style.getSelected().hasLines()) + RenderUtils.drawTracers(matrixStack, partialTicks, + boxes.stream().map(AABB::getCenter).toList(), + color.getColorI(outlineAlpha.getValueI()), false); + } + + private boolean placeLight(double rangeSq) + { + if(MC.rightClickDelay > 0 || MC.gameMode.isDestroying() + || MC.player.isHandsBusy()) + return false; + + for(BlockPos pos : targets) + { + BlockPlacingParams params = BlockPlacer.getBlockPlacingParams(pos); + if(params == null || params.requiresSneaking() + || params.distanceSq() > rangeSq) + continue; + + if(!selectLightBlock()) + return false; + + ItemStack heldStack = MC.player.getMainHandItem(); + if(!isLightBlock(heldStack)) + return false; + + currentTarget = pos; + MC.rightClickDelay = 4; + WURST.getRotationFaker().faceVectorPacket(params.hitVec()); + InteractionSimulator.rightClickBlock(params.toHitResult(), + InteractionHand.MAIN_HAND, swingHand.getSelected()); + return true; + } + + return false; + } + + private boolean selectLightBlock() + { + if(isLightBlock(MC.player.getMainHandItem())) + return true; + + return InventoryUtils.selectItem(this::isLightBlock); + } + + private boolean isLightBlock(ItemStack stack) + { + if(!(stack.getItem() instanceof BlockItem blockItem)) + return false; + + Block block = blockItem.getBlock(); + if(!lightBlocks.matchesBlock(block)) + return false; + + Inventory inventory = MC.player.getInventory(); + return !stack.isEmpty() && (MC.player.getAbilities().instabuild + || inventory.countItem(stack.getItem()) > 0); + } + + private boolean isSpawnable(BlockPos pos) + { + if(!BlockUtils.getState(pos).canBeReplaced()) + return false; + + if(!SpawnPlacements.isSpawnPositionOk(EntityTypes.CREEPER, MC.level, + pos)) + return false; + + return MC.level.getBrightness(LightLayer.BLOCK, pos) <= lightLevel + .getValueI(); + } +} diff --git a/src/main/resources/assets/wurst/translations/en_us.json b/src/main/resources/assets/wurst/translations/en_us.json index f505eda708..7f907ec5e2 100644 --- a/src/main/resources/assets/wurst/translations/en_us.json +++ b/src/main/resources/assets/wurst/translations/en_us.json @@ -32,6 +32,7 @@ "description.wurst.hack.antiwobble": "Disables the wobble effect caused by nausea and portals.", "description.wurst.hack.arrowdmg": "Massively increases arrow damage, but also consumes a lot of hunger and reduces accuracy.\n\nDoes not work with crossbows and seems to be patched on Paper servers.", "description.wurst.hack.areanuker": "Combines Excavator's area selection with SpeedNuker's speed.", + "description.wurst.hack.autospawnproofer": "Automatically places light sources (torches by default) on nearby tiles where hostile mobs could spawn.", "description.wurst.setting.arrowdmg.strength": "Strength of the effect. 10 is the highest possible as of Minecraft 1.21.", "description.wurst.setting.arrowdmg.trident_yeet_mode": "When enabled, tridents fly much further. Doesn't seem to affect damage or Riptide.\n\nWARNING: You can easily lose your trident by enabling this option!", "description.wurst.hack.attributeswap": "Swaps main-hand item attributes with the target slot.", From 22edecfdff5868143d79521b450aa5012b2134ce Mon Sep 17 00:00:00 2001 From: Jakob <28766618+Altidias@users.noreply.github.com> Date: Thu, 9 Jul 2026 10:56:05 +1000 Subject: [PATCH 3/6] Add FastFill: area filler that places a chosen block into every reachable spot in a selected box --- .../java/net/wurstclient/hack/HackList.java | 1 + .../net/wurstclient/hacks/FastFillHack.java | 412 ++++++++++++++++++ .../assets/wurst/translations/en_us.json | 1 + 3 files changed, 414 insertions(+) create mode 100644 src/main/java/net/wurstclient/hacks/FastFillHack.java diff --git a/src/main/java/net/wurstclient/hack/HackList.java b/src/main/java/net/wurstclient/hack/HackList.java index c32e3aa42a..6a113d8016 100644 --- a/src/main/java/net/wurstclient/hack/HackList.java +++ b/src/main/java/net/wurstclient/hack/HackList.java @@ -151,6 +151,7 @@ public final class HackList implements UpdateListener public final FancyChatHack fancyChatHack = new FancyChatHack(); public final FakeLagHack fakeLagHack = new FakeLagHack(); public final FastBreakHack fastBreakHack = new FastBreakHack(); + public final FastFillHack fastFillHack = new FastFillHack(); public final FastLadderHack fastLadderHack = new FastLadderHack(); public final FastPlaceHack fastPlaceHack = new FastPlaceHack(); public final FeedAuraHack feedAuraHack = new FeedAuraHack(); diff --git a/src/main/java/net/wurstclient/hacks/FastFillHack.java b/src/main/java/net/wurstclient/hacks/FastFillHack.java new file mode 100644 index 0000000000..9b0589630d --- /dev/null +++ b/src/main/java/net/wurstclient/hacks/FastFillHack.java @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2014-2026 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.ArrayList; +import java.util.Comparator; +import java.util.stream.Collectors; + +import com.mojang.blaze3d.platform.InputConstants; +import com.mojang.blaze3d.vertex.PoseStack; + +import org.lwjgl.glfw.GLFW; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.util.CommonColors; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; +import net.wurstclient.Category; +import net.wurstclient.SearchTags; +import net.wurstclient.events.GUIRenderListener; +import net.wurstclient.events.RenderListener; +import net.wurstclient.events.UpdateListener; +import net.wurstclient.hack.DontSaveState; +import net.wurstclient.hack.Hack; +import net.wurstclient.settings.BlockListSetting; +import net.wurstclient.settings.CheckboxSetting; +import net.wurstclient.settings.SliderSetting; +import net.wurstclient.settings.SliderSetting.ValueDisplay; +import net.wurstclient.settings.SwingHandSetting; +import net.wurstclient.settings.SwingHandSetting.SwingHand; +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.InteractionSimulator; +import net.wurstclient.util.InventoryUtils; +import net.wurstclient.util.RenderUtils; +import net.wurstclient.util.RotationUtils; + +@SearchTags({"fast fill", "FastFill", "area fill", "AreaFill", "printer", + "builder", "fill", "auto build"}) +@DontSaveState +public final class FastFillHack extends Hack + implements UpdateListener, RenderListener, GUIRenderListener +{ + private final SliderSetting range = + new SliderSetting("Range", 5, 1, 7, 0.05, ValueDisplay.DECIMAL); + + private final SliderSetting perTick = new SliderSetting("Per tick", + "How many blocks to place each tick. Higher is faster but sends more" + + " packets.", + 64, 1, 512, 1, ValueDisplay.INTEGER); + + private final BlockListSetting blocks = new BlockListSetting("Blocks", + "The blocks FastFill is allowed to place.", "minecraft:stone"); + + private final CheckboxSetting replace = new CheckboxSetting("Replace", + "Break any block that isn't one of the chosen blocks before placing.\n" + + "When off, FastFill only fills air and other replaceable spots.", + true); + + private final CheckboxSetting airPlace = new CheckboxSetting("Airplace", + "Place blocks even when there's nothing to place them against (mid-air)," + + " and through walls. Needs a server that allows it.", + true); + + private final CheckboxSetting autoSwitchTool = new CheckboxSetting( + "Auto switch tool", + "Switch to the best tool to break blocks even if the AutoTool hack is" + + " disabled. Only matters in survival; creative breaks instantly.", + false); + + private final SwingHandSetting swingHand = + new SwingHandSetting(this, SwingHand.SERVER); + + private Step step; + private BlockPos posStart; + private BlockPos posEnd; + private BlockPos posLookingAt; + private BlockPos min; + private BlockPos max; + + public FastFillHack() + { + super("FastFill"); + setCategory(Category.BLOCKS); + addSetting(range); + addSetting(perTick); + addSetting(blocks); + addSetting(replace); + addSetting(airPlace); + addSetting(autoSwitchTool); + addSetting(swingHand); + } + + @Override + protected void onEnable() + { + WURST.getHax().nukerHack.setEnabled(false); + WURST.getHax().speedNukerHack.setEnabled(false); + WURST.getHax().excavatorHack.setEnabled(false); + + step = Step.START_POS; + posStart = null; + posEnd = null; + posLookingAt = null; + min = null; + max = null; + + EVENTS.add(UpdateListener.class, this); + EVENTS.add(RenderListener.class, this); + EVENTS.add(GUIRenderListener.class, this); + } + + @Override + protected void onDisable() + { + EVENTS.remove(UpdateListener.class, this); + EVENTS.remove(RenderListener.class, this); + EVENTS.remove(GUIRenderListener.class, this); + + MC.gameMode.stopDestroyBlock(); + + posStart = null; + posEnd = null; + posLookingAt = null; + min = null; + max = null; + } + + @Override + public void onUpdate() + { + if(step.selectPos) + { + handlePositionSelection(); + return; + } + + fill(); + } + + private void handlePositionSelection() + { + BlockPos current = step == Step.START_POS ? posStart : posEnd; + + if(current != null + && InputConstants.isKeyDown(MC.getWindow(), GLFW.GLFW_KEY_ENTER)) + { + if(step == Step.START_POS) + step = Step.END_POS; + else + { + min = new BlockPos(Math.min(posStart.getX(), posEnd.getX()), + Math.min(posStart.getY(), posEnd.getY()), + Math.min(posStart.getZ(), posEnd.getZ())); + max = new BlockPos(Math.max(posStart.getX(), posEnd.getX()), + Math.max(posStart.getY(), posEnd.getY()), + Math.max(posStart.getZ(), posEnd.getZ())); + step = Step.FILL; + posLookingAt = null; + } + return; + } + + if(MC.hitResult instanceof BlockHitResult bhr) + { + posLookingAt = bhr.getBlockPos(); + if(MC.options.keyShift.isDown()) + posLookingAt = posLookingAt.relative(bhr.getDirection()); + }else + posLookingAt = null; + + if(posLookingAt != null && MC.options.keyUse.isDown()) + { + if(step == Step.START_POS) + posStart = posLookingAt; + else + posEnd = posLookingAt; + } + } + + private void fill() + { + Vec3 eyesVec = RotationUtils.getEyesPos(); + BlockPos eyesBlock = BlockPos.containing(eyesVec); + double rangeSq = range.getValueSq(); + int blockRange = range.getValueCeil(); + + ArrayList inReach = BlockUtils + .getAllInBoxStream(eyesBlock, blockRange).filter(this::inArea) + .filter(pos -> pos.distToCenterSqr(eyesVec) <= rangeSq) + .sorted( + Comparator.comparingDouble(pos -> pos.distToCenterSqr(eyesVec))) + .collect(Collectors.toCollection(ArrayList::new)); + + ArrayList toBreak = + replace.isChecked() + ? inReach.stream().filter(this::needsBreak) + .collect(Collectors.toCollection(ArrayList::new)) + : new ArrayList<>(); + + boolean creative = MC.player.getAbilities().instabuild; + if(!creative && !toBreak.isEmpty()) + { + equipTool(toBreak.get(0)); + BlockBreaker.breakBlocksWithPacketSpam(toBreak); + swingHand.swing(InteractionHand.MAIN_HAND); + return; + } + + if(creative && !toBreak.isEmpty()) + BlockBreaker.breakBlocksWithPacketSpam(toBreak); + + if(!selectFillBlock()) + return; + + int budget = perTick.getValueI(); + for(BlockPos pos : inReach) + { + if(budget <= 0) + break; + if(!needsFill(pos)) + continue; + + if(!isFillBlock(MC.player.getMainHandItem()) && !selectFillBlock()) + break; + if(!isFillBlock(MC.player.getMainHandItem())) + break; + + if(placeBlock(pos, rangeSq)) + budget--; + } + + swingHand.swing(InteractionHand.MAIN_HAND); + } + + private void equipTool(BlockPos pos) + { + if(WURST.getHax().autoToolHack.isEnabled()) + WURST.getHax().autoToolHack.equipIfEnabled(pos); + else if(autoSwitchTool.isChecked()) + WURST.getHax().autoToolHack.equipBestTool(pos, true, true, 0); + } + + private boolean placeBlock(BlockPos pos, double rangeSq) + { + BlockPlacingParams params = BlockPlacer.getBlockPlacingParams(pos); + BlockHitResult hitResult; + Vec3 hitVec; + + if(params != null && !params.requiresSneaking()) + { + if(params.distanceSq() > rangeSq) + return false; + hitResult = params.toHitResult(); + hitVec = params.hitVec(); + }else + { + // no supporting neighbor: airplace / through-wall with a faked hit + if(!airPlace.isChecked()) + return false; + hitVec = Vec3.atCenterOf(pos); + if(RotationUtils.getEyesPos().distanceToSqr(hitVec) > rangeSq) + return false; + hitResult = new BlockHitResult(hitVec, Direction.UP, pos, false); + } + + WURST.getRotationFaker().faceVectorPacket(hitVec); + InteractionSimulator.rightClickBlock(hitResult, + InteractionHand.MAIN_HAND, swingHand.getSelected()); + return true; + } + + private boolean inArea(BlockPos pos) + { + return min != null && pos.getX() >= min.getX() + && pos.getX() <= max.getX() && pos.getY() >= min.getY() + && pos.getY() <= max.getY() && pos.getZ() >= min.getZ() + && pos.getZ() <= max.getZ(); + } + + /** + * A replaceable spot (air, water, grass, ...) we can place a block into. + */ + private boolean needsFill(BlockPos pos) + { + return BlockUtils.getState(pos).canBeReplaced(); + } + + /** A solid block that isn't one of the chosen blocks and can be broken. */ + private boolean needsBreak(BlockPos pos) + { + BlockState state = BlockUtils.getState(pos); + if(state.canBeReplaced() || BlockUtils.isUnbreakable(pos)) + return false; + return !blocks.matchesBlock(state.getBlock()); + } + + private boolean selectFillBlock() + { + if(isFillBlock(MC.player.getMainHandItem())) + return true; + + return InventoryUtils.selectItem(this::isFillBlock); + } + + private boolean isFillBlock(ItemStack stack) + { + if(!(stack.getItem() instanceof BlockItem blockItem)) + return false; + + Block block = blockItem.getBlock(); + if(!blocks.matchesBlock(block)) + return false; + + Inventory inventory = MC.player.getInventory(); + return !stack.isEmpty() && (MC.player.getAbilities().instabuild + || inventory.countItem(stack.getItem()) > 0); + } + + @Override + public void onRender(PoseStack matrixStack, float partialTicks) + { + int black = 0x80000000; + int gray = 0x26404040; + int green1 = 0x2600FF00; + + if(min != null) + { + AABB box = + new AABB(min.getX(), min.getY(), min.getZ(), max.getX() + 1, + max.getY() + 1, max.getZ() + 1).deflate(1 / 16.0); + RenderUtils.drawOutlinedBox(matrixStack, box, black, true); + }else if(step == Step.END_POS && posStart != null + && (posEnd != null || posLookingAt != null)) + { + BlockPos other = posEnd != null ? posEnd : posLookingAt; + AABB preview = + AABB.encapsulatingFullBlocks(posStart, other).deflate(1 / 16.0); + RenderUtils.drawOutlinedBox(matrixStack, preview, black, true); + } + + ArrayList selectedBoxes = new ArrayList<>(); + if(posStart != null) + selectedBoxes.add(new AABB(posStart).deflate(1 / 16.0)); + if(posEnd != null) + selectedBoxes.add(new AABB(posEnd).deflate(1 / 16.0)); + RenderUtils.drawOutlinedBoxes(matrixStack, selectedBoxes, black, false); + RenderUtils.drawSolidBoxes(matrixStack, selectedBoxes, green1, false); + + if(posLookingAt != null) + { + AABB box = new AABB(posLookingAt).deflate(1 / 16.0); + RenderUtils.drawOutlinedBox(matrixStack, box, black, false); + RenderUtils.drawSolidBox(matrixStack, box, gray, false); + } + } + + @Override + public void onRenderGUI(GuiGraphicsExtractor context, float partialTicks) + { + if(!step.selectPos) + return; + + BlockPos current = step == Step.START_POS ? posStart : posEnd; + String message = current != null + ? "Press enter to confirm, or select a different position." + : step.message; + + Font tr = MC.font; + int msgWidth = tr.width(message); + int msgX1 = context.guiWidth() / 2 - msgWidth / 2; + int msgX2 = msgX1 + msgWidth + 2; + int msgY1 = context.guiHeight() / 2 + 1; + int msgY2 = msgY1 + 10; + + context.fill(msgX1, msgY1, msgX2, msgY2, 0x80000000); + context.text(tr, message, msgX1 + 2, msgY1 + 1, CommonColors.WHITE, + false); + } + + private enum Step + { + START_POS("Select start position.", true), + END_POS("Select end position.", true), + FILL("Filling area...", false); + + private final String message; + private final boolean selectPos; + + Step(String message, boolean selectPos) + { + this.message = message; + this.selectPos = selectPos; + } + } +} diff --git a/src/main/resources/assets/wurst/translations/en_us.json b/src/main/resources/assets/wurst/translations/en_us.json index 7f907ec5e2..d9f4ee81c2 100644 --- a/src/main/resources/assets/wurst/translations/en_us.json +++ b/src/main/resources/assets/wurst/translations/en_us.json @@ -33,6 +33,7 @@ "description.wurst.hack.arrowdmg": "Massively increases arrow damage, but also consumes a lot of hunger and reduces accuracy.\n\nDoes not work with crossbows and seems to be patched on Paper servers.", "description.wurst.hack.areanuker": "Combines Excavator's area selection with SpeedNuker's speed.", "description.wurst.hack.autospawnproofer": "Automatically places light sources (torches by default) on nearby tiles where hostile mobs could spawn.", + "description.wurst.hack.fastfill": "Block area filler. Select two corners and a block, then fly around the box while FastFill places the block into every reachable empty spot. With §lReplace§r on it also breaks any wrong blocks first.", "description.wurst.setting.arrowdmg.strength": "Strength of the effect. 10 is the highest possible as of Minecraft 1.21.", "description.wurst.setting.arrowdmg.trident_yeet_mode": "When enabled, tridents fly much further. Doesn't seem to affect damage or Riptide.\n\nWARNING: You can easily lose your trident by enabling this option!", "description.wurst.hack.attributeswap": "Swaps main-hand item attributes with the target slot.", From 3c863f4e9979114e642d77ee2fcfe11507b5339b Mon Sep 17 00:00:00 2001 From: Jakob <28766618+Altidias@users.noreply.github.com> Date: Thu, 9 Jul 2026 10:56:59 +1000 Subject: [PATCH 4/6] Add InventorySorter: sorting and stacking of containers or your own inventory --- .../java/net/wurstclient/hack/HackList.java | 2 + .../hacks/InventorySorterHack.java | 206 ++++++++++++++++++ .../mixin/HandledScreenAccessor.java | 4 + .../assets/wurst/translations/en_us.json | 1 + 4 files changed, 213 insertions(+) create mode 100644 src/main/java/net/wurstclient/hacks/InventorySorterHack.java diff --git a/src/main/java/net/wurstclient/hack/HackList.java b/src/main/java/net/wurstclient/hack/HackList.java index 6a113d8016..5a16586dda 100644 --- a/src/main/java/net/wurstclient/hack/HackList.java +++ b/src/main/java/net/wurstclient/hack/HackList.java @@ -178,6 +178,8 @@ public final class HackList implements UpdateListener public final InstaBuildHack instaBuildHack = new InstaBuildHack(); public final InstantBunkerHack instantBunkerHack = new InstantBunkerHack(); public final InvWalkHack invWalkHack = new InvWalkHack(); + public final InventorySorterHack inventorySorterHack = + new InventorySorterHack(); public final ItemEspHack itemEspHack = new ItemEspHack(); public final ItemGeneratorHack itemGeneratorHack = new ItemGeneratorHack(); public final net.wurstclient.hacks.itemhandler.ItemHandlerHack itemHandlerHack = diff --git a/src/main/java/net/wurstclient/hacks/InventorySorterHack.java b/src/main/java/net/wurstclient/hacks/InventorySorterHack.java new file mode 100644 index 0000000000..380ee8c2da --- /dev/null +++ b/src/main/java/net/wurstclient/hacks/InventorySorterHack.java @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2014-2026 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.ArrayList; + +import org.lwjgl.glfw.GLFW; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.client.gui.screens.inventory.CreativeModeInventoryScreen; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerInput; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import net.wurstclient.Category; +import net.wurstclient.SearchTags; +import net.wurstclient.events.MouseButtonPressListener; +import net.wurstclient.hack.Hack; +import net.wurstclient.mixin.HandledScreenAccessor; +import net.wurstclient.settings.CheckboxSetting; +import net.wurstclient.settings.EnumSetting; + +@SearchTags({"inventory sorter", "InventorySorter", "InventoryTweaks", + "inventory tweaks", "sort inventory", "middle click sort", "auto sort"}) +public final class InventorySorterHack extends Hack + implements MouseButtonPressListener +{ + private final EnumSetting button = new EnumSetting<>("Button", + "Which mouse button sorts the hovered inventory section.", + SortButton.values(), SortButton.MIDDLE); + + private final CheckboxSetting includeHotbar = + new CheckboxSetting("Include hotbar", + "Also sorts your hotbar when sorting your own inventory.\n" + + "When off, only the upper 27 storage slots are sorted.", + false); + + public InventorySorterHack() + { + super("InventorySorter"); + setCategory(Category.ITEMS); + addSetting(button); + addSetting(includeHotbar); + } + + @Override + protected void onEnable() + { + EVENTS.add(MouseButtonPressListener.class, this); + } + + @Override + protected void onDisable() + { + EVENTS.remove(MouseButtonPressListener.class, this); + } + + @Override + public void onMouseButtonPress(MouseButtonPressEvent event) + { + if(event.getAction() != GLFW.GLFW_PRESS + || event.getButton() != button.getSelected().glfwButton) + return; + + // only inside a real container screen, not the creative item palette + if(!(MC.gui.screen() instanceof AbstractContainerScreen screen) + || MC.gui.screen() instanceof CreativeModeInventoryScreen) + return; + + Slot hovered = ((HandledScreenAccessor)screen).getHoveredSlot(); + if(hovered == null) + return; + + sort(screen, sectionSlots(screen, hovered)); + } + + private ArrayList sectionSlots(AbstractContainerScreen screen, + Slot hovered) + { + AbstractContainerMenu menu = screen.getMenu(); + boolean playerInv = hovered.container == MC.player.getInventory(); + + ArrayList slots = new ArrayList<>(); + for(Slot slot : menu.slots) + { + if(slot.container != hovered.container) + continue; + + if(playerInv) + { + int idx = slot.getContainerSlot(); + // 0-8 hotbar, 9-35 main storage, 36+ armor/offhand + boolean main = idx >= 9 && idx <= 35; + boolean hotbar = idx >= 0 && idx <= 8; + if(!main && !(hotbar && includeHotbar.isChecked())) + continue; + } + + slots.add(slot); + } + + return slots; + } + + private void sort(AbstractContainerScreen screen, ArrayList slots) + { + if(slots.size() < 2) + return; + + // 1. merge partial stacks of the same item so each item ends up as a + // run of full stacks plus at most one partial + for(int i = 0; i < slots.size(); i++) + { + Slot dst = slots.get(i); + if(dst.getItem().isEmpty()) + continue; + + for(int j = i + 1; j < slots.size(); j++) + { + ItemStack dstStack = dst.getItem(); + if(dstStack.getCount() >= dstStack.getMaxStackSize()) + break; + + Slot src = slots.get(j); + if(src.getItem().isEmpty() || !ItemStack + .isSameItemSameComponents(dstStack, src.getItem())) + continue; + + // pick up src, drop onto dst (fills dst), return any remainder + click(screen, src); + click(screen, dst); + if(!screen.getMenu().getCarried().isEmpty()) + click(screen, src); + } + } + + // 2. group items by name via a selection sort; only ever swaps two + // slots + // holding different items, so each swap is a clean 3-click exchange + for(int a = 0; a < slots.size() - 1; a++) + { + int min = a; + for(int b = a + 1; b < slots.size(); b++) + if(compare(slots.get(b).getItem(), + slots.get(min).getItem()) < 0) + min = b; + + if(min == a) + continue; + + Slot slotA = slots.get(a); + Slot slotMin = slots.get(min); + // same item type can't reach here as a swap target (compare == 0), + // so this is always an empty<->item or itemA<->itemB exchange + click(screen, slotA); + click(screen, slotMin); + click(screen, slotA); + } + } + + private void click(AbstractContainerScreen screen, Slot slot) + { + screen.slotClicked(slot, slot.index, 0, ContainerInput.PICKUP); + } + + private int compare(ItemStack a, ItemStack b) + { + boolean ae = a.isEmpty(); + boolean be = b.isEmpty(); + if(ae || be) + return ae == be ? 0 : ae ? 1 : -1; + + return sortKey(a).compareTo(sortKey(b)); + } + + private String sortKey(ItemStack stack) + { + return stack.getHoverName().getString().toLowerCase(); + } + + private enum SortButton + { + LEFT("Left", GLFW.GLFW_MOUSE_BUTTON_LEFT), + MIDDLE("Middle", GLFW.GLFW_MOUSE_BUTTON_MIDDLE), + RIGHT("Right", GLFW.GLFW_MOUSE_BUTTON_RIGHT); + + private final String name; + private final int glfwButton; + + SortButton(String name, int glfwButton) + { + this.name = name; + this.glfwButton = glfwButton; + } + + @Override + public String toString() + { + return name; + } + } +} diff --git a/src/main/java/net/wurstclient/mixin/HandledScreenAccessor.java b/src/main/java/net/wurstclient/mixin/HandledScreenAccessor.java index 7af4594d83..4abf5a1973 100644 --- a/src/main/java/net/wurstclient/mixin/HandledScreenAccessor.java +++ b/src/main/java/net/wurstclient/mixin/HandledScreenAccessor.java @@ -8,12 +8,16 @@ package net.wurstclient.mixin; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.world.inventory.Slot; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; @Mixin(AbstractContainerScreen.class) public interface HandledScreenAccessor { + @Accessor("hoveredSlot") + Slot getHoveredSlot(); + @Accessor("leftPos") int getX(); diff --git a/src/main/resources/assets/wurst/translations/en_us.json b/src/main/resources/assets/wurst/translations/en_us.json index d9f4ee81c2..746d7da64d 100644 --- a/src/main/resources/assets/wurst/translations/en_us.json +++ b/src/main/resources/assets/wurst/translations/en_us.json @@ -34,6 +34,7 @@ "description.wurst.hack.areanuker": "Combines Excavator's area selection with SpeedNuker's speed.", "description.wurst.hack.autospawnproofer": "Automatically places light sources (torches by default) on nearby tiles where hostile mobs could spawn.", "description.wurst.hack.fastfill": "Block area filler. Select two corners and a block, then fly around the box while FastFill places the block into every reachable empty spot. With §lReplace§r on it also breaks any wrong blocks first.", + "description.wurst.hack.inventorysorter": "Click a container or your own inventory with the configured button to instantly sort and stack the section you are hovering.", "description.wurst.setting.arrowdmg.strength": "Strength of the effect. 10 is the highest possible as of Minecraft 1.21.", "description.wurst.setting.arrowdmg.trident_yeet_mode": "When enabled, tridents fly much further. Doesn't seem to affect damage or Riptide.\n\nWARNING: You can easily lose your trident by enabling this option!", "description.wurst.hack.attributeswap": "Swaps main-hand item attributes with the target slot.", From bba9bf362893d7d32f77098877750b77bbc6d524 Mon Sep 17 00:00:00 2001 From: Jakob <28766618+Altidias@users.noreply.github.com> Date: Thu, 9 Jul 2026 12:01:24 +1000 Subject: [PATCH 5/6] SpotlessApply on the new utility modules --- src/main/java/net/wurstclient/hacks/AreaNukerHack.java | 4 ++-- .../java/net/wurstclient/hacks/AutoSpawnProoferHack.java | 4 ++-- src/main/java/net/wurstclient/hacks/FastFillHack.java | 2 +- .../java/net/wurstclient/hacks/InventorySorterHack.java | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/wurstclient/hacks/AreaNukerHack.java b/src/main/java/net/wurstclient/hacks/AreaNukerHack.java index 03841c517e..b277be87e8 100644 --- a/src/main/java/net/wurstclient/hacks/AreaNukerHack.java +++ b/src/main/java/net/wurstclient/hacks/AreaNukerHack.java @@ -161,12 +161,12 @@ private void handlePositionSelection() if(MC.hitResult instanceof BlockHitResult bhr) { posLookingAt = bhr.getBlockPos(); - + if(MC.options.keyShift.isDown()) posLookingAt = posLookingAt.relative(bhr.getDirection()); }else posLookingAt = null; - + if(posLookingAt != null && MC.options.keyUse.isDown()) setStepPos(posLookingAt); } diff --git a/src/main/java/net/wurstclient/hacks/AutoSpawnProoferHack.java b/src/main/java/net/wurstclient/hacks/AutoSpawnProoferHack.java index 996f33c3f1..32d6b6afd3 100644 --- a/src/main/java/net/wurstclient/hacks/AutoSpawnProoferHack.java +++ b/src/main/java/net/wurstclient/hacks/AutoSpawnProoferHack.java @@ -216,11 +216,11 @@ private boolean isSpawnable(BlockPos pos) { if(!BlockUtils.getState(pos).canBeReplaced()) return false; - + if(!SpawnPlacements.isSpawnPositionOk(EntityTypes.CREEPER, MC.level, pos)) return false; - + return MC.level.getBrightness(LightLayer.BLOCK, pos) <= lightLevel .getValueI(); } diff --git a/src/main/java/net/wurstclient/hacks/FastFillHack.java b/src/main/java/net/wurstclient/hacks/FastFillHack.java index 9b0589630d..4fba2a27ce 100644 --- a/src/main/java/net/wurstclient/hacks/FastFillHack.java +++ b/src/main/java/net/wurstclient/hacks/FastFillHack.java @@ -213,7 +213,7 @@ private void fill() ? inReach.stream().filter(this::needsBreak) .collect(Collectors.toCollection(ArrayList::new)) : new ArrayList<>(); - + boolean creative = MC.player.getAbilities().instabuild; if(!creative && !toBreak.isEmpty()) { diff --git a/src/main/java/net/wurstclient/hacks/InventorySorterHack.java b/src/main/java/net/wurstclient/hacks/InventorySorterHack.java index 380ee8c2da..cf7ab8540b 100644 --- a/src/main/java/net/wurstclient/hacks/InventorySorterHack.java +++ b/src/main/java/net/wurstclient/hacks/InventorySorterHack.java @@ -77,7 +77,7 @@ public void onMouseButtonPress(MouseButtonPressEvent event) sort(screen, sectionSlots(screen, hovered)); } - + private ArrayList sectionSlots(AbstractContainerScreen screen, Slot hovered) { @@ -161,12 +161,12 @@ private void sort(AbstractContainerScreen screen, ArrayList slots) click(screen, slotA); } } - + private void click(AbstractContainerScreen screen, Slot slot) { screen.slotClicked(slot, slot.index, 0, ContainerInput.PICKUP); } - + private int compare(ItemStack a, ItemStack b) { boolean ae = a.isEmpty(); From befdaaf86ad100ae8d94f5b4cd9d9d9bc9e79872 Mon Sep 17 00:00:00 2001 From: Jakob <28766618+Altidias@users.noreply.github.com> Date: Thu, 9 Jul 2026 12:01:32 +1000 Subject: [PATCH 6/6] Optimize ChestESP: replace per frame linear scan of opened chests in isRecordedChest() with a cached BlockPos keyed hash idx --- .../net/wurstclient/hacks/ChestEspHack.java | 93 ++++++++++++++++++- 1 file changed, 91 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/wurstclient/hacks/ChestEspHack.java b/src/main/java/net/wurstclient/hacks/ChestEspHack.java index 258a9b82b5..e1b829d745 100644 --- a/src/main/java/net/wurstclient/hacks/ChestEspHack.java +++ b/src/main/java/net/wurstclient/hacks/ChestEspHack.java @@ -141,6 +141,11 @@ public class ChestEspHack extends Hack implements UpdateListener, "Set ESP Y limit", 62, -65, 255, 1, SliderSetting.ValueDisplay.INTEGER); private java.util.List openedChests = java.util.List.of(); private long lastOpenedChestsRefreshMs; + private java.util.Set openedChestIndex = java.util.Set.of(); + private java.util.List openedChestOversized = + java.util.List.of(); + private java.util.List openedChestIndexSource; + private String openedChestIndexDimFull; // Buried highlighting private final CheckboxSetting highlightBuried = new CheckboxSetting( @@ -933,6 +938,8 @@ private boolean isRecordedChest(AABB box, String curDimFull, String curDim) if(openedChests.isEmpty()) return false; + rebuildOpenedChestIndexIfNeeded(curDimFull, curDim); + int boxMinX = (int)Math.floor(box.minX + 1e-6); int boxMaxX = (int)Math.floor(box.maxX - 1e-6); int boxMinY = (int)Math.floor(box.minY + 1e-6); @@ -940,6 +947,42 @@ private boolean isRecordedChest(AABB box, String curDimFull, String curDim) int boxMinZ = (int)Math.floor(box.minZ + 1e-6); int boxMaxZ = (int)Math.floor(box.maxZ - 1e-6); + boolean smallBox = boxMaxX - boxMinX <= 8 && boxMaxY - boxMinY <= 8 + && boxMaxZ - boxMinZ <= 8; + if(!smallBox) + return isRecordedChestLinear(boxMinX, boxMaxX, boxMinY, boxMaxY, + boxMinZ, boxMaxZ, curDimFull, curDim); + + if(!openedChestIndex.isEmpty()) + for(int x = boxMinX; x <= boxMaxX; x++) + for(int y = boxMinY; y <= boxMaxY; y++) + for(int z = boxMinZ; z <= boxMaxZ; z++) + if(openedChestIndex.contains( + net.minecraft.core.BlockPos.asLong(x, y, z))) + return true; + + for(ChestEntry e : openedChestOversized) + { + int minX = Math.min(e.x, e.maxX); + int maxX = Math.max(e.x, e.maxX); + int minY = Math.min(e.y, e.maxY); + int maxY = Math.max(e.y, e.maxY); + int minZ = Math.min(e.z, e.maxZ); + int maxZ = Math.max(e.z, e.maxZ); + boolean overlap = + boxMinX <= maxX && boxMaxX >= minX && boxMinY <= maxY + && boxMaxY >= minY && boxMinZ <= maxZ && boxMaxZ >= minZ; + if(overlap) + return true; + } + + return false; + } + + private boolean isRecordedChestLinear(int boxMinX, int boxMaxX, int boxMinY, + int boxMaxY, int boxMinZ, int boxMaxZ, String curDimFull, String curDim) + { + String dimSuffix = ":" + curDim; for(ChestEntry e : openedChests) { if(e == null || e.dimension == null) @@ -947,7 +990,7 @@ private boolean isRecordedChest(AABB box, String curDimFull, String curDim) String ed = e.dimension; boolean sameDimension = ed.equals(curDimFull) || ed.equals(curDim) - || ed.endsWith(":" + curDim); + || ed.endsWith(dimSuffix); if(!sameDimension) continue; @@ -963,10 +1006,56 @@ private boolean isRecordedChest(AABB box, String curDimFull, String curDim) if(overlap) return true; } - return false; } + private void rebuildOpenedChestIndexIfNeeded(String curDimFull, + String curDim) + { + if(openedChestIndexSource == openedChests + && java.util.Objects.equals(openedChestIndexDimFull, curDimFull)) + return; + + java.util.HashSet index = new java.util.HashSet<>(); + java.util.ArrayList oversized = new java.util.ArrayList<>(); + String dimSuffix = ":" + curDim; + + for(ChestEntry e : openedChests) + { + if(e == null || e.dimension == null) + continue; + + String ed = e.dimension; + boolean sameDimension = ed.equals(curDimFull) || ed.equals(curDim) + || ed.endsWith(dimSuffix); + if(!sameDimension) + continue; + + int minX = Math.min(e.x, e.maxX); + int maxX = Math.max(e.x, e.maxX); + int minY = Math.min(e.y, e.maxY); + int maxY = Math.max(e.y, e.maxY); + int minZ = Math.min(e.z, e.maxZ); + int maxZ = Math.max(e.z, e.maxZ); + + if(maxX - minX > 4 || maxY - minY > 4 || maxZ - minZ > 4) + { + oversized.add(e); + continue; + } + + for(int x = minX; x <= maxX; x++) + for(int y = minY; y <= maxY; y++) + for(int z = minZ; z <= maxZ; z++) + index.add(net.minecraft.core.BlockPos.asLong(x, y, z)); + } + + openedChestIndex = index; + openedChestOversized = oversized; + openedChestIndexSource = openedChests; + openedChestIndexDimFull = curDimFull; + } + private void renderTracers(PoseStack matrixStack, float partialTicks) { boolean workstationEnabled = false;