Skip to content

Commit

Permalink
Add flocking implementation and run callbacks for navigation
Browse files Browse the repository at this point in the history
  • Loading branch information
fullwall committed Jan 11, 2014
1 parent eac9cd6 commit 1acea48
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 18 deletions.
61 changes: 43 additions & 18 deletions src/main/java/net/citizensnpcs/api/ai/NavigatorParameters.java
Expand Up @@ -20,15 +20,28 @@ public class NavigatorParameters implements Cloneable {
private double distanceMargin = 2F;
private List<BlockExaminer> examiners = Lists.newArrayList();
private float range;
private List<Runnable> 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
* <em>immediately</em> after being called.
*
*
* @param callback
* The callback
*/
Expand Down Expand Up @@ -57,7 +70,7 @@ public AttackStrategy attackStrategy() {

/**
* Sets the {@link AttackStrategy} for use when attacking entity targets.
*
*
* @param strategy
* The strategy to use
*/
Expand All @@ -74,7 +87,7 @@ public boolean avoidWater() {

/**
* Sets whether to avoid water while pathfinding
*
*
* @param avoidWater
* Whether to avoid water
*/
Expand All @@ -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
Expand Down Expand Up @@ -130,6 +143,9 @@ public NavigatorParameters clone() {
if (examiners instanceof ArrayList) {
clone.examiners = (List<BlockExaminer>) ((ArrayList<BlockExaminer>) examiners).clone();
}
if (runCallbacks instanceof ArrayList) {
clone.runCallbacks = (List<Runnable>) ((ArrayList<Runnable>) runCallbacks).clone();
}
return clone;
} catch (CloneNotSupportedException e) {
return null;
Expand All @@ -139,7 +155,7 @@ public NavigatorParameters clone() {
/**
* Returns the configured <em>default</em> attack strategy, which tries to
* perform the most Minecraft-like attack on the target.
*
*
* @return The default strategy
*/
public AttackStrategy defaultAttackStrategy() {
Expand All @@ -148,7 +164,7 @@ public AttackStrategy defaultAttackStrategy() {

/**
* Sets the default {@link AttackStrategy}.
*
*
* @param defaultStrategy
* The new default strategy
* @see #defaultAttackStrategy()
Expand All @@ -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() {
Expand All @@ -175,7 +191,7 @@ public double distanceMargin() {

/**
* Sets the distance margin.
*
*
* @see #distanceMargin()
* @param newMargin
* The new distance margin
Expand All @@ -187,7 +203,7 @@ public NavigatorParameters distanceMargin(double newMargin) {

/**
* Adds the given {@link BlockExaminer}.
*
*
* @param examiner
* The BlockExaminer to add
*/
Expand All @@ -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() {
Expand All @@ -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
Expand All @@ -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
*/
Expand All @@ -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
Expand All @@ -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
*/
Expand All @@ -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
*/
Expand All @@ -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() {
Expand All @@ -314,7 +330,7 @@ public StuckAction stuckAction() {

/**
* Sets the {@link StuckAction} of the parameters.
*
*
* @param action
* The new stuck action
* @see #stuckAction()
Expand All @@ -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
Expand Down
@@ -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<NPC> 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);
}
}
@@ -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<NPC> 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);
}

}
11 changes: 11 additions & 0 deletions 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<NPC> nearby);
}
36 changes: 36 additions & 0 deletions 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<FlockBehavior> 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<NPC> 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;
}
9 changes: 9 additions & 0 deletions 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<NPC> getNearby(NPC npc);
}
32 changes: 32 additions & 0 deletions 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<NPC> getNearby(NPC npc) {
Collection<NPC> 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;
}
}
@@ -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<NPC> 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);
}
}

0 comments on commit 1acea48

Please sign in to comment.