2023-07-17 02:56:17 +00:00
/ *
* This file is part of Baritone .
*
* Baritone is free software : you can redistribute it and / or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* Baritone is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public License
* along with Baritone . If not , see < https : //www.gnu.org/licenses/>.
* /
package baritone.process ;
import baritone.Baritone ;
2023-07-19 04:35:09 +00:00
import baritone.api.IBaritone ;
2023-07-17 04:34:48 +00:00
import baritone.api.event.events.* ;
2023-07-18 02:57:24 +00:00
import baritone.api.event.events.type.EventState ;
2023-07-17 04:34:48 +00:00
import baritone.api.event.listener.AbstractGameEventListener ;
2023-07-17 02:56:17 +00:00
import baritone.api.pathing.goals.Goal ;
2023-07-22 05:55:12 +00:00
import baritone.api.pathing.goals.GoalBlock ;
import baritone.api.pathing.goals.GoalXZ ;
2023-07-17 02:56:17 +00:00
import baritone.api.pathing.goals.GoalYLevel ;
import baritone.api.pathing.movement.IMovement ;
import baritone.api.pathing.path.IPathExecutor ;
import baritone.api.process.IBaritoneProcess ;
import baritone.api.process.IElytraProcess ;
import baritone.api.process.PathingCommand ;
import baritone.api.process.PathingCommandType ;
import baritone.api.utils.BetterBlockPos ;
import baritone.api.utils.Rotation ;
import baritone.api.utils.RotationUtils ;
import baritone.api.utils.input.Input ;
2023-07-19 04:35:09 +00:00
import baritone.pathing.movement.CalculationContext ;
2023-07-17 02:56:17 +00:00
import baritone.pathing.movement.movements.MovementFall ;
2023-07-23 06:29:07 +00:00
import baritone.process.elytra.ElytraBehavior ;
2023-07-17 02:56:17 +00:00
import baritone.process.elytra.NetherPathfinderContext ;
import baritone.process.elytra.NullElytraProcess ;
import baritone.utils.BaritoneProcessHelper ;
import baritone.utils.PathingCommandContext ;
2023-07-23 05:14:19 +00:00
import it.unimi.dsi.fastutil.longs.LongOpenHashSet ;
2023-07-23 05:00:23 +00:00
import net.minecraft.block.Block ;
2023-07-19 04:35:09 +00:00
import net.minecraft.block.state.IBlockState ;
2023-07-22 23:12:53 +00:00
import net.minecraft.init.Blocks ;
2023-07-17 02:56:17 +00:00
import net.minecraft.util.math.BlockPos ;
import net.minecraft.util.math.Vec3d ;
2023-07-23 05:38:07 +00:00
import java.util.* ;
2023-07-22 23:12:53 +00:00
2023-07-19 04:35:09 +00:00
import static baritone.api.pathing.movement.ActionCosts.COST_INF ;
2023-07-17 04:34:48 +00:00
public class ElytraProcess extends BaritoneProcessHelper implements IBaritoneProcess , IElytraProcess , AbstractGameEventListener {
2023-07-17 02:56:17 +00:00
public State state ;
2023-07-22 23:12:53 +00:00
private boolean goingToLandingSpot ;
2023-07-23 00:24:36 +00:00
private BetterBlockPos landingSpot ;
2023-07-24 12:21:46 +00:00
private boolean reachedGoal ; // this basically just prevents potential notification spam
2023-07-17 02:56:17 +00:00
private Goal goal ;
2023-07-23 06:29:07 +00:00
private ElytraBehavior behavior ;
2023-07-17 02:56:17 +00:00
private ElytraProcess ( Baritone baritone ) {
super ( baritone ) ;
2023-07-17 04:34:48 +00:00
baritone . getGameEventHandler ( ) . registerEventListener ( this ) ;
2023-07-17 02:56:17 +00:00
}
public static < T extends IElytraProcess > T create ( final Baritone baritone ) {
return ( T ) ( NetherPathfinderContext . isSupported ( )
? new ElytraProcess ( baritone )
: new NullElytraProcess ( baritone ) ) ;
}
@Override
public boolean isActive ( ) {
2023-07-18 21:48:00 +00:00
return this . behavior ! = null ;
2023-07-17 02:56:17 +00:00
}
2023-07-18 02:57:24 +00:00
@Override
public void resetState ( ) {
BlockPos destination = this . currentDestination ( ) ;
this . onLostControl ( ) ;
2023-07-24 08:41:19 +00:00
if ( destination ! = null ) {
this . pathTo ( destination ) ;
this . repackChunks ( ) ;
}
2023-07-18 02:57:24 +00:00
}
2023-07-30 22:46:18 +00:00
private static final String AUTO_JUMP_FAILURE_MSG = " Failed to compute a walking path to a spot to jump off from. Consider starting from a higher location, near an overhang. Or, you can disable elytraAutoJump and just manually begin gliding. " ;
2023-07-17 02:56:17 +00:00
@Override
public PathingCommand onTick ( boolean calcFailed , boolean isSafeToCancel ) {
2023-07-18 02:57:24 +00:00
final long seedSetting = Baritone . settings ( ) . elytraNetherSeed . value ;
2023-07-18 21:48:00 +00:00
if ( seedSetting ! = this . behavior . context . getSeed ( ) ) {
2023-07-18 02:57:24 +00:00
logDirect ( " Nether seed changed, recalculating path " ) ;
this . resetState ( ) ;
}
2023-07-18 18:52:09 +00:00
this . behavior . onTick ( ) ;
2023-07-17 02:56:17 +00:00
if ( calcFailed ) {
onLostControl ( ) ;
2023-07-30 22:46:18 +00:00
logDirect ( AUTO_JUMP_FAILURE_MSG ) ;
2023-07-17 02:56:17 +00:00
return new PathingCommand ( null , PathingCommandType . CANCEL_AND_SET_GOAL ) ;
}
2023-07-29 04:22:52 +00:00
if ( ctx . player ( ) . isElytraFlying ( ) & & this . state ! = State . LANDING & & this . behavior . pathManager . isComplete ( ) ) {
2023-07-22 23:12:53 +00:00
final BetterBlockPos last = this . behavior . pathManager . path . getLast ( ) ;
2023-07-29 04:22:52 +00:00
if ( last ! = null & & ctx . player ( ) . getDistanceSqToCenter ( last ) < ( 48 * 48 ) & & ! goingToLandingSpot ) {
2023-07-30 22:46:18 +00:00
logDirect ( " Path complete, picking a nearby safe landing spot... " ) ;
2023-07-29 04:22:52 +00:00
BetterBlockPos landingSpot = findSafeLandingSpot ( last ) ;
// if this fails we will just keep orbiting the last node until we run out of rockets or the user intervenes
if ( landingSpot ! = null ) {
2023-07-30 23:45:25 +00:00
this . pathTo0 ( landingSpot , true ) ;
2023-07-29 04:22:52 +00:00
this . landingSpot = landingSpot ;
this . goingToLandingSpot = true ;
}
}
2023-07-23 00:24:36 +00:00
if ( last ! = null & & ctx . player ( ) . getDistanceSqToCenter ( last ) < 1 ) {
2023-07-24 12:21:46 +00:00
if ( Baritone . settings ( ) . notificationOnPathComplete . value & & ! reachedGoal ) {
2023-07-18 19:36:29 +00:00
logNotification ( " Pathing complete " , false ) ;
}
2023-07-24 12:21:46 +00:00
if ( Baritone . settings ( ) . disconnectOnArrival . value & & ! reachedGoal ) {
2023-07-18 19:36:29 +00:00
// don't be active when the user logs back in
this . onLostControl ( ) ;
ctx . world ( ) . sendQuittingDisconnectingPacket ( ) ;
return new PathingCommand ( null , PathingCommandType . CANCEL_AND_SET_GOAL ) ;
}
2023-07-24 12:21:46 +00:00
reachedGoal = true ;
2023-07-29 04:22:52 +00:00
// we are goingToLandingSpot and we are in the last node of the path
if ( this . goingToLandingSpot ) {
2023-07-24 12:21:46 +00:00
this . state = State . LANDING ;
2023-07-30 22:46:18 +00:00
logDirect ( " Above the landing spot, landing... " ) ;
2023-07-22 23:12:53 +00:00
}
2023-07-17 02:56:17 +00:00
}
}
if ( this . state = = State . LANDING ) {
2023-07-23 00:24:36 +00:00
final BetterBlockPos endPos = this . landingSpot ! = null ? this . landingSpot : behavior . pathManager . path . getLast ( ) ;
2023-07-17 02:56:17 +00:00
if ( ctx . player ( ) . isElytraFlying ( ) & & endPos ! = null ) {
Vec3d from = ctx . player ( ) . getPositionVector ( ) ;
2023-07-23 00:24:36 +00:00
Vec3d to = new Vec3d ( ( ( double ) endPos . x ) + 0 . 5 , from . y , ( ( double ) endPos . z ) + 0 . 5 ) ;
2023-07-17 02:56:17 +00:00
Rotation rotation = RotationUtils . calcRotationFromVec3d ( from , to , ctx . playerRotations ( ) ) ;
2023-07-30 22:46:18 +00:00
baritone . getLookBehavior ( ) . updateTarget ( new Rotation ( rotation . getYaw ( ) , 0 ) , false ) ; // this will be overwritten, probably, by behavior tick
if ( ctx . player ( ) . posY < endPos . y - LANDING_COLUMN_HEIGHT ) {
logDirect ( " bad landing spot, trying again... " ) ;
2023-07-31 06:24:04 +00:00
landingSpotIsBad ( endPos ) ;
2023-07-30 22:46:18 +00:00
}
2023-07-17 02:56:17 +00:00
}
2023-07-30 22:46:18 +00:00
}
if ( ctx . player ( ) . isElytraFlying ( ) ) {
behavior . landingMode = this . state = = State . LANDING ;
2023-07-17 02:56:17 +00:00
this . goal = null ;
baritone . getInputOverrideHandler ( ) . clearAllKeys ( ) ;
behavior . tick ( ) ;
return new PathingCommand ( null , PathingCommandType . CANCEL_AND_SET_GOAL ) ;
2023-07-30 22:46:18 +00:00
} else if ( this . state = = State . LANDING ) {
if ( Math . sqrt ( ctx . player ( ) . motionX * ctx . player ( ) . motionX + ctx . player ( ) . motionZ * ctx . player ( ) . motionZ ) > 0 . 001 ) {
2023-07-31 05:47:39 +00:00
logDirect ( " Landed, but still moving, waiting for velocity to die down... " ) ;
2023-07-30 22:46:18 +00:00
baritone . getInputOverrideHandler ( ) . setInputForceState ( Input . SNEAK , true ) ;
return new PathingCommand ( null , PathingCommandType . REQUEST_PAUSE ) ;
}
2023-07-31 05:47:39 +00:00
logDirect ( " Done :) " ) ;
2023-07-30 22:46:18 +00:00
baritone . getInputOverrideHandler ( ) . clearAllKeys ( ) ;
this . onLostControl ( ) ;
return new PathingCommand ( null , PathingCommandType . REQUEST_PAUSE ) ;
2023-07-17 02:56:17 +00:00
}
if ( this . state = = State . FLYING | | this . state = = State . START_FLYING ) {
this . state = ctx . player ( ) . onGround & & Baritone . settings ( ) . elytraAutoJump . value
? State . LOCATE_JUMP
: State . START_FLYING ;
}
if ( this . state = = State . LOCATE_JUMP ) {
if ( this . goal = = null ) {
this . goal = new GoalYLevel ( 31 ) ;
}
final IPathExecutor executor = baritone . getPathingBehavior ( ) . getCurrent ( ) ;
if ( executor ! = null & & executor . getPath ( ) . getGoal ( ) = = this . goal ) {
final IMovement fall = executor . getPath ( ) . movements ( ) . stream ( )
. filter ( movement - > movement instanceof MovementFall )
. findFirst ( ) . orElse ( null ) ;
if ( fall ! = null ) {
final BetterBlockPos from = new BetterBlockPos (
( fall . getSrc ( ) . x + fall . getDest ( ) . x ) / 2 ,
( fall . getSrc ( ) . y + fall . getDest ( ) . y ) / 2 ,
( fall . getSrc ( ) . z + fall . getDest ( ) . z ) / 2
) ;
behavior . pathManager . pathToDestination ( from ) . whenComplete ( ( result , ex ) - > {
if ( ex = = null ) {
this . state = State . GET_TO_JUMP ;
return ;
}
onLostControl ( ) ;
} ) ;
this . state = State . PAUSE ;
} else {
onLostControl ( ) ;
2023-07-30 22:46:18 +00:00
logDirect ( AUTO_JUMP_FAILURE_MSG ) ;
2023-07-17 02:56:17 +00:00
return new PathingCommand ( null , PathingCommandType . CANCEL_AND_SET_GOAL ) ;
}
}
2023-07-19 04:35:09 +00:00
return new PathingCommandContext ( this . goal , PathingCommandType . SET_GOAL_AND_PAUSE , new WalkOffCalculationContext ( baritone ) ) ;
2023-07-17 02:56:17 +00:00
}
// yucky
if ( this . state = = State . PAUSE ) {
return new PathingCommand ( null , PathingCommandType . REQUEST_PAUSE ) ;
}
if ( this . state = = State . GET_TO_JUMP ) {
final IPathExecutor executor = baritone . getPathingBehavior ( ) . getCurrent ( ) ;
final boolean canStartFlying = ctx . player ( ) . fallDistance > 1 . 0f
& & ! isSafeToCancel
& & executor ! = null
& & executor . getPath ( ) . movements ( ) . get ( executor . getPosition ( ) ) instanceof MovementFall ;
if ( canStartFlying ) {
this . state = State . START_FLYING ;
} else {
return new PathingCommand ( null , PathingCommandType . SET_GOAL_AND_PATH ) ;
}
}
if ( this . state = = State . START_FLYING ) {
if ( ! isSafeToCancel ) {
// owned
baritone . getPathingBehavior ( ) . secretInternalSegmentCancel ( ) ;
}
baritone . getInputOverrideHandler ( ) . clearAllKeys ( ) ;
if ( ctx . player ( ) . fallDistance > 1 . 0f ) {
baritone . getInputOverrideHandler ( ) . setInputForceState ( Input . JUMP , true ) ;
}
}
return new PathingCommand ( null , PathingCommandType . CANCEL_AND_SET_GOAL ) ;
}
2023-07-31 06:24:04 +00:00
public void landingSpotIsBad ( BetterBlockPos endPos ) {
badLandingSpots . add ( endPos ) ;
goingToLandingSpot = false ;
this . landingSpot = null ;
this . state = State . FLYING ;
}
2023-07-17 02:56:17 +00:00
@Override
public void onLostControl ( ) {
this . goal = null ;
2023-07-22 23:12:53 +00:00
this . goingToLandingSpot = false ;
2023-07-30 23:45:25 +00:00
this . landingSpot = null ;
2023-07-24 12:21:46 +00:00
this . reachedGoal = false ;
2023-07-17 04:34:48 +00:00
this . state = State . START_FLYING ; // TODO: null state?
2023-07-31 05:56:33 +00:00
destroyBehaviorAsync ( ) ;
}
private void destroyBehaviorAsync ( ) {
ElytraBehavior behavior = this . behavior ;
if ( behavior ! = null ) {
Baritone . getExecutor ( ) . execute ( behavior : : destroy ) ;
2023-07-18 18:52:09 +00:00
this . behavior = null ;
}
2023-07-17 02:56:17 +00:00
}
2023-07-22 05:55:12 +00:00
@Override
public double priority ( ) {
return 0 ; // higher priority than CustomGoalProcess
}
2023-07-17 02:56:17 +00:00
@Override
public String displayName0 ( ) {
2023-07-18 02:57:24 +00:00
return " Elytra - " + this . state . description ;
2023-07-17 02:56:17 +00:00
}
@Override
2023-07-18 02:57:24 +00:00
public void repackChunks ( ) {
2023-07-18 18:52:09 +00:00
if ( this . behavior ! = null ) {
this . behavior . repackChunks ( ) ;
}
2023-07-17 02:56:17 +00:00
}
@Override
2023-07-18 02:57:24 +00:00
public BlockPos currentDestination ( ) {
return this . behavior ! = null ? this . behavior . destination : null ;
2023-07-17 02:56:17 +00:00
}
@Override
public void pathTo ( BlockPos destination ) {
2023-07-30 23:45:25 +00:00
this . pathTo0 ( destination , false ) ;
}
private void pathTo0 ( BlockPos destination , boolean appendDestination ) {
2023-07-24 00:46:12 +00:00
if ( ctx . player ( ) = = null | | ctx . player ( ) . dimension ! = - 1 ) {
return ;
}
2023-07-22 05:55:12 +00:00
this . onLostControl ( ) ;
2023-07-30 23:45:25 +00:00
this . behavior = new ElytraBehavior ( this . baritone , this , destination , appendDestination ) ;
2023-07-17 04:34:48 +00:00
if ( ctx . world ( ) ! = null ) {
this . behavior . repackChunks ( ) ;
}
2023-07-18 21:48:00 +00:00
this . behavior . pathTo ( ) ;
2023-07-17 02:56:17 +00:00
}
2023-07-22 05:55:12 +00:00
@Override
public void pathTo ( Goal iGoal ) {
final int x ;
final int y ;
final int z ;
if ( iGoal instanceof GoalXZ ) {
GoalXZ goal = ( GoalXZ ) iGoal ;
x = goal . getX ( ) ;
y = 64 ;
z = goal . getZ ( ) ;
} else if ( iGoal instanceof GoalBlock ) {
GoalBlock goal = ( GoalBlock ) iGoal ;
x = goal . x ;
y = goal . y ;
z = goal . z ;
} else {
throw new IllegalArgumentException ( " The goal must be a GoalXZ or GoalBlock " ) ;
}
if ( y < = 0 | | y > = 128 ) {
throw new IllegalArgumentException ( " The y of the goal is not between 0 and 128 " ) ;
}
this . pathTo ( new BlockPos ( x , y , z ) ) ;
}
2023-07-17 02:56:17 +00:00
@Override
public boolean isLoaded ( ) {
return true ;
}
@Override
public boolean isSafeToCancel ( ) {
2023-07-17 04:34:48 +00:00
return ! this . isActive ( ) | | ! ( this . state = = State . FLYING | | this . state = = State . START_FLYING ) ;
2023-07-17 02:56:17 +00:00
}
public enum State {
2023-07-18 02:57:24 +00:00
LOCATE_JUMP ( " Finding spot to jump off " ) ,
PAUSE ( " Waiting for elytra path " ) ,
GET_TO_JUMP ( " Walking to takeoff " ) ,
START_FLYING ( " Begin flying " ) ,
FLYING ( " Flying " ) ,
LANDING ( " Landing " ) ;
2023-07-18 04:02:23 +00:00
public final String description ;
2023-07-18 02:57:24 +00:00
State ( String desc ) {
this . description = desc ;
}
2023-07-17 02:56:17 +00:00
}
2023-07-17 04:34:48 +00:00
@Override
public void onRenderPass ( RenderEvent event ) {
if ( this . behavior ! = null ) this . behavior . onRenderPass ( event ) ;
}
@Override
public void onWorldEvent ( WorldEvent event ) {
2023-07-31 05:56:33 +00:00
if ( event . getWorld ( ) ! = null & & event . getState ( ) = = EventState . POST ) {
2023-07-18 02:57:24 +00:00
// Exiting the world, just destroy
2023-07-31 05:56:33 +00:00
destroyBehaviorAsync ( ) ;
2023-07-18 02:57:24 +00:00
}
2023-07-17 04:34:48 +00:00
}
@Override
public void onChunkEvent ( ChunkEvent event ) {
if ( this . behavior ! = null ) this . behavior . onChunkEvent ( event ) ;
}
@Override
public void onBlockChange ( BlockChangeEvent event ) {
if ( this . behavior ! = null ) this . behavior . onBlockChange ( event ) ;
}
@Override
public void onReceivePacket ( PacketEvent event ) {
if ( this . behavior ! = null ) this . behavior . onReceivePacket ( event ) ;
}
@Override
public void onPostTick ( TickEvent event ) {
2023-07-17 22:16:09 +00:00
IBaritoneProcess procThisTick = baritone . getPathingControlManager ( ) . mostRecentInControl ( ) . orElse ( null ) ;
if ( this . behavior ! = null & & procThisTick = = this ) this . behavior . onPostTick ( event ) ;
2023-07-17 04:34:48 +00:00
}
2023-07-19 04:35:09 +00:00
/ * *
* Custom calculation context which makes the player fall into lava
* /
public static final class WalkOffCalculationContext extends CalculationContext {
public WalkOffCalculationContext ( IBaritone baritone ) {
super ( baritone , true ) ;
this . allowFallIntoLava = true ;
this . minFallHeight = 8 ;
this . maxFallHeightNoWater = 10000 ;
}
@Override
public double costOfPlacingAt ( int x , int y , int z , IBlockState current ) {
return COST_INF ;
}
@Override
public double breakCostMultiplierAt ( int x , int y , int z , IBlockState current ) {
return COST_INF ;
}
@Override
public double placeBucketCost ( ) {
return COST_INF ;
}
}
2023-07-22 23:12:53 +00:00
private static boolean isInBounds ( BlockPos pos ) {
return pos . getY ( ) > = 0 & & pos . getY ( ) < 128 ;
}
2023-07-23 18:26:31 +00:00
private boolean isSafeBlock ( Block block ) {
return block = = Blocks . NETHERRACK | | block = = Blocks . GRAVEL | | block = = Blocks . NETHER_BRICK ;
}
2023-07-30 22:46:18 +00:00
2023-07-23 18:26:31 +00:00
private boolean isSafeBlock ( BlockPos pos ) {
return isSafeBlock ( ctx . world ( ) . getBlockState ( pos ) . getBlock ( ) ) ;
}
2023-07-23 00:24:36 +00:00
private boolean isAtEdge ( BlockPos pos ) {
2023-07-23 18:26:31 +00:00
return ! isSafeBlock ( pos . north ( ) )
| | ! isSafeBlock ( pos . south ( ) )
| | ! isSafeBlock ( pos . east ( ) )
| | ! isSafeBlock ( pos . west ( ) )
2023-07-23 00:24:36 +00:00
// corners
2023-07-23 18:26:31 +00:00
| | ! isSafeBlock ( pos . north ( ) . west ( ) )
| | ! isSafeBlock ( pos . north ( ) . east ( ) )
| | ! isSafeBlock ( pos . south ( ) . west ( ) )
| | ! isSafeBlock ( pos . south ( ) . east ( ) ) ;
}
private boolean isColumnAir ( BlockPos landingSpot , int minHeight ) {
BlockPos . MutableBlockPos mut = new BlockPos . MutableBlockPos ( landingSpot ) ;
final int maxY = mut . getY ( ) + minHeight ;
for ( int y = mut . getY ( ) + 1 ; y < = maxY ; y + + ) {
mut . setPos ( mut . getX ( ) , y , mut . getZ ( ) ) ;
if ( ! ctx . world ( ) . isAirBlock ( mut ) ) {
return false ;
}
}
return true ;
2023-07-23 00:24:36 +00:00
}
2023-07-29 04:22:52 +00:00
private boolean hasAirBubble ( BlockPos pos ) {
2023-07-30 23:45:25 +00:00
final int radius = 4 ; // Half of the full width, rounded down, as we're counting blocks in each direction from the center
2023-07-29 15:52:37 +00:00
BlockPos . MutableBlockPos mut = new BlockPos . MutableBlockPos ( ) ;
2023-07-29 04:22:52 +00:00
for ( int x = - radius ; x < = radius ; x + + ) {
for ( int y = - radius ; y < = radius ; y + + ) {
for ( int z = - radius ; z < = radius ; z + + ) {
2023-07-29 15:52:37 +00:00
mut . setPos ( pos . getX ( ) + x , pos . getY ( ) + y , pos . getZ ( ) + z ) ;
if ( ! ctx . world ( ) . isAirBlock ( mut ) ) {
2023-07-29 04:22:52 +00:00
return false ;
}
}
}
}
return true ;
}
2023-07-23 18:26:31 +00:00
private BetterBlockPos checkLandingSpot ( BlockPos pos , LongOpenHashSet checkedSpots ) {
2023-07-22 23:12:53 +00:00
BlockPos . MutableBlockPos mut = new BlockPos . MutableBlockPos ( pos ) ;
while ( mut . getY ( ) > = 0 ) {
2023-07-23 06:02:29 +00:00
if ( checkedSpots . contains ( mut . toLong ( ) ) ) {
2023-07-23 18:26:31 +00:00
return null ;
2023-07-23 06:02:29 +00:00
}
checkedSpots . add ( mut . toLong ( ) ) ;
2023-07-23 18:26:31 +00:00
Block block = ctx . world ( ) . getBlockState ( mut ) . getBlock ( ) ;
2023-07-23 05:00:23 +00:00
2023-07-23 18:26:31 +00:00
if ( isSafeBlock ( block ) ) {
if ( ! isAtEdge ( mut ) ) {
return new BetterBlockPos ( mut ) ;
}
return null ;
2023-07-23 05:00:23 +00:00
} else if ( block ! = Blocks . AIR ) {
2023-07-23 18:26:31 +00:00
return null ;
2023-07-22 23:12:53 +00:00
}
mut . setPos ( mut . getX ( ) , mut . getY ( ) - 1 , mut . getZ ( ) ) ;
}
2023-07-23 18:26:31 +00:00
return null ; // void
2023-07-22 23:12:53 +00:00
}
2023-07-30 22:46:18 +00:00
private static final int LANDING_COLUMN_HEIGHT = 15 ;
private Set < BetterBlockPos > badLandingSpots = new HashSet < > ( ) ;
2023-07-29 04:22:52 +00:00
private BetterBlockPos findSafeLandingSpot ( BetterBlockPos start ) {
2023-07-23 06:02:29 +00:00
Queue < BetterBlockPos > queue = new PriorityQueue < > ( Comparator . < BetterBlockPos > comparingInt ( pos - > ( pos . x - start . x ) * ( pos . x - start . x ) + ( pos . z - start . z ) * ( pos . z - start . z ) ) . thenComparingInt ( pos - > - pos . y ) ) ;
2023-07-23 00:24:36 +00:00
Set < BetterBlockPos > visited = new HashSet < > ( ) ;
2023-07-23 05:14:19 +00:00
LongOpenHashSet checkedPositions = new LongOpenHashSet ( ) ;
2023-07-22 23:12:53 +00:00
queue . add ( start ) ;
while ( ! queue . isEmpty ( ) ) {
2023-07-23 00:24:36 +00:00
BetterBlockPos pos = queue . poll ( ) ;
2023-07-22 23:12:53 +00:00
if ( ctx . world ( ) . isBlockLoaded ( pos ) & & isInBounds ( pos ) & & ctx . world ( ) . getBlockState ( pos ) . getBlock ( ) = = Blocks . AIR ) {
2023-07-23 18:26:31 +00:00
BetterBlockPos actualLandingSpot = checkLandingSpot ( pos , checkedPositions ) ;
2023-07-30 22:46:18 +00:00
if ( actualLandingSpot ! = null & & isColumnAir ( actualLandingSpot , LANDING_COLUMN_HEIGHT ) & & hasAirBubble ( actualLandingSpot . up ( LANDING_COLUMN_HEIGHT ) ) & & ! badLandingSpots . contains ( actualLandingSpot . up ( LANDING_COLUMN_HEIGHT ) ) ) {
return actualLandingSpot . up ( LANDING_COLUMN_HEIGHT ) ;
2023-07-22 23:12:53 +00:00
}
if ( visited . add ( pos . north ( ) ) ) queue . add ( pos . north ( ) ) ;
if ( visited . add ( pos . east ( ) ) ) queue . add ( pos . east ( ) ) ;
if ( visited . add ( pos . south ( ) ) ) queue . add ( pos . south ( ) ) ;
if ( visited . add ( pos . west ( ) ) ) queue . add ( pos . west ( ) ) ;
if ( visited . add ( pos . up ( ) ) ) queue . add ( pos . up ( ) ) ;
if ( visited . add ( pos . down ( ) ) ) queue . add ( pos . down ( ) ) ;
}
}
return null ;
}
2023-07-17 02:56:17 +00:00
}