introduce tickables and ai boilerplate

This commit is contained in:
Tofaa 2024-01-18 06:15:21 +03:00
parent e52bc7474a
commit 41da2e4c8e
15 changed files with 352 additions and 9 deletions

View file

@ -5,8 +5,21 @@
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="9d5d9b6f-43c8-41a4-bb42-a66ffc96c9b0" name="Changes" comment=""> <list default="true" id="9d5d9b6f-43c8-41a4-bb42-a66ffc96c9b0" name="Changes" comment="">
<change afterPath="$PROJECT_DIR$/src/main/java/me/tofaa/entitylib/Tickable.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/me/tofaa/entitylib/TickingContainer.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/me/tofaa/entitylib/entity/WrapperEntityCreature.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/me/tofaa/entitylib/entity/WrapperExperienceOrbEntity.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/me/tofaa/entitylib/entity/ai/AIGroup.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/me/tofaa/entitylib/entity/ai/GoalSelector.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/me/tofaa/entitylib/entity/ai/TargetSelector.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/main/java/me/tofaa/entitylib/world/WrapperWorld.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/test-plugin/src/main/java/me/tofaa/entitylib/SpawnClickableFrogCommand.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/test-plugin/src/main/java/me/tofaa/entitylib/TestDisplayCommand.java" beforeDir="false" afterPath="$PROJECT_DIR$/test-plugin/src/main/java/me/tofaa/entitylib/TestDisplayCommand.java" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/main/java/me/tofaa/entitylib/EntityLib.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/me/tofaa/entitylib/EntityLib.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/me/tofaa/entitylib/entity/WrapperEntity.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/me/tofaa/entitylib/entity/WrapperEntity.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/java/me/tofaa/entitylib/entity/WrapperLivingEntity.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/me/tofaa/entitylib/entity/WrapperLivingEntity.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/test-plugin/src/main/java/me/tofaa/entitylib/EntityLibPlugin.java" beforeDir="false" afterPath="$PROJECT_DIR$/test-plugin/src/main/java/me/tofaa/entitylib/EntityLibPlugin.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/test-plugin/src/main/resources/plugin.yml" beforeDir="false" afterPath="$PROJECT_DIR$/test-plugin/src/main/resources/plugin.yml" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -77,7 +90,7 @@
"WebServerToolWindowFactoryState": "false", "WebServerToolWindowFactoryState": "false",
"git-widget-placeholder": "master", "git-widget-placeholder": "master",
"ignore.virus.scanning.warn.message": "true", "ignore.virus.scanning.warn.message": "true",
"jdk.selected.JAVA_MODULE": "corretto-1.8", "jdk.selected.JAVA_MODULE": "corretto-17",
"kotlin-language-version-configured": "true", "kotlin-language-version-configured": "true",
"last_opened_file_path": "D:/Github/EntityLib", "last_opened_file_path": "D:/Github/EntityLib",
"node.js.detected.package.eslint": "true", "node.js.detected.package.eslint": "true",
@ -208,7 +221,7 @@
<workItem from="1704485939274" duration="2440000" /> <workItem from="1704485939274" duration="2440000" />
<workItem from="1704502790346" duration="6191000" /> <workItem from="1704502790346" duration="6191000" />
<workItem from="1705192736239" duration="496000" /> <workItem from="1705192736239" duration="496000" />
<workItem from="1705534524814" duration="1694000" /> <workItem from="1705534524814" duration="8652000" />
</task> </task>
<servers /> <servers />
</component> </component>

View file

