From 47cfdefff87e1e6163e46f224ca9c50b3d51afbd Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 19:42:14 +0000 Subject: [PATCH] Add support for Minecraft 26.3 Adds a v26_3 NMS module based on v26_2 and routes 26.3 servers to it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01M29zjaS6FKDHdSRMvxwdYn --- core/pom.xml | 4 + .../core/HolographicDisplaysCore.java | 2 +- .../holographicdisplays/core/NMSVersion.java | 7 +- .../core/NMSVersionStringParserTest.java | 2 + nms/pom.xml | 1 + nms/v26_3/pom.xml | 43 ++++++ .../nms/v26_3/DataWatcherKey.java | 56 +++++++ .../nms/v26_3/DataWatcherPacketBuilder.java | 86 +++++++++++ .../nms/v26_3/EntityDestroyNMSPacket.java | 29 ++++ .../nms/v26_3/EntityMetadataNMSPacket.java | 44 ++++++ .../nms/v26_3/EntityMountNMSPacket.java | 42 +++++ .../nms/v26_3/EntitySpawnNMSPacket.java | 49 ++++++ .../nms/v26_3/EntityTeleportNMSPacket.java | 37 +++++ .../nms/v26_3/EntityTypeConstants.java | 21 +++ .../nms/v26_3/InboundPacketHandler.java | 44 ++++++ .../nms/v26_3/PacketByteBuffer.java | 88 +++++++++++ .../VersionClickableNMSPacketEntity.java | 47 ++++++ .../nms/v26_3/VersionItemNMSPacketEntity.java | 56 +++++++ .../nms/v26_3/VersionNMSManager.java | 143 ++++++++++++++++++ .../nms/v26_3/VersionNMSPacket.java | 22 +++ .../nms/v26_3/VersionTextNMSPacketEntity.java | 70 +++++++++ pom.xml | 5 + 22 files changed, 895 insertions(+), 3 deletions(-) create mode 100644 nms/v26_3/pom.xml create mode 100644 nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/DataWatcherKey.java create mode 100644 nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/DataWatcherPacketBuilder.java create mode 100644 nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/EntityDestroyNMSPacket.java create mode 100644 nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/EntityMetadataNMSPacket.java create mode 100644 nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/EntityMountNMSPacket.java create mode 100644 nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/EntitySpawnNMSPacket.java create mode 100644 nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/EntityTeleportNMSPacket.java create mode 100644 nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/EntityTypeConstants.java create mode 100644 nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/InboundPacketHandler.java create mode 100644 nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/PacketByteBuffer.java create mode 100644 nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/VersionClickableNMSPacketEntity.java create mode 100644 nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/VersionItemNMSPacketEntity.java create mode 100644 nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/VersionNMSManager.java create mode 100644 nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/VersionNMSPacket.java create mode 100644 nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/VersionTextNMSPacketEntity.java diff --git a/core/pom.xml b/core/pom.xml index f29be2e8..bb63d95f 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -134,5 +134,9 @@ ${project.groupId} illusion-nms-v26_2 + + ${project.groupId} + illusion-nms-v26_3 + 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 9fb92f39..257b808a 100644 --- a/core/src/main/java/me/filoghost/holographicdisplays/core/HolographicDisplaysCore.java +++ b/core/src/main/java/me/filoghost/holographicdisplays/core/HolographicDisplaysCore.java @@ -42,7 +42,7 @@ public void enable(Plugin plugin, ErrorCollector errorCollector) throws PluginEn nmsManager = NMSVersion.getCurrent().createNMSManager(errorCollector); } catch (UnknownVersionException e) { throw new PluginEnableException( - "Holographic Displays only supports Spigot from 1.8 to 26.2, but your server is running " + "Holographic Displays only supports Spigot from 1.8 to 26.3, but your server is running " + Bukkit.getServer().getVersion()); } catch (OutdatedVersionException e) { throw new PluginEnableException( 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 d91bf7af..021a9455 100644 --- a/core/src/main/java/me/filoghost/holographicdisplays/core/NMSVersion.java +++ b/core/src/main/java/me/filoghost/holographicdisplays/core/NMSVersion.java @@ -7,7 +7,7 @@ import me.filoghost.fcommons.logging.ErrorCollector; import me.filoghost.holographicdisplays.nms.common.NMSManager; -import me.filoghost.holographicdisplays.nms.v26_2.VersionNMSManager; +import me.filoghost.holographicdisplays.nms.v26_3.VersionNMSManager; import org.bukkit.Bukkit; /** @@ -44,7 +44,8 @@ public enum NMSVersion { /* 1.21.9- 1.21.10 */ v1_21_R6(me.filoghost.holographicdisplays.nms.v1_21_R6.VersionNMSManager::new), /* 1.21.11 */ v1_21_R7(me.filoghost.holographicdisplays.nms.v1_21_R7.VersionNMSManager::new), /* 26.1.2 */ v26_1(me.filoghost.holographicdisplays.nms.v26_1.VersionNMSManager::new), - /* 26.2 - X */ v26_2(VersionNMSManager::new), + /* 26.2 */ v26_2(me.filoghost.holographicdisplays.nms.v26_2.VersionNMSManager::new), + /* 26.3 - X */ v26_3(VersionNMSManager::new), /* Other versions */ UNKNOWN(NMSManagerFactory.unknownVersion()); private static final NMSVersion CURRENT_VERSION = detectCurrentVersion(); @@ -85,6 +86,8 @@ private static NMSVersion detectVersion(String bukkitVersion, String nmsPackageN return v26_1; case "26.2": return v26_2; + case "26.3": + return v26_3; default: return UNKNOWN; } diff --git a/core/src/test/java/me/filoghost/holographicdisplays/core/NMSVersionStringParserTest.java b/core/src/test/java/me/filoghost/holographicdisplays/core/NMSVersionStringParserTest.java index dd93f9b5..b2359fb8 100644 --- a/core/src/test/java/me/filoghost/holographicdisplays/core/NMSVersionStringParserTest.java +++ b/core/src/test/java/me/filoghost/holographicdisplays/core/NMSVersionStringParserTest.java @@ -26,9 +26,11 @@ static Stream normalizeYearBasedVersionTestArguments() { // Spigot format, unchanged since before the year-based versioning switch Arguments.of("26.1.2-R0.1-SNAPSHOT", "26.1.2"), Arguments.of("26.2-R0.1-SNAPSHOT", "26.2"), + Arguments.of("26.3-R0.1-SNAPSHOT", "26.3"), // Paper release format: ".build.-" Arguments.of("26.2.build.62-beta", "26.2"), + Arguments.of("26.3.build.1-alpha", "26.3"), Arguments.of("26.1.2.build.5-default", "26.1.2"), // Paper development build format: "-DEV-@" diff --git a/nms/pom.xml b/nms/pom.xml index f4d6c5d2..bffa2ef9 100644 --- a/nms/pom.xml +++ b/nms/pom.xml @@ -50,6 +50,7 @@ v1_21_r7 v26_1 v26_2 + v26_3 diff --git a/nms/v26_3/pom.xml b/nms/v26_3/pom.xml new file mode 100644 index 00000000..ca0f59d8 --- /dev/null +++ b/nms/v26_3/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + + studio.magemonkey + illusion-nms + 3.0.15-SNAPSHOT + + + illusion-nms-v26_3 + Illusion NMS v26_3 + + + + ${project.groupId} + illusion-nms-common + + + + org.spigotmc + spigot + 26.3-R0.1-SNAPSHOT + + provided + + + org.spigotmc + spigot-api + 26.3-R0.1-SNAPSHOT + provided + + + + org.joml + joml + 1.10.9 + provided + + + + diff --git a/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/DataWatcherKey.java b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/DataWatcherKey.java new file mode 100644 index 00000000..2e24872b --- /dev/null +++ b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/DataWatcherKey.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.nms.v26_3; + +import io.netty.handler.codec.EncoderException; +import net.minecraft.network.chat.Component; +import net.minecraft.network.syncher.EntityDataSerializer; +import net.minecraft.network.syncher.EntityDataSerializers; +import net.minecraft.world.item.ItemStack; + +import java.util.Optional; + +class DataWatcherKey { + + private static final EntityDataSerializer BYTE_SERIALIZER = EntityDataSerializers.BYTE; + private static final EntityDataSerializer INT_SERIALIZER = EntityDataSerializers.INT; + private static final EntityDataSerializer BOOLEAN_SERIALIZER = EntityDataSerializers.BOOLEAN; + private static final EntityDataSerializer ITEM_STACK_SERIALIZER = EntityDataSerializers.ITEM_STACK; + private static final EntityDataSerializer> OPTIONAL_CHAT_COMPONENT_SERIALIZER = EntityDataSerializers.OPTIONAL_COMPONENT; + + 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<>(18, INT_SERIALIZER); + + private final int index; + private final EntityDataSerializer serializer; + private final int serializerTypeID; + + private DataWatcherKey(int index, EntityDataSerializer serializer) { + this.index = index; + this.serializer = serializer; + this.serializerTypeID = EntityDataSerializers.getSerializedId(serializer); + if (serializerTypeID < 0) { + throw new EncoderException("Could not find serializer ID of " + serializer); + } + } + + int getIndex() { + return index; + } + + EntityDataSerializer getSerializer() { + return serializer; + } + + int getSerializerTypeID() { + return serializerTypeID; + } + +} diff --git a/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/DataWatcherPacketBuilder.java b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/DataWatcherPacketBuilder.java new file mode 100644 index 00000000..102e53b3 --- /dev/null +++ b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/DataWatcherPacketBuilder.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.nms.v26_3; + +import me.filoghost.fcommons.Strings; +import me.filoghost.holographicdisplays.nms.common.EntityID; +import net.minecraft.network.chat.Component; +import net.minecraft.network.syncher.SynchedEntityData; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.craftbukkit.util.CraftChatMessage; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +abstract class DataWatcherPacketBuilder { + + private static final int MAX_CUSTOM_NAME_LENGTH = 5000; + + protected final EntityID entityId; + protected List> watchers = new ArrayList<>(); + + public DataWatcherPacketBuilder(EntityID entityId) { + this.entityId = entityId; + } + + DataWatcherPacketBuilder setInvisible() { + watchers.add(new SynchedEntityData.DataValue<>(DataWatcherKey.ENTITY_STATUS.getIndex(), + DataWatcherKey.ENTITY_STATUS.getSerializer(), + (byte) 0x20)); + return this; + } + + DataWatcherPacketBuilder setArmorStandMarker() { + setInvisible(); + // Small, no gravity, no base plate, marker + watchers.add(new SynchedEntityData.DataValue<>(DataWatcherKey.ARMOR_STAND_STATUS.getIndex(), + DataWatcherKey.ARMOR_STAND_STATUS.getSerializer(), + (byte) (0x01 | 0x02 | 0x08 | 0x10))); + return this; + } + + DataWatcherPacketBuilder setCustomName(String customName) { + watchers.add(new SynchedEntityData.DataValue<>(DataWatcherKey.CUSTOM_NAME.getIndex(), + DataWatcherKey.CUSTOM_NAME.getSerializer(), + getCustomNameDataWatcherValue(customName))); + watchers.add(new SynchedEntityData.DataValue<>(DataWatcherKey.CUSTOM_NAME_VISIBILITY.getIndex(), + DataWatcherKey.CUSTOM_NAME_VISIBILITY.getSerializer(), + !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) { + watchers.add(new SynchedEntityData.DataValue<>(DataWatcherKey.ITEM_STACK.getIndex(), + DataWatcherKey.ITEM_STACK.getSerializer(), + CraftItemStack.asNMSCopy(itemStack))); + return this; + } + + DataWatcherPacketBuilder setSlimeSmall() { + watchers.add(new SynchedEntityData.DataValue<>(DataWatcherKey.SLIME_SIZE.getIndex(), + DataWatcherKey.SLIME_SIZE.getSerializer(), + 0)); + return this; + } + + T build() { + return createPacket(); + } + + abstract T createPacket(); + +} diff --git a/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/EntityDestroyNMSPacket.java b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/EntityDestroyNMSPacket.java new file mode 100644 index 00000000..84053aba --- /dev/null +++ b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/EntityDestroyNMSPacket.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.nms.v26_3; + +import me.filoghost.holographicdisplays.nms.common.EntityID; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket; + +class EntityDestroyNMSPacket extends VersionNMSPacket { + + private final Packet rawPacket; + + EntityDestroyNMSPacket(EntityID entityID) { + this.rawPacket = new ClientboundRemoveEntitiesPacket(entityID.getNumericID()); + } + + EntityDestroyNMSPacket(EntityID entityID1, EntityID entityID2) { + this.rawPacket = new ClientboundRemoveEntitiesPacket(entityID1.getNumericID(), entityID2.getNumericID()); + } + + @Override + Packet getRawPacket() { + return rawPacket; + } + +} diff --git a/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/EntityMetadataNMSPacket.java b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/EntityMetadataNMSPacket.java new file mode 100644 index 00000000..50da7a10 --- /dev/null +++ b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/EntityMetadataNMSPacket.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.nms.v26_3; + +import me.filoghost.holographicdisplays.nms.common.EntityID; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; +import net.minecraft.network.syncher.SynchedEntityData; + +import java.util.List; + +class EntityMetadataNMSPacket extends VersionNMSPacket { + + private final Packet rawPacket; + + private EntityMetadataNMSPacket(EntityID entityId, List> watchers) { + this.rawPacket = new ClientboundSetEntityDataPacket(entityId.getNumericID(), watchers); + } + + @Override + Packet getRawPacket() { + return rawPacket; + } + + public static DataWatcherPacketBuilder builder(EntityID entityID) { + return new Builder(entityID); + } + + + private static class Builder extends DataWatcherPacketBuilder { + public Builder(EntityID entityId) { + super(entityId); + } + + @Override + EntityMetadataNMSPacket createPacket() { + return new EntityMetadataNMSPacket(entityId, watchers); + } + } + +} diff --git a/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/EntityMountNMSPacket.java b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/EntityMountNMSPacket.java new file mode 100644 index 00000000..6ca98677 --- /dev/null +++ b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/EntityMountNMSPacket.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.nms.v26_3; + +import me.filoghost.holographicdisplays.nms.common.EntityID; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientboundSetPassengersPacket; + +import java.lang.reflect.Constructor; + +class EntityMountNMSPacket extends VersionNMSPacket { + + private final Packet rawPacket; + + EntityMountNMSPacket(EntityID vehicleEntityID, EntityID passengerEntityID) { + PacketByteBuffer packetByteBuffer = PacketByteBuffer.get(); + + // We'll leave this as a byte buffer for now, since I think the ClientboundSetPassengersPacket constructor + // requires modifying the vehicle entity to have the passenger before we can create this packet + packetByteBuffer.writeVarInt(vehicleEntityID.getNumericID()); + packetByteBuffer.writeVarIntArray(passengerEntityID.getNumericID()); + + try { + Constructor ctor = + ClientboundSetPassengersPacket.class.getDeclaredConstructor(FriendlyByteBuf.class); + ctor.setAccessible(true); + this.rawPacket = ctor.newInstance(packetByteBuffer.getInternalSerializer()); + } catch (Exception e) { + throw new RuntimeException("Failed to create EntityMountNMSPacket", e); + } + } + + @Override + Packet getRawPacket() { + return rawPacket; + } + +} diff --git a/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/EntitySpawnNMSPacket.java b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/EntitySpawnNMSPacket.java new file mode 100644 index 00000000..bdcdd0d1 --- /dev/null +++ b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/EntitySpawnNMSPacket.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.nms.v26_3; + +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.ClientboundAddEntityPacket; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.phys.Vec3; + +class EntitySpawnNMSPacket extends VersionNMSPacket { + + private final Packet rawPacket; + + EntitySpawnNMSPacket(EntityID entityID, + EntityType entityType, + PositionCoordinates position, + double positionOffsetY) { + this.rawPacket = new ClientboundAddEntityPacket( + entityID.getNumericID(), + entityID.getUUID(), + position.getX(), + position.getY() + positionOffsetY, + position.getZ(), + // x-rot (yaw) + 0, + // y-rot (pitch) + 0, + // The entity type to spawn + entityType, + // Apply velocity? 1 if present and zero (otherwise by default a random velocity is applied) + entityType == EntityTypeConstants.ITEM ? 1 : 0, + // Velocity + new Vec3(0, 0, 0), + // y-head-rot (???) + 0 + ); + } + + @Override + Packet getRawPacket() { + return rawPacket; + } + +} diff --git a/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/EntityTeleportNMSPacket.java b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/EntityTeleportNMSPacket.java new file mode 100644 index 00000000..361f8fdd --- /dev/null +++ b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/EntityTeleportNMSPacket.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.nms.v26_3; + +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.ClientboundTeleportEntityPacket; +import net.minecraft.world.entity.PositionMoveRotation; +import net.minecraft.world.entity.Relative; +import net.minecraft.world.phys.Vec3; + +import java.util.HashSet; +import java.util.Set; + +class EntityTeleportNMSPacket extends VersionNMSPacket { + + private final Packet rawPacket; + + EntityTeleportNMSPacket(EntityID entityID, PositionCoordinates position, double positionOffsetY) { + Vec3 pos = new Vec3(position.getX(), position.getY() + positionOffsetY, position.getZ()); + Vec3 delta = new Vec3(0, 0, 0); + PositionMoveRotation positionMoveRotation = new PositionMoveRotation(pos, delta, 0, 0); + Set relatives = new HashSet<>(); + + this.rawPacket = new ClientboundTeleportEntityPacket(entityID.getNumericID(), positionMoveRotation, relatives, false); + } + + @Override + Packet getRawPacket() { + return rawPacket; + } + +} diff --git a/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/EntityTypeConstants.java b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/EntityTypeConstants.java new file mode 100644 index 00000000..d9a87716 --- /dev/null +++ b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/EntityTypeConstants.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.nms.v26_3; + +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.EntityTypes; +import net.minecraft.world.entity.decoration.ArmorStand; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.monster.cubemob.Slime; + +// Sourced from net.minecraft.world.entity.EntityTypes +class EntityTypeConstants { + + static final EntityType ARMOR_STAND = EntityTypes.ARMOR_STAND; + static final EntityType ITEM = EntityTypes.ITEM; + static final EntityType SLIME = EntityTypes.SLIME; + +} diff --git a/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/InboundPacketHandler.java b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/InboundPacketHandler.java new file mode 100644 index 00000000..4ba076cf --- /dev/null +++ b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/InboundPacketHandler.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.nms.v26_3; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import me.filoghost.fcommons.logging.Log; +import me.filoghost.holographicdisplays.nms.common.NMSErrors; +import me.filoghost.holographicdisplays.nms.common.PacketListener; +import net.minecraft.network.protocol.game.ServerboundInteractPacket; +import org.bukkit.entity.Player; + +class InboundPacketHandler extends ChannelInboundHandlerAdapter { + + public static final String HANDLER_NAME = "holographic_displays_listener"; + + 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 ServerboundInteractPacket) { + int entityID = ((ServerboundInteractPacket) packet).entityId(); + 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/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/PacketByteBuffer.java b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/PacketByteBuffer.java new file mode 100644 index 00000000..021ae036 --- /dev/null +++ b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/PacketByteBuffer.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.nms.v26_3; + +import io.netty.buffer.Unpooled; +import net.minecraft.network.RegistryFriendlyByteBuf; +import org.bukkit.craftbukkit.CraftRegistry; + +import java.util.UUID; + +class PacketByteBuffer { + + private static final ThreadLocal LOCAL_INSTANCE = ThreadLocal.withInitial(PacketByteBuffer::new); + + private final RegistryFriendlyByteBuf serializer; + + static PacketByteBuffer get() { + PacketByteBuffer instance = LOCAL_INSTANCE.get(); + instance.clear(); + return instance; + } + + private PacketByteBuffer() { + this.serializer = + new RegistryFriendlyByteBuf(Unpooled.buffer(), CraftRegistry.getMinecraftRegistry()); + } + + 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.writeVarInt(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.writeUUID(uuid); + } + + void writeDataWatcherEntry(DataWatcherKey key, T value) { + serializer.writeByte(key.getIndex()); + writeVarInt(key.getSerializerTypeID()); + key.getSerializer().codec().encode(serializer, value); + } + + void writeDataWatcherEntriesEnd() { + serializer.writeByte(0xFF); + } + + public RegistryFriendlyByteBuf getInternalSerializer() { + return serializer; + } + + void clear() { + serializer.clear(); + } + +} diff --git a/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/VersionClickableNMSPacketEntity.java b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/VersionClickableNMSPacketEntity.java new file mode 100644 index 00000000..b5f35b9b --- /dev/null +++ b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/VersionClickableNMSPacketEntity.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.nms.v26_3; + +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, EntityTypeConstants.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/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/VersionItemNMSPacketEntity.java b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/VersionItemNMSPacketEntity.java new file mode 100644 index 00000000..73f60d27 --- /dev/null +++ b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/VersionItemNMSPacketEntity.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.nms.v26_3; + +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, EntityTypeConstants.ARMOR_STAND, position, ITEM_Y_OFFSET), + EntityMetadataNMSPacket.builder(vehicleID) + .setArmorStandMarker() + .build(), + new EntitySpawnNMSPacket(itemID, EntityTypeConstants.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/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/VersionNMSManager.java b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/VersionNMSManager.java new file mode 100644 index 00000000..0c87122a --- /dev/null +++ b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/VersionNMSManager.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.nms.v26_3; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoop; +import me.filoghost.fcommons.logging.ErrorCollector; +import me.filoghost.fcommons.logging.Log; +import me.filoghost.fcommons.reflection.ReflectField; +import me.filoghost.holographicdisplays.nms.common.*; +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.Connection; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.network.ServerCommonPacketListenerImpl; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class VersionNMSManager implements NMSManager { + + private static final ReflectField NETWORK_MANAGER_FIELD = + ReflectField.lookup(Connection.class, ServerCommonPacketListenerImpl.class, "connection"); + 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(); + } + + /* + * As of 26.2, the entity ID counter is no longer a static field on Entity, but is exposed through + * ServerLevel#getNextEntityId(), which is backed by a single counter shared by all levels. + */ + private Supplier getEntityIDGenerator(ErrorCollector errorCollector) { + try { + List worlds = Bukkit.getWorlds(); + if (worlds.isEmpty()) { + throw new IllegalStateException("No worlds are loaded"); + } + ServerLevel serverLevel = ((CraftWorld) worlds.get(0)).getHandle(); + return serverLevel::getNextEntityId; + } catch (Exception 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) { + ServerGamePacketListenerImpl playerConnection = ((CraftPlayer) player).getHandle().connection; + Connection networkManager; + try { + networkManager = NETWORK_MANAGER_FIELD.get(playerConnection); + } catch (ReflectiveOperationException e) { + Log.warning(NMSErrors.EXCEPTION_MODIFYING_CHANNEL_PIPELINE, e); + return; + } + Channel channel = networkManager.channel; + if (channel == null) { + return; + } + EventLoop eventLoop = channel.eventLoop(); + + Runnable safeModifierTask = () -> { + if (!player.isOnline()) { + return; + } + try { + pipelineModifierTask.accept(channel.pipeline()); + } catch (Exception e) { + Log.warning(NMSErrors.EXCEPTION_MODIFYING_CHANNEL_PIPELINE, e); + } + }; + + if (eventLoop.inEventLoop()) { + safeModifierTask.run(); + } else { + eventLoop.execute(safeModifierTask); + } + } + +} diff --git a/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/VersionNMSPacket.java b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/VersionNMSPacket.java new file mode 100644 index 00000000..b3e8aa76 --- /dev/null +++ b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/VersionNMSPacket.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.nms.v26_3; + +import me.filoghost.holographicdisplays.nms.common.PacketGroup; +import net.minecraft.network.protocol.Packet; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.Player; + +abstract class VersionNMSPacket implements PacketGroup { + + @Override + public void sendTo(Player player) { + ((CraftPlayer) player).getHandle().connection.send(getRawPacket()); + } + + abstract Packet getRawPacket(); + +} diff --git a/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/VersionTextNMSPacketEntity.java b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/VersionTextNMSPacketEntity.java new file mode 100644 index 00000000..15944979 --- /dev/null +++ b/nms/v26_3/src/main/java/me/filoghost/holographicdisplays/nms/v26_3/VersionTextNMSPacketEntity.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) filoghost and contributors + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package me.filoghost.holographicdisplays.nms.v26_3; + +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, EntityTypeConstants.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, EntityTypeConstants.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 d6591683..af141e51 100644 --- a/pom.xml +++ b/pom.xml @@ -207,6 +207,11 @@ illusion-nms-v26_2 ${nms.version} + + ${project.groupId} + illusion-nms-v26_3 + ${nms.version} + org.spigotmc spigot-api