From 1acea48e59a1c2a8ac5b93e98ca0be1cb03d75d9 Mon Sep 17 00:00:00 2001 From: fullwall Date: Sat, 11 Jan 2014 14:28:03 +0800 Subject: [PATCH] Add flocking implementation and run callbacks for navigation --- .../api/ai/NavigatorParameters.java | 61 +++++++++++++------ .../api/ai/flocking/AlignmentBehavior.java | 27 ++++++++ .../api/ai/flocking/CohesionBehavior.java | 30 +++++++++ .../api/ai/flocking/FlockBehavior.java | 11 ++++ .../citizensnpcs/api/ai/flocking/Flocker.java | 36 +++++++++++ .../api/ai/flocking/NPCFlock.java | 9 +++ .../api/ai/flocking/RadiusNPCFlock.java | 32 ++++++++++ .../api/ai/flocking/SeparationBehavior.java | 28 +++++++++ 8 files changed, 216 insertions(+), 18 deletions(-) create mode 100644 src/main/java/net/citizensnpcs/api/ai/flocking/AlignmentBehavior.java create mode 100644 src/main/java/net/citizensnpcs/api/ai/flocking/CohesionBehavior.java create mode 100644 src/main/java/net/citizensnpcs/api/ai/flocking/FlockBehavior.java create mode 100644 src/main/java/net/citizensnpcs/api/ai/flocking/Flocker.java create mode 100644 src/main/java/net/citizensnpcs/api/ai/flocking/NPCFlock.java create mode 100644 src/main/java/net/citizensnpcs/api/ai/flocking/RadiusNPCFlock.java create mode 100644 src/main/java/net/citizensnpcs/api/ai/flocking/SeparationBehavior.java diff --git a/src/main/java/net/citizensnpcs/api/ai/NavigatorParameters.java b/src/main/java/net/citizensnpcs/api/ai/NavigatorParameters.java index f5aa65d4..31bce239 100644 --- a/src/main/java/net/citizensnpcs/api/ai/NavigatorParameters.java +++ b/src/main/java/net/citizensnpcs/api/ai/NavigatorParameters.java @@ -20,15 +20,28 @@ public class NavigatorParameters implements Cloneable { private double distanceMargin = 2F; private List examiners = Lists.newArrayList(); private float range; + private List runCallbacks = Lists.newArrayListWithExpectedSize(3); private float speedModifier = 1F; private int stationaryTicks = -1; private StuckAction stuckAction; private boolean useNewPathfinder; + /** + * Adds a {@link Runnable} callback that will be called every tick while the + * path is running. + * + * @param callback + * The callback to add + */ + public NavigatorParameters addRunCallback(Runnable callback) { + runCallbacks.add(callback); + return this; + } + /** * Adds a {@link NavigatorCallback} that will be removed * immediately after being called. - * + * * @param callback * The callback */ @@ -57,7 +70,7 @@ public AttackStrategy attackStrategy() { /** * Sets the {@link AttackStrategy} for use when attacking entity targets. - * + * * @param strategy * The strategy to use */ @@ -74,7 +87,7 @@ public boolean avoidWater() { /** * Sets whether to avoid water while pathfinding - * + * * @param avoidWater * Whether to avoid water */ @@ -94,7 +107,7 @@ public float baseSpeed() { * Sets the base movement speed of the {@link Navigator}. Note that this is * mob-specific and may not always be sane. Using {@link #speedModifier()} * is preferred. - * + * * @see #speedModifier() * @param speed * The new movement speed @@ -130,6 +143,9 @@ public NavigatorParameters clone() { if (examiners instanceof ArrayList) { clone.examiners = (List) ((ArrayList) examiners).clone(); } + if (runCallbacks instanceof ArrayList) { + clone.runCallbacks = (List) ((ArrayList) runCallbacks).clone(); + } return clone; } catch (CloneNotSupportedException e) { return null; @@ -139,7 +155,7 @@ public NavigatorParameters clone() { /** * Returns the configured default attack strategy, which tries to * perform the most Minecraft-like attack on the target. - * + * * @return The default strategy */ public AttackStrategy defaultAttackStrategy() { @@ -148,7 +164,7 @@ public AttackStrategy defaultAttackStrategy() { /** * Sets the default {@link AttackStrategy}. - * + * * @param defaultStrategy * The new default strategy * @see #defaultAttackStrategy() @@ -162,11 +178,11 @@ public NavigatorParameters defaultAttackStrategy(AttackStrategy defaultStrategy) * Returns the distance margin or leeway that the {@link Navigator} will be * able to stop from the target destination. The margin will be measured * against the block distance squared. - * + * * For example: if the distance margin were 2, then the {@link Navigator} * could stop moving towards the target when it is 2 blocks squared away * from it. - * + * * @return The distance margin */ public double distanceMargin() { @@ -175,7 +191,7 @@ public double distanceMargin() { /** * Sets the distance margin. - * + * * @see #distanceMargin() * @param newMargin * The new distance margin @@ -187,7 +203,7 @@ public NavigatorParameters distanceMargin(double newMargin) { /** * Adds the given {@link BlockExaminer}. - * + * * @param examiner * The BlockExaminer to add */ @@ -198,7 +214,7 @@ public NavigatorParameters examiner(BlockExaminer examiner) { /** * Gets a copy of all current {@link BlockExaminer}s. - * + * * @return An array of all current examiners */ public BlockExaminer[] examiners() { @@ -207,7 +223,7 @@ public BlockExaminer[] examiners() { /** * Modifieds the given speed value based on the current parameters. - * + * * @param toModify * The speed value to modify * @return The modified speed @@ -228,7 +244,7 @@ public float range() { * Sets the pathfinding range in blocks. The pathfinding range determines * how far away the {@link Navigator} will attempt to pathfind before giving * up to save computation. - * + * * @param range * The new range */ @@ -249,7 +265,7 @@ public float speed() { * Sets the base movement speed of the {@link Navigator}. Note that this is * mob-specific and may not always be sane. Using {@link #speedModifier()} * is preferred. - * + * * @see #speedModifier() * @param speed * The new movement speed @@ -273,7 +289,7 @@ public float speedModifier() { * Sets the movement speed modifier of the {@link Navigator}. This is a * percentage modifier that alters the movement speed returned in * {@link #speed()}. - * + * * @param percent * The new speed modifier */ @@ -293,7 +309,7 @@ public int stationaryTicks() { /** * Sets the number of stationary ticks before navigation is cancelled with a * {@link CancelReason} of STUCK. - * + * * @param ticks * The new number of stationary ticks */ @@ -305,7 +321,7 @@ public NavigatorParameters stationaryTicks(int ticks) { /** * Gets the {@link StuckAction} of these parameters. This will be run when * the navigation is stuck and must either be fixed up or cancelled. - * + * * @return The current stuck action */ public StuckAction stuckAction() { @@ -314,7 +330,7 @@ public StuckAction stuckAction() { /** * Sets the {@link StuckAction} of the parameters. - * + * * @param action * The new stuck action * @see #stuckAction() @@ -324,6 +340,15 @@ public NavigatorParameters stuckAction(StuckAction action) { return this; } + /** + * FOR INTERNAL USE ONLY: ticks all {@link Runnable} callbacks. + */ + public void tick() { + for (int i = 0; i < runCallbacks.size(); i++) { + runCallbacks.get(i).run(); + } + } + /** * @see #useNewPathfinder(boolean) * @return Whether to use the new pathfinder diff --git a/src/main/java/net/citizensnpcs/api/ai/flocking/AlignmentBehavior.java b/src/main/java/net/citizensnpcs/api/ai/flocking/AlignmentBehavior.java new file mode 100644 index 00000000..1bf1ac01 --- /dev/null +++ b/src/main/java/net/citizensnpcs/api/ai/flocking/AlignmentBehavior.java @@ -0,0 +1,27 @@ +package net.citizensnpcs.api.ai.flocking; + +import java.util.Collection; + +import net.citizensnpcs.api.npc.NPC; + +import org.bukkit.util.Vector; + +public class AlignmentBehavior implements FlockBehavior { + private final double weight; + + public AlignmentBehavior(double weight) { + this.weight = weight; + } + + @Override + public Vector getVector(NPC npc, Collection nearby) { + if (nearby.isEmpty()) + return new Vector(0, 0, 0); + Vector velocities = new Vector(0, 0, 0); + for (NPC neighbor : nearby) { + velocities = velocities.add(neighbor.getEntity().getVelocity()); + } + Vector desired = velocities.multiply((double) 1 / nearby.size()); + return desired.subtract(npc.getEntity().getVelocity()).multiply(weight); + } +} diff --git a/src/main/java/net/citizensnpcs/api/ai/flocking/CohesionBehavior.java b/src/main/java/net/citizensnpcs/api/ai/flocking/CohesionBehavior.java new file mode 100644 index 00000000..c07cd9ea --- /dev/null +++ b/src/main/java/net/citizensnpcs/api/ai/flocking/CohesionBehavior.java @@ -0,0 +1,30 @@ +package net.citizensnpcs.api.ai.flocking; + +import java.util.Collection; + +import net.citizensnpcs.api.npc.NPC; + +import org.bukkit.Location; +import org.bukkit.util.Vector; + +public class CohesionBehavior implements FlockBehavior { + private final double weight; + + public CohesionBehavior(double weight) { + this.weight = weight; + } + + @Override + public Vector getVector(NPC npc, Collection nearby) { + if (nearby.isEmpty()) + return new Vector(0, 0, 0); + Location dummy = new Location(null, 0, 0, 0); + Vector positions = new Vector(0, 0, 0); + for (NPC neighbor : nearby) { + positions = positions.add(neighbor.getEntity().getLocation(dummy).toVector()); + } + Vector center = positions.multiply((double) 1 / nearby.size()); + return npc.getEntity().getLocation(dummy).toVector().subtract(center).multiply(weight); + } + +} diff --git a/src/main/java/net/citizensnpcs/api/ai/flocking/FlockBehavior.java b/src/main/java/net/citizensnpcs/api/ai/flocking/FlockBehavior.java new file mode 100644 index 00000000..ea9505da --- /dev/null +++ b/src/main/java/net/citizensnpcs/api/ai/flocking/FlockBehavior.java @@ -0,0 +1,11 @@ +package net.citizensnpcs.api.ai.flocking; + +import java.util.Collection; + +import net.citizensnpcs.api.npc.NPC; + +import org.bukkit.util.Vector; + +public interface FlockBehavior { + Vector getVector(NPC npc, Collection nearby); +} diff --git a/src/main/java/net/citizensnpcs/api/ai/flocking/Flocker.java b/src/main/java/net/citizensnpcs/api/ai/flocking/Flocker.java new file mode 100644 index 00000000..1b060fa0 --- /dev/null +++ b/src/main/java/net/citizensnpcs/api/ai/flocking/Flocker.java @@ -0,0 +1,36 @@ +package net.citizensnpcs.api.ai.flocking; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import net.citizensnpcs.api.npc.NPC; + +import org.bukkit.util.Vector; + +public class Flocker implements Runnable { + private final List behaviors; + private final NPCFlock flock; + private final NPC npc; + + public Flocker(NPC npc, NPCFlock flock, FlockBehavior... behaviors) { + this.npc = npc; + this.flock = flock; + this.behaviors = Arrays.asList(behaviors); + } + + @Override + public void run() { + Collection nearby = flock.getNearby(npc); + if (nearby.isEmpty()) + return; + Vector base = new Vector(0, 0, 0); + for (FlockBehavior behavior : behaviors) { + base.add(behavior.getVector(npc, nearby)); + } + npc.getEntity().setVelocity(npc.getEntity().getVelocity().add(base)); + } + + public static double HIGH_INFLUENCE = 1.0 / 20.0; + public static double LOW_INFLUENCE = HIGH_INFLUENCE / 10; +} diff --git a/src/main/java/net/citizensnpcs/api/ai/flocking/NPCFlock.java b/src/main/java/net/citizensnpcs/api/ai/flocking/NPCFlock.java new file mode 100644 index 00000000..75c56f4e --- /dev/null +++ b/src/main/java/net/citizensnpcs/api/ai/flocking/NPCFlock.java @@ -0,0 +1,9 @@ +package net.citizensnpcs.api.ai.flocking; + +import java.util.Collection; + +import net.citizensnpcs.api.npc.NPC; + +public interface NPCFlock { + public Collection getNearby(NPC npc); +} diff --git a/src/main/java/net/citizensnpcs/api/ai/flocking/RadiusNPCFlock.java b/src/main/java/net/citizensnpcs/api/ai/flocking/RadiusNPCFlock.java new file mode 100644 index 00000000..20e30ee8 --- /dev/null +++ b/src/main/java/net/citizensnpcs/api/ai/flocking/RadiusNPCFlock.java @@ -0,0 +1,32 @@ +package net.citizensnpcs.api.ai.flocking; + +import java.util.Collection; + +import net.citizensnpcs.api.CitizensAPI; +import net.citizensnpcs.api.npc.NPC; + +import org.bukkit.entity.Entity; + +import com.google.common.collect.Lists; + +public class RadiusNPCFlock implements NPCFlock { + private final double radius; + + public RadiusNPCFlock(double radius) { + this.radius = radius; + } + + @Override + public Collection getNearby(NPC npc) { + Collection ret = Lists.newArrayList(); + for (Entity entity : npc.getEntity().getNearbyEntities(radius, radius, radius)) { + NPC npc2 = CitizensAPI.getNPCRegistry().getNPC(entity); + if (npc2 != null) { + if (!npc2.getNavigator().isNavigating()) + continue; + ret.add(npc2); + } + } + return ret; + } +} diff --git a/src/main/java/net/citizensnpcs/api/ai/flocking/SeparationBehavior.java b/src/main/java/net/citizensnpcs/api/ai/flocking/SeparationBehavior.java new file mode 100644 index 00000000..c0c80d05 --- /dev/null +++ b/src/main/java/net/citizensnpcs/api/ai/flocking/SeparationBehavior.java @@ -0,0 +1,28 @@ +package net.citizensnpcs.api.ai.flocking; + +import java.util.Collection; + +import net.citizensnpcs.api.npc.NPC; + +import org.bukkit.util.Vector; + +public class SeparationBehavior implements FlockBehavior { + private final double weight; + + public SeparationBehavior(double weight) { + this.weight = weight; + } + + @Override + public Vector getVector(NPC npc, Collection nearby) { + if (nearby.isEmpty()) + return new Vector(0, 0, 0); + Vector steering = new Vector(0, 0, 0); + Vector pos = npc.getEntity().getLocation().toVector(); + for (NPC neighbor : nearby) { + Vector repulse = pos.subtract(neighbor.getEntity().getLocation().toVector()).multiply(1.0 / 3.0); + steering = repulse.add(steering); + } + return steering.multiply(weight); + } +}