@ -5,15 +5,13 @@ import com.github.retrooper.packetevents.event.PacketListenerAbstract;
import com.github.retrooper.packetevents.event.PacketReceiveEvent; import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.manager.server.ServerVersion; 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.EntityType;
import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
import com.github.retrooper.packetevents.protocol.packettype.PacketType; import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.InteractionHand; import com.github.retrooper.packetevents.protocol.player.InteractionHand;
import com.github.retrooper.packetevents.protocol.player.User; import com.github.retrooper.packetevents.protocol.player.User;
import com.github.retrooper.packetevents.wrapper.PacketWrapper; import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity; import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
import me.tofaa.entitylib.entity.EntityIdProvider; import me.tofaa.entitylib.entity.*;
import me.tofaa.entitylib.entity.EntityInteractionProcessor;
import me.tofaa.entitylib.entity.WrapperEntity;
import me.tofaa.entitylib.entity.WrapperLivingEntity;
import me.tofaa.entitylib.exception.InvalidVersionException; import me.tofaa.entitylib.exception.InvalidVersionException;
import me.tofaa.entitylib.meta.EntityMeta; import me.tofaa.entitylib.meta.EntityMeta;
import me.tofaa.entitylib.meta.Metadata; import me.tofaa.entitylib.meta.Metadata;
@ -116,6 +114,28 @@ public final class EntityLib {
return entities.get(uuid); 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.
* <p>
* 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.
* <br>
* This method will throw a RuntimeException if an entity with the given uuid or id already exists.
* <br>
* 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)}.
* </p>
* @param entity the entity to register
* @return the same entity passed.
* @param <T> instance of WrapperEntity, used to infer its type.
*/
public static @NotNull <T extends WrapperEntity> 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. * Creates a new WrapperEntity with the given UUID and EntityType.
@ -136,6 +156,9 @@ public final class EntityLib {
if (meta instanceof LivingEntityMeta) { if (meta instanceof LivingEntityMeta) {
entity = new WrapperLivingEntity(entityId, uuid, entityType, meta); entity = new WrapperLivingEntity(entityId, uuid, entityType, meta);
} }
else if (entityType == EntityTypes.EXPERIENCE_ORB) {
entity = new WrapperExperienceOrbEntity(entityId, uuid, entityType, meta);
}
else { else {
entity = new WrapperEntity(entityId, uuid, entityType, meta); entity = new WrapperEntity(entityId, uuid, entityType, meta);
} }
@ -148,6 +171,24 @@ public final class EntityLib {
return createEntity(entityIdProvider.provide(), uuid, entityType); 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 * @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. * @return the metadata of the entity with the given id. If the entity does not exist, this method will return null.

View file

@ -0,0 +1,7 @@
package me.tofaa.entitylib;
public interface Tickable {
void update(long time);
}

View file

@ -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<Tickable> getTickables();
}

View file

@ -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.player.User;
import com.github.retrooper.packetevents.protocol.world.Location; import com.github.retrooper.packetevents.protocol.world.Location;
import com.github.retrooper.packetevents.util.Vector3d; 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.PacketWrapper;
import com.github.retrooper.packetevents.wrapper.play.server.*; import com.github.retrooper.packetevents.wrapper.play.server.*;
import me.tofaa.entitylib.EntityLib; import me.tofaa.entitylib.EntityLib;
import me.tofaa.entitylib.Tickable;
import me.tofaa.entitylib.meta.EntityMeta; import me.tofaa.entitylib.meta.EntityMeta;
import me.tofaa.entitylib.meta.types.ObjectData; import me.tofaa.entitylib.meta.types.ObjectData;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.util.*; import java.util.*;
public class WrapperEntity { public class WrapperEntity implements Tickable {
private final EntityType entityType; private final EntityType entityType;
private final int entityId; private final int entityId;
private final Optional<UUID> uuid; private final Optional<UUID> uuid;
@ -78,6 +79,15 @@ public class WrapperEntity {
return true; return true;
} }
public boolean hasNoGravity() {
return meta.isHasNoGravity();
}
public void setHasNoGravity(boolean hasNoGravity) {
meta.setHasNoGravity(hasNoGravity);
refresh();
}
public void rotateHead(float yaw, float pitch) { public void rotateHead(float yaw, float pitch) {
sendPacketToViewers( sendPacketToViewers(
new WrapperPlayServerEntityRotation(entityId, yaw, pitch, onGround) new WrapperPlayServerEntityRotation(entityId, yaw, pitch, onGround)
@ -88,6 +98,14 @@ public class WrapperEntity {
rotateHead(location.getYaw(), location.getPitch()); 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() { public void remove() {
if (!spawned) return; if (!spawned) return;
spawned = false; spawned = false;
@ -176,12 +194,47 @@ public class WrapperEntity {
return spawned; 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() { public Vector3d getVelocity() {
return velocity; return velocity;
} }
public void setVelocity(Vector3d velocity) { public void setVelocity(Vector3d velocity) {
this.velocity = 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() { private WrapperPlayServerEntityVelocity getVelocityPacket() {
@ -189,4 +242,8 @@ public class WrapperEntity {
return new WrapperPlayServerEntityVelocity(entityId, velocity); return new WrapperPlayServerEntityVelocity(entityId, velocity);
} }
@Override
public void update(long time) {
}
} }

View file

@ -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.
* <p>
* To create a new {@link WrapperEntityCreature} use {@link EntityLib#createEntityCreature(int, UUID, EntityType)} or {@link EntityLib#createEntityCreature(UUID, EntityType)}.
* <br>
* 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.
* <br>
* The {@link WrapperEntityCreature} can be inherited to create custom entities.
* </p>
*/
public class WrapperEntityCreature extends WrapperLivingEntity{
public WrapperEntityCreature(int entityId, @NotNull UUID uuid, EntityType entityType, EntityMeta meta) {
super(entityId, uuid, entityType, meta);
}
}

View file

@ -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.
* <p>
* 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.
* </p>
*/
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);
}
}

View file

@ -2,6 +2,7 @@ package me.tofaa.entitylib.entity;
import com.github.retrooper.packetevents.protocol.entity.type.EntityType; import com.github.retrooper.packetevents.protocol.entity.type.EntityType;
import me.tofaa.entitylib.meta.EntityMeta; import me.tofaa.entitylib.meta.EntityMeta;
import me.tofaa.entitylib.meta.types.LivingEntityMeta;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.UUID; import java.util.UUID;
@ -9,14 +10,36 @@ import java.util.UUID;
public class WrapperLivingEntity extends WrapperEntity{ public class WrapperLivingEntity extends WrapperEntity{
private final WrapperEntityEquipment equipment; private final WrapperEntityEquipment equipment;
private float maxHealth;
public WrapperLivingEntity(int entityId, @NotNull UUID uuid, EntityType entityType, EntityMeta meta) { public WrapperLivingEntity(int entityId, @NotNull UUID uuid, EntityType entityType, EntityMeta meta) {
super(entityId, uuid, entityType, meta); super(entityId, uuid, entityType, meta);
this.equipment = new WrapperEntityEquipment(this); 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() { public WrapperEntityEquipment getEquipment() {
return equipment; return equipment;
} }
@Override
public LivingEntityMeta getMeta() {
return (LivingEntityMeta) super.getMeta();
}
} }

View file

@ -0,0 +1,4 @@
package me.tofaa.entitylib.entity.ai;
public interface AIGroup {
}

View file

@ -0,0 +1,4 @@
package me.tofaa.entitylib.entity.ai;
public class GoalSelector {
}

View file

@ -0,0 +1,4 @@
package me.tofaa.entitylib.entity.ai;
public class TargetSelector {
}

View file

@ -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<WrapperEntity> entities;
private final Set<User> players;
public WrapperWorld(UUID uuid) {
this.uuid = uuid;
this.entities = new HashSet<>();
this.players = new HashSet<>();
}
}

View file

@ -22,6 +22,7 @@ public final class EntityLibPlugin extends JavaPlugin {
getCommand("testapi").setExecutor(new TestCommand()); getCommand("testapi").setExecutor(new TestCommand());
getCommand("testentity").setExecutor(new TestEntityCommand()); getCommand("testentity").setExecutor(new TestEntityCommand());
getCommand("testdisplay").setExecutor(new TestDisplayCommand()); getCommand("testdisplay").setExecutor(new TestDisplayCommand());
getCommand("spawnclickablefrog").setExecutor(new SpawnClickableFrogCommand());
instance = this; instance = this;
} }
} }

View file

@ -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<WrapperEntity, BukkitTask> 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;
}
}

View file

@ -14,3 +14,6 @@ commands:
testdisplay: testdisplay:
description: Test PEDisplay API description: Test PEDisplay API
usage: /<command> usage: /<command>
spawnclickablefrog:
description: Spawn a clickable frog
usage: /<command>