|
| 1 | +/* |
| 2 | + * Copyright (c) 2014-2026 Wurst-Imperium and contributors. |
| 3 | + * |
| 4 | + * This source code is subject to the terms of the GNU General Public |
| 5 | + * License, version 3. If a copy of the GPL was not distributed with this |
| 6 | + * file, You can obtain one at: https://www.gnu.org/licenses/gpl-3.0.txt |
| 7 | + */ |
| 8 | +package net.wurstclient.hacks; |
| 9 | + |
| 10 | +import com.mojang.blaze3d.platform.InputConstants; |
| 11 | +import com.mojang.blaze3d.vertex.PoseStack; |
| 12 | +import java.util.ArrayList; |
| 13 | +import java.util.Comparator; |
| 14 | +import java.util.HashSet; |
| 15 | +import java.util.Iterator; |
| 16 | +import java.util.function.Predicate; |
| 17 | +import java.util.stream.Collectors; |
| 18 | + |
| 19 | +import org.lwjgl.glfw.GLFW; |
| 20 | +import net.minecraft.client.gui.Font; |
| 21 | +import net.minecraft.client.gui.GuiGraphicsExtractor; |
| 22 | +import net.minecraft.core.BlockPos; |
| 23 | +import net.minecraft.util.CommonColors; |
| 24 | +import net.minecraft.util.Mth; |
| 25 | +import net.minecraft.world.InteractionHand; |
| 26 | +import net.minecraft.world.phys.AABB; |
| 27 | +import net.minecraft.world.phys.BlockHitResult; |
| 28 | +import net.minecraft.world.phys.Vec3; |
| 29 | +import net.wurstclient.Category; |
| 30 | +import net.wurstclient.SearchTags; |
| 31 | +import net.wurstclient.events.GUIRenderListener; |
| 32 | +import net.wurstclient.events.RenderListener; |
| 33 | +import net.wurstclient.events.UpdateListener; |
| 34 | +import net.wurstclient.hack.DontSaveState; |
| 35 | +import net.wurstclient.hack.Hack; |
| 36 | +import net.wurstclient.settings.CheckboxSetting; |
| 37 | +import net.wurstclient.settings.SliderSetting; |
| 38 | +import net.wurstclient.settings.SliderSetting.ValueDisplay; |
| 39 | +import net.wurstclient.settings.SwingHandSetting; |
| 40 | +import net.wurstclient.settings.SwingHandSetting.SwingHand; |
| 41 | +import net.wurstclient.util.BlockBreaker; |
| 42 | +import net.wurstclient.util.BlockUtils; |
| 43 | +import net.wurstclient.util.RenderUtils; |
| 44 | +import net.wurstclient.util.RotationUtils; |
| 45 | + |
| 46 | +@SearchTags({"area nuker", "AreaNuker", "speed excavator", "SpeedExcavator", |
| 47 | + "area speed nuker"}) |
| 48 | +@DontSaveState |
| 49 | +public final class AreaNukerHack extends Hack |
| 50 | + implements UpdateListener, RenderListener, GUIRenderListener |
| 51 | +{ |
| 52 | + private final SliderSetting range = |
| 53 | + new SliderSetting("Range", 5, 1, 6, 0.05, ValueDisplay.DECIMAL); |
| 54 | + |
| 55 | + private final CheckboxSetting autoSwitchTool = new CheckboxSetting( |
| 56 | + "Auto switch tool", |
| 57 | + "Automatically switch to the best tool in your hotbar for the current" |
| 58 | + + " block even if the AutoTool hack is disabled.", |
| 59 | + false); |
| 60 | + |
| 61 | + private final SwingHandSetting swingHand = new SwingHandSetting( |
| 62 | + SwingHandSetting.genericMiningDescription(this), SwingHand.OFF); |
| 63 | + |
| 64 | + private Step step; |
| 65 | + private BlockPos posStart; |
| 66 | + private BlockPos posEnd; |
| 67 | + private BlockPos posLookingAt; |
| 68 | + private Area area; |
| 69 | + private BlockPos currentBlock; |
| 70 | + |
| 71 | + private boolean prevAutoToolEnabled; |
| 72 | + |
| 73 | + public AreaNukerHack() |
| 74 | + { |
| 75 | + super("AreaNuker"); |
| 76 | + setCategory(Category.BLOCKS); |
| 77 | + addSetting(range); |
| 78 | + addSetting(autoSwitchTool); |
| 79 | + addSetting(swingHand); |
| 80 | + } |
| 81 | + |
| 82 | + @Override |
| 83 | + public String getRenderName() |
| 84 | + { |
| 85 | + if(step == Step.NUKE && area != null && area.blocksList.size() > 0) |
| 86 | + { |
| 87 | + double broken = area.blocksList.size() - area.remainingBlocks; |
| 88 | + int pct = (int)(broken / area.blocksList.size() * 100); |
| 89 | + return getName() + " " + pct + "%"; |
| 90 | + } |
| 91 | + return getName(); |
| 92 | + } |
| 93 | + |
| 94 | + @Override |
| 95 | + protected void onEnable() |
| 96 | + { |
| 97 | + // disable conflicting block-breaking hacks |
| 98 | + WURST.getHax().autoMineHack.setEnabled(false); |
| 99 | + WURST.getHax().excavatorHack.setEnabled(false); |
| 100 | + WURST.getHax().nukerHack.setEnabled(false); |
| 101 | + WURST.getHax().nukerLegitHack.setEnabled(false); |
| 102 | + WURST.getHax().speedNukerHack.setEnabled(false); |
| 103 | + WURST.getHax().tunnellerHack.setEnabled(false); |
| 104 | + WURST.getHax().veinMinerHack.setEnabled(false); |
| 105 | + |
| 106 | + prevAutoToolEnabled = WURST.getHax().autoToolHack.isEnabled(); |
| 107 | + |
| 108 | + step = Step.START_POS; |
| 109 | + posStart = null; |
| 110 | + posEnd = null; |
| 111 | + posLookingAt = null; |
| 112 | + area = null; |
| 113 | + currentBlock = null; |
| 114 | + |
| 115 | + EVENTS.add(UpdateListener.class, this); |
| 116 | + EVENTS.add(RenderListener.class, this); |
| 117 | + EVENTS.add(GUIRenderListener.class, this); |
| 118 | + } |
| 119 | + |
| 120 | + @Override |
| 121 | + protected void onDisable() |
| 122 | + { |
| 123 | + EVENTS.remove(UpdateListener.class, this); |
| 124 | + EVENTS.remove(RenderListener.class, this); |
| 125 | + EVENTS.remove(GUIRenderListener.class, this); |
| 126 | + |
| 127 | + MC.gameMode.stopDestroyBlock(); |
| 128 | + |
| 129 | + posStart = null; |
| 130 | + posEnd = null; |
| 131 | + posLookingAt = null; |
| 132 | + area = null; |
| 133 | + currentBlock = null; |
| 134 | + |
| 135 | + if(!prevAutoToolEnabled && WURST.getHax().autoToolHack.isEnabled()) |
| 136 | + WURST.getHax().autoToolHack.setEnabled(false); |
| 137 | + } |
| 138 | + |
| 139 | + @Override |
| 140 | + public void onUpdate() |
| 141 | + { |
| 142 | + if(step.selectPos) |
| 143 | + handlePositionSelection(); |
| 144 | + else if(step == Step.SCAN_AREA) |
| 145 | + scanArea(); |
| 146 | + else if(step == Step.NUKE) |
| 147 | + nuke(); |
| 148 | + } |
| 149 | + |
| 150 | + private void handlePositionSelection() |
| 151 | + { |
| 152 | + if(getStepPos() != null |
| 153 | + && InputConstants.isKeyDown(MC.getWindow(), GLFW.GLFW_KEY_ENTER)) |
| 154 | + { |
| 155 | + step = Step.values()[step.ordinal() + 1]; |
| 156 | + if(!step.selectPos) |
| 157 | + posLookingAt = null; |
| 158 | + return; |
| 159 | + } |
| 160 | + |
| 161 | + if(MC.hitResult instanceof BlockHitResult bhr) |
| 162 | + { |
| 163 | + posLookingAt = bhr.getBlockPos(); |
| 164 | + |
| 165 | + if(MC.options.keyShift.isDown()) |
| 166 | + posLookingAt = posLookingAt.relative(bhr.getDirection()); |
| 167 | + }else |
| 168 | + posLookingAt = null; |
| 169 | + |
| 170 | + if(posLookingAt != null && MC.options.keyUse.isDown()) |
| 171 | + setStepPos(posLookingAt); |
| 172 | + } |
| 173 | + |
| 174 | + private BlockPos getStepPos() |
| 175 | + { |
| 176 | + return step == Step.START_POS ? posStart : posEnd; |
| 177 | + } |
| 178 | + |
| 179 | + private void setStepPos(BlockPos pos) |
| 180 | + { |
| 181 | + if(step == Step.START_POS) |
| 182 | + posStart = pos; |
| 183 | + else |
| 184 | + posEnd = pos; |
| 185 | + } |
| 186 | + |
| 187 | + private void scanArea() |
| 188 | + { |
| 189 | + if(area == null) |
| 190 | + area = new Area(posStart, posEnd); |
| 191 | + |
| 192 | + // scan in chunks per tick so huge areas don't freeze the game |
| 193 | + for(int i = 0; i < area.scanSpeed && area.iterator.hasNext(); i++) |
| 194 | + { |
| 195 | + area.scannedBlocks++; |
| 196 | + BlockPos pos = area.iterator.next(); |
| 197 | + if(BlockUtils.canBeClicked(pos)) |
| 198 | + { |
| 199 | + area.blocksList.add(pos); |
| 200 | + area.blocksSet.add(pos); |
| 201 | + } |
| 202 | + } |
| 203 | + |
| 204 | + area.progress = (float)area.scannedBlocks / area.totalBlocks; |
| 205 | + |
| 206 | + if(!area.iterator.hasNext()) |
| 207 | + { |
| 208 | + area.remainingBlocks = area.blocksList.size(); |
| 209 | + step = Step.NUKE; |
| 210 | + } |
| 211 | + } |
| 212 | + |
| 213 | + private void nuke() |
| 214 | + { |
| 215 | + // wait for AutoEat to finish eating |
| 216 | + if(WURST.getHax().autoEatHack.isEating()) |
| 217 | + return; |
| 218 | + |
| 219 | + ArrayList<BlockPos> blocks = getValidBlocksInRange(); |
| 220 | + currentBlock = blocks.isEmpty() ? null : blocks.get(0); |
| 221 | + |
| 222 | + if(!blocks.isEmpty()) |
| 223 | + { |
| 224 | + // equip best tool for the closest block |
| 225 | + if(WURST.getHax().autoToolHack.isEnabled()) |
| 226 | + WURST.getHax().autoToolHack.equipIfEnabled(blocks.get(0)); |
| 227 | + else if(autoSwitchTool.isChecked()) |
| 228 | + WURST.getHax().autoToolHack.equipBestTool(blocks.get(0), true, |
| 229 | + true, 0); |
| 230 | + |
| 231 | + // the speed: break every in-range block at once via packet spam |
| 232 | + BlockBreaker.breakBlocksWithPacketSpam(blocks); |
| 233 | + swingHand.swing(InteractionHand.MAIN_HAND); |
| 234 | + }else |
| 235 | + MC.gameMode.stopDestroyBlock(); |
| 236 | + |
| 237 | + // recount remaining blocks; finish when the box is clear |
| 238 | + Predicate<BlockPos> breakable = MC.player.getAbilities().instabuild |
| 239 | + ? BlockUtils::canBeClicked : pos -> BlockUtils.canBeClicked(pos) |
| 240 | + && !BlockUtils.isUnbreakable(pos); |
| 241 | + area.remainingBlocks = |
| 242 | + (int)area.blocksList.parallelStream().filter(breakable).count(); |
| 243 | + |
| 244 | + if(area.remainingBlocks == 0) |
| 245 | + setEnabled(false); |
| 246 | + } |
| 247 | + |
| 248 | + private ArrayList<BlockPos> getValidBlocksInRange() |
| 249 | + { |
| 250 | + Vec3 eyesVec = RotationUtils.getEyesPos(); |
| 251 | + BlockPos eyesBlock = BlockPos.containing(eyesVec); |
| 252 | + double rangeSq = range.getValueSq(); |
| 253 | + int blockRange = range.getValueCeil(); |
| 254 | + |
| 255 | + return BlockUtils.getAllInBoxStream(eyesBlock, blockRange) |
| 256 | + .filter(pos -> pos.distToCenterSqr(eyesVec) <= rangeSq) |
| 257 | + .filter(area.blocksSet::contains).filter(BlockUtils::canBeClicked) |
| 258 | + .filter(pos -> !BlockUtils.isUnbreakable(pos)) |
| 259 | + .sorted( |
| 260 | + Comparator.comparingDouble(pos -> pos.distToCenterSqr(eyesVec))) |
| 261 | + .collect(Collectors.toCollection(ArrayList::new)); |
| 262 | + } |
| 263 | + |
| 264 | + @Override |
| 265 | + public void onRender(PoseStack matrixStack, float partialTicks) |
| 266 | + { |
| 267 | + int black = 0x80000000; |
| 268 | + int gray = 0x26404040; |
| 269 | + int green1 = 0x2600FF00; |
| 270 | + int green2 = 0x4D00FF00; |
| 271 | + |
| 272 | + // confirmed area box |
| 273 | + if(area != null) |
| 274 | + { |
| 275 | + AABB areaBox = new AABB(area.minX, area.minY, area.minZ, |
| 276 | + area.minX + area.sizeX + 1, area.minY + area.sizeY + 1, |
| 277 | + area.minZ + area.sizeZ + 1).deflate(1 / 16.0); |
| 278 | + RenderUtils.drawOutlinedBox(matrixStack, areaBox, black, true); |
| 279 | + |
| 280 | + // scanning sweep highlight |
| 281 | + if(area.progress < 1) |
| 282 | + { |
| 283 | + double scanX = |
| 284 | + Mth.lerp(area.progress, areaBox.minX, areaBox.maxX); |
| 285 | + AABB scanner = areaBox.setMinX(scanX).setMaxX(scanX); |
| 286 | + RenderUtils.drawOutlinedBox(matrixStack, scanner, black, true); |
| 287 | + RenderUtils.drawSolidBox(matrixStack, scanner, green2, true); |
| 288 | + } |
| 289 | + } |
| 290 | + |
| 291 | + // live preview between start and the position being chosen for end |
| 292 | + if(area == null && step == Step.END_POS && posStart != null |
| 293 | + && getStepPos() != null) |
| 294 | + { |
| 295 | + AABB preview = AABB.encapsulatingFullBlocks(posStart, getStepPos()) |
| 296 | + .deflate(1 / 16.0); |
| 297 | + RenderUtils.drawOutlinedBox(matrixStack, preview, black, true); |
| 298 | + } |
| 299 | + |
| 300 | + // already-selected corner positions |
| 301 | + ArrayList<AABB> selectedBoxes = new ArrayList<>(); |
| 302 | + if(posStart != null) |
| 303 | + selectedBoxes.add(new AABB(posStart).deflate(1 / 16.0)); |
| 304 | + if(posEnd != null) |
| 305 | + selectedBoxes.add(new AABB(posEnd).deflate(1 / 16.0)); |
| 306 | + RenderUtils.drawOutlinedBoxes(matrixStack, selectedBoxes, black, false); |
| 307 | + RenderUtils.drawSolidBoxes(matrixStack, selectedBoxes, green1, false); |
| 308 | + |
| 309 | + // block currently under the crosshair during selection |
| 310 | + if(posLookingAt != null) |
| 311 | + { |
| 312 | + AABB box = new AABB(posLookingAt).deflate(1 / 16.0); |
| 313 | + RenderUtils.drawOutlinedBox(matrixStack, box, black, false); |
| 314 | + RenderUtils.drawSolidBox(matrixStack, box, gray, false); |
| 315 | + } |
| 316 | + } |
| 317 | + |
| 318 | + @Override |
| 319 | + public void onRenderGUI(GuiGraphicsExtractor context, float partialTicks) |
| 320 | + { |
| 321 | + String message; |
| 322 | + if(step.selectPos && getStepPos() != null) |
| 323 | + message = "Press enter to confirm, or select a different position."; |
| 324 | + else |
| 325 | + message = step.message; |
| 326 | + |
| 327 | + Font tr = MC.font; |
| 328 | + int msgWidth = tr.width(message); |
| 329 | + |
| 330 | + int msgX1 = context.guiWidth() / 2 - msgWidth / 2; |
| 331 | + int msgX2 = msgX1 + msgWidth + 2; |
| 332 | + int msgY1 = context.guiHeight() / 2 + 1; |
| 333 | + int msgY2 = msgY1 + 10; |
| 334 | + |
| 335 | + context.fill(msgX1, msgY1, msgX2, msgY2, 0x80000000); |
| 336 | + context.text(tr, message, msgX1 + 2, msgY1 + 1, CommonColors.WHITE, |
| 337 | + false); |
| 338 | + } |
| 339 | + |
| 340 | + private enum Step |
| 341 | + { |
| 342 | + START_POS("Select start position.", true), |
| 343 | + END_POS("Select end position.", true), |
| 344 | + SCAN_AREA("Scanning area...", false), |
| 345 | + NUKE("Nuking area...", false); |
| 346 | + |
| 347 | + private final String message; |
| 348 | + private final boolean selectPos; |
| 349 | + |
| 350 | + Step(String message, boolean selectPos) |
| 351 | + { |
| 352 | + this.message = message; |
| 353 | + this.selectPos = selectPos; |
| 354 | + } |
| 355 | + } |
| 356 | + |
| 357 | + private static final class Area |
| 358 | + { |
| 359 | + private final int minX, minY, minZ; |
| 360 | + private final int sizeX, sizeY, sizeZ; |
| 361 | + |
| 362 | + private final int totalBlocks, scanSpeed; |
| 363 | + private final Iterator<BlockPos> iterator; |
| 364 | + |
| 365 | + private int scannedBlocks, remainingBlocks; |
| 366 | + private float progress; |
| 367 | + |
| 368 | + private final ArrayList<BlockPos> blocksList = new ArrayList<>(); |
| 369 | + private final HashSet<BlockPos> blocksSet = new HashSet<>(); |
| 370 | + |
| 371 | + private Area(BlockPos start, BlockPos end) |
| 372 | + { |
| 373 | + minX = Math.min(start.getX(), end.getX()); |
| 374 | + minY = Math.min(start.getY(), end.getY()); |
| 375 | + minZ = Math.min(start.getZ(), end.getZ()); |
| 376 | + |
| 377 | + sizeX = Math.abs(start.getX() - end.getX()); |
| 378 | + sizeY = Math.abs(start.getY() - end.getY()); |
| 379 | + sizeZ = Math.abs(start.getZ() - end.getZ()); |
| 380 | + |
| 381 | + totalBlocks = (sizeX + 1) * (sizeY + 1) * (sizeZ + 1); |
| 382 | + scanSpeed = Mth.clamp(totalBlocks / 30, 1, 16384); |
| 383 | + iterator = BlockUtils.getAllInBox(start, end).iterator(); |
| 384 | + } |
| 385 | + } |
| 386 | +} |
0 commit comments