diff --git a/core/pom.xml b/core/pom.xml index cd40ef6a..27ad6f3c 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -109,6 +109,11 @@ holographicdisplays-nms-v1_19_r3 + + ${project.groupId} + holographicdisplays-nms-v1_20_r1 + + org.spigotmc spigot-api diff --git a/core/src/main/java/me/filoghost/holographicdisplays/core/HolographicDisplaysCore.java b/core/src/main/java/me/filoghost/holographicdisplays/core/HolographicDisplaysCore.java index 493fed11..485c4dab 100644 --- a/core/src/main/java/me/filoghost/holographicdisplays/core/HolographicDisplaysCore.java +++ b/core/src/main/java/me/filoghost/holographicdisplays/core/HolographicDisplaysCore.java @@ -40,7 +40,7 @@ public class HolographicDisplaysCore { try { nmsManager = NMSVersion.getCurrent().createNMSManager(errorCollector); } catch (UnknownVersionException e) { - throw new PluginEnableException("Holographic Displays only supports Spigot from 1.8 to 1.19.3."); + throw new PluginEnableException("Holographic Displays only supports Spigot from 1.8 to 1.20."); } catch (OutdatedVersionException e) { throw new PluginEnableException("Holographic Displays only supports " + e.getMinimumSupportedVersion() + " and above."); } catch (Throwable t) { 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 d71fb443..5797366e 100644 --- a/core/src/main/java/me/filoghost/holographicdisplays/core/NMSVersion.java +++ b/core/src/main/java/me/filoghost/holographicdisplays/core/NMSVersion.java @@ -40,7 +40,8 @@ public enum NMSVersion { /* 1.18.2 */ v1_18_R2(errorCollector -> new me.filoghost.holographicdisplays.nms.v1_18_R2.VersionNMSManager(errorCollector)), /* 1.19 - 1.19.2 */ v1_19_R1(errorCollector -> new me.filoghost.holographicdisplays.nms.v1_19_R1.VersionNMSManager(errorCollector)), /* 1.19.3 */ v1_19_R2(errorCollector -> new me.filoghost.holographicdisplays.nms.v1_19_R2.VersionNMSManager(errorCollector)), - /* 1.19.4 - ? */ v1_19_R3(errorCollector -> new me.filoghost.holographicdisplays.nms.v1_19_R3.VersionNMSManager(errorCollector)), + /* 1.19.4 */ v1_19_R3(errorCollector -> new me.filoghost.holographicdisplays.nms.v1_19_R3.VersionNMSManager(errorCollector)), + /* 1.20 - ? */ v1_20_R1(errorCollector -> new me.filoghost.holographicdisplays.nms.v1_20_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 d6573447..da1886b3 100644 --- a/nms/pom.xml +++ b/nms/pom.xml @@ -30,6 +30,7 @@ v1_19_r1 v1_19_r2 v1_19_r3 + v1_20_r1 diff --git a/nms/v1_20_r1/pom.xml b/nms/v1_20_r1/pom.xml new file mode 100644 index 00000000..1d75e646 --- /dev/null +++ b/nms/v1_20_r1/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + + me.filoghost.holographicdisplays + holographicdisplays-nms + 3.0.3-SNAPSHOT + + + holographicdisplays-nms-v1_20_r1 + HolographicDisplays NMS v1_20_R1 + + + + ${project.groupId} + holographicdisplays-nms-common + + + + org.spigotmc + spigot + 1.20-R0.1-SNAPSHOT + provided + + + + org.joml + joml + 1.10.5 + provided + + + + diff --git a/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/DataWatcherKey.java b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/DataWatcherKey.java new file mode 100644 index 00000000..3be7f771 --- /dev/null +++ b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_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_20_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.k; + private static final DataWatcherSerializer ITEM_STACK_SERIALIZER = DataWatcherRegistry.h; + private static final DataWatcherSerializer> OPTIONAL_CHAT_COMPONENT_SERIALIZER = DataWatcherRegistry.g; + + 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_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/DataWatcherPacketBuilder.java b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/DataWatcherPacketBuilder.java new file mode 100644 index 00000000..547221bb --- /dev/null +++ b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_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_20_R1; + +import me.filoghost.fcommons.Strings; +import net.minecraft.network.chat.IChatBaseComponent; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_20_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_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/EntityDestroyNMSPacket.java b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/EntityDestroyNMSPacket.java new file mode 100644 index 00000000..8dbda47f --- /dev/null +++ b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_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_20_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.getInternalSerializer()); + } + + EntityDestroyNMSPacket(EntityID entityID1, EntityID entityID2) { + PacketByteBuffer packetByteBuffer = PacketByteBuffer.get(); + + packetByteBuffer.writeVarIntArray(entityID1.getNumericID(), entityID2.getNumericID()); + + this.rawPacket = new PacketPlayOutEntityDestroy(packetByteBuffer.getInternalSerializer()); + } + + @Override + Packet getRawPacket() { + return rawPacket; + } + +} diff --git a/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/EntityMetadataNMSPacket.java b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/EntityMetadataNMSPacket.java new file mode 100644 index 00000000..d150f673 --- /dev/null +++ b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_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_20_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.getInternalSerializer()); + } + + @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_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/EntityMountNMSPacket.java b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/EntityMountNMSPacket.java new file mode 100644 index 00000000..61fe80c1 --- /dev/null +++ b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_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_20_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.getInternalSerializer()); + } + + @Override + Packet getRawPacket() { + return rawPacket; + } + +} diff --git a/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/EntitySpawnNMSPacket.java b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/EntitySpawnNMSPacket.java new file mode 100644 index 00000000..f5d9a48a --- /dev/null +++ b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_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_20_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.getInternalSerializer()); + } + + @Override + Packet getRawPacket() { + return rawPacket; + } + +} diff --git a/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/EntityTeleportNMSPacket.java b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/EntityTeleportNMSPacket.java new file mode 100644 index 00000000..30392513 --- /dev/null +++ b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_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_20_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.getInternalSerializer()); + } + + @Override + Packet getRawPacket() { + return rawPacket; + } + +} diff --git a/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/EntityTypeID.java b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/EntityTypeID.java new file mode 100644 index 00000000..c2f8c71d --- /dev/null +++ b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_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_20_R1; + +class EntityTypeID { + + static final int ARMOR_STAND = 2; + static final int ITEM = 54; + static final int SLIME = 88; + +} diff --git a/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/InboundPacketHandler.java b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/InboundPacketHandler.java new file mode 100644 index 00000000..772af83c --- /dev/null +++ b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_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_20_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_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/PacketByteBuffer.java b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/PacketByteBuffer.java new file mode 100644 index 00000000..93cdca7b --- /dev/null +++ b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/PacketByteBuffer.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.nms.v1_20_R1; + +import io.netty.buffer.Unpooled; +import net.minecraft.network.PacketDataSerializer; + +import java.util.UUID; + +class PacketByteBuffer { + + private static final PacketByteBuffer INSTANCE = new PacketByteBuffer(); + + private final PacketDataSerializer serializer; + + static PacketByteBuffer get() { + INSTANCE.clear(); + return INSTANCE; + } + + private PacketByteBuffer() { + this.serializer = new PacketDataSerializer(Unpooled.buffer()); + } + + void writeBoolean(boolean flag) { + serializer.writeBoolean(flag); + } + + void writeByte(int i) { + serializer.writeByte(i); + } + + void writeShort(int i) { + serializer.writeShort(i); + } + + void writeInt(int i) { + serializer.writeInt(i); + } + + void writeDouble(double d) { + serializer.writeDouble(d); + } + + void writeVarInt(int i) { + serializer.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) { + serializer.a(uuid); + } + + void writeDataWatcherEntry(DataWatcherKey key, T value) { + serializer.writeByte(key.getIndex()); + writeVarInt(key.getSerializerTypeID()); + key.getSerializer().a(serializer, value); + } + + void writeDataWatcherEntriesEnd() { + serializer.writeByte(0xFF); + } + + public PacketDataSerializer getInternalSerializer() { + return serializer; + } + + void clear() { + serializer.clear(); + } + +} diff --git a/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/VersionClickableNMSPacketEntity.java b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/VersionClickableNMSPacketEntity.java new file mode 100644 index 00000000..5ec49e40 --- /dev/null +++ b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_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_20_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_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/VersionItemNMSPacketEntity.java b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/VersionItemNMSPacketEntity.java new file mode 100644 index 00000000..5820c4cb --- /dev/null +++ b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_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_20_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_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/VersionNMSManager.java b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/VersionNMSManager.java new file mode 100644 index 00000000..b2cd2819 --- /dev/null +++ b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/VersionNMSManager.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.nms.v1_20_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_20_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, "d"); + private static final ReflectField NETWORK_MANAGER_FIELD = ReflectField.lookup(NetworkManager.class, PlayerConnection.class, "h"); + 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().c; + NetworkManager networkManager; + try { + networkManager = NETWORK_MANAGER_FIELD.get(playerConnection); + } catch (ReflectiveOperationException e) { + Log.warning(NMSErrors.EXCEPTION_MODIFYING_CHANNEL_PIPELINE, e); + return; + } + 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_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/VersionNMSPacket.java b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/VersionNMSPacket.java new file mode 100644 index 00000000..ea76c40e --- /dev/null +++ b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_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_20_R1; + +import me.filoghost.holographicdisplays.nms.common.PacketGroup; +import net.minecraft.network.protocol.Packet; +import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; + +abstract class VersionNMSPacket implements PacketGroup { + + @Override + public void sendTo(Player player) { + ((CraftPlayer) player).getHandle().c.a(getRawPacket()); + } + + abstract Packet getRawPacket(); + +} diff --git a/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/VersionTextNMSPacketEntity.java b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_R1/VersionTextNMSPacketEntity.java new file mode 100644 index 00000000..f8db1f75 --- /dev/null +++ b/nms/v1_20_r1/src/main/java/me/filoghost/holographicdisplays/nms/v1_20_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_20_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 09817baf..e1730466 100644 --- a/pom.xml +++ b/pom.xml @@ -201,6 +201,12 @@ ${project.version} + + ${project.groupId} + holographicdisplays-nms-v1_20_r1 + ${project.version} + + org.spigotmc spigot-api