2025-03-11 00:23:54 +01:00
|
|
|
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;
|
2025-03-11 10:26:48 +01:00
|
|
|
import game.util.ExtMath;
|
2025-03-11 00:23:54 +01:00
|
|
|
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<BoundingBox> 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);
|
|
|
|
}
|