3131import net .wurstclient .hack .Hack ;
3232import net .wurstclient .settings .ButtonSetting ;
3333import net .wurstclient .settings .CheckboxSetting ;
34+ import net .wurstclient .settings .EnumSetting ;
3435import net .wurstclient .settings .FileSetting ;
3536import net .wurstclient .settings .SliderSetting ;
3637import net .wurstclient .settings .SliderSetting .ValueDisplay ;
3738import net .wurstclient .settings .TextFieldSetting ;
39+ import net .wurstclient .settings .ChunkAreaSetting ;
3840import net .wurstclient .util .ChatUtils ;
3941import net .wurstclient .util .MathUtils ;
42+ import net .minecraft .core .registries .BuiltInRegistries ;
43+ import net .minecraft .world .entity .Mob ;
44+ import net .minecraft .world .entity .item .ItemEntity ;
45+ import net .minecraft .world .level .block .state .BlockState ;
46+ import net .wurstclient .util .chunk .ChunkSearcherCoordinator ;
47+ import net .wurstclient .util .chunk .ChunkSearcher .Result ;
4048
4149@ SearchTags ({"auto fly" , "autofly" , "waypoint fly" , "auto flight" })
4250public final class AutoFlyHack extends Hack
4351 implements UpdateListener , GUIRenderListener , RenderListener
4452{
53+ private static final int STOP_SCAN_COOLDOWN_TICKS = 10 ;
54+ private static final ChunkAreaSetting .ChunkArea STOP_BLOCK_AREA =
55+ ChunkAreaSetting .ChunkArea .A65 ;
56+
57+ public static enum StopOnType
58+ {
59+ OFF ("Off" ),
60+ MOBS ("Mobs" ),
61+ BLOCKS ("Blocks" ),
62+ ITEMS ("Items" ),
63+ END_PORTAL ("End portal" ),
64+ NETHER_PORTAL ("Nether portal" );
65+
66+ private final String name ;
67+
68+ StopOnType (String name )
69+ {
70+ this .name = name ;
71+ }
72+
73+ @ Override
74+ public String toString ()
75+ {
76+ return name ;
77+ }
78+ }
79+
4580 private final TextFieldSetting waypointText = new TextFieldSetting (
4681 "Waypoints" ,
4782 "Waypoints list. Format: x y z or x z (no Y). Separate by ';' or new lines." ,
@@ -107,6 +142,15 @@ public final class AutoFlyHack extends Hack
107142 new CheckboxSetting ("Disable AutoFly on arrival" ,
108143 "Turns off AutoFly when it reaches a waypoint." , false );
109144
145+ private final EnumSetting <StopOnType > stopOn = new EnumSetting <>("Stop on" ,
146+ "Stop AutoFly if it detects something while flying." ,
147+ StopOnType .values (), StopOnType .OFF );
148+
149+ private final TextFieldSetting stopKeyword = new TextFieldSetting (
150+ "Stop keyword" ,
151+ "Keyword to match against the selected Stop on type (ignored for portals)." ,
152+ "" );
153+
110154 private final List <AutoFlyTarget > targets = new ArrayList <>();
111155 private AutoFlyTarget currentTarget ;
112156 private int currentIndex = -1 ;
@@ -153,6 +197,10 @@ public final class AutoFlyHack extends Hack
153197 private boolean verticalAssistActive ;
154198
155199 private boolean closeHorizLatched ;
200+ private int stopScanCooldown ;
201+ private ChunkSearcherCoordinator stopBlockCoordinator ;
202+ private StopOnType stopBlockCoordinatorType ;
203+ private String stopBlockCoordinatorKeyword ;
156204
157205 public AutoFlyHack ()
158206 {
@@ -176,6 +224,8 @@ public AutoFlyHack()
176224 addSetting (allowManualAdjust );
177225 addSetting (disableFlightOnArrival );
178226 addSetting (disableAutoFlyOnArrival );
227+ addSetting (stopOn );
228+ addSetting (stopKeyword );
179229 }
180230
181231 @ Override
@@ -234,6 +284,10 @@ protected void onEnable()
234284 currentIndex = -1 ;
235285 currentTarget = null ;
236286 closeHorizLatched = false ;
287+ stopScanCooldown = 0 ;
288+ stopBlockCoordinator = null ;
289+ stopBlockCoordinatorType = null ;
290+ stopBlockCoordinatorKeyword = null ;
237291 selectNextTarget (false );
238292 flightWasEnabled = WURST .getHax ().flightHack .isEnabled ();
239293 savedFlightSpeed = -1 ;
@@ -317,6 +371,9 @@ public void onUpdate()
317371 }
318372 }
319373
374+ if (checkStopOn ())
375+ return ;
376+
320377 if (allowManualAdjust .isChecked () && isManualInputActive ())
321378 {
322379 beginManualAdjust (MC .player .position ());
@@ -472,6 +529,197 @@ public void onUpdate()
472529 }
473530 }
474531
532+ private boolean checkStopOn ()
533+ {
534+ StopOnType type = stopOn .getSelected ();
535+ if (type == null || type == StopOnType .OFF )
536+ return false ;
537+
538+ if (MC .player == null || MC .level == null )
539+ return false ;
540+
541+ switch (type )
542+ {
543+ case MOBS ->
544+ {
545+ String kw = getStopKeyword ();
546+ if (kw .isEmpty ())
547+ return false ;
548+
549+ // No explicit range cap: scan what the client has loaded
550+ // (entitiesForRendering).
551+ for (var e : MC .level .entitiesForRendering ())
552+ {
553+ if (!(e instanceof Mob m ) || !m .isAlive () || m .isRemoved ())
554+ continue ;
555+
556+ String name = safeString (m .getName ().getString ());
557+ String id = safeString (BuiltInRegistries .ENTITY_TYPE
558+ .getKey (m .getType ()).toString ());
559+ if (containsIgnoreCase (name , kw )
560+ || containsIgnoreCase (id , kw ))
561+ {
562+ stopAutoFly ("Stopped: Found " + name );
563+ return true ;
564+ }
565+ }
566+ return false ;
567+ }
568+
569+ case ITEMS ->
570+ {
571+ String kw = getStopKeyword ();
572+ if (kw .isEmpty ())
573+ return false ;
574+
575+ // No explicit range cap: scan what the client has loaded.
576+ for (var ent : MC .level .entitiesForRendering ())
577+ {
578+ if (!(ent instanceof ItemEntity e ) || !e .isAlive ()
579+ || e .isRemoved ())
580+ continue ;
581+ if (e .getItem () == null || e .getItem ().isEmpty ())
582+ continue ;
583+
584+ var stack = e .getItem ();
585+ String name = safeString (stack .getHoverName ().getString ());
586+ String id = safeString (BuiltInRegistries .ITEM
587+ .getKey (stack .getItem ()).toString ());
588+ if (containsIgnoreCase (name , kw )
589+ || containsIgnoreCase (id , kw ))
590+ {
591+ stopAutoFly ("Stopped: Found " + name );
592+ return true ;
593+ }
594+ }
595+ return false ;
596+ }
597+
598+ case BLOCKS ->
599+ {
600+ String kw = getStopKeyword ();
601+ if (kw .isEmpty ())
602+ return false ;
603+
604+ return scanBlocksForKeyword (kw , null );
605+ }
606+
607+ case END_PORTAL ->
608+ {
609+ return scanBlocksForKeyword ("" , Blocks .END_PORTAL );
610+ }
611+
612+ case NETHER_PORTAL ->
613+ {
614+ return scanBlocksForKeyword ("" , Blocks .NETHER_PORTAL );
615+ }
616+
617+ case OFF ->
618+ {
619+ return false ;
620+ }
621+ }
622+
623+ return false ;
624+ }
625+
626+ private boolean scanBlocksForKeyword (String keyword ,
627+ net .minecraft .world .level .block .Block mustMatch )
628+ {
629+ // Throttle block scanning/update.
630+ if (stopScanCooldown -- > 0 )
631+ return false ;
632+ stopScanCooldown = STOP_SCAN_COOLDOWN_TICKS ;
633+
634+ ensureStopBlockCoordinatorConfigured (keyword , mustMatch );
635+ if (stopBlockCoordinator == null )
636+ return false ;
637+
638+ stopBlockCoordinator .update ();
639+
640+ Result hit =
641+ stopBlockCoordinator .getReadyMatches ().findFirst ().orElse (null );
642+ if (hit == null )
643+ return false ;
644+
645+ if (mustMatch != null )
646+ {
647+ stopAutoFly ("Stopped: Found " + (mustMatch == Blocks .END_PORTAL
648+ ? "End Portal" : "Nether Portal" ));
649+ return true ;
650+ }
651+
652+ String id = safeString (
653+ BuiltInRegistries .BLOCK .getKey (hit .state ().getBlock ()).toString ());
654+ stopAutoFly ("Stopped: Found " + id );
655+ return true ;
656+ }
657+
658+ private void ensureStopBlockCoordinatorConfigured (String keyword ,
659+ net .minecraft .world .level .block .Block mustMatch )
660+ {
661+ StopOnType type = stopOn .getSelected ();
662+ if (type == null )
663+ return ;
664+
665+ String kw = keyword == null ? "" : keyword .trim ();
666+
667+ boolean needsReset =
668+ stopBlockCoordinator == null || stopBlockCoordinatorType != type
669+ || !java .util .Objects .equals (stopBlockCoordinatorKeyword , kw );
670+
671+ if (!needsReset )
672+ return ;
673+
674+ stopBlockCoordinatorType = type ;
675+ stopBlockCoordinatorKeyword = kw ;
676+
677+ ChunkAreaSetting area = new ChunkAreaSetting (
678+ "Stop scan area (internal)" , "" , STOP_BLOCK_AREA );
679+ stopBlockCoordinator = new ChunkSearcherCoordinator (area );
680+
681+ if (mustMatch != null )
682+ {
683+ stopBlockCoordinator .setTargetBlock (mustMatch );
684+ return ;
685+ }
686+
687+ stopBlockCoordinator .setQuery ((pos , state ) -> {
688+ BlockState s = state ;
689+ if (s == null )
690+ return false ;
691+ String id = BuiltInRegistries .BLOCK .getKey (s .getBlock ()).toString ();
692+ return containsIgnoreCase (id , kw );
693+ });
694+ }
695+
696+ private void stopAutoFly (String message )
697+ {
698+ ChatUtils .message (message );
699+ setEnabled (false );
700+ }
701+
702+ private String getStopKeyword ()
703+ {
704+ String v = stopKeyword .getValue ();
705+ return v == null ? "" : v .trim ();
706+ }
707+
708+ private static boolean containsIgnoreCase (String haystack , String needle )
709+ {
710+ if (haystack == null || needle == null )
711+ return false ;
712+ if (needle .isEmpty ())
713+ return false ;
714+ return haystack .toLowerCase (Locale .ROOT )
715+ .contains (needle .toLowerCase (Locale .ROOT ));
716+ }
717+
718+ private static String safeString (String s )
719+ {
720+ return s == null ? "" : s ;
721+ }
722+
475723 @ Override
476724 public void onRenderGUI (GuiGraphics context , float partialTicks )
477725 {
0 commit comments