@@ -54,6 +54,25 @@ public final class AutoFlyHack extends Hack
5454 private static final ChunkAreaSetting .ChunkArea STOP_BLOCK_AREA =
5555 ChunkAreaSetting .ChunkArea .A65 ;
5656
57+ public static enum RouteType
58+ {
59+ WAYPOINTS ("Waypoints" ),
60+ GRID ("Grid" );
61+
62+ private final String name ;
63+
64+ RouteType (String name )
65+ {
66+ this .name = name ;
67+ }
68+
69+ @ Override
70+ public String toString ()
71+ {
72+ return name ;
73+ }
74+ }
75+
5776 public static enum StopOnType
5877 {
5978 OFF ("Off" ),
@@ -81,6 +100,29 @@ public String toString()
81100 "Waypoints" ,
82101 "Waypoints list. Format: x y z or x z (no Y). Separate by ';' or new lines." ,
83102 "" );
103+
104+ private final EnumSetting <RouteType > routeType = new EnumSetting <>(
105+ "Route type" ,
106+ "Where AutoFly gets its targets from.\n \n "
107+ + "Waypoints: Use the Waypoints list above (or JSON if empty).\n "
108+ + "Grid: Generate a square search grid from your current position." ,
109+ RouteType .values (), RouteType .WAYPOINTS );
110+
111+ private final SliderSetting gridSideLength =
112+ new SliderSetting ("Grid side length" ,
113+ "Side length of the square search area (in blocks).\n \n "
114+ + "The grid starts at your position and extends in +X and +Z." ,
115+ 500 , 10 , 50000 , 10 , ValueDisplay .INTEGER .withSuffix (" blocks" ));
116+
117+ private final SliderSetting gridPassSize =
118+ new SliderSetting ("Grid pass size" ,
119+ "Distance between each back-and-forth pass (in blocks)." , 50 , 1 ,
120+ 1000 , 1 , ValueDisplay .INTEGER .withSuffix (" blocks" ));
121+
122+ private final ButtonSetting startGridButton = new ButtonSetting (
123+ "Start grid" ,
124+ "Generate grid targets from your current position and start flying." ,
125+ this ::startGridFromPlayer );
84126 private final TextFieldSetting importFile = new TextFieldSetting (
85127 "Import file" ,
86128 "SeedMapper export JSON filename. Leave empty to use the latest file in seedmapper/exports." ,
@@ -201,12 +243,18 @@ public String toString()
201243 private ChunkSearcherCoordinator stopBlockCoordinator ;
202244 private StopOnType stopBlockCoordinatorType ;
203245 private String stopBlockCoordinatorKeyword ;
246+ private boolean stopHold ;
247+ private int stopIgnoreTicks ;
204248
205249 public AutoFlyHack ()
206250 {
207251 super ("AutoFly" );
208252 setCategory (Category .MOVEMENT );
209253 addSetting (waypointText );
254+ addSetting (routeType );
255+ addSetting (gridSideLength );
256+ addSetting (gridPassSize );
257+ addSetting (startGridButton );
210258 addSetting (importFile );
211259 addSetting (exportJsonPicker );
212260 addSetting (reloadJsonButton );
@@ -241,7 +289,12 @@ protected void onEnable()
241289 if (useExistingTargetsOnEnable )
242290 useExistingTargetsOnEnable = false ;
243291 else
244- loadTargetsFromSettings ();
292+ {
293+ if (routeType .getSelected () == RouteType .GRID )
294+ loadTargetsFromGrid (MC .player .blockPosition ());
295+ else
296+ loadTargetsFromSettings ();
297+ }
245298 if (targets .isEmpty ())
246299 {
247300 ChatUtils .error ("No AutoFly waypoints loaded." );
@@ -288,6 +341,8 @@ protected void onEnable()
288341 stopBlockCoordinator = null ;
289342 stopBlockCoordinatorType = null ;
290343 stopBlockCoordinatorKeyword = null ;
344+ stopHold = false ;
345+ stopIgnoreTicks = 0 ;
291346 selectNextTarget (false );
292347 flightWasEnabled = WURST .getHax ().flightHack .isEnabled ();
293348 savedFlightSpeed = -1 ;
@@ -350,6 +405,8 @@ protected void onDisable()
350405 currentIndex = -1 ;
351406 savedFlightVSpeed = -1 ;
352407 closeHorizLatched = false ;
408+ stopHold = false ;
409+ stopIgnoreTicks = 0 ;
353410 }
354411
355412 @ Override
@@ -361,6 +418,18 @@ public void onUpdate()
361418 return ;
362419 }
363420
421+ if (stopIgnoreTicks > 0 )
422+ stopIgnoreTicks --;
423+
424+ if (stopHold )
425+ {
426+ // Allow player to decide what to do next (e.g. cycle waypoint).
427+ PathProcessor .releaseControls ();
428+ resetAutoKeyFlags ();
429+ clearMovementKeys ();
430+ return ;
431+ }
432+
364433 if (currentTarget == null )
365434 {
366435 selectNextTarget (false );
@@ -531,6 +600,9 @@ public void onUpdate()
531600
532601 private boolean checkStopOn ()
533602 {
603+ if (stopIgnoreTicks > 0 )
604+ return false ;
605+
534606 StopOnType type = stopOn .getSelected ();
535607 if (type == null || type == StopOnType .OFF )
536608 return false ;
@@ -695,8 +767,14 @@ private void ensureStopBlockCoordinatorConfigured(String keyword,
695767
696768 private void stopAutoFly (String message )
697769 {
698- ChatUtils .message (message );
699- setEnabled (false );
770+ ChatUtils .message (message + " (use Next waypoint to continue)" );
771+ stopHold = true ;
772+ stopIgnoreTicks = 0 ;
773+
774+ // Stop immediately, even if keys were held from the previous tick.
775+ PathProcessor .releaseControls ();
776+ resetAutoKeyFlags ();
777+ clearMovementKeys ();
700778 }
701779
702780 private String getStopKeyword ()
@@ -756,6 +834,132 @@ private void loadTargetsFromSettings()
756834 return ;
757835 }
758836
837+ loadTargetsFromJson ();
838+ }
839+
840+ private void startGridFromPlayer ()
841+ {
842+ if (MC .player == null )
843+ {
844+ ChatUtils .error ("Join a world before starting a grid." );
845+ return ;
846+ }
847+
848+ routeType .setSelected (RouteType .GRID );
849+ loadTargetsFromGrid (MC .player .blockPosition ());
850+ if (targets .isEmpty ())
851+ return ;
852+
853+ restartWithExistingTargets ();
854+ }
855+
856+ private void loadTargetsFromGrid (BlockPos start )
857+ {
858+ targets .clear ();
859+ if (start == null )
860+ return ;
861+
862+ int side = (int )Math .round (gridSideLength .getValue ());
863+ int pass = (int )Math .round (gridPassSize .getValue ());
864+
865+ if (side < 1 )
866+ {
867+ ChatUtils .error ("Grid side length must be at least 1 block." );
868+ return ;
869+ }
870+ if (pass < 1 )
871+ pass = 1 ;
872+
873+ int passes = (int )Math .ceil (side / (double )pass );
874+ long estTargets = 2L * passes + 1L ;
875+ if (estTargets > 20000L )
876+ {
877+ ChatUtils .error ("Grid is too dense (" + estTargets
878+ + " targets). Increase pass size or decrease side length." );
879+ return ;
880+ }
881+
882+ int x0 = start .getX ();
883+ int z0 = start .getZ ();
884+ int x1 = x0 + side ;
885+ int z1 = z0 + side ;
886+ int y = 0 ; // Not used when hasY=false
887+
888+ boolean toEnd = true ;
889+ int z = z0 ;
890+ while (true )
891+ {
892+ int xEnd = toEnd ? x1 : x0 ;
893+ targets .add (new AutoFlyTarget (new BlockPos (xEnd , y , z ), false ));
894+
895+ if (z == z1 )
896+ break ;
897+
898+ int nextZ = z + pass ;
899+ if (nextZ > z1 )
900+ nextZ = z1 ;
901+
902+ // Shift over to the next pass while staying at the current X end.
903+ targets .add (new AutoFlyTarget (new BlockPos (xEnd , y , nextZ ), false ));
904+
905+ z = nextZ ;
906+ toEnd = !toEnd ;
907+ }
908+
909+ ChatUtils .message (String .format (Locale .ROOT ,
910+ "AutoFly grid: side=%d, pass=%d, targets=%d (%d,%d -> %d,%d)" , side ,
911+ pass , targets .size (), x0 , z0 , x1 , z1 ));
912+ }
913+
914+ private void restartWithExistingTargets ()
915+ {
916+ currentTarget = null ;
917+ currentIndex = -1 ;
918+ pausedNoY = false ;
919+ stopHold = false ;
920+ stopIgnoreTicks = 0 ;
921+ arrivalPause = false ;
922+ arrivalPauseUntilMs = 0L ;
923+ arrivedMessageSent = false ;
924+ arrivedHold = false ;
925+ manualAdjustHold = false ;
926+ manualAdjustStartMs = 0L ;
927+ manualAdjustStartPos = null ;
928+ lastManualInputMs = 0L ;
929+ lastAutoControlMs = 0L ;
930+ lastManualAdjustExitMs = 0L ;
931+ verticalMode = VerticalMode .NONE ;
932+ recoveryGoal = null ;
933+ pathFinder = null ;
934+ pathProcessor = null ;
935+ lastProgressMs = System .currentTimeMillis ();
936+ lastProgressDist = Double .NaN ;
937+ lastRepathMs = 0L ;
938+ stuckRepathCount = 0 ;
939+ lastMovePos = MC .player != null ? MC .player .position () : null ;
940+ lastMoveMs = System .currentTimeMillis ();
941+ lastHorizPos = lastMovePos ;
942+ lastHorizMoveMs = lastMoveMs ;
943+ autoKeyUpDown = false ;
944+ autoKeyDownDown = false ;
945+ autoKeyLeftDown = false ;
946+ autoKeyRightDown = false ;
947+ autoKeyJumpDown = false ;
948+ autoKeyShiftDown = false ;
949+ climbAttemptUntilMs = 0L ;
950+ lastClimbAttemptMs = 0L ;
951+ climbTargetY = 0.0 ;
952+ closeHorizLatched = false ;
953+ clearPathingState ();
954+
955+ if (!isEnabled ())
956+ {
957+ useExistingTargetsOnEnable = true ;
958+ setEnabled (true );
959+ return ;
960+ }
961+
962+ selectNextTarget (false );
759963 }
760964
761965 private void loadTargetsFromText (String text )
@@ -912,6 +1116,8 @@ private void reloadJsonTargets()
9121116 currentTarget = null ;
9131117 currentIndex = -1 ;
9141118 pausedNoY = false ;
1119+ stopHold = false ;
1120+ stopIgnoreTicks = 0 ;
9151121 arrivalPause = false ;
9161122 arrivalPauseUntilMs = 0L ;
9171123 arrivedMessageSent = false ;
@@ -984,6 +1190,9 @@ private void selectTarget(int index)
9841190 {
9851191 if (index < 0 || index >= targets .size ())
9861192 return ;
1193+ if (stopHold )
1194+ stopIgnoreTicks = 60 ;
1195+ stopHold = false ;
9871196 currentIndex = index ;
9881197 currentTarget = targets .get (index );
9891198 pausedNoY = false ;
@@ -1041,6 +1250,9 @@ else if(idx >= count)
10411250 MC .player .position (), targetRadius .getValue ()))
10421251 continue ;
10431252
1253+ if (stopHold )
1254+ stopIgnoreTicks = 60 ;
1255+ stopHold = false ;
10441256 currentIndex = idx ;
10451257 currentTarget = candidate ;
10461258 pausedNoY = false ;
@@ -1235,6 +1447,8 @@ private String getStateLabel()
12351447 {
12361448 if (pathFinder != null )
12371449 return "Pathing" ;
1450+ if (stopHold )
1451+ return "Stopped" ;
12381452 if (manualAdjustHold )
12391453 return "Adjust" ;
12401454 if (arrivedHold )
@@ -1264,6 +1478,8 @@ public void setTargetFromCommand(BlockPos pos, boolean hasY,
12641478 currentIndex = 0 ;
12651479 currentTarget = targets .get (0 );
12661480 pausedNoY = false ;
1481+ stopHold = false ;
1482+ stopIgnoreTicks = 0 ;
12671483 arrivalPause = false ;
12681484 arrivalPauseUntilMs = 0L ;
12691485 arrivedMessageSent = false ;
0 commit comments