2323import net .wurstclient .hack .Hack ;
2424import net .wurstclient .settings .CheckboxSetting ;
2525import net .wurstclient .util .BlockUtils ;
26+ import net .wurstclient .util .ChatUtils ;
2627import net .wurstclient .settings .SliderSetting ;
2728import net .wurstclient .settings .SliderSetting .ValueDisplay ;
2829
@@ -72,56 +73,112 @@ public final class AntiVoidHack extends Hack implements UpdateListener
7273 private boolean rescueActive ;
7374 private boolean jumpWasDown ;
7475 private int lastHurtTimeSeen ;
75- private long lastAutoRescueMs ;
76+ private boolean flightEnabledByAntiVoid ;
7677
7778 // Always-on update listener (registered in constructor)
7879 private final UpdateListener alwaysListener = new UpdateListener ()
7980 {
81+ private boolean hurtAlerted ;
82+ private boolean launchesActive ;
83+
8084 @ Override
8185 public void onUpdate ()
8286 {
8387 LocalPlayer p = MC .player ;
8488 if (p == null )
8589 return ;
86- // Avoid enabling during early startup before other hacks restore
8790 if (MC .level == null || MC .level .getGameTime () < STARTUP_GRACE_TICKS )
8891 {
8992 lastHurtTimeSeen = p .hurtTime ;
9093 return ;
9194 }
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 )
95+ if (p .connection == null )
96+ {
97+ lastHurtTimeSeen = p .hurtTime ;
98+ return ;
99+ }
100+
101+ // Auto-enable on out_of_world damage
102+ if (autoEnableOnOutOfWorld .isChecked ()
103+ && p .hurtTime > lastHurtTimeSeen
104+ && isOutOfWorldDamage (p .getLastDamageSource ()))
96105 {
97- DamageSource src = p .getLastDamageSource ();
98- if (isOutOfWorldDamage (src ))
106+ if (!isEnabled ())
107+ {
108+ setEnabled (true );
109+ ChatUtils .message ("Void damage! Enabled AntiVoid." );
110+ }
111+ if (useFlight .isChecked ()
112+ && !WURST .getHax ().flightHack .isEnabled ())
99113 {
100- long now = System .currentTimeMillis ();
101- if (now - lastAutoRescueMs > 500 )
102- {
103- if (!isEnabled ())
104- setEnabled (true );
105- rescueToFixedLevel (p );
106- lastAutoRescueMs = now ;
107- }
114+ WURST .getHax ().flightHack .setEnabled (true );
115+ flightEnabledByAntiVoid = true ;
108116 }
117+ hurtAlerted = false ;
118+ launchesActive = true ;
109119 }
110- // Height-based auto-enable at fixed thresholds while falling
120+
121+ // Height-based auto-enable
111122 if (autoEnableByHeight .isChecked () && MC .level != null )
112123 {
113124 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 ())
125+ if (p .getDeltaMovement ().y < 0 && p .fallDistance > 2F
126+ && y <= fixedVoidLevel () && !isEnabled ())
127+ {
118128 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 );
129+ ChatUtils .message ("Below void threshold (Y=" + (int )y
130+ + "), enabled AntiVoid." );
131+ }
123132 }
124- lastHurtTimeSeen = ht ;
133+
134+ // Launch: every tick AntiVoid is on and player is below
135+ // safe Y, force-send flying position packets to move up.
136+ // Flying packets (onGround=false) are accepted by servers
137+ // unlike grounded teleports.
138+ if (!isEnabled ())
139+ {
140+ launchesActive = false ;
141+ lastHurtTimeSeen = p .hurtTime ;
142+ return ;
143+ }
144+
145+ if (!launchesActive )
146+ {
147+ lastHurtTimeSeen = p .hurtTime ;
148+ return ;
149+ }
150+
151+ double safeY = rescueTargetY ();
152+ boolean stillDamaged =
153+ p .hurtTime > 0 || isOutOfWorldDamage (p .getLastDamageSource ());
154+ if (!stillDamaged && p .getY () >= safeY )
155+ {
156+ launchesActive = false ;
157+ lastHurtTimeSeen = p .hurtTime ;
158+ return ;
159+ }
160+
161+ // Launch upward: move the player client-side AND send
162+ // flying position packets the server will accept.
163+ double targetY = p .getY () + 0.6 ;
164+ p .setPos (p .getX (), targetY , p .getZ ());
165+ p .setDeltaMovement (p .getDeltaMovement ().x , 0.42 ,
166+ p .getDeltaMovement ().z );
167+ p .fallDistance = 0 ;
168+
169+ // Send as flying (onGround=false) — server trusts these
170+ p .connection .send (new ServerboundMovePlayerPacket .Pos (p .getX (),
171+ targetY , p .getZ (), false , p .horizontalCollision ));
172+
173+ if (!hurtAlerted )
174+ {
175+ ChatUtils .message (
176+ "Launching out of void (Y=" + (int )targetY + ")..." );
177+ hurtAlerted = true ;
178+ }
179+
180+ launchesActive = true ;
181+ lastHurtTimeSeen = p .hurtTime ;
125182 }
126183 };
127184
@@ -148,12 +205,16 @@ protected void onEnable()
148205 airWalkY = Double .NaN ;
149206 rescueActive = false ;
150207 jumpWasDown = false ;
208+ flightEnabledByAntiVoid = false ;
151209 EVENTS .add (UpdateListener .class , this );
152210 }
153211
154212 @ Override
155213 protected void onDisable ()
156214 {
215+ if (flightEnabledByAntiVoid && WURST .getHax ().flightHack .isEnabled ())
216+ WURST .getHax ().flightHack .setEnabled (false );
217+ flightEnabledByAntiVoid = false ;
157218 EVENTS .remove (UpdateListener .class , this );
158219 lastSafePos = null ;
159220 airWalkActive = false ;
@@ -183,9 +244,6 @@ public void onUpdate()
183244 if (!airWalkActive && (player .onGround () || player .isInWater ()
184245 || player .isInLava () || player .onClimbable ()))
185246 rescueActive = false ;
186-
187- // Do not force-disable here; alwaysListener handles
188- // flight-vs-threshold.
189247
190248 if (player .onGround () && !player .isInWater () && !player .isInLava ())
191249 lastSafePos = player .position ();
@@ -199,35 +257,60 @@ public void onUpdate()
199257 return ;
200258 }
201259
260+ // Detect falling into void/lava
202261 if (player .getDeltaMovement ().y >= 0 || player .fallDistance <= 2F )
203262 return ;
204263
205- if (!isOverVoid (player ) && !isOverLava (player ))
264+ boolean overVoid = isOverVoid (player );
265+ boolean overLava = isOverLava (player );
266+
267+ if (!overVoid && !overLava )
206268 return ;
207269
270+ // Alert on rescue
271+ {
272+ String cause = overVoid ? "void" : "lava" ;
273+ ChatUtils .message ("Falling into " + cause + " (Y="
274+ + (int )player .getY () + "), rescuing..." );
275+ }
276+
208277 if (useFlight .isChecked ())
209278 {
210- // Hand off to Flight and stop our intervention
211- rescueActive = true ;
212279 var hax = WURST .getHax ();
213280 if (!hax .flightHack .isEnabled ())
281+ {
214282 hax .flightHack .setEnabled (true );
283+ ChatUtils .message ("Enabled Flight to escape "
284+ + (overVoid ? "void" : "lava" ) + "." );
285+ }
286+ rescueActive = true ;
215287 return ;
216288 }
217289
218290 if (useAirWalk .isChecked ())
219291 {
220- airWalkActive = true ;
221- rescueActive = true ;
222- airWalkY = player .getY ();
292+ if (!airWalkActive )
293+ {
294+ ChatUtils .message ("Enabled AirWalk to escape "
295+ + (overVoid ? "void" : "lava" ) + "." );
296+ airWalkActive = true ;
297+ rescueActive = true ;
298+ airWalkY = player .getY ();
299+ }
223300 applyAirWalk (player );
224301 return ;
225302 }
226303
304+ // Fallback: rubberband to last safe position
227305 if (lastSafePos == null )
228306 return ;
229307
230- rescueActive = true ;
308+ if (!rescueActive )
309+ {
310+ ChatUtils
311+ .message ("No safe Y — rubberbanding to last ground position." );
312+ rescueActive = true ;
313+ }
231314 player .setDeltaMovement (0 , 0 , 0 );
232315 player .fallDistance = 0 ;
233316 player .setOnGround (true );
@@ -272,14 +355,6 @@ private boolean isBackOnSurface(LocalPlayer player)
272355 return BlockUtils .getBlockCollisions (checkBox ).findAny ().isPresent ();
273356 }
274357
275- private boolean isFlyingHackEnabled ()
276- {
277- return WURST .getHax ().flightHack .isEnabled ()
278- || WURST .getHax ().creativeFlightHack .isEnabled ()
279- || WURST .getHax ().elytraFlightHack .isEnabled ()
280- || WURST .getHax ().jetpackHack .isEnabled ();
281- }
282-
283358 private boolean isOverVoid (LocalPlayer player )
284359 {
285360 double voidY = fixedVoidLevel ();
@@ -357,27 +432,13 @@ private double fixedVoidLevel()
357432
358433 // No height band method needed; using fixed thresholds.
359434
360- private void rescueToFixedLevel (LocalPlayer player )
435+ /**
436+ * Returns a safe Y level above void damage based on fixedVoidLevel().
437+ * Overworld: -117 (4 blocks above -121 damage), Nether/End: -57.
438+ */
439+ private double rescueTargetY ()
361440 {
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- }
441+ return fixedVoidLevel () + 10.0 ;
381442 }
382443
383444 private boolean isOutOfWorldDamage (DamageSource src )
0 commit comments