2018-08-08 03:16:53 +00:00
/ *
* This file is part of Baritone .
*
* Baritone is free software : you can redistribute it and / or modify
2018-09-17 22:11:40 +00:00
* it under the terms of the GNU Lesser General Public License as published by
2018-08-08 03:16:53 +00:00
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
2018-08-08 04:15:22 +00:00
* Baritone is distributed in the hope that it will be useful ,
2018-08-08 03:16:53 +00:00
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
2018-09-17 22:11:40 +00:00
* GNU Lesser General Public License for more details .
2018-08-08 03:16:53 +00:00
*
2018-09-17 22:11:40 +00:00
* You should have received a copy of the GNU Lesser General Public License
2018-08-08 03:16:53 +00:00
* along with Baritone . If not , see < https : //www.gnu.org/licenses/>.
* /
2018-08-22 20:15:56 +00:00
package baritone.pathing.path ;
2018-08-02 14:01:34 +00:00
2018-08-22 20:15:56 +00:00
import baritone.Baritone ;
2018-08-28 00:37:21 +00:00
import baritone.api.event.events.TickEvent ;
2018-09-04 16:55:56 +00:00
import baritone.pathing.movement.* ;
2018-09-15 14:38:53 +00:00
import baritone.pathing.movement.movements.* ;
2018-08-22 20:15:56 +00:00
import baritone.utils.BlockStateInterface ;
import baritone.utils.Helper ;
2018-09-15 01:29:35 +00:00
import baritone.utils.Utils ;
2018-09-15 14:38:53 +00:00
import baritone.utils.pathing.BetterBlockPos ;
2018-08-04 19:53:50 +00:00
import net.minecraft.init.Blocks ;
2018-08-02 14:01:34 +00:00
import net.minecraft.util.Tuple ;
import net.minecraft.util.math.BlockPos ;
2018-08-09 23:37:08 +00:00
import java.util.Collections ;
import java.util.HashSet ;
import java.util.Set ;
2018-08-22 20:15:56 +00:00
import static baritone.pathing.movement.MovementState.MovementStatus.* ;
2018-08-04 22:28:36 +00:00
2018-08-02 14:01:34 +00:00
/ * *
2018-08-04 19:53:50 +00:00
* Behavior to execute a precomputed path . Does not ( yet ) deal with path segmentation or stitching
* or cutting ( jumping onto the next path if it starts with a backtrack of this path ' s ending )
*
* @author leijurv
2018-08-02 14:01:34 +00:00
* /
2018-08-13 19:35:44 +00:00
public class PathExecutor implements Helper {
2018-08-31 22:21:59 +00:00
private static final double MAX_MAX_DIST_FROM_PATH = 3 ;
2018-08-02 14:01:34 +00:00
private static final double MAX_DIST_FROM_PATH = 2 ;
2018-09-16 20:28:23 +00:00
/ * *
* Default value is equal to 10 seconds . It ' s find to decrease it , but it must be at least 5 . 5s ( 110 ticks ) .
* For more information , see issue # 102 .
*
* @see < a href = " https://github.com/cabaletta/baritone/issues/102 " > Issue # 102 < / a >
* @see < a href = " https://i.imgur.com/5s5GLnI.png " > Anime < / a >
* /
private static final double MAX_TICKS_AWAY = 200 ;
2018-08-02 14:01:34 +00:00
private final IPath path ;
2018-08-04 19:53:50 +00:00
private int pathPosition ;
private int ticksAway ;
private int ticksOnCurrent ;
2018-09-16 02:35:35 +00:00
private Double currentMovementOriginalCostEstimate ;
2018-08-13 18:49:36 +00:00
private Integer costEstimateIndex ;
2018-08-04 19:53:50 +00:00
private boolean failed ;
2018-08-09 23:37:08 +00:00
private boolean recalcBP = true ;
private HashSet < BlockPos > toBreak = new HashSet < > ( ) ;
private HashSet < BlockPos > toPlace = new HashSet < > ( ) ;
2018-08-12 15:40:44 +00:00
private HashSet < BlockPos > toWalkInto = new HashSet < > ( ) ;
2018-08-02 14:01:34 +00:00
2018-08-05 03:25:05 +00:00
public PathExecutor ( IPath path ) {
2018-08-02 14:01:34 +00:00
this . path = path ;
2018-08-04 19:53:50 +00:00
this . pathPosition = 0 ;
2018-08-02 14:01:34 +00:00
}
2018-08-13 19:35:44 +00:00
/ * *
* Tick this executor
*
* @param event
* @return True if a movement just finished ( and the player is therefore in a " stable " state , like ,
* not sneaking out over lava ) , false otherwise
* /
public boolean onTick ( TickEvent event ) {
2018-08-06 23:09:28 +00:00
if ( event . getType ( ) = = TickEvent . Type . OUT ) {
2018-08-13 19:35:44 +00:00
throw new IllegalStateException ( ) ;
2018-08-06 20:07:30 +00:00
}
2018-09-15 14:42:03 +00:00
if ( pathPosition = = path . length ( ) - 1 ) {
pathPosition + + ;
}
if ( pathPosition > = path . length ( ) ) {
2018-09-15 01:29:35 +00:00
return true ; // stop bugging me, I'm done
2018-08-04 19:53:50 +00:00
}
2018-09-15 14:38:53 +00:00
BetterBlockPos whereShouldIBe = path . positions ( ) . get ( pathPosition ) ;
BetterBlockPos whereAmI = playerFeet ( ) ;
2018-08-04 19:53:50 +00:00
if ( ! whereShouldIBe . equals ( whereAmI ) ) {
2018-09-15 14:38:53 +00:00
2018-09-15 17:04:48 +00:00
if ( pathPosition = = 0 & & whereAmI . equals ( whereShouldIBe . up ( ) ) & & Math . abs ( player ( ) . motionY ) < 0 . 1 & & ! ( path . movements ( ) . get ( 0 ) instanceof MovementAscend ) & & ! ( path . movements ( ) . get ( 0 ) instanceof MovementPillar ) ) {
2018-09-15 14:38:53 +00:00
// avoid the Wrong Y coordinate bug
new MovementDownward ( whereAmI , whereShouldIBe ) . update ( ) ;
return false ;
}
2018-08-20 23:23:32 +00:00
//System.out.println("Should be at " + whereShouldIBe + " actually am at " + whereAmI);
2018-08-04 19:53:50 +00:00
if ( ! Blocks . AIR . equals ( BlockStateInterface . getBlock ( whereAmI . down ( ) ) ) ) { //do not skip if standing on air, because our position isn't stable to skip
2018-09-09 18:06:02 +00:00
for ( int i = 0 ; i < pathPosition - 1 & & i < path . length ( ) ; i + + ) { //this happens for example when you lag out and get teleported back a couple blocks
2018-08-04 19:53:50 +00:00
if ( whereAmI . equals ( path . positions ( ) . get ( i ) ) ) {
2018-09-11 20:45:43 +00:00
logDebug ( " Skipping back " + ( pathPosition - i ) + " steps, to " + i ) ;
2018-08-23 18:14:37 +00:00
int previousPos = pathPosition ;
2018-08-04 19:53:50 +00:00
pathPosition = Math . max ( i - 1 , 0 ) ; // previous step might not actually be done
2018-08-23 18:14:37 +00:00
for ( int j = pathPosition ; j < = previousPos ; j + + ) {
path . movements ( ) . get ( j ) . reset ( ) ;
}
2018-09-15 01:29:35 +00:00
clearKeys ( ) ;
2018-08-13 19:35:44 +00:00
return false ;
2018-08-04 19:53:50 +00:00
}
}
for ( int i = pathPosition + 2 ; i < path . length ( ) ; i + + ) { //dont check pathPosition+1. the movement tells us when it's done (e.g. sneak placing)
if ( whereAmI . equals ( path . positions ( ) . get ( i ) ) ) {
2018-08-11 20:08:16 +00:00
if ( i - pathPosition > 2 ) {
2018-09-11 20:45:43 +00:00
logDebug ( " Skipping forward " + ( i - pathPosition ) + " steps, to " + i ) ;
2018-08-11 20:08:16 +00:00
}
2018-08-20 23:23:32 +00:00
System . out . println ( " Double skip sundae " ) ;
2018-08-04 19:53:50 +00:00
pathPosition = i - 1 ;
2018-09-15 01:29:35 +00:00
clearKeys ( ) ;
2018-08-13 19:35:44 +00:00
return false ;
2018-08-04 19:53:50 +00:00
}
}
}
}
2018-09-15 01:29:35 +00:00
Tuple < Double , BlockPos > status = path . closestPathPos ( ) ;
if ( possiblyOffPath ( status , MAX_DIST_FROM_PATH ) ) {
2018-08-04 19:53:50 +00:00
ticksAway + + ;
2018-09-15 01:29:35 +00:00
System . out . println ( " FAR AWAY FROM PATH FOR " + ticksAway + " TICKS. Current distance: " + status . getFirst ( ) + " . Threshold: " + MAX_DIST_FROM_PATH ) ;
2018-08-04 19:53:50 +00:00
if ( ticksAway > MAX_TICKS_AWAY ) {
2018-09-11 20:45:43 +00:00
logDebug ( " Too far away from path for too long, cancelling path " ) ;
2018-09-15 01:29:35 +00:00
cancel ( ) ;
2018-08-13 19:35:44 +00:00
return false ;
2018-08-04 19:53:50 +00:00
}
2018-08-02 14:01:34 +00:00
} else {
2018-08-04 19:53:50 +00:00
ticksAway = 0 ;
}
2018-09-15 01:29:35 +00:00
if ( possiblyOffPath ( status , MAX_MAX_DIST_FROM_PATH ) ) { // ok, stop right away, we're way too far.
logDebug ( " too far from path " ) ;
cancel ( ) ;
return false ;
2018-08-31 22:21:59 +00:00
}
2018-08-04 19:53:50 +00:00
//this commented block is literally cursed.
/ * Out . log ( actions . get ( pathPosition ) ) ;
if ( pathPosition < actions . size ( ) - 1 ) { //if there are two ActionBridges in a row and they are at right angles, walk diagonally. This makes it so you walk at 45 degrees along a zigzag path instead of doing inefficient zigging and zagging
if ( ( actions . get ( pathPosition ) instanceof ActionBridge ) & & ( actions . get ( pathPosition + 1 ) instanceof ActionBridge ) ) {
ActionBridge curr = ( ActionBridge ) actions . get ( pathPosition ) ;
ActionBridge next = ( ActionBridge ) actions . get ( pathPosition + 1 ) ;
if ( curr . dx ( ) ! = next . dx ( ) | | curr . dz ( ) ! = next . dz ( ) ) { //two movement are not parallel, so this is a right angle
if ( curr . amIGood ( ) & & next . amIGood ( ) ) { //nothing in the way
BlockPos cornerToCut1 = new BlockPos ( next . to . getX ( ) - next . from . getX ( ) + curr . from . getX ( ) , next . to . getY ( ) , next . to . getZ ( ) - next . from . getZ ( ) + curr . from . getZ ( ) ) ;
BlockPos cornerToCut2 = cornerToCut1 . up ( ) ;
//Block corner1 = Baritone.get(cornerToCut1).getBlock();
//Block corner2 = Baritone.get(cornerToCut2).getBlock();
//Out.gui("Cutting conner " + cornerToCut1 + " " + corner1, Out.Mode.Debug);
if ( ! Action . avoidWalkingInto ( cornerToCut1 ) & & ! Action . avoidWalkingInto ( cornerToCut2 ) ) {
double x = ( next . from . getX ( ) + next . to . getX ( ) + 1 . 0D ) * 0 . 5D ;
double z = ( next . from . getZ ( ) + next . to . getZ ( ) + 1 . 0D ) * 0 . 5D ;
MovementManager . clearMovement ( ) ;
if ( ! MovementManager . forward & & curr . oneInTen ! = null & & curr . oneInTen ) {
MovementManager . clearMovement ( ) ;
MovementManager . forward = LookManager . lookAtCoords ( x , 0 , z , false ) ;
} else {
MovementManager . moveTowardsCoords ( x , 0 , z ) ;
}
if ( MovementManager . forward & & ! MovementManager . backward ) {
thePlayer . setSprinting ( true ) ;
}
return false ;
}
}
}
2018-08-02 14:01:34 +00:00
}
2018-08-04 19:53:50 +00:00
} * /
2018-08-29 23:22:57 +00:00
long start = System . nanoTime ( ) / 1000000L ;
2018-08-06 23:09:28 +00:00
for ( int i = pathPosition - 10 ; i < pathPosition + 10 ; i + + ) {
2018-09-15 01:29:35 +00:00
if ( i < 0 | | i > = path . movements ( ) . size ( ) ) {
continue ;
}
Movement m = path . movements ( ) . get ( i ) ;
HashSet < BlockPos > prevBreak = new HashSet < > ( m . toBreak ( ) ) ;
HashSet < BlockPos > prevPlace = new HashSet < > ( m . toPlace ( ) ) ;
HashSet < BlockPos > prevWalkInto = new HashSet < > ( m . toWalkInto ( ) ) ;
m . toBreakCached = null ;
m . toPlaceCached = null ;
m . toWalkIntoCached = null ;
if ( ! prevBreak . equals ( new HashSet < > ( m . toBreak ( ) ) ) ) {
recalcBP = true ;
}
if ( ! prevPlace . equals ( new HashSet < > ( m . toPlace ( ) ) ) ) {
recalcBP = true ;
}
if ( ! prevWalkInto . equals ( new HashSet < > ( m . toWalkInto ( ) ) ) ) {
recalcBP = true ;
2018-08-09 23:37:08 +00:00
}
}
if ( recalcBP ) {
HashSet < BlockPos > newBreak = new HashSet < > ( ) ;
HashSet < BlockPos > newPlace = new HashSet < > ( ) ;
2018-08-12 15:40:44 +00:00
HashSet < BlockPos > newWalkInto = new HashSet < > ( ) ;
2018-08-13 18:49:36 +00:00
for ( int i = pathPosition ; i < path . movements ( ) . size ( ) ; i + + ) {
2018-08-09 23:37:08 +00:00
newBreak . addAll ( path . movements ( ) . get ( i ) . toBreak ( ) ) ;
newPlace . addAll ( path . movements ( ) . get ( i ) . toPlace ( ) ) ;
2018-08-12 15:40:44 +00:00
newWalkInto . addAll ( path . movements ( ) . get ( i ) . toWalkInto ( ) ) ;
2018-08-06 23:09:28 +00:00
}
2018-08-09 23:37:08 +00:00
toBreak = newBreak ;
toPlace = newPlace ;
2018-08-12 15:40:44 +00:00
toWalkInto = newWalkInto ;
2018-08-09 23:37:08 +00:00
recalcBP = false ;
}
2018-08-29 23:22:57 +00:00
long end = System . nanoTime ( ) / 1000000L ;
2018-08-09 23:37:08 +00:00
if ( end - start > 0 ) {
2018-09-17 00:11:04 +00:00
System . out . println ( " Recalculating break and place took " + ( end - start ) + " ms " ) ;
2018-08-06 23:09:28 +00:00
}
2018-08-04 19:53:50 +00:00
Movement movement = path . movements ( ) . get ( pathPosition ) ;
2018-08-13 18:49:36 +00:00
if ( costEstimateIndex = = null | | costEstimateIndex ! = pathPosition ) {
costEstimateIndex = pathPosition ;
2018-09-02 21:12:33 +00:00
// do this only once, when the movement starts, and deliberately get the cost as cached when this path was calculated, not the cost as it is right now
2018-09-16 02:35:35 +00:00
currentMovementOriginalCostEstimate = movement . getCost ( null ) ;
2018-08-26 15:49:56 +00:00
for ( int i = 1 ; i < Baritone . settings ( ) . costVerificationLookahead . get ( ) & & pathPosition + i < path . length ( ) - 1 ; i + + ) {
2018-09-02 21:12:33 +00:00
if ( path . movements ( ) . get ( pathPosition + i ) . calculateCostWithoutCaching ( ) > = ActionCosts . COST_INF ) {
2018-09-11 20:45:43 +00:00
logDebug ( " Something has changed in the world and a future movement has become impossible. Cancelling. " ) ;
2018-09-15 01:29:35 +00:00
cancel ( ) ;
2018-08-26 15:49:56 +00:00
return true ;
}
}
2018-08-13 18:49:36 +00:00
}
2018-09-02 17:43:34 +00:00
double currentCost = movement . recalculateCost ( ) ;
if ( currentCost > = ActionCosts . COST_INF ) {
2018-09-11 20:45:43 +00:00
logDebug ( " Something has changed in the world and this movement has become impossible. Cancelling. " ) ;
2018-09-15 01:29:35 +00:00
cancel ( ) ;
2018-09-02 17:43:34 +00:00
return true ;
}
2018-09-16 02:35:35 +00:00
if ( ! movement . calculatedWhileLoaded ( ) & & currentCost - currentMovementOriginalCostEstimate > Baritone . settings ( ) . maxCostIncrease . get ( ) ) {
logDebug ( " Original cost " + currentMovementOriginalCostEstimate + " current cost " + currentCost + " . Cancelling. " ) ;
2018-09-15 01:29:35 +00:00
cancel ( ) ;
2018-09-11 22:50:46 +00:00
return true ;
}
2018-08-04 22:28:36 +00:00
MovementState . MovementStatus movementStatus = movement . update ( ) ;
if ( movementStatus = = UNREACHABLE | | movementStatus = = FAILED ) {
2018-09-11 20:45:43 +00:00
logDebug ( " Movement returns status " + movementStatus ) ;
2018-09-15 01:29:35 +00:00
cancel ( ) ;
2018-08-13 19:35:44 +00:00
return true ;
2018-08-04 22:28:36 +00:00
}
if ( movementStatus = = SUCCESS ) {
2018-08-21 03:17:55 +00:00
//System.out.println("Movement done, next path");
2018-08-04 19:53:50 +00:00
pathPosition + + ;
ticksOnCurrent = 0 ;
2018-09-15 01:29:35 +00:00
clearKeys ( ) ;
2018-08-07 15:01:26 +00:00
onTick ( event ) ;
2018-08-13 19:35:44 +00:00
return true ;
2018-08-04 19:53:50 +00:00
} else {
2018-08-26 15:12:57 +00:00
sprintIfRequested ( ) ;
2018-08-04 19:53:50 +00:00
ticksOnCurrent + + ;
2018-09-16 02:35:35 +00:00
if ( ticksOnCurrent > currentMovementOriginalCostEstimate + Baritone . settings ( ) . movementTimeoutTicks . get ( ) ) {
2018-09-15 01:29:35 +00:00
// only cancel if the total time has exceeded the initial estimate
2018-08-13 18:49:36 +00:00
// as you break the blocks required, the remaining cost goes down, to the point where
2018-08-20 23:23:32 +00:00
// ticksOnCurrent is greater than recalculateCost + 100
2018-08-13 18:49:36 +00:00
// this is why we cache cost at the beginning, and don't recalculate for this comparison every tick
2018-09-16 02:35:35 +00:00
logDebug ( " This movement has taken too long ( " + ticksOnCurrent + " ticks, expected " + currentMovementOriginalCostEstimate + " ). Cancelling. " ) ;
2018-09-15 01:29:35 +00:00
cancel ( ) ;
2018-08-13 19:35:44 +00:00
return true ;
2018-08-04 19:53:50 +00:00
}
}
2018-08-13 19:35:44 +00:00
return false ; // movement is in progress
2018-08-04 19:53:50 +00:00
}
2018-09-15 01:29:35 +00:00
private boolean possiblyOffPath ( Tuple < Double , BlockPos > status , double leniency ) {
double distanceFromPath = status . getFirst ( ) ;
if ( distanceFromPath > leniency ) {
// when we're midair in the middle of a fall, we're very far from both the beginning and the end, but we aren't actually off path
if ( path . movements ( ) . get ( pathPosition ) instanceof MovementFall ) {
BlockPos fallDest = path . positions ( ) . get ( pathPosition + 1 ) ; // .get(pathPosition) is the block we fell off of
2018-09-15 19:51:37 +00:00
if ( Utils . playerFlatDistanceToCenter ( fallDest ) < leniency ) { // ignore Y by using flat distance
2018-09-15 01:29:35 +00:00
return false ;
}
return true ;
} else {
return true ;
}
} else {
return false ;
}
}
2018-08-26 15:12:57 +00:00
private void sprintIfRequested ( ) {
2018-09-15 01:29:35 +00:00
// first and foremost, if allowSprint is off, or if we don't have enough hunger, don't try and sprint
2018-09-04 16:55:56 +00:00
if ( ! new CalculationContext ( ) . canSprint ( ) ) {
2018-08-26 15:12:57 +00:00
player ( ) . setSprinting ( false ) ;
return ;
}
2018-09-15 01:29:35 +00:00
// if the movement requested sprinting, then we're done
2018-08-26 15:12:57 +00:00
if ( Baritone . INSTANCE . getInputOverrideHandler ( ) . isInputForcedDown ( mc . gameSettings . keyBindSprint ) ) {
if ( ! player ( ) . isSprinting ( ) ) {
player ( ) . setSprinting ( true ) ;
}
return ;
}
2018-09-15 01:29:35 +00:00
// however, descend doesn't request sprinting, beceause it doesn't know the context of what movement comes after it
Movement current = path . movements ( ) . get ( pathPosition ) ;
if ( current instanceof MovementDescend & & pathPosition < path . length ( ) - 2 ) {
// (dest - src) + dest is offset 1 more in the same direction
// so it's the block we'd need to worry about running into if we decide to sprint straight through this descend
BlockPos into = current . getDest ( ) . subtract ( current . getSrc ( ) . down ( ) ) . add ( current . getDest ( ) ) ;
for ( int y = 0 ; y < = 2 ; y + + ) { // we could hit any of the three blocks
if ( MovementHelper . avoidWalkingInto ( BlockStateInterface . getBlock ( into . up ( y ) ) ) ) {
2018-09-12 01:26:10 +00:00
logDebug ( " Sprinting would be unsafe " ) ;
player ( ) . setSprinting ( false ) ;
return ;
}
}
2018-09-15 01:29:35 +00:00
2018-08-26 15:12:57 +00:00
Movement next = path . movements ( ) . get ( pathPosition + 1 ) ;
2018-09-15 01:29:35 +00:00
if ( canSprintInto ( current , next ) ) {
if ( playerFeet ( ) . equals ( current . getDest ( ) ) ) {
2018-08-26 15:12:57 +00:00
pathPosition + + ;
2018-09-15 01:29:35 +00:00
clearKeys ( ) ;
2018-08-26 15:12:57 +00:00
}
if ( ! player ( ) . isSprinting ( ) ) {
player ( ) . setSprinting ( true ) ;
}
return ;
}
2018-09-11 20:45:43 +00:00
//logDebug("Turning off sprinting " + movement + " " + next + " " + movement.getDirection() + " " + next.getDirection().down() + " " + next.getDirection().down().equals(movement.getDirection()));
2018-08-26 15:12:57 +00:00
}
player ( ) . setSprinting ( false ) ;
}
2018-09-15 01:29:35 +00:00
private static boolean canSprintInto ( Movement current , Movement next ) {
if ( next instanceof MovementDescend ) {
if ( next . getDirection ( ) . equals ( current . getDirection ( ) ) ) {
return true ;
}
}
if ( next instanceof MovementTraverse ) {
if ( next . getDirection ( ) . down ( ) . equals ( current . getDirection ( ) ) & & MovementHelper . canWalkOn ( next . getDest ( ) . down ( ) ) ) {
return true ;
}
}
if ( next instanceof MovementDiagonal & & Baritone . settings ( ) . allowOvershootDiagonalDescend . get ( ) ) {
return true ;
}
return false ;
}
private static void clearKeys ( ) {
// i'm just sick and tired of this snippet being everywhere lol
Baritone . INSTANCE . getInputOverrideHandler ( ) . clearAllKeys ( ) ;
}
private void cancel ( ) {
clearKeys ( ) ;
pathPosition = path . length ( ) + 3 ;
failed = true ;
}
2018-08-09 20:08:56 +00:00
public int getPosition ( ) {
return pathPosition ;
}
2018-08-04 19:53:50 +00:00
public IPath getPath ( ) {
return path ;
}
public boolean failed ( ) {
return failed ;
}
public boolean finished ( ) {
return pathPosition > = path . length ( ) ;
2018-08-02 14:01:34 +00:00
}
2018-08-09 23:37:08 +00:00
public Set < BlockPos > toBreak ( ) {
return Collections . unmodifiableSet ( toBreak ) ;
}
public Set < BlockPos > toPlace ( ) {
return Collections . unmodifiableSet ( toPlace ) ;
}
2018-08-12 15:40:44 +00:00
public Set < BlockPos > toWalkInto ( ) {
return Collections . unmodifiableSet ( toWalkInto ) ;
}
2018-08-02 14:01:34 +00:00
}