+ * This method DOES NOT create a new entity, it simply registers the entity to EntityLib.
+ * To construct a {@link WrapperEntity} you need to call {@link EntityLib#createMeta(int, EntityType)} and pass the created metadata to the constructor of the entity.
+ *
+ * This method will throw a RuntimeException if an entity with the given uuid or id already exists.
+ *
+ * The entity is not modified in any way, simply stored internally for it to be accessible thru {@link EntityLib#getEntity(UUID)} and {@link EntityLib#getEntity(int)}.
+ *
+ * @param entity the entity to register
+ * @return the same entity passed.
+ * @param instance of WrapperEntity, used to infer its type.
+ */
+ public static @NotNull T register(@NotNull T entity) {
+ checkInit();
+ if (entities.containsKey(entity.getUuid())) throw new RuntimeException("An entity with that uuid already exists");
+ if (entitiesById.containsKey(entity.getEntityId())) throw new RuntimeException("An entity with that id already exists");
+ entities.put(entity.getUuid(), entity);
+ entitiesById.put(entity.getEntityId(), entity);
+ return entity;
+ }
/**
* Creates a new WrapperEntity with the given UUID and EntityType.
@@ -136,6 +156,9 @@ public final class EntityLib {
if (meta instanceof LivingEntityMeta) {
entity = new WrapperLivingEntity(entityId, uuid, entityType, meta);
}
+ else if (entityType == EntityTypes.EXPERIENCE_ORB) {
+ entity = new WrapperExperienceOrbEntity(entityId, uuid, entityType, meta);
+ }
else {
entity = new WrapperEntity(entityId, uuid, entityType, meta);
}
@@ -148,6 +171,24 @@ public final class EntityLib {
return createEntity(entityIdProvider.provide(), uuid, entityType);
}
+ public static @NotNull WrapperEntityCreature createEntityCreature(int entityId, @NotNull UUID uuid, @NotNull EntityType entityType) {
+ checkInit();
+ if (entities.containsKey(uuid)) throw new RuntimeException("An entity with that uuid already exists");
+ if (entitiesById.containsKey(entityId)) throw new RuntimeException("An entity with that id already exists");
+ EntityMeta meta = createMeta(entityId, entityType);
+ if (!(meta instanceof LivingEntityMeta)) {
+ throw new RuntimeException("Entity type " + entityType + " is not a living entity, EntityCreature requires a living entity");
+ }
+ WrapperEntityCreature entity = new WrapperEntityCreature(entityId, uuid, entityType, meta);
+ entities.put(uuid, entity);
+ entitiesById.put(entityId, entity);
+ return entity;
+ }
+
+ public static @NotNull WrapperEntityCreature createEntityCreature(@NotNull UUID uuid, @NotNull EntityType entityType) {
+ return createEntityCreature(entityIdProvider.provide(), uuid, entityType);
+ }
+
/**
* @param entityId the entity id
* @return the metadata of the entity with the given id. If the entity does not exist, this method will return null.
diff --git a/src/main/java/me/tofaa/entitylib/Tickable.java b/src/main/java/me/tofaa/entitylib/Tickable.java
new file mode 100644
index 0000000..815d0e4
--- /dev/null
+++ b/src/main/java/me/tofaa/entitylib/Tickable.java
@@ -0,0 +1,7 @@
+package me.tofaa.entitylib;
+
+public interface Tickable {
+
+ void update(long time);
+
+}
diff --git a/src/main/java/me/tofaa/entitylib/TickingContainer.java b/src/main/java/me/tofaa/entitylib/TickingContainer.java
new file mode 100644
index 0000000..82bb8a1
--- /dev/null
+++ b/src/main/java/me/tofaa/entitylib/TickingContainer.java
@@ -0,0 +1,17 @@
+package me.tofaa.entitylib;
+
+public interface TickingContainer {
+
+ void addTickable(Tickable tickable);
+
+ void removeTickable(Tickable tickable);
+
+ default void update(long time) {
+ for (Tickable tickable : getTickables()) {
+ tickable.update(time);
+ }
+ }
+
+ Iterable getTickables();
+
+}
diff --git a/src/main/java/me/tofaa/entitylib/entity/WrapperEntity.java b/src/main/java/me/tofaa/entitylib/entity/WrapperEntity.java
index d2a8c67..3a63be4 100644
--- a/src/main/java/me/tofaa/entitylib/entity/WrapperEntity.java
+++ b/src/main/java/me/tofaa/entitylib/entity/WrapperEntity.java
@@ -4,17 +4,18 @@ import com.github.retrooper.packetevents.protocol.entity.type.EntityType;
import com.github.retrooper.packetevents.protocol.player.User;
import com.github.retrooper.packetevents.protocol.world.Location;
import com.github.retrooper.packetevents.util.Vector3d;
-import com.github.retrooper.packetevents.util.Vector3f;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import com.github.retrooper.packetevents.wrapper.play.server.*;
import me.tofaa.entitylib.EntityLib;
+import me.tofaa.entitylib.Tickable;
import me.tofaa.entitylib.meta.EntityMeta;
import me.tofaa.entitylib.meta.types.ObjectData;
import org.jetbrains.annotations.NotNull;
+import java.awt.*;
import java.util.*;
-public class WrapperEntity {
+public class WrapperEntity implements Tickable {
private final EntityType entityType;
private final int entityId;
private final Optional uuid;
@@ -78,6 +79,15 @@ public class WrapperEntity {
return true;
}
+ public boolean hasNoGravity() {
+ return meta.isHasNoGravity();
+ }
+
+ public void setHasNoGravity(boolean hasNoGravity) {
+ meta.setHasNoGravity(hasNoGravity);
+ refresh();
+ }
+
public void rotateHead(float yaw, float pitch) {
sendPacketToViewers(
new WrapperPlayServerEntityRotation(entityId, yaw, pitch, onGround)
@@ -88,6 +98,14 @@ public class WrapperEntity {
rotateHead(location.getYaw(), location.getPitch());
}
+ public void rotateHead(WrapperEntity entity) {
+ rotateHead(entity.getLocation());
+ }
+
+ public Location getLocation() {
+ return new Location(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
+ }
+
public void remove() {
if (!spawned) return;
spawned = false;
@@ -176,12 +194,47 @@ public class WrapperEntity {
return spawned;
}
+ public boolean hasVelocity() {
+ if (isOnGround()) {
+ // if the entity is on the ground and only "moves" downwards, it does not have a velocity.
+ return Double.compare(velocity.x, 0) != 0 || Double.compare(velocity.z, 0) != 0 || velocity.y > 0;
+ } else {
+ // The entity does not have velocity if the velocity is zero
+ return !velocity.equals(Vector3d.zero());
+ }
+ }
+
+ public boolean isOnGround() {
+ return onGround;
+ }
+
public Vector3d getVelocity() {
return velocity;
}
public void setVelocity(Vector3d velocity) {
this.velocity = velocity;
+ sendPacketToViewers(getVelocityPacket());
+ }
+
+ public double getX() {
+ return location.getX();
+ }
+
+ public double getY() {
+ return location.getY();
+ }
+
+ public double getZ() {
+ return location.getZ();
+ }
+
+ public float getYaw() {
+ return location.getYaw();
+ }
+
+ public float getPitch() {
+ return location.getPitch();
}
private WrapperPlayServerEntityVelocity getVelocityPacket() {
@@ -189,4 +242,8 @@ public class WrapperEntity {
return new WrapperPlayServerEntityVelocity(entityId, velocity);
}
+ @Override
+ public void update(long time) {
+
+ }
}
diff --git a/src/main/java/me/tofaa/entitylib/entity/WrapperEntityCreature.java b/src/main/java/me/tofaa/entitylib/entity/WrapperEntityCreature.java
new file mode 100644
index 0000000..3cb1f19
--- /dev/null
+++ b/src/main/java/me/tofaa/entitylib/entity/WrapperEntityCreature.java
@@ -0,0 +1,26 @@
+package me.tofaa.entitylib.entity;
+
+import com.github.retrooper.packetevents.protocol.entity.type.EntityType;
+import me.tofaa.entitylib.meta.EntityMeta;
+import me.tofaa.entitylib.EntityLib;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.UUID;
+
+/**
+ * Represents a {@link WrapperEntity} with goals, AI and pathfinding.
+ *
+ * To create a new {@link WrapperEntityCreature} use {@link EntityLib#createEntityCreature(int, UUID, EntityType)} or {@link EntityLib#createEntityCreature(UUID, EntityType)}.
+ *
+ * Creature entities require some sort of ticking mechanism on your server to work properly. They need to be dynamically updated every tick.
+ * Goal and Target selectors are grouped into AIGroups, which are then added to the entity. The AIGroups are then updated every tick.
+ *
+ * The {@link WrapperEntityCreature} can be inherited to create custom entities.
+ *
+ * For this to work, this method needs to be called every tick until the entity reaches the location.
+ * We don't have ticking or updating in this library, so you'll have to do it yourself.
+ * This is an attempt to mimmick the vanilla behavior.
+ *
+ */
+ public void updateSliding() {
+ if (hasNoGravity()) {
+ setVelocity(getVelocity().add(0, -0.3f, 0));
+ }
+
+ double d = 8.0;
+ Vector3d distance = new Vector3d(slideTowards.getX() - getX(), slideTowards.getY() - getY(), slideTowards.getZ() - getZ());
+ double length = distance.length();
+ if (length < 8.0) {
+ double f = 1 - (length / 8);
+ setVelocity(getVelocity().add(distance.normalize().multiply(f * f * 0.1)));
+ }
+ float g = 0.98f;
+ if (this.isOnGround()) {
+ g = 0.6f * 0.98f;
+ }
+ setVelocity(getVelocity().multiply(g, 0.98f, g));
+ if (isOnGround()) {
+ setVelocity(getVelocity().multiply(1, -0.9f, 1));
+ }
+ }
+
+ public Location getSlideTowards() {
+ return slideTowards;
+ }
+
+ public void setSlideTowards(Location slideTowards) {
+ this.slideTowards = slideTowards;
+ }
+
+ public short getExperience() {
+ return experience;
+ }
+
+ public void setExperience(short experience) {
+ getViewers().forEach(this::removeViewer);
+ this.experience = experience;
+ getViewers().forEach(this::addViewer);
+ }
+
+}
diff --git a/src/main/java/me/tofaa/entitylib/entity/WrapperLivingEntity.java b/src/main/java/me/tofaa/entitylib/entity/WrapperLivingEntity.java
index e9d6133..cf8f0e9 100644
--- a/src/main/java/me/tofaa/entitylib/entity/WrapperLivingEntity.java
+++ b/src/main/java/me/tofaa/entitylib/entity/WrapperLivingEntity.java
@@ -2,6 +2,7 @@ package me.tofaa.entitylib.entity;
import com.github.retrooper.packetevents.protocol.entity.type.EntityType;
import me.tofaa.entitylib.meta.EntityMeta;
+import me.tofaa.entitylib.meta.types.LivingEntityMeta;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
@@ -9,14 +10,36 @@ import java.util.UUID;
public class WrapperLivingEntity extends WrapperEntity{
private final WrapperEntityEquipment equipment;
+ private float maxHealth;
public WrapperLivingEntity(int entityId, @NotNull UUID uuid, EntityType entityType, EntityMeta meta) {
super(entityId, uuid, entityType, meta);
this.equipment = new WrapperEntityEquipment(this);
}
+
+ public float getHealth() {
+ return getMeta().getHealth();
+ }
+
+ public void setHealth(float health) {
+ getMeta().setHealth(Math.min(Math.max(health, 0), getMaxHealth()));
+ }
+
+ public float getMaxHealth() {
+ return maxHealth;
+ }
+
+ public void setMaxHealth(float maxHealth) {
+ this.maxHealth = maxHealth;
+ }
+
public WrapperEntityEquipment getEquipment() {
return equipment;
}
+ @Override
+ public LivingEntityMeta getMeta() {
+ return (LivingEntityMeta) super.getMeta();
+ }
}
diff --git a/src/main/java/me/tofaa/entitylib/entity/ai/AIGroup.java b/src/main/java/me/tofaa/entitylib/entity/ai/AIGroup.java
new file mode 100644
index 0000000..0a59dfa
--- /dev/null
+++ b/src/main/java/me/tofaa/entitylib/entity/ai/AIGroup.java
@@ -0,0 +1,4 @@
+package me.tofaa.entitylib.entity.ai;
+
+public interface AIGroup {
+}
diff --git a/src/main/java/me/tofaa/entitylib/entity/ai/GoalSelector.java b/src/main/java/me/tofaa/entitylib/entity/ai/GoalSelector.java
new file mode 100644
index 0000000..63c5965
--- /dev/null
+++ b/src/main/java/me/tofaa/entitylib/entity/ai/GoalSelector.java
@@ -0,0 +1,4 @@
+package me.tofaa.entitylib.entity.ai;
+
+public class GoalSelector {
+}
diff --git a/src/main/java/me/tofaa/entitylib/entity/ai/TargetSelector.java b/src/main/java/me/tofaa/entitylib/entity/ai/TargetSelector.java
new file mode 100644
index 0000000..3429ed6
--- /dev/null
+++ b/src/main/java/me/tofaa/entitylib/entity/ai/TargetSelector.java
@@ -0,0 +1,4 @@
+package me.tofaa.entitylib.entity.ai;
+
+public class TargetSelector {
+}
diff --git a/src/main/java/me/tofaa/entitylib/world/WrapperWorld.java b/src/main/java/me/tofaa/entitylib/world/WrapperWorld.java
new file mode 100644
index 0000000..5504283
--- /dev/null
+++ b/src/main/java/me/tofaa/entitylib/world/WrapperWorld.java
@@ -0,0 +1,22 @@
+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<>();
+ }
+
+}
diff --git a/test-plugin/src/main/java/me/tofaa/entitylib/EntityLibPlugin.java b/test-plugin/src/main/java/me/tofaa/entitylib/EntityLibPlugin.java
index 0db8986..7a27d2f 100644
--- a/test-plugin/src/main/java/me/tofaa/entitylib/EntityLibPlugin.java
+++ b/test-plugin/src/main/java/me/tofaa/entitylib/EntityLibPlugin.java
@@ -22,6 +22,7 @@ public final class EntityLibPlugin extends JavaPlugin {
getCommand("testapi").setExecutor(new TestCommand());
getCommand("testentity").setExecutor(new TestEntityCommand());
getCommand("testdisplay").setExecutor(new TestDisplayCommand());
+ getCommand("spawnclickablefrog").setExecutor(new SpawnClickableFrogCommand());
instance = this;
}
}
diff --git a/test-plugin/src/main/java/me/tofaa/entitylib/SpawnClickableFrogCommand.java b/test-plugin/src/main/java/me/tofaa/entitylib/SpawnClickableFrogCommand.java
new file mode 100644
index 0000000..e170b3a
--- /dev/null
+++ b/test-plugin/src/main/java/me/tofaa/entitylib/SpawnClickableFrogCommand.java
@@ -0,0 +1,53 @@
+package me.tofaa.entitylib;
+
+import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
+import io.github.retrooper.packetevents.util.SpigotConversionUtil;
+import me.tofaa.entitylib.entity.WrapperEntity;
+import me.tofaa.entitylib.meta.mobs.FrogMeta;
+import net.kyori.adventure.text.Component;
+import org.bukkit.Bukkit;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.bukkit.scheduler.BukkitTask;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+import java.util.UUID;
+
+public class SpawnClickableFrogCommand implements CommandExecutor {
+
+ private final Map updateTasks = new HashMap<>();
+ private final FrogMeta.Variant[] variants = FrogMeta.Variant.values();
+
+ @Override
+ public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String[] strings) {
+ Player player = (Player) commandSender;
+ WrapperEntity e = EntityLib.createEntity(UUID.randomUUID(), EntityTypes.FROG);
+ FrogMeta meta = (FrogMeta) e.getMeta();
+ meta.setHasGlowingEffect(true);
+ meta.setCustomNameVisible(true);
+ meta.setCustomName(Component.text("CLICK ME!"));
+ updateTasks.put(e, Bukkit.getScheduler().runTaskTimerAsynchronously(
+ EntityLibPlugin.instance,
+ new Runnable() {
+ int i = 0;
+ Random random = new Random();
+
+ @Override
+ public void run() {
+ if (!e.hasSpawned()) return;
+ int r = random.nextInt(2);
+ meta.setVariant(variants[r]);
+ meta.setCustomName(Component.text("CLICKED: " + i + " TIMES"));
+ }
+ },
+ 20, 20));
+ e.addViewer(player.getUniqueId());
+ e.spawn(SpigotConversionUtil.fromBukkitLocation(player.getLocation()));
+ return false;
+ }
+}
diff --git a/test-plugin/src/main/resources/plugin.yml b/test-plugin/src/main/resources/plugin.yml
index 2403c5b..86f70f1 100644
--- a/test-plugin/src/main/resources/plugin.yml
+++ b/test-plugin/src/main/resources/plugin.yml
@@ -13,4 +13,7 @@ commands:
usage: /
testdisplay:
description: Test PEDisplay API
+ usage: /
+ spawnclickablefrog:
+ description: Spawn a clickable frog
usage: /
\ No newline at end of file