aiGroupRef;
+ protected WrapperEntityCreature entity;
+
+ public GoalSelector(WrapperEntityCreature entity) {
+ this.entity = entity;
+ }
+
+ /**
+ * Whether this {@link GoalSelector} should start.
+ *
+ * @return true to start
+ */
+ public abstract boolean shouldStart();
+
+ /**
+ * Starts this {@link GoalSelector}.
+ */
+ public abstract void start();
+
+ /**
+ * Called every tick when this {@link GoalSelector} is running.
+ *
+ * @param time the time of the update in milliseconds
+ */
+ public abstract void tick(long time);
+
+ /**
+ * Whether this {@link GoalSelector} should end.
+ *
+ * @return true to end
+ */
+ public abstract boolean shouldEnd();
+
+ /**
+ * Ends this {@link GoalSelector}.
+ */
+ public abstract void end();
+
+
+ /**
+ * Gets the entity behind the goal selector.
+ *
+ * @return the entity
+ */
+ @NotNull
+ public WrapperEntityCreature getEntityCreature() {
+ return entity;
+ }
+
+ /**
+ * Changes the entity affected by the goal selector.
+ *
+ * WARNING: this does not add the goal selector to {@code entityCreature},
+ * this only change the internal entity AI group's field. Be sure to remove the goal from
+ * the previous entity AI group and add it to the new one using {@link AIGroup#getGoalSelectors()}.
+ *
+ * @param entity the new affected entity
+ */
+ public void setEntityCreature(@NotNull WrapperEntityCreature entity) {
+ this.entity = entity;
+ }
+
+ void setAIGroup(@NotNull AIGroup group) {
+ this.aiGroupRef = new WeakReference<>(group);
+ }
+
+ @Nullable
+ protected AIGroup getAIGroup() {
+ return this.aiGroupRef.get();
+ }
+
}
diff --git a/src/main/java/me/tofaa/entitylib/entity/ai/GoalSelectorList.java b/src/main/java/me/tofaa/entitylib/entity/ai/GoalSelectorList.java
new file mode 100644
index 0000000..b783262
--- /dev/null
+++ b/src/main/java/me/tofaa/entitylib/entity/ai/GoalSelectorList.java
@@ -0,0 +1,54 @@
+package me.tofaa.entitylib.entity.ai;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.function.UnaryOperator;
+
+final class GoalSelectorList extends ArrayList {
+
+ final AIGroup aiGroup;
+
+ GoalSelectorList(AIGroup aiGroup) {
+ this.aiGroup = aiGroup;
+ }
+
+ @Override
+ public GoalSelector set(int index, GoalSelector element) {
+ element.setAIGroup(aiGroup);
+ return super.set(index, element);
+ }
+
+ @Override
+ public boolean add(GoalSelector element) {
+ element.setAIGroup(aiGroup);
+ return super.add(element);
+ }
+
+ @Override
+ public void add(int index, GoalSelector element) {
+ element.setAIGroup(aiGroup);
+ super.add(index, element);
+ }
+
+ @Override
+ public boolean addAll(Collection extends GoalSelector> c) {
+ c.forEach(goalSelector -> goalSelector.setAIGroup(aiGroup));
+ return super.addAll(c);
+ }
+
+ @Override
+ public boolean addAll(int index, Collection extends GoalSelector> c) {
+ c.forEach(goalSelector -> goalSelector.setAIGroup(aiGroup));
+ return super.addAll(index, c);
+ }
+
+ @Override
+ public void replaceAll(UnaryOperator operator) {
+ super.replaceAll(goalSelector -> {
+ goalSelector = operator.apply(goalSelector);
+ goalSelector.setAIGroup(aiGroup);
+ return goalSelector;
+ });
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/me/tofaa/entitylib/entity/ai/TargetSelector.java b/src/main/java/me/tofaa/entitylib/entity/ai/TargetSelector.java
deleted file mode 100644
index 3429ed6..0000000
--- a/src/main/java/me/tofaa/entitylib/entity/ai/TargetSelector.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package me.tofaa.entitylib.entity.ai;
-
-public class TargetSelector {
-}
diff --git a/src/main/java/me/tofaa/entitylib/entity/ai/goals/RandomHeadMovementGoal.java b/src/main/java/me/tofaa/entitylib/entity/ai/goals/RandomHeadMovementGoal.java
new file mode 100644
index 0000000..e085ace
--- /dev/null
+++ b/src/main/java/me/tofaa/entitylib/entity/ai/goals/RandomHeadMovementGoal.java
@@ -0,0 +1,81 @@
+package me.tofaa.entitylib.entity.ai.goals;
+
+import com.github.retrooper.packetevents.util.Vector3d;
+import me.tofaa.entitylib.entity.WrapperEntityCreature;
+import me.tofaa.entitylib.entity.ai.GoalSelector;
+import me.tofaa.entitylib.extras.CoordinateUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Random;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+public class RandomHeadMovementGoal extends GoalSelector {
+
+ private static final Random RANDOM = new Random();
+ private final int chancePerTick;
+ private final Supplier minimalLookTimeSupplier;
+ private final Function randomDirectionFunction;
+ private Vector3d lookDirection;
+ private int lookTime = 0;
+
+ public RandomHeadMovementGoal(WrapperEntityCreature entityCreature, int chancePerTick) {
+ this(entityCreature, chancePerTick,
+ // These two functions act similarly enough to how MC randomly looks around.
+ // Look in one direction for at most 40 ticks and at minimum 20 ticks.
+ () -> 20 + RANDOM.nextInt(20),
+ // Look at a random block
+ (creature) -> {
+ final double n = Math.PI * 2 * RANDOM.nextDouble();
+ return new Vector3d(
+ (float) Math.cos(n),
+ 0,
+ (float) Math.sin(n)
+ );
+ });
+ }
+
+ /**
+ * @param entityCreature Creature that should randomly look around.
+ * @param chancePerTick The chance (per tick) that the entity looks around. Setting this to N would mean there is a 1 in N chance.
+ * @param minimalLookTimeSupplier A supplier that returns the minimal amount of time an entity looks in a direction.
+ * @param randomDirectionFunction A function that returns a random vector that the entity will look in/at.
+ */
+ public RandomHeadMovementGoal(
+ WrapperEntityCreature entityCreature,
+ int chancePerTick,
+ @NotNull Supplier minimalLookTimeSupplier,
+ @NotNull Function randomDirectionFunction) {
+ super(entityCreature);
+ this.chancePerTick = chancePerTick;
+ this.minimalLookTimeSupplier = minimalLookTimeSupplier;
+ this.randomDirectionFunction = randomDirectionFunction;
+ }
+
+ @Override
+ public boolean shouldStart() {
+ return RANDOM.nextInt(chancePerTick) == 0;
+ }
+
+ @Override
+ public void start() {
+ lookTime = minimalLookTimeSupplier.get();
+ lookDirection = randomDirectionFunction.apply(entity);
+ }
+
+ @Override
+ public void tick(long time) {
+ --lookTime;
+ entity.teleport(CoordinateUtil.withDirection(entity.getLocation(), lookDirection));
+ }
+
+ @Override
+ public boolean shouldEnd() {
+ return this.lookTime < 0;
+ }
+
+ @Override
+ public void end() {
+
+ }
+}
diff --git a/src/main/java/me/tofaa/entitylib/extras/CoordinateUtil.java b/src/main/java/me/tofaa/entitylib/extras/CoordinateUtil.java
new file mode 100644
index 0000000..25e5c39
--- /dev/null
+++ b/src/main/java/me/tofaa/entitylib/extras/CoordinateUtil.java
@@ -0,0 +1,38 @@
+package me.tofaa.entitylib.extras;
+
+import com.github.retrooper.packetevents.protocol.world.Location;
+import com.github.retrooper.packetevents.util.Vector3d;
+
+public final class CoordinateUtil {
+
+ private CoordinateUtil() {}
+
+ public static Location withDirection(Location location, Vector3d direction) {
+ /*
+ * Sin = Opp / Hyp
+ * Cos = Adj / Hyp
+ * Tan = Opp / Adj
+ *
+ * x = -Opp
+ * z = Adj
+ */
+ final double x = direction.getX();
+ final double z = direction.getZ();
+ if (x == 0 && z == 0) {
+ float pitch = direction.getY() > 0 ? -90f : 90f;
+ return new Location(location.getX(), location.getY(), location.getZ(), location.getYaw(), pitch);
+ }
+ final double theta = Math.atan2(-x, z);
+ final double xz = Math.sqrt(square(x) + square(z));
+ final double _2PI = 2 * Math.PI;
+
+ return new Location(location.getX(), location.getY(), location.getZ(),
+ (float) Math.toDegrees((theta + _2PI) % _2PI),
+ (float) Math.toDegrees(Math.atan(-direction.getY() / xz)));
+ }
+
+ public static double square(double in) {
+ return in * in;
+ }
+
+}
diff --git a/src/main/java/me/tofaa/entitylib/world/WrapperWorld.java b/src/main/java/me/tofaa/entitylib/world/WrapperWorld.java
deleted file mode 100644
index 5504283..0000000
--- a/src/main/java/me/tofaa/entitylib/world/WrapperWorld.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package me.tofaa.entitylib.world;
-
-import com.github.retrooper.packetevents.protocol.player.User;
-import me.tofaa.entitylib.entity.WrapperEntity;
-
-import java.util.HashSet;
-import java.util.Set;
-import java.util.UUID;
-
-public class WrapperWorld {
-
- private final UUID uuid;
- private final Set entities;
- private final Set players;
-
- public WrapperWorld(UUID uuid) {
- this.uuid = uuid;
- this.entities = new HashSet<>();
- this.players = new HashSet<>();
- }
-
-}