package game.pathfinding; import java.util.List; import game.entity.Entity; import game.entity.attributes.AttributeInstance; import game.entity.attributes.Attributes; import game.entity.types.EntityLiving; import game.util.ExtMath; import game.world.BlockPos; import game.world.BoundingBox; import game.world.Vec3; import game.world.World; public abstract class PathNavigate { protected EntityLiving theEntity; protected World worldObj; /** The PathEntity being followed. */ protected PathEntity currentPath; protected double speed; /** * The number of blocks (extra) +/- in each axis that get pulled out as cache for the pathfinder's search space */ private final AttributeInstance pathSearchRange; /** Time, in number of ticks, following the current path */ private int totalTicks; /** * The time when the last position check was done (to detect successful movement) */ private int ticksAtLastPos; /** * Coordinates of the entity's position last time a check was done (part of monitoring getting 'stuck') */ private Vec3 lastPosCheck = new Vec3(0.0D, 0.0D, 0.0D); private float heightRequirement = 1.0F; private final PathFinder pathFinder; public PathNavigate(EntityLiving entitylivingIn, World worldIn) { this.theEntity = entitylivingIn; this.worldObj = worldIn; this.pathSearchRange = entitylivingIn.getEntityAttribute(Attributes.FOLLOW_RANGE); this.pathFinder = this.getPathFinder(); } protected abstract PathFinder getPathFinder(); /** * Sets the speed */ public void setSpeed(double speedIn) { this.speed = speedIn; } /** * Gets the maximum distance that the path finding will search in. */ public float getPathSearchRange() { return (float)this.pathSearchRange.getAttributeValue(); } /** * Returns the path to the given coordinates. Args : x, y, z */ public final PathEntity getPathToXYZ(double x, double y, double z) { return this.getPathToPos(new BlockPos(ExtMath.floord(x), (int)y, ExtMath.floord(z))); } /** * Returns path to given BlockPos */ public PathEntity getPathToPos(BlockPos pos) { if (!this.canNavigate()) { return null; } else { float f = this.getPathSearchRange(); // this.worldObj.profiler.start("pathfind"); BlockPos blockpos = new BlockPos(this.theEntity); int i = (int)(f + 8.0F); PathCache chunkcache = new PathCache(this.worldObj, blockpos.add(-i, -i, -i), blockpos.add(i, i, i)); PathEntity pathentity = this.pathFinder.createEntityPathTo(chunkcache, this.theEntity, pos, f); // this.worldObj.profiler.end(); return pathentity; } } /** * Try to find and set a path to XYZ. Returns true if successful. Args : x, y, z, speed */ public boolean tryMoveToXYZ(double x, double y, double z, double speedIn) { PathEntity pathentity = this.getPathToXYZ((double)ExtMath.floord(x), (double)((int)y), (double)ExtMath.floord(z)); return this.setPath(pathentity, speedIn); } /** * Sets vertical space requirement for path */ public void setHeightRequirement(float jumpHeight) { this.heightRequirement = jumpHeight; } /** * Returns the path to the given EntityLiving. Args : entity */ public PathEntity getPathToEntityLiving(Entity entityIn) { if (!this.canNavigate()) { return null; } else { float f = this.getPathSearchRange(); // this.worldObj.profiler.start("pathfind"); BlockPos blockpos = (new BlockPos(this.theEntity)).up(); int i = (int)(f + 16.0F); PathCache chunkcache = new PathCache(this.worldObj, blockpos.add(-i, -i, -i), blockpos.add(i, i, i)); PathEntity pathentity = this.pathFinder.createEntityPathTo(chunkcache, this.theEntity, entityIn, f); // this.worldObj.profiler.end(); return pathentity; } } /** * Try to find and set a path to EntityLiving. Returns true if successful. Args : entity, speed */ public boolean tryMoveToEntityLiving(Entity entityIn, double speedIn) { PathEntity pathentity = this.getPathToEntityLiving(entityIn); return pathentity != null ? this.setPath(pathentity, speedIn) : false; } /** * Sets a new path. If it's diferent from the old path. Checks to adjust path for sun avoiding, and stores start * coords. Args : path, speed */ public boolean setPath(PathEntity pathentityIn, double speedIn) { if (pathentityIn == null) { this.currentPath = null; return false; } else { if (!pathentityIn.isSamePath(this.currentPath)) { this.currentPath = pathentityIn; } this.removeSunnyPath(); if (this.currentPath.getCurrentPathLength() == 0) { return false; } else { this.speed = speedIn; Vec3 vec3 = this.getEntityPosition(); this.ticksAtLastPos = this.totalTicks; this.lastPosCheck = vec3; return true; } } } /** * gets the actively used PathEntity */ public PathEntity getPath() { return this.currentPath; } public void onUpdateNavigation() { ++this.totalTicks; if (!this.noPath()) { if (this.canNavigate()) { this.pathFollow(); } else if (this.currentPath != null && this.currentPath.getCurrentPathIndex() < this.currentPath.getCurrentPathLength()) { Vec3 vec3 = this.getEntityPosition(); Vec3 vec31 = this.currentPath.getVectorFromIndex(this.theEntity, this.currentPath.getCurrentPathIndex()); if (vec3.yCoord > vec31.yCoord && !this.theEntity.onGround && ExtMath.floord(vec3.xCoord) == ExtMath.floord(vec31.xCoord) && ExtMath.floord(vec3.zCoord) == ExtMath.floord(vec31.zCoord)) { this.currentPath.setCurrentPathIndex(this.currentPath.getCurrentPathIndex() + 1); } } if (!this.noPath()) { Vec3 vec32 = this.currentPath.getPosition(this.theEntity); if (vec32 != null) { BoundingBox axisalignedbb1 = (new BoundingBox(vec32.xCoord, vec32.yCoord, vec32.zCoord, vec32.xCoord, vec32.yCoord, vec32.zCoord)).expand(0.5D, 0.5D, 0.5D); List list = this.worldObj.getCollidingBoundingBoxes(this.theEntity, axisalignedbb1.addCoord(0.0D, -1.0D, 0.0D)); double d0 = -1.0D; axisalignedbb1 = axisalignedbb1.offset(0.0D, 1.0D, 0.0D); for (BoundingBox axisalignedbb : list) { d0 = axisalignedbb.calculateYOffset(axisalignedbb1, d0); } this.theEntity.getMoveHelper().setMoveTo(vec32.xCoord, vec32.yCoord + d0, vec32.zCoord, this.speed); } } } } protected void pathFollow() { Vec3 vec3 = this.getEntityPosition(); int i = this.currentPath.getCurrentPathLength(); for (int j = this.currentPath.getCurrentPathIndex(); j < this.currentPath.getCurrentPathLength(); ++j) { if (this.currentPath.getPathPointFromIndex(j).yCoord != (int)vec3.yCoord) { i = j; break; } } float f = this.theEntity.width * this.theEntity.width * this.heightRequirement; for (int k = this.currentPath.getCurrentPathIndex(); k < i; ++k) { Vec3 vec31 = this.currentPath.getVectorFromIndex(this.theEntity, k); if (vec3.squareDistanceTo(vec31) < (double)f) { this.currentPath.setCurrentPathIndex(k + 1); } } int j1 = ExtMath.ceilf(this.theEntity.width); int k1 = (int)this.theEntity.height + 1; int l = j1; for (int i1 = i - 1; i1 >= this.currentPath.getCurrentPathIndex(); --i1) { if (this.isDirectPathBetweenPoints(vec3, this.currentPath.getVectorFromIndex(this.theEntity, i1), j1, k1, l)) { this.currentPath.setCurrentPathIndex(i1); break; } } this.checkForStuck(vec3); } /** * Checks if entity haven't been moved when last checked and if so, clears current {@link * game.pathfinding.PathEntity} */ protected void checkForStuck(Vec3 positionVec3) { if (this.totalTicks - this.ticksAtLastPos > 100) { if (positionVec3.squareDistanceTo(this.lastPosCheck) < 2.25D) { this.clearPathEntity(); } this.ticksAtLastPos = this.totalTicks; this.lastPosCheck = positionVec3; } } /** * If null path or reached the end */ public boolean noPath() { return this.currentPath == null || this.currentPath.isFinished(); } /** * sets active PathEntity to null */ public void clearPathEntity() { this.currentPath = null; } protected abstract Vec3 getEntityPosition(); /** * If on ground or swimming and can swim */ protected abstract boolean canNavigate(); /** * Returns true if the entity is in water or lava, false otherwise */ protected boolean isInLiquid() { return this.theEntity.isInLiquid() || this.theEntity.isInMolten(); } /** * Trims path data from the end to the first sun covered block */ protected void removeSunnyPath() { } /** * Returns true when an entity of specified size could safely walk in a straight line between the two points. Args: * pos1, pos2, entityXSize, entityYSize, entityZSize */ protected abstract boolean isDirectPathBetweenPoints(Vec3 posVec31, Vec3 posVec32, int sizeX, int sizeY, int sizeZ); }