package net.kennux.cubicworld.entity;
import java.util.Random;
import net.kennux.cubicworld.CubicWorld;
import net.kennux.cubicworld.pathfinder.Path;
import net.kennux.cubicworld.util.Mathf;
import net.kennux.cubicworld.voxel.VoxelWorld;
import com.badlogic.gdx.math.Vector3;
/**
* <pre>
* Abstract mob entity class.
* A mob entity strictly follows one target entity or moves randomly around the world.
*
* Overload the update() method and perform your target recognition in it.
* </pre>
*
* @author kennux
*
*/
public abstract class AMobEntity extends ACharacterEntity
{
/**
* The target entity.
* Only set this using the getter and setter methods.
*/
private AEntity targetEntity;
/**
* The path the entity is currently walking along.
*/
private Path currentPath;
/**
* The path which is getting processed right now.
*/
private Path nextPath;
/**
* The current step data of the path.
* Gets only set if you use setPath() for setting a new or changed path.
*/
private Vector3[] currentSteps;
/**
* The current random movement vector.
*/
private Vector3 randomMovementVector;
private Object pathLockObject = new Object();
private int currentStepIndex = 0;
/**
* The current time millis when the last path claculation was.
*/
private long lastPathFind = 0;
public AMobEntity()
{
}
public AMobEntity(VoxelWorld voxelWorld)
{
super(voxelWorld);
}
/**
* Returns the entity's jump strength. Standard is 1 blocks per second.
*
* @return
*/
public float getEntityJumpStrength()
{
return 1;
}
/**
* Returns your entity movement speed in here if you don't want the standard of 5 blocks per second.
*
* @return
*/
public float getEntitySpeed()
{
return 5;
}
@Override
public void init()
{
}
/**
* Returns true if the last path recalculation was more than 2 seconds ago.
*
* @return
*/
private boolean isTimeToGetNewPath()
{
return (System.currentTimeMillis() - this.lastPathFind) > 2000;
}
/**
* Sets the new current path.
* ALWAYS use this method, because it also creates the currentSteps array and resets the stepcounter.
*
* @param p
*/
protected void setPath(Path p)
{
synchronized (this.pathLockObject)
{
this.currentPath = p;
this.currentSteps = p.getStepData();
this.currentStepIndex = 0;
}
}
/**
* Sets the target entity for this mob.
*/
public void setTargetEntity(AEntity target)
{
synchronized (this.pathLockObject)
{
// Set entity
this.targetEntity = target;
// Re-calculate path
if (!this.voxelWorld.getVoxelspacePosition(this.getPosition()).equals(this.voxelWorld.getVoxelspacePosition(target.getPosition())))
this.nextPath = this.voxelWorld.findPath(new Vector3(this.getPosition()), new Vector3(target.getPosition()), true, Mathf.ceilToInt(this.getCharacterHeight()));
this.lastPathFind = System.currentTimeMillis();
}
}
/**
* Checks if this entity should run around randomly or follow a path.
*/
public void update()
{
if (this.master.isServer())
{
synchronized (this.pathLockObject)
{
// New path ready?
if (this.nextPath != null && this.nextPath.isProcessed())
{
// New path found?
if (this.nextPath.isFound())
{
// Set new path
this.setPath(this.nextPath);
this.nextPath = null;
}
else
{
// Retry path calculation
this.setTargetEntity(this.targetEntity);
}
}
// Got entity not in my position and no path?
if (this.targetEntity != null && this.currentPath == null && this.isTimeToGetNewPath() && this.nextPath == null && new Vector3(this.targetEntity.getPosition()).sub(this.getPosition()).len() > 1.0f)
this.setTargetEntity(this.targetEntity);
// Got path and target is too far away or invalid?
if ((this.currentPath != null && this.targetEntity != null && this.currentPath.isFound() && this.isTimeToGetNewPath() && new Vector3(this.currentPath.getEndPosition()).sub(this.targetEntity.getPosition()).len() > 1.0f))
{
// Recalc
this.setTargetEntity(this.targetEntity);
}
Vector3 movement = new Vector3();
// Move towards path or randomly?
if (this.targetEntity != null && this.currentPath != null && this.currentSteps != null)
{
// Get current step and blockspace position
Vector3 blockspacePosition = this.voxelWorld.getVoxelspacePosition(this.getPosition());
Vector3 currentStepPosition = this.currentSteps[this.currentStepIndex];
if (blockspacePosition.equals(currentStepPosition))
{
// Get next step
this.currentStepIndex++;
if (this.currentStepIndex == this.currentSteps.length - 1)
this.currentSteps = null;
else
currentStepPosition = this.currentSteps[this.currentStepIndex];
}
if (this.currentSteps != null)
{
// Move towards
movement = new Vector3(currentStepPosition).sub(this.voxelWorld.getVoxelspacePosition(this.getPosition())).nor();
if (movement.y > 0 && this.isGrounded())
{
// Jump
this.impulse(new Vector3(0, 1, 0), this.getEntityJumpStrength());
}
}
}
else if (this.targetEntity == null)
{
// Move randomly and change direction every 60th tick.
if (CubicWorld.getServer().tick % 60 == 0 || this.randomMovementVector == null)
{
Random r = new Random();
this.randomMovementVector = new Vector3(0.5f - r.nextFloat(), 0, 0.5f - r.nextFloat()).nor();
}
// Move along the random vector
movement = new Vector3(this.randomMovementVector);
}
// Multiplicate with entity speed
movement.x *= this.getEntitySpeed();
movement.y *= this.getEntitySpeed();
movement.z *= this.getEntitySpeed();
// Move
this.move(movement);
Vector3 direction = movement.nor();
if (this.targetEntity != null)
{
direction = new Vector3(this.targetEntity.getPosition()).sub(this.getPosition()).nor();
}
// Calculate yaw from direction to target
this.setEulerAngles(new Vector3(0, (float) Math.toDegrees(Math.atan2(direction.x, direction.z)), 0));
}
}
super.update();
}
}