From 41da2e4c8ed24c1d5b271948f9a6d2db74946b0f Mon Sep 17 00:00:00 2001 From: Tofaa <82680183+Tofaa2@users.noreply.github.com> Date: Thu, 18 Jan 2024 06:15:21 +0300 Subject: [PATCH] introduce tickables and ai boilerplate --- .idea/workspace.xml | 19 +++++- .../java/me/tofaa/entitylib/EntityLib.java | 49 +++++++++++-- .../java/me/tofaa/entitylib/Tickable.java | 7 ++ .../me/tofaa/entitylib/TickingContainer.java | 17 +++++ .../tofaa/entitylib/entity/WrapperEntity.java | 61 ++++++++++++++++- .../entity/WrapperEntityCreature.java | 26 +++++++ .../entity/WrapperExperienceOrbEntity.java | 68 +++++++++++++++++++ .../entitylib/entity/WrapperLivingEntity.java | 23 +++++++ .../me/tofaa/entitylib/entity/ai/AIGroup.java | 4 ++ .../entitylib/entity/ai/GoalSelector.java | 4 ++ .../entitylib/entity/ai/TargetSelector.java | 4 ++ .../tofaa/entitylib/world/WrapperWorld.java | 22 ++++++ .../me/tofaa/entitylib/EntityLibPlugin.java | 1 + .../entitylib/SpawnClickableFrogCommand.java | 53 +++++++++++++++ test-plugin/src/main/resources/plugin.yml | 3 + 15 files changed, 352 insertions(+), 9 deletions(-) create mode 100644 src/main/java/me/tofaa/entitylib/Tickable.java create mode 100644 src/main/java/me/tofaa/entitylib/TickingContainer.java create mode 100644 src/main/java/me/tofaa/entitylib/entity/WrapperEntityCreature.java create mode 100644 src/main/java/me/tofaa/entitylib/entity/WrapperExperienceOrbEntity.java create mode 100644 src/main/java/me/tofaa/entitylib/entity/ai/AIGroup.java create mode 100644 src/main/java/me/tofaa/entitylib/entity/ai/GoalSelector.java create mode 100644 src/main/java/me/tofaa/entitylib/entity/ai/TargetSelector.java create mode 100644 src/main/java/me/tofaa/entitylib/world/WrapperWorld.java create mode 100644 test-plugin/src/main/java/me/tofaa/entitylib/SpawnClickableFrogCommand.java diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 3f229b5..8960a0e 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -5,8 +5,21 @@ + + + + + + + + + - + + + + + diff --git a/src/main/java/me/tofaa/entitylib/EntityLib.java b/src/main/java/me/tofaa/entitylib/EntityLib.java index d1e4176..f536679 100644 --- a/src/main/java/me/tofaa/entitylib/EntityLib.java +++ b/src/main/java/me/tofaa/entitylib/EntityLib.java @@ -5,15 +5,13 @@ import com.github.retrooper.packetevents.event.PacketListenerAbstract; import com.github.retrooper.packetevents.event.PacketReceiveEvent; import com.github.retrooper.packetevents.manager.server.ServerVersion; import com.github.retrooper.packetevents.protocol.entity.type.EntityType; +import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes; import com.github.retrooper.packetevents.protocol.packettype.PacketType; import com.github.retrooper.packetevents.protocol.player.InteractionHand; import com.github.retrooper.packetevents.protocol.player.User; import com.github.retrooper.packetevents.wrapper.PacketWrapper; import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity; -import me.tofaa.entitylib.entity.EntityIdProvider; -import me.tofaa.entitylib.entity.EntityInteractionProcessor; -import me.tofaa.entitylib.entity.WrapperEntity; -import me.tofaa.entitylib.entity.WrapperLivingEntity; +import me.tofaa.entitylib.entity.*; import me.tofaa.entitylib.exception.InvalidVersionException; import me.tofaa.entitylib.meta.EntityMeta; import me.tofaa.entitylib.meta.Metadata; @@ -116,6 +114,28 @@ public final class EntityLib { return entities.get(uuid); } + /** + * Registers a custom entity to EntityLib. This exists to allow developers to extend {@link WrapperEntity} and its subclasses simply and define their own logic. + *

+ * 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. + *

+ */ +public class WrapperEntityCreature extends WrapperLivingEntity{ + + public WrapperEntityCreature(int entityId, @NotNull UUID uuid, EntityType entityType, EntityMeta meta) { + super(entityId, uuid, entityType, meta); + } +} diff --git a/src/main/java/me/tofaa/entitylib/entity/WrapperExperienceOrbEntity.java b/src/main/java/me/tofaa/entitylib/entity/WrapperExperienceOrbEntity.java new file mode 100644 index 0000000..8ee2ba1 --- /dev/null +++ b/src/main/java/me/tofaa/entitylib/entity/WrapperExperienceOrbEntity.java @@ -0,0 +1,68 @@ +package me.tofaa.entitylib.entity; + +import com.github.retrooper.packetevents.protocol.entity.type.EntityType; +import com.github.retrooper.packetevents.protocol.world.Location; +import com.github.retrooper.packetevents.util.Vector3d; +import me.tofaa.entitylib.meta.EntityMeta; +import org.jetbrains.annotations.NotNull; + +import java.util.UUID; + +public class WrapperExperienceOrbEntity extends WrapperEntity { + + private short experience; + private Location slideTowards; + + public WrapperExperienceOrbEntity(int entityId, @NotNull UUID uuid, EntityType entityType, EntityMeta meta) { + super(entityId, uuid, entityType, meta); + } + + /** + * Applies a slight slide motion towards the given location. + *

+ * 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