diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index ca4de999a..8504f739e 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -163,6 +163,10 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev protected boolean removed; private final Set passengers = new CopyOnWriteArraySet<>(); + + private final Set leashedEntities = new CopyOnWriteArraySet<>(); + private Entity leashHolder; + protected EntityType entityType; // UNSAFE to change, modify at your own risk // Network synchronization, send the absolute position of the entity each X milliseconds @@ -474,6 +478,15 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev } player.sendPacket(getPassengersPacket()); } + // Leashes + if (leashHolder != null && (player.equals(leashHolder) || leashHolder.isViewer(player))) { + player.sendPacket(getAttachEntityPacket()); + } + for (Entity entity : leashedEntities) { + if (entity.isViewer(player)) { + player.sendPacket(entity.getAttachEntityPacket()); + } + }; // Head position player.sendPacket(new EntityHeadLookPacket(getEntityId(), position.yaw())); } @@ -492,6 +505,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev if (passenger != player) passenger.updateOldViewer(player); } } + leashedEntities.forEach(entity -> player.sendPacket(new AttachEntityPacket(entity, null))); player.sendPacket(destroyPacketCache); } @@ -1100,6 +1114,40 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev return new SetPassengersPacket(getEntityId(), passengers.stream().map(Entity::getEntityId).toList()); } + /** + * Gets the entities that this entity is leashing. + * + * @return an unmodifiable list containing all the leashed entities + */ + public @NotNull Set getLeashedEntities() { + return Collections.unmodifiableSet(leashedEntities); + } + + /** + * Gets the current leash holder. + * + * @return the entity leashing this entity, null if no leash holder + */ + public @Nullable Entity getLeashHolder() { + return leashHolder; + } + + /** + * Sets the leash holder to this entity. + * + * @param entity the new leash holder + */ + public void setLeashHolder(@Nullable Entity entity) { + if (leashHolder != null) leashHolder.leashedEntities.remove(this); + if (entity != null) entity.leashedEntities.add(this); + this.leashHolder = entity; + sendPacketToViewersAndSelf(getAttachEntityPacket()); + } + + protected @NotNull AttachEntityPacket getAttachEntityPacket() { + return new AttachEntityPacket(this, leashHolder); + } + /** * Entity statuses can be found here. * @@ -1545,6 +1593,9 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev final Entity vehicle = this.vehicle; if (vehicle != null) vehicle.removePassenger(this); + Set leashedEntities = getLeashedEntities(); + leashedEntities.forEach(entity -> entity.setLeashHolder(null)); + MinecraftServer.process().dispatcher().removeElement(this); this.removed = true; if (permanent) {