diff --git a/core/pom.xml b/core/pom.xml
index f75cd2aa..9c5a77af 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -104,6 +104,11 @@
holographicdisplays-nms-v1_18_r2
+
+ ${project.groupId}
+ holographicdisplays-nms-v1_19_r1
+
+
org.spigotmc
spigot-api
diff --git a/core/src/main/java/me/filoghost/holographicdisplays/core/NMSVersion.java b/core/src/main/java/me/filoghost/holographicdisplays/core/NMSVersion.java
index a015bd9b..bbecaaab 100644
--- a/core/src/main/java/me/filoghost/holographicdisplays/core/NMSVersion.java
+++ b/core/src/main/java/me/filoghost/holographicdisplays/core/NMSVersion.java
@@ -37,7 +37,8 @@ public enum NMSVersion {
/* 1.16.4 - 1.16.5 */ v1_16_R3(errorCollector -> new me.filoghost.holographicdisplays.nms.v1_16_R3.VersionNMSManager(errorCollector)),
/* 1.17 */ v1_17_R1(errorCollector -> new me.filoghost.holographicdisplays.nms.v1_17_R1.VersionNMSManager(errorCollector)),
/* 1.18 - 1.18.1 */ v1_18_R1(errorCollector -> new me.filoghost.holographicdisplays.nms.v1_18_R1.VersionNMSManager(errorCollector)),
- /* 1.18.2 - ? */ v1_18_R2(errorCollector -> new me.filoghost.holographicdisplays.nms.v1_18_R2.VersionNMSManager(errorCollector)),
+ /* 1.18.2 */ v1_18_R2(errorCollector -> new me.filoghost.holographicdisplays.nms.v1_18_R2.VersionNMSManager(errorCollector)),
+ /* 1.19 - ? */ v1_19_R1(errorCollector -> new me.filoghost.holographicdisplays.nms.v1_19_R1.VersionNMSManager(errorCollector)),
/* Other versions */ UNKNOWN(NMSManagerFactory.unknownVersion());
private static final NMSVersion CURRENT_VERSION = detectCurrentVersion();
diff --git a/nms/pom.xml b/nms/pom.xml
index 52082ba9..d6fd8512 100644
--- a/nms/pom.xml
+++ b/nms/pom.xml
@@ -29,6 +29,7 @@
v1_17_r1
v1_18_r1
v1_18_r2
+ v1_19_r1
diff --git a/nms/v1_19_r1/pom.xml b/nms/v1_19_r1/pom.xml
new file mode 100644
index 00000000..72001c76
--- /dev/null
+++ b/nms/v1_19_r1/pom.xml
@@ -0,0 +1,29 @@
+
+
+ 4.0.0
+
+
+ me.filoghost.holographicdisplays
+ holographicdisplays-nms
+ 3.0.0-SNAPSHOT
+
+
+ holographicdisplays-nms-v1_19_r1
+ HolographicDisplays NMS v1_19_R1
+
+
+
+ ${project.groupId}
+ holographicdisplays-nms-common
+
+
+
+ org.spigotmc
+ spigot
+ 1.19-R0.1-SNAPSHOT
+ provided
+
+
+
+
diff --git a/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/DataWatcherKey.java b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/DataWatcherKey.java
new file mode 100644
index 00000000..947e95d8
--- /dev/null
+++ b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/DataWatcherKey.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) filoghost and contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+package me.filoghost.holographicdisplays.nms.v1_19_R1;
+
+import io.netty.handler.codec.EncoderException;
+import net.minecraft.network.chat.IChatBaseComponent;
+import net.minecraft.network.syncher.DataWatcherRegistry;
+import net.minecraft.network.syncher.DataWatcherSerializer;
+import net.minecraft.world.item.ItemStack;
+
+import java.util.Optional;
+
+class DataWatcherKey {
+
+ private static final DataWatcherSerializer BYTE_SERIALIZER = DataWatcherRegistry.a;
+ private static final DataWatcherSerializer INT_SERIALIZER = DataWatcherRegistry.b;
+ private static final DataWatcherSerializer BOOLEAN_SERIALIZER = DataWatcherRegistry.i;
+ private static final DataWatcherSerializer ITEM_STACK_SERIALIZER = DataWatcherRegistry.g;
+ private static final DataWatcherSerializer> OPTIONAL_CHAT_COMPONENT_SERIALIZER = DataWatcherRegistry.f;
+
+ static final DataWatcherKey ENTITY_STATUS = new DataWatcherKey<>(0, BYTE_SERIALIZER);
+ static final DataWatcherKey> CUSTOM_NAME = new DataWatcherKey<>(2, OPTIONAL_CHAT_COMPONENT_SERIALIZER);
+ static final DataWatcherKey CUSTOM_NAME_VISIBILITY = new DataWatcherKey<>(3, BOOLEAN_SERIALIZER);
+ static final DataWatcherKey ITEM_STACK = new DataWatcherKey<>(8, ITEM_STACK_SERIALIZER);
+ static final DataWatcherKey ARMOR_STAND_STATUS = new DataWatcherKey<>(15, BYTE_SERIALIZER);
+ static final DataWatcherKey SLIME_SIZE = new DataWatcherKey<>(16, INT_SERIALIZER);
+
+ private final int index;
+ private final DataWatcherSerializer serializer;
+ private final int serializerTypeID;
+
+ private DataWatcherKey(int index, DataWatcherSerializer serializer) {
+ this.index = index;
+ this.serializer = serializer;
+ this.serializerTypeID = DataWatcherRegistry.b(serializer);
+ if (serializerTypeID < 0) {
+ throw new EncoderException("Could not find serializer ID of " + serializer);
+ }
+ }
+
+ int getIndex() {
+ return index;
+ }
+
+ DataWatcherSerializer getSerializer() {
+ return serializer;
+ }
+
+ int getSerializerTypeID() {
+ return serializerTypeID;
+ }
+
+}
diff --git a/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/DataWatcherPacketBuilder.java b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/DataWatcherPacketBuilder.java
new file mode 100644
index 00000000..5b53bcfa
--- /dev/null
+++ b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/DataWatcherPacketBuilder.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) filoghost and contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+package me.filoghost.holographicdisplays.nms.v1_19_R1;
+
+import me.filoghost.fcommons.Strings;
+import net.minecraft.network.chat.IChatBaseComponent;
+import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftItemStack;
+import org.bukkit.craftbukkit.v1_19_R1.util.CraftChatMessage;
+import org.bukkit.inventory.ItemStack;
+
+import java.util.Optional;
+
+abstract class DataWatcherPacketBuilder {
+
+ private static final int MAX_CUSTOM_NAME_LENGTH = 5000;
+
+ private final PacketByteBuffer packetByteBuffer;
+
+ DataWatcherPacketBuilder(PacketByteBuffer packetByteBuffer) {
+ this.packetByteBuffer = packetByteBuffer;
+ }
+
+ DataWatcherPacketBuilder setInvisible() {
+ packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.ENTITY_STATUS, (byte) 0x20); // Invisible
+ return this;
+ }
+
+ DataWatcherPacketBuilder setArmorStandMarker() {
+ setInvisible();
+ packetByteBuffer.writeDataWatcherEntry(
+ DataWatcherKey.ARMOR_STAND_STATUS, (byte) (0x01 | 0x02 | 0x08 | 0x10)); // Small, no gravity, no base plate, marker
+ return this;
+ }
+
+ DataWatcherPacketBuilder setCustomName(String customName) {
+ packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.CUSTOM_NAME, getCustomNameDataWatcherValue(customName));
+ packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.CUSTOM_NAME_VISIBILITY, !Strings.isEmpty(customName));
+ return this;
+ }
+
+ private Optional getCustomNameDataWatcherValue(String customName) {
+ customName = Strings.truncate(customName, MAX_CUSTOM_NAME_LENGTH);
+ if (!Strings.isEmpty(customName)) {
+ return Optional.of(CraftChatMessage.fromString(customName, false, true)[0]);
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ DataWatcherPacketBuilder setItemStack(ItemStack itemStack) {
+ packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.ITEM_STACK, CraftItemStack.asNMSCopy(itemStack));
+ return this;
+ }
+
+ DataWatcherPacketBuilder setSlimeSmall() {
+ packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.SLIME_SIZE, 1);
+ return this;
+ }
+
+ T build() {
+ packetByteBuffer.writeDataWatcherEntriesEnd();
+ return createPacket(packetByteBuffer);
+ }
+
+ abstract T createPacket(PacketByteBuffer packetByteBuffer);
+
+}
diff --git a/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/EntityDestroyNMSPacket.java b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/EntityDestroyNMSPacket.java
new file mode 100644
index 00000000..716302da
--- /dev/null
+++ b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/EntityDestroyNMSPacket.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) filoghost and contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+package me.filoghost.holographicdisplays.nms.v1_19_R1;
+
+import me.filoghost.holographicdisplays.nms.common.EntityID;
+import net.minecraft.network.protocol.Packet;
+import net.minecraft.network.protocol.game.PacketPlayOutEntityDestroy;
+
+class EntityDestroyNMSPacket extends VersionNMSPacket {
+
+ private final Packet> rawPacket;
+
+ EntityDestroyNMSPacket(EntityID entityID) {
+ PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
+
+ packetByteBuffer.writeVarIntArray(entityID.getNumericID());
+
+ this.rawPacket = new PacketPlayOutEntityDestroy(packetByteBuffer);
+ }
+
+ EntityDestroyNMSPacket(EntityID entityID1, EntityID entityID2) {
+ PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
+
+ packetByteBuffer.writeVarIntArray(entityID1.getNumericID(), entityID2.getNumericID());
+
+ this.rawPacket = new PacketPlayOutEntityDestroy(packetByteBuffer);
+ }
+
+ @Override
+ Packet> getRawPacket() {
+ return rawPacket;
+ }
+
+}
diff --git a/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/EntityMetadataNMSPacket.java b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/EntityMetadataNMSPacket.java
new file mode 100644
index 00000000..9d8ba102
--- /dev/null
+++ b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/EntityMetadataNMSPacket.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) filoghost and contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+package me.filoghost.holographicdisplays.nms.v1_19_R1;
+
+import me.filoghost.holographicdisplays.nms.common.EntityID;
+import net.minecraft.network.protocol.Packet;
+import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata;
+
+class EntityMetadataNMSPacket extends VersionNMSPacket {
+
+ private final Packet> rawPacket;
+
+ private EntityMetadataNMSPacket(PacketByteBuffer packetByteBuffer) {
+ this.rawPacket = new PacketPlayOutEntityMetadata(packetByteBuffer);
+ }
+
+ @Override
+ Packet> getRawPacket() {
+ return rawPacket;
+ }
+
+ public static DataWatcherPacketBuilder builder(EntityID entityID) {
+ PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
+ packetByteBuffer.writeVarInt(entityID.getNumericID());
+ return new Builder(packetByteBuffer);
+ }
+
+
+ private static class Builder extends DataWatcherPacketBuilder {
+
+ private Builder(PacketByteBuffer packetByteBuffer) {
+ super(packetByteBuffer);
+ }
+
+ @Override
+ EntityMetadataNMSPacket createPacket(PacketByteBuffer packetByteBuffer) {
+ return new EntityMetadataNMSPacket(packetByteBuffer);
+ }
+
+ }
+
+}
diff --git a/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/EntityMountNMSPacket.java b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/EntityMountNMSPacket.java
new file mode 100644
index 00000000..b7f6bef5
--- /dev/null
+++ b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/EntityMountNMSPacket.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) filoghost and contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+package me.filoghost.holographicdisplays.nms.v1_19_R1;
+
+import me.filoghost.holographicdisplays.nms.common.EntityID;
+import net.minecraft.network.protocol.Packet;
+import net.minecraft.network.protocol.game.PacketPlayOutMount;
+
+class EntityMountNMSPacket extends VersionNMSPacket {
+
+ private final Packet> rawPacket;
+
+ EntityMountNMSPacket(EntityID vehicleEntityID, EntityID passengerEntityID) {
+ PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
+
+ packetByteBuffer.writeVarInt(vehicleEntityID.getNumericID());
+ packetByteBuffer.writeVarIntArray(passengerEntityID.getNumericID());
+
+ this.rawPacket = new PacketPlayOutMount(packetByteBuffer);
+ }
+
+ @Override
+ Packet> getRawPacket() {
+ return rawPacket;
+ }
+
+}
diff --git a/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/EntitySpawnNMSPacket.java b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/EntitySpawnNMSPacket.java
new file mode 100644
index 00000000..460e7923
--- /dev/null
+++ b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/EntitySpawnNMSPacket.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) filoghost and contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+package me.filoghost.holographicdisplays.nms.v1_19_R1;
+
+import me.filoghost.holographicdisplays.common.PositionCoordinates;
+import me.filoghost.holographicdisplays.nms.common.EntityID;
+import net.minecraft.network.protocol.Packet;
+import net.minecraft.network.protocol.game.PacketPlayOutSpawnEntity;
+
+class EntitySpawnNMSPacket extends VersionNMSPacket {
+
+ private final Packet> rawPacket;
+
+ EntitySpawnNMSPacket(EntityID entityID, int entityTypeID, PositionCoordinates position, double positionOffsetY) {
+ PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
+
+ packetByteBuffer.writeVarInt(entityID.getNumericID());
+ packetByteBuffer.writeUUID(entityID.getUUID());
+ packetByteBuffer.writeVarInt(entityTypeID);
+
+ // Position
+ packetByteBuffer.writeDouble(position.getX());
+ packetByteBuffer.writeDouble(position.getY() + positionOffsetY);
+ packetByteBuffer.writeDouble(position.getZ());
+
+ // Rotation
+ packetByteBuffer.writeByte(0);
+ packetByteBuffer.writeByte(0);
+
+ // Object data
+ if (entityTypeID == EntityTypeID.ITEM) {
+ packetByteBuffer.writeInt(1); // Velocity is present and zero (otherwise by default a random velocity is applied)
+ } else {
+ packetByteBuffer.writeInt(0);
+ }
+
+ // Velocity
+ packetByteBuffer.writeShort(0);
+ packetByteBuffer.writeShort(0);
+ packetByteBuffer.writeShort(0);
+
+ this.rawPacket = new PacketPlayOutSpawnEntity(packetByteBuffer);
+ }
+
+ @Override
+ Packet> getRawPacket() {
+ return rawPacket;
+ }
+
+}
diff --git a/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/EntityTeleportNMSPacket.java b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/EntityTeleportNMSPacket.java
new file mode 100644
index 00000000..c0b92681
--- /dev/null
+++ b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/EntityTeleportNMSPacket.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) filoghost and contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+package me.filoghost.holographicdisplays.nms.v1_19_R1;
+
+import me.filoghost.holographicdisplays.common.PositionCoordinates;
+import me.filoghost.holographicdisplays.nms.common.EntityID;
+import net.minecraft.network.protocol.Packet;
+import net.minecraft.network.protocol.game.PacketPlayOutEntityTeleport;
+
+class EntityTeleportNMSPacket extends VersionNMSPacket {
+
+ private final Packet> rawPacket;
+
+ EntityTeleportNMSPacket(EntityID entityID, PositionCoordinates position, double positionOffsetY) {
+ PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
+
+ packetByteBuffer.writeVarInt(entityID.getNumericID());
+
+ // Position
+ packetByteBuffer.writeDouble(position.getX());
+ packetByteBuffer.writeDouble(position.getY() + positionOffsetY);
+ packetByteBuffer.writeDouble(position.getZ());
+
+ // Rotation
+ packetByteBuffer.writeByte(0);
+ packetByteBuffer.writeByte(0);
+
+ // On ground
+ packetByteBuffer.writeBoolean(false);
+
+ this.rawPacket = new PacketPlayOutEntityTeleport(packetByteBuffer);
+ }
+
+ @Override
+ Packet> getRawPacket() {
+ return rawPacket;
+ }
+
+}
diff --git a/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/EntityTypeID.java b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/EntityTypeID.java
new file mode 100644
index 00000000..49995e4d
--- /dev/null
+++ b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/EntityTypeID.java
@@ -0,0 +1,14 @@
+/*
+ * Copyright (C) filoghost and contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+package me.filoghost.holographicdisplays.nms.v1_19_R1;
+
+class EntityTypeID {
+
+ static final int ARMOR_STAND = 2;
+ static final int ITEM = 44;
+ static final int SLIME = 83;
+
+}
diff --git a/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/InboundPacketHandler.java b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/InboundPacketHandler.java
new file mode 100644
index 00000000..8dcb1fcc
--- /dev/null
+++ b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/InboundPacketHandler.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) filoghost and contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+package me.filoghost.holographicdisplays.nms.v1_19_R1;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import me.filoghost.fcommons.logging.Log;
+import me.filoghost.fcommons.reflection.ReflectField;
+import me.filoghost.holographicdisplays.nms.common.NMSErrors;
+import me.filoghost.holographicdisplays.nms.common.PacketListener;
+import net.minecraft.network.protocol.game.PacketPlayInUseEntity;
+import org.bukkit.entity.Player;
+
+class InboundPacketHandler extends ChannelInboundHandlerAdapter {
+
+ public static final String HANDLER_NAME = "holographic_displays_listener";
+ private static final ReflectField ENTITY_ID_FIELD = ReflectField.lookup(int.class, PacketPlayInUseEntity.class, "a");
+
+ private final Player player;
+ private final PacketListener packetListener;
+
+ InboundPacketHandler(Player player, PacketListener packetListener) {
+ this.player = player;
+ this.packetListener = packetListener;
+ }
+
+ @Override
+ public void channelRead(ChannelHandlerContext context, Object packet) throws Exception {
+ try {
+ if (packet instanceof PacketPlayInUseEntity) {
+ int entityID = ENTITY_ID_FIELD.get(packet);
+ boolean cancel = packetListener.onAsyncEntityInteract(player, entityID);
+ if (cancel) {
+ return;
+ }
+ }
+ } catch (Throwable t) {
+ Log.warning(NMSErrors.EXCEPTION_ON_PACKET_READ, t);
+ }
+ super.channelRead(context, packet);
+ }
+
+}
diff --git a/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/PacketByteBuffer.java b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/PacketByteBuffer.java
new file mode 100644
index 00000000..2159f56b
--- /dev/null
+++ b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/PacketByteBuffer.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) filoghost and contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+package me.filoghost.holographicdisplays.nms.v1_19_R1;
+
+import io.netty.buffer.Unpooled;
+import net.minecraft.network.PacketDataSerializer;
+
+import java.util.UUID;
+
+class PacketByteBuffer extends PacketDataSerializer {
+
+ private static final PacketByteBuffer INSTANCE = new PacketByteBuffer();
+
+ static PacketByteBuffer get() {
+ INSTANCE.clear();
+ return INSTANCE;
+ }
+
+ private PacketByteBuffer() {
+ super(Unpooled.buffer());
+ }
+
+ void writeVarInt(int i) {
+ super.d(i);
+ }
+
+ void writeVarIntArray(int i1) {
+ writeVarInt(1);
+ writeVarInt(i1);
+ }
+
+ void writeVarIntArray(int i1, int i2) {
+ writeVarInt(2);
+ writeVarInt(i1);
+ writeVarInt(i2);
+ }
+
+ void writeUUID(UUID uuid) {
+ super.a(uuid);
+ }
+
+ void writeDataWatcherEntry(DataWatcherKey key, T value) {
+ writeByte(key.getIndex());
+ writeVarInt(key.getSerializerTypeID());
+ key.getSerializer().a(this, value);
+ }
+
+ void writeDataWatcherEntriesEnd() {
+ writeByte(0xFF);
+ }
+
+}
diff --git a/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/VersionClickableNMSPacketEntity.java b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/VersionClickableNMSPacketEntity.java
new file mode 100644
index 00000000..65970a88
--- /dev/null
+++ b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/VersionClickableNMSPacketEntity.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) filoghost and contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+package me.filoghost.holographicdisplays.nms.v1_19_R1;
+
+import me.filoghost.holographicdisplays.common.PositionCoordinates;
+import me.filoghost.holographicdisplays.nms.common.EntityID;
+import me.filoghost.holographicdisplays.nms.common.PacketGroup;
+import me.filoghost.holographicdisplays.nms.common.entity.ClickableNMSPacketEntity;
+
+class VersionClickableNMSPacketEntity implements ClickableNMSPacketEntity {
+
+ private final EntityID slimeID;
+
+ VersionClickableNMSPacketEntity(EntityID slimeID) {
+ this.slimeID = slimeID;
+ }
+
+ @Override
+ public EntityID getID() {
+ return slimeID;
+ }
+
+ @Override
+ public PacketGroup newSpawnPackets(PositionCoordinates position) {
+ return PacketGroup.of(
+ new EntitySpawnNMSPacket(slimeID, EntityTypeID.SLIME, position, SLIME_Y_OFFSET),
+ EntityMetadataNMSPacket.builder(slimeID)
+ .setInvisible()
+ .setSlimeSmall() // Required for a correct client-side collision box
+ .build()
+ );
+ }
+
+ @Override
+ public PacketGroup newTeleportPackets(PositionCoordinates position) {
+ return new EntityTeleportNMSPacket(slimeID, position, SLIME_Y_OFFSET);
+ }
+
+ @Override
+ public PacketGroup newDestroyPackets() {
+ return new EntityDestroyNMSPacket(slimeID);
+ }
+
+}
diff --git a/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/VersionItemNMSPacketEntity.java b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/VersionItemNMSPacketEntity.java
new file mode 100644
index 00000000..a7e006a9
--- /dev/null
+++ b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/VersionItemNMSPacketEntity.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) filoghost and contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+package me.filoghost.holographicdisplays.nms.v1_19_R1;
+
+import me.filoghost.holographicdisplays.common.PositionCoordinates;
+import me.filoghost.holographicdisplays.nms.common.EntityID;
+import me.filoghost.holographicdisplays.nms.common.PacketGroup;
+import me.filoghost.holographicdisplays.nms.common.entity.ItemNMSPacketEntity;
+import org.bukkit.inventory.ItemStack;
+
+class VersionItemNMSPacketEntity implements ItemNMSPacketEntity {
+
+ private final EntityID itemID;
+ private final EntityID vehicleID;
+
+ VersionItemNMSPacketEntity(EntityID itemID, EntityID vehicleID) {
+ this.itemID = itemID;
+ this.vehicleID = vehicleID;
+ }
+
+ @Override
+ public PacketGroup newSpawnPackets(PositionCoordinates position, ItemStack itemStack) {
+ return PacketGroup.of(
+ new EntitySpawnNMSPacket(vehicleID, EntityTypeID.ARMOR_STAND, position, ITEM_Y_OFFSET),
+ EntityMetadataNMSPacket.builder(vehicleID)
+ .setArmorStandMarker()
+ .build(),
+ new EntitySpawnNMSPacket(itemID, EntityTypeID.ITEM, position, ITEM_Y_OFFSET),
+ EntityMetadataNMSPacket.builder(itemID)
+ .setItemStack(itemStack)
+ .build(),
+ new EntityMountNMSPacket(vehicleID, itemID)
+ );
+ }
+
+ @Override
+ public PacketGroup newChangePackets(ItemStack itemStack) {
+ return EntityMetadataNMSPacket.builder(itemID)
+ .setItemStack(itemStack)
+ .build();
+ }
+
+ @Override
+ public PacketGroup newTeleportPackets(PositionCoordinates position) {
+ return new EntityTeleportNMSPacket(vehicleID, position, ITEM_Y_OFFSET);
+ }
+
+ @Override
+ public PacketGroup newDestroyPackets() {
+ return new EntityDestroyNMSPacket(itemID, vehicleID);
+ }
+
+}
diff --git a/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/VersionNMSManager.java b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/VersionNMSManager.java
new file mode 100644
index 00000000..d368f26b
--- /dev/null
+++ b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/VersionNMSManager.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) filoghost and contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+package me.filoghost.holographicdisplays.nms.v1_19_R1;
+
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelPipeline;
+import me.filoghost.fcommons.logging.ErrorCollector;
+import me.filoghost.fcommons.logging.Log;
+import me.filoghost.fcommons.reflection.ReflectField;
+import me.filoghost.holographicdisplays.nms.common.EntityID;
+import me.filoghost.holographicdisplays.nms.common.FallbackEntityIDGenerator;
+import me.filoghost.holographicdisplays.nms.common.NMSErrors;
+import me.filoghost.holographicdisplays.nms.common.NMSManager;
+import me.filoghost.holographicdisplays.nms.common.PacketListener;
+import me.filoghost.holographicdisplays.nms.common.entity.ClickableNMSPacketEntity;
+import me.filoghost.holographicdisplays.nms.common.entity.ItemNMSPacketEntity;
+import me.filoghost.holographicdisplays.nms.common.entity.TextNMSPacketEntity;
+import net.minecraft.network.NetworkManager;
+import net.minecraft.server.network.PlayerConnection;
+import net.minecraft.world.entity.Entity;
+import org.bukkit.craftbukkit.v1_19_R1.entity.CraftPlayer;
+import org.bukkit.entity.Player;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+public class VersionNMSManager implements NMSManager {
+
+ private static final ReflectField ENTITY_ID_COUNTER_FIELD = ReflectField.lookup(AtomicInteger.class, Entity.class, "c");
+ private final Supplier entityIDGenerator;
+
+ public VersionNMSManager(ErrorCollector errorCollector) {
+ this.entityIDGenerator = getEntityIDGenerator(errorCollector);
+
+ // Force initialization of class to eventually throw exceptions early
+ DataWatcherKey.ENTITY_STATUS.getIndex();
+ }
+
+ private Supplier getEntityIDGenerator(ErrorCollector errorCollector) {
+ try {
+ AtomicInteger nmsEntityIDCounter = ENTITY_ID_COUNTER_FIELD.getStatic();
+ return nmsEntityIDCounter::incrementAndGet;
+ } catch (ReflectiveOperationException e) {
+ errorCollector.add(e, NMSErrors.EXCEPTION_GETTING_ENTITY_ID_GENERATOR);
+ return new FallbackEntityIDGenerator();
+ }
+ }
+
+ private EntityID newEntityID() {
+ return new EntityID(entityIDGenerator);
+ }
+
+ @Override
+ public TextNMSPacketEntity newTextPacketEntity() {
+ return new VersionTextNMSPacketEntity(newEntityID());
+ }
+
+ @Override
+ public ItemNMSPacketEntity newItemPacketEntity() {
+ return new VersionItemNMSPacketEntity(newEntityID(), newEntityID());
+ }
+
+ @Override
+ public ClickableNMSPacketEntity newClickablePacketEntity() {
+ return new VersionClickableNMSPacketEntity(newEntityID());
+ }
+
+ @Override
+ public void injectPacketListener(Player player, PacketListener packetListener) {
+ modifyPipeline(player, (ChannelPipeline pipeline) -> {
+ ChannelHandler currentListener = pipeline.get(InboundPacketHandler.HANDLER_NAME);
+ if (currentListener != null) {
+ pipeline.remove(InboundPacketHandler.HANDLER_NAME);
+ }
+ pipeline.addBefore("packet_handler", InboundPacketHandler.HANDLER_NAME, new InboundPacketHandler(player, packetListener));
+ });
+ }
+
+ @Override
+ public void uninjectPacketListener(Player player) {
+ modifyPipeline(player, (ChannelPipeline pipeline) -> {
+ ChannelHandler currentListener = pipeline.get(InboundPacketHandler.HANDLER_NAME);
+ if (currentListener != null) {
+ pipeline.remove(InboundPacketHandler.HANDLER_NAME);
+ }
+ });
+ }
+
+ /*
+ * Modifying the pipeline in the main thread can cause deadlocks, delays and other concurrency issues,
+ * which can be avoided by using the event loop. Thanks to ProtocolLib for this insight.
+ */
+ private void modifyPipeline(Player player, Consumer pipelineModifierTask) {
+ PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().b;
+ NetworkManager networkManager = playerConnection.a();
+ Channel channel = networkManager.m;
+
+ channel.eventLoop().execute(() -> {
+ if (!player.isOnline()) {
+ return;
+ }
+ try {
+ pipelineModifierTask.accept(channel.pipeline());
+ } catch (Exception e) {
+ Log.warning(NMSErrors.EXCEPTION_MODIFYING_CHANNEL_PIPELINE, e);
+ }
+ });
+ }
+
+}
diff --git a/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/VersionNMSPacket.java b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/VersionNMSPacket.java
new file mode 100644
index 00000000..02107299
--- /dev/null
+++ b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/VersionNMSPacket.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) filoghost and contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+package me.filoghost.holographicdisplays.nms.v1_19_R1;
+
+import me.filoghost.holographicdisplays.nms.common.PacketGroup;
+import net.minecraft.network.protocol.Packet;
+import org.bukkit.craftbukkit.v1_19_R1.entity.CraftPlayer;
+import org.bukkit.entity.Player;
+
+abstract class VersionNMSPacket implements PacketGroup {
+
+ @Override
+ public void sendTo(Player player) {
+ ((CraftPlayer) player).getHandle().b.a(getRawPacket());
+ }
+
+ abstract Packet> getRawPacket();
+
+}
diff --git a/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/VersionTextNMSPacketEntity.java b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/VersionTextNMSPacketEntity.java
new file mode 100644
index 00000000..392ebd6f
--- /dev/null
+++ b/nms/v1_19_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_19_R1/VersionTextNMSPacketEntity.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) filoghost and contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+package me.filoghost.holographicdisplays.nms.v1_19_R1;
+
+import me.filoghost.holographicdisplays.common.PositionCoordinates;
+import me.filoghost.holographicdisplays.nms.common.EntityID;
+import me.filoghost.holographicdisplays.nms.common.IndividualTextPacketGroup;
+import me.filoghost.holographicdisplays.nms.common.PacketGroup;
+import me.filoghost.holographicdisplays.nms.common.entity.TextNMSPacketEntity;
+
+class VersionTextNMSPacketEntity implements TextNMSPacketEntity {
+
+ private final EntityID armorStandID;
+
+ VersionTextNMSPacketEntity(EntityID armorStandID) {
+ this.armorStandID = armorStandID;
+ }
+
+ @Override
+ public PacketGroup newSpawnPackets(PositionCoordinates position, String text) {
+ return PacketGroup.of(
+ new EntitySpawnNMSPacket(armorStandID, EntityTypeID.ARMOR_STAND, position, ARMOR_STAND_Y_OFFSET),
+ EntityMetadataNMSPacket.builder(armorStandID)
+ .setArmorStandMarker()
+ .setCustomName(text)
+ .build()
+ );
+ }
+
+ @Override
+ public IndividualTextPacketGroup newSpawnPackets(PositionCoordinates position) {
+ return IndividualTextPacketGroup.of(
+ new EntitySpawnNMSPacket(armorStandID, EntityTypeID.ARMOR_STAND, position, ARMOR_STAND_Y_OFFSET),
+ (String text) -> EntityMetadataNMSPacket.builder(armorStandID)
+ .setArmorStandMarker()
+ .setCustomName(text)
+ .build()
+ );
+ }
+
+ @Override
+ public PacketGroup newChangePackets(String text) {
+ return EntityMetadataNMSPacket.builder(armorStandID)
+ .setCustomName(text)
+ .build();
+ }
+
+ @Override
+ public IndividualTextPacketGroup newChangePackets() {
+ return IndividualTextPacketGroup.of(
+ (String text) -> EntityMetadataNMSPacket.builder(armorStandID)
+ .setCustomName(text)
+ .build()
+ );
+ }
+
+ @Override
+ public PacketGroup newTeleportPackets(PositionCoordinates position) {
+ return new EntityTeleportNMSPacket(armorStandID, position, ARMOR_STAND_Y_OFFSET);
+ }
+
+ @Override
+ public PacketGroup newDestroyPackets() {
+ return new EntityDestroyNMSPacket(armorStandID);
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index 62cb46a6..7999794a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -195,6 +195,12 @@
${project.version}
+
+ ${project.groupId}
+ holographicdisplays-nms-v1_19_r1
+ ${project.version}
+
+
org.spigotmc
spigot-api