269 lines
8.5 KiB
Java
269 lines
8.5 KiB
Java
package net.citizensnpcs.npc.ai;
|
|
|
|
import org.bukkit.Location;
|
|
import org.bukkit.block.Block;
|
|
import org.bukkit.block.BlockFace;
|
|
import org.bukkit.entity.Entity;
|
|
import org.bukkit.entity.LivingEntity;
|
|
import org.bukkit.util.Vector;
|
|
|
|
import net.citizensnpcs.api.ai.AttackStrategy;
|
|
import net.citizensnpcs.api.ai.EntityTarget;
|
|
import net.citizensnpcs.api.ai.NavigatorParameters;
|
|
import net.citizensnpcs.api.ai.PathStrategy;
|
|
import net.citizensnpcs.api.ai.TargetType;
|
|
import net.citizensnpcs.api.ai.event.CancelReason;
|
|
import net.citizensnpcs.api.astar.pathfinder.MinecraftBlockExaminer;
|
|
import net.citizensnpcs.api.npc.NPC;
|
|
import net.citizensnpcs.api.util.BoundingBox;
|
|
import net.citizensnpcs.util.NMS;
|
|
|
|
public class MCTargetStrategy implements PathStrategy, EntityTarget {
|
|
private final boolean aggro;
|
|
private int attackTicks;
|
|
private CancelReason cancelReason;
|
|
private final Entity handle;
|
|
private final NPC npc;
|
|
private final NavigatorParameters parameters;
|
|
private final Entity target;
|
|
private TargetNavigator targetNavigator;
|
|
private int updateCounter = -1;
|
|
|
|
public MCTargetStrategy(NPC npc, org.bukkit.entity.Entity target, boolean aggro, NavigatorParameters params) {
|
|
this.npc = npc;
|
|
parameters = params;
|
|
handle = npc.getEntity();
|
|
this.target = target;
|
|
TargetNavigator nms = NMS.getTargetNavigator(npc.getEntity(), target, params);
|
|
targetNavigator = nms != null && !params.useNewPathfinder() ? nms : new AStarTargeter();
|
|
this.aggro = aggro;
|
|
}
|
|
|
|
private boolean canAttack() {
|
|
BoundingBox handleBB = NMS.getBoundingBox(handle), targetBB = NMS.getBoundingBox(target);
|
|
return attackTicks <= 0 && handleBB.maxY > targetBB.minY && handleBB.minY < targetBB.maxY
|
|
&& distance() <= parameters.attackRange() && ((LivingEntity) handle).hasLineOfSight(target);
|
|
}
|
|
|
|
@Override
|
|
public void clearCancelReason() {
|
|
cancelReason = null;
|
|
}
|
|
|
|
private double distance() {
|
|
return handle.getLocation().distance(target.getLocation());
|
|
}
|
|
|
|
@Override
|
|
public CancelReason getCancelReason() {
|
|
return cancelReason;
|
|
}
|
|
|
|
@Override
|
|
public Location getCurrentDestination() {
|
|
return targetNavigator.getCurrentDestination();
|
|
}
|
|
|
|
@Override
|
|
public Iterable<Vector> getPath() {
|
|
return targetNavigator.getPath();
|
|
}
|
|
|
|
@Override
|
|
public org.bukkit.entity.Entity getTarget() {
|
|
return target;
|
|
}
|
|
|
|
@Override
|
|
public Location getTargetAsLocation() {
|
|
return getTarget().getLocation();
|
|
}
|
|
|
|
@Override
|
|
public TargetType getTargetType() {
|
|
return TargetType.ENTITY;
|
|
}
|
|
|
|
@Override
|
|
public boolean isAggressive() {
|
|
return aggro;
|
|
}
|
|
|
|
@Override
|
|
public void stop() {
|
|
targetNavigator.stop();
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "MCTargetStrategy [target=" + target + "]";
|
|
}
|
|
|
|
@Override
|
|
public boolean update() {
|
|
if (target == null || !target.isValid()) {
|
|
cancelReason = CancelReason.TARGET_DIED;
|
|
return true;
|
|
}
|
|
if (target.getWorld() != handle.getWorld()) {
|
|
cancelReason = CancelReason.TARGET_MOVED_WORLD;
|
|
return true;
|
|
}
|
|
if (cancelReason != null)
|
|
return true;
|
|
|
|
if (parameters.straightLineTargetingDistance() > 0 && !(targetNavigator instanceof StraightLineTargeter)) {
|
|
targetNavigator = new StraightLineTargeter(targetNavigator);
|
|
}
|
|
if (!aggro && distance() <= parameters.distanceMargin()) {
|
|
stop();
|
|
return false;
|
|
} else if (updateCounter == -1 || updateCounter++ > parameters.updatePathRate()) {
|
|
targetNavigator.setPath();
|
|
updateCounter = 0;
|
|
}
|
|
targetNavigator.update();
|
|
|
|
NMS.look(handle, target);
|
|
if (aggro && canAttack()) {
|
|
AttackStrategy strategy = parameters.attackStrategy();
|
|
if (strategy != null && strategy.handle((LivingEntity) handle, (LivingEntity) getTarget())) {
|
|
} else if (strategy != parameters.defaultAttackStrategy()) {
|
|
parameters.defaultAttackStrategy().handle((LivingEntity) handle, (LivingEntity) getTarget());
|
|
}
|
|
attackTicks = parameters.attackDelayTicks();
|
|
}
|
|
if (attackTicks > 0) {
|
|
attackTicks--;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private class AStarTargeter implements TargetNavigator {
|
|
private int failureTimes = 0;
|
|
private PathStrategy strategy;
|
|
|
|
@Override
|
|
public Location getCurrentDestination() {
|
|
if (strategy == null)
|
|
return null;
|
|
return strategy.getCurrentDestination();
|
|
}
|
|
|
|
@Override
|
|
public Iterable<Vector> getPath() {
|
|
return strategy.getPath();
|
|
}
|
|
|
|
@Override
|
|
public void setPath() {
|
|
// TODO: should use fallback-style pathfinding
|
|
setStrategy();
|
|
strategy.update();
|
|
CancelReason subReason = strategy.getCancelReason();
|
|
if (subReason == CancelReason.STUCK) {
|
|
if (failureTimes++ > 10) {
|
|
cancelReason = strategy.getCancelReason();
|
|
}
|
|
} else {
|
|
failureTimes = 0;
|
|
cancelReason = strategy.getCancelReason();
|
|
}
|
|
}
|
|
|
|
private void setStrategy() {
|
|
Location location = parameters.entityTargetLocationMapper().apply(target);
|
|
if (location == null)
|
|
throw new IllegalStateException("mapper should not return null");
|
|
if (!npc.isFlyable()) {
|
|
Block block = location.getBlock();
|
|
while (!MinecraftBlockExaminer.canStandOn(block.getRelative(BlockFace.DOWN))) {
|
|
block = block.getRelative(BlockFace.DOWN);
|
|
if (block.getY() <= 0) {
|
|
block = location.getBlock();
|
|
break;
|
|
}
|
|
}
|
|
location = block.getLocation();
|
|
}
|
|
strategy = npc.isFlyable() ? new FlyingAStarNavigationStrategy(npc, location, parameters)
|
|
: new AStarNavigationStrategy(npc, location, parameters);
|
|
}
|
|
|
|
@Override
|
|
public void stop() {
|
|
if (strategy != null) {
|
|
strategy.stop();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void update() {
|
|
strategy.update();
|
|
}
|
|
}
|
|
|
|
private class StraightLineTargeter implements TargetNavigator {
|
|
private PathStrategy active;
|
|
private final TargetNavigator fallback;
|
|
|
|
public StraightLineTargeter(TargetNavigator navigator) {
|
|
fallback = navigator;
|
|
}
|
|
|
|
@Override
|
|
public Location getCurrentDestination() {
|
|
return active == null ? null : active.getCurrentDestination();
|
|
}
|
|
|
|
@Override
|
|
public Iterable<Vector> getPath() {
|
|
return active != null ? active.getPath() : fallback.getPath();
|
|
}
|
|
|
|
@Override
|
|
public void setPath() {
|
|
Location location = parameters.entityTargetLocationMapper().apply(target);
|
|
if (location == null)
|
|
throw new IllegalStateException("mapper should not return null");
|
|
|
|
if (parameters.straightLineTargetingDistance() > 0
|
|
&& npc.getStoredLocation().distance(location) <= parameters.straightLineTargetingDistance()) {
|
|
active = new StraightLineNavigationStrategy(npc, target, parameters);
|
|
return;
|
|
}
|
|
active = null;
|
|
fallback.setPath();
|
|
}
|
|
|
|
@Override
|
|
public void stop() {
|
|
if (active != null) {
|
|
active.stop();
|
|
}
|
|
fallback.stop();
|
|
}
|
|
|
|
@Override
|
|
public void update() {
|
|
if (active != null) {
|
|
active.update();
|
|
} else {
|
|
fallback.update();
|
|
}
|
|
}
|
|
}
|
|
|
|
public static interface TargetNavigator {
|
|
Location getCurrentDestination();
|
|
|
|
Iterable<Vector> getPath();
|
|
|
|
void setPath();
|
|
|
|
void stop();
|
|
|
|
void update();
|
|
}
|
|
}
|