1515import net .minecraft .world .level .block .state .BlockState ;
1616import net .minecraft .world .phys .AABB ;
1717import net .minecraft .world .phys .Vec3 ;
18+ import net .minecraft .world .damagesource .DamageSource ;
19+ import java .util .Locale ;
1820import net .wurstclient .Category ;
1921import net .wurstclient .SearchTags ;
2022import net .wurstclient .events .UpdateListener ;
2123import net .wurstclient .hack .Hack ;
2224import net .wurstclient .settings .CheckboxSetting ;
25+ import net .wurstclient .util .BlockUtils ;
2326import net .wurstclient .settings .SliderSetting ;
2427import net .wurstclient .settings .SliderSetting .ValueDisplay ;
25- import net .wurstclient .util .BlockUtils ;
2628
2729@ SearchTags ({"anti void" , "void" })
2830public final class AntiVoidHack extends Hack implements UpdateListener
2931{
30- private final SliderSetting voidLevel = new SliderSetting ("Void level" ,
31- "Y level where the void begins." , 0 , -256 , 0 , 1 , ValueDisplay .INTEGER );
32-
32+ private static final long STARTUP_GRACE_TICKS = 40L ; // ~2s after join
3333 private final CheckboxSetting useAirWalk = new CheckboxSetting (
3434 "Use AirWalk" ,
3535 "Prevents falling into the void/lava by air-walking instead of rubberbanding." ,
@@ -39,19 +39,105 @@ public final class AntiVoidHack extends Hack implements UpdateListener
3939 "Detect lava" ,
4040 "Also prevents falling into lava when it is directly below you." , true );
4141
42+ private final CheckboxSetting gateAtVoidLevel = new CheckboxSetting (
43+ "Respond only at void level" ,
44+ "Only trigger when reaching the standard void level (End: -60, Others: -125).\n "
45+ + "For lava, triggers one block above the lava surface." ,
46+ false );
47+
48+ private final CheckboxSetting useFlight = new CheckboxSetting ("Use Flight" ,
49+ "When triggered, enable Flight instead of rubberbanding/AirWalk." ,
50+ false );
51+
52+ private final CheckboxSetting autoEnableOnOutOfWorld =
53+ new CheckboxSetting ("Auto-enable on out_of_world" ,
54+ "Automatically enables AntiVoid and rescues to the fixed void level"
55+ + " when taking out_of_world damage." ,
56+ false );
57+
58+ private final SliderSetting lavaBufferBlocks = new SliderSetting (
59+ "Lava buffer (blocks)" , 2 , 0 , 12 , 1 , ValueDisplay .INTEGER );
60+
61+ // Fixed thresholds are used; no per-dimension sliders.
62+
63+ private final CheckboxSetting autoEnableByHeight = new CheckboxSetting (
64+ "Auto-enable by height" ,
65+ "Automatically enables AntiVoid when your Y is within a safety band below 0.\n "
66+ + "Defaults: End -65..0, Others -125..-60." ,
67+ true );
68+
4269 private Vec3 lastSafePos ;
4370 private boolean airWalkActive ;
4471 private double airWalkY ;
4572 private boolean rescueActive ;
4673 private boolean jumpWasDown ;
74+ private int lastHurtTimeSeen ;
75+ private long lastAutoRescueMs ;
76+
77+ // Always-on update listener (registered in constructor)
78+ private final UpdateListener alwaysListener = new UpdateListener ()
79+ {
80+ @ Override
81+ public void onUpdate ()
82+ {
83+ LocalPlayer p = MC .player ;
84+ if (p == null )
85+ return ;
86+ // Avoid enabling during early startup before other hacks restore
87+ if (MC .level == null || MC .level .getGameTime () < STARTUP_GRACE_TICKS )
88+ {
89+ lastHurtTimeSeen = p .hurtTime ;
90+ return ;
91+ }
92+ // We still allow auto-enable at thresholds even if flight is
93+ // active.
94+ int ht = p .hurtTime ;
95+ if (autoEnableOnOutOfWorld .isChecked () && ht > lastHurtTimeSeen )
96+ {
97+ DamageSource src = p .getLastDamageSource ();
98+ if (isOutOfWorldDamage (src ))
99+ {
100+ long now = System .currentTimeMillis ();
101+ if (now - lastAutoRescueMs > 500 )
102+ {
103+ if (!isEnabled ())
104+ setEnabled (true );
105+ rescueToFixedLevel (p );
106+ lastAutoRescueMs = now ;
107+ }
108+ }
109+ }
110+ // Height-based auto-enable at fixed thresholds while falling
111+ if (autoEnableByHeight .isChecked () && MC .level != null )
112+ {
113+ double y = p .getY ();
114+ boolean falling =
115+ p .getDeltaMovement ().y < 0 && p .fallDistance > 2F ;
116+ double threshold = fixedVoidLevel ();
117+ if (falling && y <= threshold && !isEnabled ())
118+ setEnabled (true );
119+ // If flight is active and we're above threshold, keep AntiVoid
120+ // off
121+ if (isFlyingHackEnabled () && y > threshold && isEnabled ())
122+ setEnabled (false );
123+ }
124+ lastHurtTimeSeen = ht ;
125+ }
126+ };
47127
48128 public AntiVoidHack ()
49129 {
50130 super ("AntiVoid" );
51131 setCategory (Category .MOVEMENT );
52- addSetting (voidLevel );
53132 addSetting (useAirWalk );
54133 addSetting (detectLava );
134+ addSetting (gateAtVoidLevel );
135+ addSetting (useFlight );
136+ addSetting (autoEnableOnOutOfWorld );
137+ addSetting (autoEnableByHeight );
138+ addSetting (lavaBufferBlocks );
139+ // Always-on listener to catch out_of_world damage even when disabled
140+ EVENTS .add (UpdateListener .class , alwaysListener );
55141 }
56142
57143 @ Override
@@ -97,12 +183,9 @@ public void onUpdate()
97183 if (!airWalkActive && (player .onGround () || player .isInWater ()
98184 || player .isInLava () || player .onClimbable ()))
99185 rescueActive = false ;
100-
101- if (rescueActive && isFlyingHackEnabled ())
102- {
103- setEnabled (false );
104- return ;
105- }
186+
187+ // Do not force-disable here; alwaysListener handles
188+ // flight-vs-threshold.
106189
107190 if (player .onGround () && !player .isInWater () && !player .isInLava ())
108191 lastSafePos = player .position ();
@@ -122,6 +205,16 @@ public void onUpdate()
122205 if (!isOverVoid (player ) && !isOverLava (player ))
123206 return ;
124207
208+ if (useFlight .isChecked ())
209+ {
210+ // Hand off to Flight and stop our intervention
211+ rescueActive = true ;
212+ var hax = WURST .getHax ();
213+ if (!hax .flightHack .isEnabled ())
214+ hax .flightHack .setEnabled (true );
215+ return ;
216+ }
217+
125218 if (useAirWalk .isChecked ())
126219 {
127220 airWalkActive = true ;
@@ -175,7 +268,6 @@ private boolean isBackOnSurface(LocalPlayer player)
175268 {
176269 if (player .onGround ())
177270 return true ;
178-
179271 AABB checkBox = player .getBoundingBox ().move (0 , -0.05 , 0 );
180272 return BlockUtils .getBlockCollisions (checkBox ).findAny ().isPresent ();
181273 }
@@ -190,7 +282,9 @@ private boolean isFlyingHackEnabled()
190282
191283 private boolean isOverVoid (LocalPlayer player )
192284 {
193- double voidY = voidLevel .getValue ();
285+ double voidY = fixedVoidLevel ();
286+ if (gateAtVoidLevel .isChecked () && player .getY () > voidY )
287+ return false ;
194288 if (player .getY () <= voidY && !player .isInWater () && !player .isInLava ())
195289 return true ;
196290
@@ -218,25 +312,83 @@ private boolean isOverLava(LocalPlayer player)
218312 {
219313 if (!detectLava .isChecked ())
220314 return false ;
221-
315+ Integer lavaY = lavaYBelow (player );
316+ if (lavaY == null )
317+ return false ;
318+ double buffer = lavaBufferBlocks .getValue ();
319+ return player .getY () <= lavaY + buffer ;
320+ }
321+
322+ private Integer lavaYBelow (LocalPlayer player )
323+ {
222324 int x = player .getBlockX ();
223325 int z = player .getBlockZ ();
224326 int startY = player .getBlockY ();
225327 int minY = MC .level .getMinY ();
226-
227328 BlockPos .MutableBlockPos pos = new BlockPos .MutableBlockPos ();
228329 for (int y = startY ; y >= minY ; y --)
229330 {
230331 pos .set (x , y , z );
231332 BlockState state = MC .level .getBlockState (pos );
232-
233333 if (!state .getFluidState ().isEmpty ())
234- return state .getFluidState ().is (FluidTags .LAVA );
235-
334+ {
335+ if (state .getFluidState ().is (FluidTags .LAVA ))
336+ return y ;
337+ return null ; // other fluid blocks detection stops search
338+ }
236339 if (!state .isAir ())
237- return false ;
340+ return null ; // solid found before lava
238341 }
239-
240- return false ;
342+ return null ;
343+ }
344+
345+ private double fixedVoidLevel ()
346+ {
347+ if (MC .level == null )
348+ return -120.0 ;
349+ String key = MC .level .dimension ().identifier ().getPath ();
350+ if ("the_end" .equals (key ))
351+ return -60.0 ;
352+ if ("the_nether" .equals (key ))
353+ return -60.0 ;
354+ // Overworld
355+ return -120.0 ;
356+ }
357+
358+ // No height band method needed; using fixed thresholds.
359+
360+ private void rescueToFixedLevel (LocalPlayer player )
361+ {
362+ double targetY = fixedVoidLevel () + 1.0 ;
363+ Vec3 here = player .position ();
364+ player .setDeltaMovement (0 , 0 , 0 );
365+ player .fallDistance = 0 ;
366+ player .setOnGround (true );
367+ player .setPos (here .x , targetY , here .z );
368+ if (player .connection != null )
369+ {
370+ player .connection .send (new ServerboundMovePlayerPacket .Pos (here .x ,
371+ targetY , here .z , true , player .horizontalCollision ));
372+ }
373+ lastSafePos = new Vec3 (here .x , targetY , here .z );
374+ // If configured, immediately hold the player using AirWalk after rescue
375+ if (useAirWalk .isChecked ())
376+ {
377+ airWalkActive = true ;
378+ rescueActive = true ;
379+ airWalkY = targetY ;
380+ }
381+ }
382+
383+ private boolean isOutOfWorldDamage (DamageSource src )
384+ {
385+ if (src == null )
386+ return false ;
387+ String id = src .getMsgId ();
388+ if (id == null )
389+ return false ;
390+ String norm = id .toLowerCase (Locale .ROOT ).replaceAll ("[^a-z]" , "" );
391+ // Accept common forms: out_of_world, outOfWorld, minecraft:out_of_world
392+ return norm .endsWith ("outofworld" );
241393 }
242394}
0 commit comments