1313import java .util .ArrayList ;
1414import java .util .Comparator ;
1515import java .util .HashSet ;
16+ import java .util .List ;
1617import java .util .UUID ;
1718import net .minecraft .ChatFormatting ;
1819import net .minecraft .core .BlockPos ;
1920import net .minecraft .network .chat .Component ;
21+ import net .minecraft .sounds .SoundEvents ;
2022import net .minecraft .world .entity .Entity ;
2123import net .minecraft .world .entity .item .ItemEntity ;
2224import net .minecraft .world .item .ItemStack ;
2527import net .minecraft .world .level .block .Block ;
2628import net .minecraft .world .level .block .Blocks ;
2729import net .minecraft .world .level .chunk .LevelChunk ;
30+ import net .minecraft .world .level .chunk .LevelChunkSection ;
2831import net .minecraft .world .phys .AABB ;
2932import net .minecraft .world .phys .Vec3 ;
3033import net .wurstclient .Category ;
@@ -60,6 +63,9 @@ public final class RoofEspHack extends Hack implements UpdateListener,
6063 new CheckboxSetting ("Chat alerts" ,
6164 "Sends a chat alert when the number of RoofESP detections changes." ,
6265 false );
66+ private final CheckboxSetting soundAlerts = new CheckboxSetting (
67+ "Sound alerts" ,
68+ "Plays a sound when the number of RoofESP detections changes." , false );
6369 private final SliderSetting alertCooldown =
6470 new SliderSetting ("Alert cooldown" , "Minimum time between chat alerts." ,
6571 5 , 0 , 60 , 1 , SliderSetting .ValueDisplay .INTEGER .withSuffix ("s" ));
@@ -86,6 +92,13 @@ public final class RoofEspHack extends Hack implements UpdateListener,
8692 "Block render limit" ,
8793 "Maximum amount of roof blocks to process each scan. 0 = unlimited." ,
8894 500 , 0 , 5000 , 1 , SliderSetting .ValueDisplay .INTEGER );
95+ private final CheckboxSetting detectLowerLadders =
96+ new CheckboxSetting ("Detect ladders 118-128" ,
97+ "Also detect ladder blocks between Y=118 and Y=127 in the Nether." ,
98+ false );
99+ private final CheckboxSetting ignoreNaturalRoofBlocks =
100+ new CheckboxSetting ("Ignore natural roof blocks" ,
101+ "Hides natural roof noise blocks (red/brown mushrooms)." , true );
89102
90103 private final ArrayList <AABB > boxes = new ArrayList <>();
91104 private final ArrayList <Vec3 > tracerEnds = new ArrayList <>();
@@ -109,13 +122,16 @@ public RoofEspHack()
109122 addSetting (highlightFill );
110123 addSetting (tracerFlash );
111124 addSetting (chatAlerts );
125+ addSetting (soundAlerts );
112126 addSetting (alertCooldown );
113127 addSetting (ignoreOwnDrops );
114128 addSetting (showCountInHackList );
115129 addSetting (stickyArea );
116130 addSetting (area );
117131 addSetting (blockScanInterval );
118132 addSetting (blockRenderLimit );
133+ addSetting (detectLowerLadders );
134+ addSetting (ignoreNaturalRoofBlocks );
119135 }
120136
121137 @ Override
@@ -172,7 +188,7 @@ public void onUpdate()
172188 targets .addAll (cachedBlockTargets );
173189 collectRoofItems (targets );
174190
175- targets .sort (Comparator .comparingDouble (t -> t . distanceSq ));
191+ targets .sort (Comparator .comparingDouble (this :: distanceSqToPlayer ));
176192 int limit = getEffectiveGlobalEspLimit ();
177193 if (limit > 0 && targets .size () > limit )
178194 targets .subList (limit , targets .size ()).clear ();
@@ -202,44 +218,84 @@ private void updateBlockCache()
202218
203219 private void collectRoofBlocks (ArrayList <Target > targets )
204220 {
205- int minY = Math .max (ROOF_Y , MC .level .getMinY ());
221+ if (MC .level == null )
222+ return ;
223+
224+ int worldMinY = MC .level .getMinY ();
225+ int minY = Math .max (ROOF_Y , worldMinY );
226+ int ladderMinY = Math .max (ROOF_Y - 10 , MC .level .getMinY ());
227+ boolean includeLowerLadders = detectLowerLadders .isChecked ();
206228 int maxYExclusive = MC .level .getMaxY ();
207- if (minY >= maxYExclusive )
229+ if (minY >= maxYExclusive && ! includeLowerLadders )
208230 return ;
209231 int cap = blockRenderLimit .getValueI ();
210232 HashSet <Long > dedupe = new HashSet <>();
211233
212- for (LevelChunk chunk : getChunksInScanArea ())
234+ for (LevelChunk chunk : getChunksInScanAreaSortedByDistance ())
213235 {
214236 if (cap > 0 && targets .size () >= cap )
215237 break ;
216238
239+ LevelChunkSection [] sections = chunk .getSections ();
240+ if (sections == null || sections .length == 0 )
241+ continue ;
242+
217243 BlockPos .MutableBlockPos pos = new BlockPos .MutableBlockPos ();
218- int xStart = chunk .getPos ().getMinBlockX ();
219- int zStart = chunk .getPos ().getMinBlockZ ();
220- for (int y = minY ; y < maxYExclusive ; y ++)
244+ int scanMinY = includeLowerLadders ? ladderMinY : minY ;
245+ int firstSectionY = chunk .getMinY () >> 4 ;
246+ int minSectionIndex = Math .max (0 , (scanMinY >> 4 ) - firstSectionY );
247+ int maxSectionIndex = Math .min (sections .length - 1 ,
248+ ((maxYExclusive - 1 ) >> 4 ) - firstSectionY );
249+ for (int sectionIndex =
250+ minSectionIndex ; sectionIndex <= maxSectionIndex ; sectionIndex ++)
221251 {
252+ LevelChunkSection section = sections [sectionIndex ];
253+ if (section == null || section .hasOnlyAir ())
254+ continue ;
255+
222256 if (cap > 0 && targets .size () >= cap )
223257 break ;
224- for (int x = xStart ; x < xStart + 16 ; x ++)
258+
259+ int baseY = (firstSectionY + sectionIndex ) << 4 ;
260+ for (int ly = 0 ; ly < 16 ; ly ++)
225261 {
262+ int y = baseY + ly ;
263+ if (y < scanMinY || y >= maxYExclusive )
264+ continue ;
265+
226266 if (cap > 0 && targets .size () >= cap )
227267 break ;
228- for (int z = zStart ; z < zStart + 16 ; z ++)
268+ for (int lx = 0 ; lx < 16 ; lx ++)
229269 {
230- pos .set (x , y , z );
231- Block block = chunk .getBlockState (pos ).getBlock ();
232- if (block == Blocks .AIR || block == Blocks .CAVE_AIR
233- || block == Blocks .VOID_AIR )
234- continue ;
235- if (block == Blocks .RED_MUSHROOM
236- || block == Blocks .BROWN_MUSHROOM )
237- continue ;
238- long key = pos .asLong ();
239- if (!dedupe .add (key ))
240- continue ;
241- AABB box = new AABB (pos );
242- targets .add (new Target (box , box .getCenter ()));
270+ if (cap > 0 && targets .size () >= cap )
271+ break ;
272+ int x = chunk .getPos ().getMinBlockX () + lx ;
273+ for (int lz = 0 ; lz < 16 ; lz ++)
274+ {
275+ pos .set (x , y , chunk .getPos ().getMinBlockZ () + lz );
276+ Block block =
277+ section .getBlockState (lx , ly , lz ).getBlock ();
278+ if (y < ROOF_Y )
279+ {
280+ if (!includeLowerLadders
281+ || block != Blocks .LADDER )
282+ continue ;
283+ }else
284+ {
285+ if (block == Blocks .AIR
286+ || block == Blocks .CAVE_AIR
287+ || block == Blocks .VOID_AIR )
288+ continue ;
289+ if (ignoreNaturalRoofBlocks .isChecked ()
290+ && isNaturalRoofBlock (block ))
291+ continue ;
292+ }
293+ long key = pos .asLong ();
294+ if (!dedupe .add (key ))
295+ continue ;
296+ AABB box = new AABB (pos );
297+ targets .add (new Target (box , box .getCenter ()));
298+ }
243299 }
244300 }
245301 }
@@ -268,6 +324,26 @@ private Iterable<LevelChunk> getChunksInScanArea()
268324 return chunks ;
269325 }
270326
327+ private List <LevelChunk > getChunksInScanAreaSortedByDistance ()
328+ {
329+ ArrayList <LevelChunk > chunks = new ArrayList <>();
330+ for (LevelChunk chunk : getChunksInScanArea ())
331+ chunks .add (chunk );
332+ if (anchorChunk == null )
333+ return chunks ;
334+ chunks .sort (Comparator .comparingInt (chunk -> {
335+ int dx = chunk .getPos ().x () - anchorChunk .x ();
336+ int dz = chunk .getPos ().z () - anchorChunk .z ();
337+ return dx * dx + dz * dz ;
338+ }));
339+ return chunks ;
340+ }
341+
342+ private boolean isNaturalRoofBlock (Block block )
343+ {
344+ return block == Blocks .RED_MUSHROOM || block == Blocks .BROWN_MUSHROOM ;
345+ }
346+
271347 private int getChunkRange (ChunkAreaSetting .ChunkArea selectedArea )
272348 {
273349 String enumName = selectedArea .name (); // A11 -> 11x11
@@ -388,29 +464,45 @@ private int getEffectiveGlobalEspLimit()
388464 .getEffectiveGlobalEspRenderLimit ();
389465 }
390466
467+ private double distanceSqToPlayer (Target target )
468+ {
469+ return MC .player == null ? Double .MAX_VALUE
470+ : MC .player .distanceToSqr (target .end ());
471+ }
472+
391473 private void sendAlertIfNeeded (int currentCount )
392474 {
393- if (!chatAlerts .isChecked ())
475+ boolean chatEnabled = chatAlerts .isChecked ();
476+ boolean soundEnabled = soundAlerts .isChecked ();
477+ if (!chatEnabled && !soundEnabled )
394478 {
395479 lastAlertCount = currentCount ;
396480 return ;
397481 }
482+
398483 if (currentCount == lastAlertCount )
399484 return ;
485+
400486 long now = System .currentTimeMillis ();
401487 long cooldownMs = alertCooldown .getValueI () * 1000L ;
402488 if (now - lastAlertMs < cooldownMs )
403489 return ;
490+
404491 lastAlertMs = now ;
405492 lastAlertCount = currentCount ;
406- ChatUtils .component (Component .literal ("[RoofESP] " )
407- .withStyle (ChatFormatting .DARK_RED )
408- .append (
409- Component .literal ("Detected " ).withStyle (ChatFormatting .GRAY ))
410- .append (Component .literal (Integer .toString (currentCount ))
411- .withStyle (ChatFormatting .RED ))
412- .append (Component .literal (" roof targets at/above Y=128." )
413- .withStyle (ChatFormatting .GRAY )));
493+
494+ if (chatEnabled )
495+ ChatUtils .component (Component .literal ("[RoofESP] " )
496+ .withStyle (ChatFormatting .DARK_RED )
497+ .append (Component .literal ("Detected " )
498+ .withStyle (ChatFormatting .GRAY ))
499+ .append (Component .literal (Integer .toString (currentCount ))
500+ .withStyle (ChatFormatting .RED ))
501+ .append (Component .literal (" roof targets at/above Y=128." )
502+ .withStyle (ChatFormatting .GRAY )));
503+
504+ if (soundEnabled && MC .player != null )
505+ MC .player .playSound (SoundEvents .EXPERIENCE_ORB_PICKUP , 0.7F , 1.35F );
414506 }
415507
416508 @ Override
@@ -445,12 +537,8 @@ public void onRender(PoseStack matrixStack, float partialTicks)
445537 }
446538 }
447539
448- private record Target (AABB box , Vec3 end , double distanceSq )
540+ private record Target (AABB box , Vec3 end )
449541 {
450- private Target (AABB box , Vec3 end )
451- {
452- this (box , end , MC .player == null ? Double .MAX_VALUE
453- : MC .player .distanceToSqr (end ));
454- }
542+ // compact immutable render target
455543 }
456544}
0 commit comments