Skip to content

Commit d2d4470

Browse files
committed
Add AreaNuker: instantly break every in reach block inside a selected box while moving through it
1 parent f69067f commit d2d4470

3 files changed

Lines changed: 388 additions & 0 deletions

File tree

src/main/java/net/wurstclient/hack/HackList.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ public final class HackList implements UpdateListener
144144
public final EntityCountHack entityCountHack = new EntityCountHack();
145145
public final EntityControlHack entityControlHack = new EntityControlHack();
146146
public final ExcavatorHack excavatorHack = new ExcavatorHack();
147+
public final AreaNukerHack areaNukerHack = new AreaNukerHack();
147148
public final ExtraElytraHack extraElytraHack = new ExtraElytraHack();
148149
public final FancyChatHack fancyChatHack = new FancyChatHack();
149150
public final FakeLagHack fakeLagHack = new FakeLagHack();
Lines changed: 386 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,386 @@
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

Comments
 (0)