package net.minecraft.pathfinding; import net.minecraft.block.Block; import net.minecraft.block.material.Material; import net.minecraft.entity.EntityLiving; import net.minecraft.util.MathHelper; import net.minecraft.util.Vec3; import net.minecraft.world.World; public class PathNavigate { private EntityLiving theEntity; private World worldObj; /** The PathEntity being followed. */ private PathEntity currentPath; private float speed; /** * The number of blocks (extra) +/- in each axis that get pulled out as cache for the pathfinder's search space */ private float pathSearchRange; private boolean noSunPathfind = false; /** 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 = Vec3.createVectorHelper(0.0D, 0.0D, 0.0D); /** * Specifically, if a wooden door block is even considered to be passable by the pathfinder */ private boolean canPassOpenWoodenDoors = true; /** If door blocks are considered passable even when closed */ private boolean canPassClosedWoodenDoors = false; /** If water blocks are avoided (at least by the pathfinder) */ private boolean avoidsWater = false; /** * If the entity can swim. Swimming AI enables this and the pathfinder will also cause the entity to swim straight * upwards when underwater */ private boolean canSwim = false; public PathNavigate(EntityLiving par1EntityLiving, World par2World, float par3) { this.theEntity = par1EntityLiving; this.worldObj = par2World; this.pathSearchRange = par3; } public void setAvoidsWater(boolean par1) { this.avoidsWater = par1; } public boolean getAvoidsWater() { return this.avoidsWater; } public void setBreakDoors(boolean par1) { this.canPassClosedWoodenDoors = par1; } /** * Sets if the entity can enter open doors */ public void setEnterDoors(boolean par1) { this.canPassOpenWoodenDoors = par1; } /** * Returns true if the entity can break doors, false otherwise */ public boolean getCanBreakDoors() { return this.canPassClosedWoodenDoors; } /** * Sets if the path should avoid sunlight */ public void setAvoidSun(boolean par1) { this.noSunPathfind = par1; } /** * Sets the speed */ public void setSpeed(float par1) { this.speed = par1; } /** * Sets if the entity can swim */ public void setCanSwim(boolean par1) { this.canSwim = par1; } /** * Returns the path to the given coordinates */ public PathEntity getPathToXYZ(double par1, double par3, double par5) { return !this.canNavigate() ? null : this.worldObj.getEntityPathToXYZ(this.theEntity, MathHelper.floor_double(par1), (int)par3, MathHelper.floor_double(par5), this.pathSearchRange, this.canPassOpenWoodenDoors, this.canPassClosedWoodenDoors, this.avoidsWater, this.canSwim); } /** * Try to find and set a path to XYZ. Returns true if successful. */ public boolean tryMoveToXYZ(double par1, double par3, double par5, float par7) { PathEntity pathentity = this.getPathToXYZ((double)MathHelper.floor_double(par1), (double)((int)par3), (double)MathHelper.floor_double(par5)); return this.setPath(pathentity, par7); } /** * Returns the path to the given EntityLiving */ public PathEntity getPathToEntityLiving(EntityLiving par1EntityLiving) { return !this.canNavigate() ? null : this.worldObj.getPathEntityToEntity(this.theEntity, par1EntityLiving, this.pathSearchRange, this.canPassOpenWoodenDoors, this.canPassClosedWoodenDoors, this.avoidsWater, this.canSwim); } /** * Try to find and set a path to EntityLiving. Returns true if successful. */ public boolean tryMoveToEntityLiving(EntityLiving par1EntityLiving, float par2) { PathEntity pathentity = this.getPathToEntityLiving(par1EntityLiving); return pathentity != null ? this.setPath(pathentity, par2) : false; } /** * sets the active path data if path is 100% unique compared to old path, checks to adjust path for sun avoiding * ents and stores end coords */ public boolean setPath(PathEntity par1PathEntity, float par2) { if (par1PathEntity == null) { this.currentPath = null; return false; } else { if (!par1PathEntity.isSamePath(this.currentPath)) { this.currentPath = par1PathEntity; } if (this.noSunPathfind) { this.removeSunnyPath(); } if (this.currentPath.getCurrentPathLength() == 0) { return false; } else { this.speed = par2; Vec3 vec3 = this.getEntityPosition(); this.ticksAtLastPos = this.totalTicks; this.lastPosCheck.xCoord = vec3.xCoord; this.lastPosCheck.yCoord = vec3.yCoord; this.lastPosCheck.zCoord = vec3.zCoord; 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(); } if (!this.noPath()) { Vec3 vec3 = this.currentPath.getPosition(this.theEntity); if (vec3 != null) { this.theEntity.getMoveHelper().setMoveTo(vec3.xCoord, vec3.yCoord, vec3.zCoord, this.speed); } } } } private 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; int k; for (k = this.currentPath.getCurrentPathIndex(); k < i; ++k) { if (vec3.squareDistanceTo(this.currentPath.getVectorFromIndex(this.theEntity, k)) < (double)f) { this.currentPath.setCurrentPathIndex(k + 1); } } k = MathHelper.ceiling_float_int(this.theEntity.width); int l = (int)this.theEntity.height + 1; int i1 = k; for (int j1 = i - 1; j1 >= this.currentPath.getCurrentPathIndex(); --j1) { if (this.isDirectPathBetweenPoints(vec3, this.currentPath.getVectorFromIndex(this.theEntity, j1), k, l, i1)) { this.currentPath.setCurrentPathIndex(j1); break; } } if (this.totalTicks - this.ticksAtLastPos > 100) { if (vec3.squareDistanceTo(this.lastPosCheck) < 2.25D) { this.clearPathEntity(); } this.ticksAtLastPos = this.totalTicks; this.lastPosCheck.xCoord = vec3.xCoord; this.lastPosCheck.yCoord = vec3.yCoord; this.lastPosCheck.zCoord = vec3.zCoord; } } /** * 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; } private Vec3 getEntityPosition() { return this.worldObj.getWorldVec3Pool().getVecFromPool(this.theEntity.posX, (double)this.getPathableYPos(), this.theEntity.posZ); } /** * Gets the safe pathing Y position for the entity depending on if it can path swim or not */ private int getPathableYPos() { if (this.theEntity.isInWater() && this.canSwim) { int i = (int)this.theEntity.boundingBox.minY; int j = this.worldObj.getBlockId(MathHelper.floor_double(this.theEntity.posX), i, MathHelper.floor_double(this.theEntity.posZ)); int k = 0; do { if (j != Block.waterMoving.blockID && j != Block.waterStill.blockID) { return i; } ++i; j = this.worldObj.getBlockId(MathHelper.floor_double(this.theEntity.posX), i, MathHelper.floor_double(this.theEntity.posZ)); ++k; } while (k <= 16); return (int)this.theEntity.boundingBox.minY; } else { return (int)(this.theEntity.boundingBox.minY + 0.5D); } } /** * If on ground or swimming and can swim */ private boolean canNavigate() { return this.theEntity.onGround || this.canSwim && this.isInFluid(); } /** * Returns true if the entity is in water or lava, false otherwise */ private boolean isInFluid() { return this.theEntity.isInWater() || this.theEntity.handleLavaMovement(); } /** * Trims path data from the end to the first sun covered block */ private void removeSunnyPath() { if (!this.worldObj.canBlockSeeTheSky(MathHelper.floor_double(this.theEntity.posX), (int)(this.theEntity.boundingBox.minY + 0.5D), MathHelper.floor_double(this.theEntity.posZ))) { for (int i = 0; i < this.currentPath.getCurrentPathLength(); ++i) { PathPoint pathpoint = this.currentPath.getPathPointFromIndex(i); if (this.worldObj.canBlockSeeTheSky(pathpoint.xCoord, pathpoint.yCoord, pathpoint.zCoord)) { this.currentPath.setCurrentPathLength(i - 1); return; } } } } /** * 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 */ private boolean isDirectPathBetweenPoints(Vec3 par1Vec3, Vec3 par2Vec3, int par3, int par4, int par5) { int l = MathHelper.floor_double(par1Vec3.xCoord); int i1 = MathHelper.floor_double(par1Vec3.zCoord); double d0 = par2Vec3.xCoord - par1Vec3.xCoord; double d1 = par2Vec3.zCoord - par1Vec3.zCoord; double d2 = d0 * d0 + d1 * d1; if (d2 < 1.0E-8D) { return false; } else { double d3 = 1.0D / Math.sqrt(d2); d0 *= d3; d1 *= d3; par3 += 2; par5 += 2; if (!this.isSafeToStandAt(l, (int)par1Vec3.yCoord, i1, par3, par4, par5, par1Vec3, d0, d1)) { return false; } else { par3 -= 2; par5 -= 2; double d4 = 1.0D / Math.abs(d0); double d5 = 1.0D / Math.abs(d1); double d6 = (double)(l * 1) - par1Vec3.xCoord; double d7 = (double)(i1 * 1) - par1Vec3.zCoord; if (d0 >= 0.0D) { ++d6; } if (d1 >= 0.0D) { ++d7; } d6 /= d0; d7 /= d1; int j1 = d0 < 0.0D ? -1 : 1; int k1 = d1 < 0.0D ? -1 : 1; int l1 = MathHelper.floor_double(par2Vec3.xCoord); int i2 = MathHelper.floor_double(par2Vec3.zCoord); int j2 = l1 - l; int k2 = i2 - i1; do { if (j2 * j1 <= 0 && k2 * k1 <= 0) { return true; } if (d6 < d7) { d6 += d4; l += j1; j2 = l1 - l; } else { d7 += d5; i1 += k1; k2 = i2 - i1; } } while (this.isSafeToStandAt(l, (int)par1Vec3.yCoord, i1, par3, par4, par5, par1Vec3, d0, d1)); return false; } } } /** * Returns true when an entity could stand at a position, including solid blocks under the entire entity. Args: * xOffset, yOffset, zOffset, entityXSize, entityYSize, entityZSize, originPosition, vecX, vecZ */ private boolean isSafeToStandAt(int par1, int par2, int par3, int par4, int par5, int par6, Vec3 par7Vec3, double par8, double par10) { int k1 = par1 - par4 / 2; int l1 = par3 - par6 / 2; if (!this.isPositionClear(k1, par2, l1, par4, par5, par6, par7Vec3, par8, par10)) { return false; } else { for (int i2 = k1; i2 < k1 + par4; ++i2) { for (int j2 = l1; j2 < l1 + par6; ++j2) { double d2 = (double)i2 + 0.5D - par7Vec3.xCoord; double d3 = (double)j2 + 0.5D - par7Vec3.zCoord; if (d2 * par8 + d3 * par10 >= 0.0D) { int k2 = this.worldObj.getBlockId(i2, par2 - 1, j2); if (k2 <= 0) { return false; } Material material = Block.blocksList[k2].blockMaterial; if (material == Material.water && !this.theEntity.isInWater()) { return false; } if (material == Material.lava) { return false; } } } } return true; } } /** * Returns true if an entity does not collide with any solid blocks at the position. Args: xOffset, yOffset, * zOffset, entityXSize, entityYSize, entityZSize, originPosition, vecX, vecZ */ private boolean isPositionClear(int par1, int par2, int par3, int par4, int par5, int par6, Vec3 par7Vec3, double par8, double par10) { for (int k1 = par1; k1 < par1 + par4; ++k1) { for (int l1 = par2; l1 < par2 + par5; ++l1) { for (int i2 = par3; i2 < par3 + par6; ++i2) { double d2 = (double)k1 + 0.5D - par7Vec3.xCoord; double d3 = (double)i2 + 0.5D - par7Vec3.zCoord; if (d2 * par8 + d3 * par10 >= 0.0D) { int j2 = this.worldObj.getBlockId(k1, l1, i2); if (j2 > 0 && !Block.blocksList[j2].getBlocksMovement(this.worldObj, k1, l1, i2)) { return false; } } } } } return true; } }