Add other major NMS implementations

This commit is contained in:
filoghost 2021-08-09 09:20:54 +02:00
parent 2d0969e171
commit 4f86a8e9c2
159 changed files with 8007 additions and 99 deletions

View File

@ -0,0 +1,54 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_10_R1;
import com.google.common.base.Optional;
import io.netty.handler.codec.EncoderException;
import net.minecraft.server.v1_10_R1.DataWatcherRegistry;
import net.minecraft.server.v1_10_R1.DataWatcherSerializer;
import net.minecraft.server.v1_10_R1.ItemStack;
class DataWatcherKey<T> {
private static final DataWatcherSerializer<Byte> BYTE_SERIALIZER = DataWatcherRegistry.a;
private static final DataWatcherSerializer<Integer> INT_SERIALIZER = DataWatcherRegistry.b;
private static final DataWatcherSerializer<Boolean> BOOLEAN_SERIALIZER = DataWatcherRegistry.h;
private static final DataWatcherSerializer<Optional<ItemStack>> ITEM_STACK_SERIALIZER = DataWatcherRegistry.f;
private static final DataWatcherSerializer<String> STRING_SERIALIZER = DataWatcherRegistry.d;
static final DataWatcherKey<Byte> ENTITY_STATUS = new DataWatcherKey<>(0, BYTE_SERIALIZER);
static final DataWatcherKey<String> CUSTOM_NAME = new DataWatcherKey<>(2, STRING_SERIALIZER);
static final DataWatcherKey<Boolean> CUSTOM_NAME_VISIBILITY = new DataWatcherKey<>(3, BOOLEAN_SERIALIZER);
static final DataWatcherKey<Optional<ItemStack>> ITEM_STACK = new DataWatcherKey<>(6, ITEM_STACK_SERIALIZER);
static final DataWatcherKey<Byte> ARMOR_STAND_STATUS = new DataWatcherKey<>(11, BYTE_SERIALIZER);
static final DataWatcherKey<Integer> SLIME_SIZE = new DataWatcherKey<>(12, INT_SERIALIZER);
private final int index;
private final DataWatcherSerializer<T> serializer;
private final int serializerTypeID;
private DataWatcherKey(int index, DataWatcherSerializer<T> 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<T> getSerializer() {
return serializer;
}
int getSerializerTypeID() {
return serializerTypeID;
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_10_R1;
import com.google.common.base.Optional;
import me.filoghost.fcommons.Strings;
import org.bukkit.craftbukkit.v1_10_R1.inventory.CraftItemStack;
import org.bukkit.inventory.ItemStack;
abstract class DataWatcherPacketBuilder<T> {
private final PacketByteBuffer packetByteBuffer;
DataWatcherPacketBuilder(PacketByteBuffer packetByteBuffer) {
this.packetByteBuffer = packetByteBuffer;
}
DataWatcherPacketBuilder<T> setInvisible() {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.ENTITY_STATUS, (byte) 0x20); // Invisible
return this;
}
DataWatcherPacketBuilder<T> setArmorStandMarker() {
setInvisible();
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.ARMOR_STAND_STATUS, (byte) (0x01 | 0x08 | 0x10)); // Small, no base plate, marker
return this;
}
DataWatcherPacketBuilder<T> setCustomName(String customName) {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.CUSTOM_NAME, Strings.truncate(customName, 300));
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.CUSTOM_NAME_VISIBILITY, !Strings.isEmpty(customName));
return this;
}
DataWatcherPacketBuilder<T> setItemStack(ItemStack itemStack) {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.ITEM_STACK, Optional.fromNullable(CraftItemStack.asNMSCopy(itemStack)));
return this;
}
DataWatcherPacketBuilder<T> setSlimeSmall() {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.SLIME_SIZE, 1);
return this;
}
T build() {
packetByteBuffer.writeDataWatcherEntriesEnd();
return createPacket(packetByteBuffer);
}
abstract T createPacket(PacketByteBuffer packetByteBuffer);
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_10_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_10_R1.Packet;
import net.minecraft.server.v1_10_R1.PacketPlayOutEntityDestroy;
class EntityDestroyNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntityDestroyNMSPacket(EntityID... entityIDs) {
int[] entityIDsArray = new int[entityIDs.length];
for (int i = 0; i < entityIDs.length; i++) {
entityIDsArray[i] = entityIDs[i].getNumericID();
}
this.rawPacket = new PacketPlayOutEntityDestroy(entityIDsArray);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_10_R1;
import me.filoghost.fcommons.reflection.ReflectField;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_10_R1.DataWatcher;
import net.minecraft.server.v1_10_R1.Packet;
import net.minecraft.server.v1_10_R1.PacketDataSerializer;
import net.minecraft.server.v1_10_R1.PacketPlayOutSpawnEntityLiving;
class EntityLivingSpawnNMSPacket extends VersionNMSPacket {
private static final ReflectField<DataWatcher> DATA_WATCHER_FIELD =
ReflectField.lookup(DataWatcher.class, PacketPlayOutSpawnEntityLiving.class, "m");
private final Packet<?> rawPacket;
private EntityLivingSpawnNMSPacket(Packet<?> rawPacket, DataWatcher dataWatcher) {
this.rawPacket = rawPacket;
try {
DATA_WATCHER_FIELD.set(rawPacket, dataWatcher);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
public static DataWatcherPacketBuilder<EntityLivingSpawnNMSPacket> builder(
EntityID entityID, int entityTypeID, double positionX, double positionY, double positionZ) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(entityID.getNumericID());
packetByteBuffer.writeUUID(entityID.getUUID());
packetByteBuffer.writeByte(entityTypeID);
// Position
packetByteBuffer.writeDouble(positionX);
packetByteBuffer.writeDouble(positionY);
packetByteBuffer.writeDouble(positionZ);
// Rotation
packetByteBuffer.writeByte(0);
packetByteBuffer.writeByte(0);
// Head rotation
packetByteBuffer.writeByte(0);
// Velocity
packetByteBuffer.writeShort(0);
packetByteBuffer.writeShort(0);
packetByteBuffer.writeShort(0);
// Entries are deserialized and saved into a field which is not used during the re-serialization, set as empty
packetByteBuffer.writeDataWatcherEntriesEnd();
Packet<?> rawPacket = writeData(new PacketPlayOutSpawnEntityLiving(), packetByteBuffer);
return new Builder(rawPacket, PacketByteBuffer.get());
}
private static class Builder extends DataWatcherPacketBuilder<EntityLivingSpawnNMSPacket> {
private final Packet<?> rawPacket;
private Builder(Packet<?> rawPacket, PacketByteBuffer packetByteBuffer) {
super(packetByteBuffer);
this.rawPacket = rawPacket;
}
@Override
EntityLivingSpawnNMSPacket createPacket(PacketByteBuffer packetByteBuffer) {
return new EntityLivingSpawnNMSPacket(rawPacket, new SerializedDataWatcher(packetByteBuffer));
}
}
private static class SerializedDataWatcher extends DataWatcher {
private final byte[] serializedValue;
SerializedDataWatcher(PacketByteBuffer dataByteBuffer) {
super(null);
serializedValue = new byte[dataByteBuffer.readableBytes()];
dataByteBuffer.readBytes(serializedValue);
}
@Override
public void a(PacketDataSerializer packetDataSerializer) {
packetDataSerializer.writeBytes(serializedValue);
}
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_10_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_10_R1.Packet;
import net.minecraft.server.v1_10_R1.PacketPlayOutEntityMetadata;
class EntityMetadataNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
private EntityMetadataNMSPacket(PacketByteBuffer packetByteBuffer) {
this.rawPacket = writeData(new PacketPlayOutEntityMetadata(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
public static DataWatcherPacketBuilder<EntityMetadataNMSPacket> builder(EntityID entityID) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(entityID.getNumericID());
return new Builder(packetByteBuffer);
}
private static class Builder extends DataWatcherPacketBuilder<EntityMetadataNMSPacket> {
private Builder(PacketByteBuffer packetByteBuffer) {
super(packetByteBuffer);
}
@Override
EntityMetadataNMSPacket createPacket(PacketByteBuffer packetByteBuffer) {
return new EntityMetadataNMSPacket(packetByteBuffer);
}
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_10_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_10_R1.Packet;
import net.minecraft.server.v1_10_R1.PacketPlayOutMount;
class EntityMountNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntityMountNMSPacket(EntityID vehicleEntityID, EntityID passengerEntityID) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(vehicleEntityID.getNumericID());
packetByteBuffer.writeIntArray(passengerEntityID.getNumericID());
this.rawPacket = writeData(new PacketPlayOutMount(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_10_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_10_R1.Packet;
import net.minecraft.server.v1_10_R1.PacketPlayOutSpawnEntity;
class EntitySpawnNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntitySpawnNMSPacket(EntityID entityID, int entityTypeID, double positionX, double positionY, double positionZ) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(entityID.getNumericID());
packetByteBuffer.writeUUID(entityID.getUUID());
packetByteBuffer.writeByte(entityTypeID);
// Position
packetByteBuffer.writeDouble(positionX);
packetByteBuffer.writeDouble(positionY);
packetByteBuffer.writeDouble(positionZ);
// 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 = writeData(new PacketPlayOutSpawnEntity(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_10_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_10_R1.Packet;
import net.minecraft.server.v1_10_R1.PacketPlayOutEntityTeleport;
class EntityTeleportNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntityTeleportNMSPacket(EntityID entityID, double positionX, double positionY, double positionZ) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(entityID.getNumericID());
// Position
packetByteBuffer.writeDouble(positionX);
packetByteBuffer.writeDouble(positionY);
packetByteBuffer.writeDouble(positionZ);
// Rotation
packetByteBuffer.writeByte(0);
packetByteBuffer.writeByte(0);
// On ground
packetByteBuffer.writeBoolean(false);
this.rawPacket = writeData(new PacketPlayOutEntityTeleport(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,14 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_10_R1;
class EntityTypeID {
static final int ARMOR_STAND = 78;
static final int ITEM = 2;
static final int SLIME = 55;
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_10_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.common.nms.NMSErrors;
import me.filoghost.holographicdisplays.common.nms.PacketListener;
import net.minecraft.server.v1_10_R1.PacketPlayInUseEntity;
import org.bukkit.entity.Player;
public class InboundPacketHandler extends ChannelInboundHandlerAdapter {
public static final String HANDLER_NAME = "holographic_displays_listener";
private static final ReflectField<Integer> ENTITY_ID_FIELD = ReflectField.lookup(int.class, PacketPlayInUseEntity.class, "a");
private final Player player;
private final PacketListener packetListener;
public 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);
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_10_R1;
import io.netty.buffer.Unpooled;
import net.minecraft.server.v1_10_R1.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 writeUUID(UUID uuid) {
super.a(uuid);
}
void writeIntArray(int... array) {
super.a(array);
}
<T> void writeDataWatcherEntry(DataWatcherKey<T> key, T value) {
writeByte(key.getIndex());
writeVarInt(key.getSerializerTypeID());
key.getSerializer().a(this, value);
}
void writeDataWatcherEntriesEnd() {
writeByte(255);
}
}

View File

@ -5,32 +5,113 @@
*/
package me.filoghost.holographicdisplays.nms.v1_10_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.common.nms.EntityID;
import me.filoghost.holographicdisplays.common.nms.FallbackEntityIDGenerator;
import me.filoghost.holographicdisplays.common.nms.NMSErrors;
import me.filoghost.holographicdisplays.common.nms.NMSManager;
import me.filoghost.holographicdisplays.common.nms.NMSPacketList;
import me.filoghost.holographicdisplays.common.nms.PacketListener;
import net.minecraft.server.v1_10_R1.Entity;
import net.minecraft.server.v1_10_R1.NetworkManager;
import net.minecraft.server.v1_10_R1.PlayerConnection;
import org.bukkit.craftbukkit.v1_10_R1.entity.CraftPlayer;
import org.bukkit.entity.Player;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class VersionNMSManager implements NMSManager {
private static final ReflectField<Integer> ENTITY_ID_COUNTER_FIELD = ReflectField.lookup(int.class, Entity.class, "entityCount");
private final Supplier<Integer> fallbackEntityIDGenerator;
private final Supplier<Integer> entityIDGenerator;
public VersionNMSManager(ErrorCollector errorCollector) {
this.fallbackEntityIDGenerator = new FallbackEntityIDGenerator();
this.entityIDGenerator = getEntityIDGenerator(errorCollector);
// Force initialization of class to eventually throw exceptions early
DataWatcherKey.ENTITY_STATUS.getIndex();
}
private Supplier<Integer> getEntityIDGenerator(ErrorCollector errorCollector) {
try {
testStaticFieldReadWrite(ENTITY_ID_COUNTER_FIELD);
return () -> {
try {
int nmsEntityIDCounter = ENTITY_ID_COUNTER_FIELD.getStatic();
ENTITY_ID_COUNTER_FIELD.setStatic(nmsEntityIDCounter + 1);
return nmsEntityIDCounter;
} catch (ReflectiveOperationException e) {
// Should not happen, access is tested beforehand
return fallbackEntityIDGenerator.get();
}
};
} catch (ReflectiveOperationException e) {
errorCollector.add(e, NMSErrors.EXCEPTION_GETTING_ENTITY_ID_GENERATOR);
return fallbackEntityIDGenerator;
}
}
private <T> void testStaticFieldReadWrite(ReflectField<T> field) throws ReflectiveOperationException {
T value = field.getStatic();
field.setStatic(value);
}
@Override
public EntityID newEntityID() {
return null;
return new EntityID(entityIDGenerator);
}
@Override
public NMSPacketList createPacketList() {
return null;
return new VersionNMSPacketList();
}
@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<ChannelPipeline> pipelineModifierTask) {
PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection;
NetworkManager networkManager = playerConnection.a();
Channel channel = networkManager.channel;
channel.eventLoop().execute(() -> {
try {
pipelineModifierTask.accept(channel.pipeline());
} catch (Exception e) {
Log.warning(NMSErrors.EXCEPTION_MODIFYING_CHANNEL_PIPELINE, e);
}
});
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_10_R1;
import me.filoghost.holographicdisplays.common.nms.NMSPacket;
import net.minecraft.server.v1_10_R1.Packet;
import org.bukkit.craftbukkit.v1_10_R1.entity.CraftPlayer;
import org.bukkit.entity.Player;
import java.io.IOException;
abstract class VersionNMSPacket implements NMSPacket {
@Override
public void sendTo(Player player) {
((CraftPlayer) player).getHandle().playerConnection.sendPacket(getRawPacket());
}
abstract Packet<?> getRawPacket();
protected static <T extends Packet<?>> T writeData(T packet, PacketByteBuffer packetByteBuffer) {
try {
packet.a(packetByteBuffer);
return packet;
} catch (IOException e) {
// Never thrown by the implementations
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,102 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_10_R1;
import me.filoghost.holographicdisplays.common.nms.AbstractNMSPacketList;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import me.filoghost.holographicdisplays.common.nms.IndividualCustomName;
import me.filoghost.holographicdisplays.common.nms.IndividualNMSPacket;
import org.bukkit.inventory.ItemStack;
class VersionNMSPacketList extends AbstractNMSPacketList {
@Override
public void addArmorStandSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ) {
add(new EntitySpawnNMSPacket(entityID, EntityTypeID.ARMOR_STAND, positionX, positionY, positionZ));
add(EntityMetadataNMSPacket.builder(entityID)
.setArmorStandMarker()
.build()
);
}
@Override
public void addArmorStandSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ, String customName) {
add(new EntitySpawnNMSPacket(entityID, EntityTypeID.ARMOR_STAND, positionX, positionY, positionZ));
add(EntityMetadataNMSPacket.builder(entityID)
.setArmorStandMarker()
.setCustomName(customName)
.build()
);
}
@Override
public void addArmorStandSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ, IndividualCustomName individualCustomName) {
add(new EntitySpawnNMSPacket(entityID, EntityTypeID.ARMOR_STAND, positionX, positionY, positionZ));
add(new IndividualNMSPacket(player -> EntityMetadataNMSPacket.builder(entityID)
.setArmorStandMarker()
.setCustomName(individualCustomName.get(player))
.build()
));
}
@Override
public void addArmorStandNameChangePackets(EntityID entityID, String customName) {
add(EntityMetadataNMSPacket.builder(entityID)
.setCustomName(customName)
.build()
);
}
@Override
public void addArmorStandNameChangePackets(EntityID entityID, IndividualCustomName individualCustomName) {
add(new IndividualNMSPacket(player -> EntityMetadataNMSPacket.builder(entityID)
.setCustomName(individualCustomName.get(player))
.build()
));
}
@Override
public void addItemSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ, ItemStack itemStack) {
add(new EntitySpawnNMSPacket(entityID, EntityTypeID.ITEM, positionX, positionY, positionZ));
add(EntityMetadataNMSPacket.builder(entityID)
.setItemStack(itemStack)
.build()
);
}
@Override
public void addItemStackChangePackets(EntityID entityID, ItemStack itemStack) {
add(EntityMetadataNMSPacket.builder(entityID)
.setItemStack(itemStack)
.build()
);
}
@Override
public void addSlimeSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ) {
add(EntityLivingSpawnNMSPacket.builder(entityID, EntityTypeID.SLIME, positionX, positionY, positionZ)
.setInvisible()
.setSlimeSmall() // Required for a correct client-side collision box
.build()
);
}
@Override
public void addEntityDestroyPackets(EntityID... entityIDs) {
add(new EntityDestroyNMSPacket(entityIDs));
}
@Override
public void addTeleportPackets(EntityID entityID, double positionX, double positionY, double positionZ) {
add(new EntityTeleportNMSPacket(entityID, positionX, positionY, positionZ));
}
@Override
public void addMountPackets(EntityID vehicleEntityID, EntityID passengerEntityID) {
add(new EntityMountNMSPacket(vehicleEntityID, passengerEntityID));
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_11_R1;
import io.netty.handler.codec.EncoderException;
import net.minecraft.server.v1_11_R1.DataWatcherRegistry;
import net.minecraft.server.v1_11_R1.DataWatcherSerializer;
import net.minecraft.server.v1_11_R1.ItemStack;
class DataWatcherKey<T> {
private static final DataWatcherSerializer<Byte> BYTE_SERIALIZER = DataWatcherRegistry.a;
private static final DataWatcherSerializer<Integer> INT_SERIALIZER = DataWatcherRegistry.b;
private static final DataWatcherSerializer<Boolean> BOOLEAN_SERIALIZER = DataWatcherRegistry.h;
private static final DataWatcherSerializer<ItemStack> ITEM_STACK_SERIALIZER = DataWatcherRegistry.f;
private static final DataWatcherSerializer<String> STRING_SERIALIZER = DataWatcherRegistry.d;
static final DataWatcherKey<Byte> ENTITY_STATUS = new DataWatcherKey<>(0, BYTE_SERIALIZER);
static final DataWatcherKey<String> CUSTOM_NAME = new DataWatcherKey<>(2, STRING_SERIALIZER);
static final DataWatcherKey<Boolean> CUSTOM_NAME_VISIBILITY = new DataWatcherKey<>(3, BOOLEAN_SERIALIZER);
static final DataWatcherKey<ItemStack> ITEM_STACK = new DataWatcherKey<>(6, ITEM_STACK_SERIALIZER);
static final DataWatcherKey<Byte> ARMOR_STAND_STATUS = new DataWatcherKey<>(11, BYTE_SERIALIZER);
static final DataWatcherKey<Integer> SLIME_SIZE = new DataWatcherKey<>(12, INT_SERIALIZER);
private final int index;
private final DataWatcherSerializer<T> serializer;
private final int serializerTypeID;
private DataWatcherKey(int index, DataWatcherSerializer<T> 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<T> getSerializer() {
return serializer;
}
int getSerializerTypeID() {
return serializerTypeID;
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_11_R1;
import me.filoghost.fcommons.Strings;
import org.bukkit.craftbukkit.v1_11_R1.inventory.CraftItemStack;
import org.bukkit.inventory.ItemStack;
abstract class DataWatcherPacketBuilder<T> {
private final PacketByteBuffer packetByteBuffer;
DataWatcherPacketBuilder(PacketByteBuffer packetByteBuffer) {
this.packetByteBuffer = packetByteBuffer;
}
DataWatcherPacketBuilder<T> setInvisible() {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.ENTITY_STATUS, (byte) 0x20); // Invisible
return this;
}
DataWatcherPacketBuilder<T> setArmorStandMarker() {
setInvisible();
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.ARMOR_STAND_STATUS, (byte) (0x01 | 0x08 | 0x10)); // Small, no base plate, marker
return this;
}
DataWatcherPacketBuilder<T> setCustomName(String customName) {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.CUSTOM_NAME, Strings.truncate(customName, 300));
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.CUSTOM_NAME_VISIBILITY, !Strings.isEmpty(customName));
return this;
}
DataWatcherPacketBuilder<T> setItemStack(ItemStack itemStack) {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.ITEM_STACK, CraftItemStack.asNMSCopy(itemStack));
return this;
}
DataWatcherPacketBuilder<T> setSlimeSmall() {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.SLIME_SIZE, 1);
return this;
}
T build() {
packetByteBuffer.writeDataWatcherEntriesEnd();
return createPacket(packetByteBuffer);
}
abstract T createPacket(PacketByteBuffer packetByteBuffer);
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_11_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_11_R1.Packet;
import net.minecraft.server.v1_11_R1.PacketPlayOutEntityDestroy;
class EntityDestroyNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntityDestroyNMSPacket(EntityID... entityIDs) {
int[] entityIDsArray = new int[entityIDs.length];
for (int i = 0; i < entityIDs.length; i++) {
entityIDsArray[i] = entityIDs[i].getNumericID();
}
this.rawPacket = new PacketPlayOutEntityDestroy(entityIDsArray);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_11_R1;
import me.filoghost.fcommons.reflection.ReflectField;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_11_R1.DataWatcher;
import net.minecraft.server.v1_11_R1.Packet;
import net.minecraft.server.v1_11_R1.PacketDataSerializer;
import net.minecraft.server.v1_11_R1.PacketPlayOutSpawnEntityLiving;
class EntityLivingSpawnNMSPacket extends VersionNMSPacket {
private static final ReflectField<DataWatcher> DATA_WATCHER_FIELD =
ReflectField.lookup(DataWatcher.class, PacketPlayOutSpawnEntityLiving.class, "m");
private final Packet<?> rawPacket;
private EntityLivingSpawnNMSPacket(Packet<?> rawPacket, DataWatcher dataWatcher) {
this.rawPacket = rawPacket;
try {
DATA_WATCHER_FIELD.set(rawPacket, dataWatcher);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
public static DataWatcherPacketBuilder<EntityLivingSpawnNMSPacket> builder(
EntityID entityID, int entityTypeID, double positionX, double positionY, double positionZ) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(entityID.getNumericID());
packetByteBuffer.writeUUID(entityID.getUUID());
packetByteBuffer.writeVarInt(entityTypeID);
// Position
packetByteBuffer.writeDouble(positionX);
packetByteBuffer.writeDouble(positionY);
packetByteBuffer.writeDouble(positionZ);
// Rotation
packetByteBuffer.writeByte(0);
packetByteBuffer.writeByte(0);
// Head rotation
packetByteBuffer.writeByte(0);
// Velocity
packetByteBuffer.writeShort(0);
packetByteBuffer.writeShort(0);
packetByteBuffer.writeShort(0);
// Entries are deserialized and saved into a field which is not used during the re-serialization, set as empty
packetByteBuffer.writeDataWatcherEntriesEnd();
Packet<?> rawPacket = writeData(new PacketPlayOutSpawnEntityLiving(), packetByteBuffer);
return new Builder(rawPacket, PacketByteBuffer.get());
}
private static class Builder extends DataWatcherPacketBuilder<EntityLivingSpawnNMSPacket> {
private final Packet<?> rawPacket;
private Builder(Packet<?> rawPacket, PacketByteBuffer packetByteBuffer) {
super(packetByteBuffer);
this.rawPacket = rawPacket;
}
@Override
EntityLivingSpawnNMSPacket createPacket(PacketByteBuffer packetByteBuffer) {
return new EntityLivingSpawnNMSPacket(rawPacket, new SerializedDataWatcher(packetByteBuffer));
}
}
private static class SerializedDataWatcher extends DataWatcher {
private final byte[] serializedValue;
SerializedDataWatcher(PacketByteBuffer dataByteBuffer) {
super(null);
serializedValue = new byte[dataByteBuffer.readableBytes()];
dataByteBuffer.readBytes(serializedValue);
}
@Override
public void a(PacketDataSerializer packetDataSerializer) {
packetDataSerializer.writeBytes(serializedValue);
}
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_11_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_11_R1.Packet;
import net.minecraft.server.v1_11_R1.PacketPlayOutEntityMetadata;
class EntityMetadataNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
private EntityMetadataNMSPacket(PacketByteBuffer packetByteBuffer) {
this.rawPacket = writeData(new PacketPlayOutEntityMetadata(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
public static DataWatcherPacketBuilder<EntityMetadataNMSPacket> builder(EntityID entityID) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(entityID.getNumericID());
return new Builder(packetByteBuffer);
}
private static class Builder extends DataWatcherPacketBuilder<EntityMetadataNMSPacket> {
private Builder(PacketByteBuffer packetByteBuffer) {
super(packetByteBuffer);
}
@Override
EntityMetadataNMSPacket createPacket(PacketByteBuffer packetByteBuffer) {
return new EntityMetadataNMSPacket(packetByteBuffer);
}
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_11_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_11_R1.Packet;
import net.minecraft.server.v1_11_R1.PacketPlayOutMount;
class EntityMountNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntityMountNMSPacket(EntityID vehicleEntityID, EntityID passengerEntityID) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(vehicleEntityID.getNumericID());
packetByteBuffer.writeIntArray(passengerEntityID.getNumericID());
this.rawPacket = writeData(new PacketPlayOutMount(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_11_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_11_R1.Packet;
import net.minecraft.server.v1_11_R1.PacketPlayOutSpawnEntity;
class EntitySpawnNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntitySpawnNMSPacket(EntityID entityID, int entityTypeID, double positionX, double positionY, double positionZ) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(entityID.getNumericID());
packetByteBuffer.writeUUID(entityID.getUUID());
packetByteBuffer.writeByte(entityTypeID);
// Position
packetByteBuffer.writeDouble(positionX);
packetByteBuffer.writeDouble(positionY);
packetByteBuffer.writeDouble(positionZ);
// 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 = writeData(new PacketPlayOutSpawnEntity(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_11_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_11_R1.Packet;
import net.minecraft.server.v1_11_R1.PacketPlayOutEntityTeleport;
class EntityTeleportNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntityTeleportNMSPacket(EntityID entityID, double positionX, double positionY, double positionZ) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(entityID.getNumericID());
// Position
packetByteBuffer.writeDouble(positionX);
packetByteBuffer.writeDouble(positionY);
packetByteBuffer.writeDouble(positionZ);
// Rotation
packetByteBuffer.writeByte(0);
packetByteBuffer.writeByte(0);
// On ground
packetByteBuffer.writeBoolean(false);
this.rawPacket = writeData(new PacketPlayOutEntityTeleport(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,14 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_11_R1;
class EntityTypeID {
static final int ARMOR_STAND = 78;
static final int ITEM = 2;
static final int SLIME = 55;
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_11_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.common.nms.NMSErrors;
import me.filoghost.holographicdisplays.common.nms.PacketListener;
import net.minecraft.server.v1_11_R1.PacketPlayInUseEntity;
import org.bukkit.entity.Player;
public class InboundPacketHandler extends ChannelInboundHandlerAdapter {
public static final String HANDLER_NAME = "holographic_displays_listener";
private static final ReflectField<Integer> ENTITY_ID_FIELD = ReflectField.lookup(int.class, PacketPlayInUseEntity.class, "a");
private final Player player;
private final PacketListener packetListener;
public 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);
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_11_R1;
import io.netty.buffer.Unpooled;
import net.minecraft.server.v1_11_R1.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 writeUUID(UUID uuid) {
super.a(uuid);
}
void writeIntArray(int... array) {
super.a(array);
}
<T> void writeDataWatcherEntry(DataWatcherKey<T> key, T value) {
writeByte(key.getIndex());
writeVarInt(key.getSerializerTypeID());
key.getSerializer().a(this, value);
}
void writeDataWatcherEntriesEnd() {
writeByte(255);
}
}

View File

@ -5,32 +5,113 @@
*/
package me.filoghost.holographicdisplays.nms.v1_11_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.common.nms.EntityID;
import me.filoghost.holographicdisplays.common.nms.FallbackEntityIDGenerator;
import me.filoghost.holographicdisplays.common.nms.NMSErrors;
import me.filoghost.holographicdisplays.common.nms.NMSManager;
import me.filoghost.holographicdisplays.common.nms.NMSPacketList;
import me.filoghost.holographicdisplays.common.nms.PacketListener;
import net.minecraft.server.v1_11_R1.Entity;
import net.minecraft.server.v1_11_R1.NetworkManager;
import net.minecraft.server.v1_11_R1.PlayerConnection;
import org.bukkit.craftbukkit.v1_11_R1.entity.CraftPlayer;
import org.bukkit.entity.Player;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class VersionNMSManager implements NMSManager {
private static final ReflectField<Integer> ENTITY_ID_COUNTER_FIELD = ReflectField.lookup(int.class, Entity.class, "entityCount");
private final Supplier<Integer> fallbackEntityIDGenerator;
private final Supplier<Integer> entityIDGenerator;
public VersionNMSManager(ErrorCollector errorCollector) {
this.fallbackEntityIDGenerator = new FallbackEntityIDGenerator();
this.entityIDGenerator = getEntityIDGenerator(errorCollector);
// Force initialization of class to eventually throw exceptions early
DataWatcherKey.ENTITY_STATUS.getIndex();
}
private Supplier<Integer> getEntityIDGenerator(ErrorCollector errorCollector) {
try {
testStaticFieldReadWrite(ENTITY_ID_COUNTER_FIELD);
return () -> {
try {
int nmsEntityIDCounter = ENTITY_ID_COUNTER_FIELD.getStatic();
ENTITY_ID_COUNTER_FIELD.setStatic(nmsEntityIDCounter + 1);
return nmsEntityIDCounter;
} catch (ReflectiveOperationException e) {
// Should not happen, access is tested beforehand
return fallbackEntityIDGenerator.get();
}
};
} catch (ReflectiveOperationException e) {
errorCollector.add(e, NMSErrors.EXCEPTION_GETTING_ENTITY_ID_GENERATOR);
return fallbackEntityIDGenerator;
}
}
private <T> void testStaticFieldReadWrite(ReflectField<T> field) throws ReflectiveOperationException {
T value = field.getStatic();
field.setStatic(value);
}
@Override
public EntityID newEntityID() {
return null;
return new EntityID(entityIDGenerator);
}
@Override
public NMSPacketList createPacketList() {
return null;
return new VersionNMSPacketList();
}
@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<ChannelPipeline> pipelineModifierTask) {
PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection;
NetworkManager networkManager = playerConnection.a();
Channel channel = networkManager.channel;
channel.eventLoop().execute(() -> {
try {
pipelineModifierTask.accept(channel.pipeline());
} catch (Exception e) {
Log.warning(NMSErrors.EXCEPTION_MODIFYING_CHANNEL_PIPELINE, e);
}
});
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_11_R1;
import me.filoghost.holographicdisplays.common.nms.NMSPacket;
import net.minecraft.server.v1_11_R1.Packet;
import org.bukkit.craftbukkit.v1_11_R1.entity.CraftPlayer;
import org.bukkit.entity.Player;
import java.io.IOException;
abstract class VersionNMSPacket implements NMSPacket {
@Override
public void sendTo(Player player) {
((CraftPlayer) player).getHandle().playerConnection.sendPacket(getRawPacket());
}
abstract Packet<?> getRawPacket();
protected static <T extends Packet<?>> T writeData(T packet, PacketByteBuffer packetByteBuffer) {
try {
packet.a(packetByteBuffer);
return packet;
} catch (IOException e) {
// Never thrown by the implementations
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,102 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_11_R1;
import me.filoghost.holographicdisplays.common.nms.AbstractNMSPacketList;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import me.filoghost.holographicdisplays.common.nms.IndividualCustomName;
import me.filoghost.holographicdisplays.common.nms.IndividualNMSPacket;
import org.bukkit.inventory.ItemStack;
class VersionNMSPacketList extends AbstractNMSPacketList {
@Override
public void addArmorStandSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ) {
add(new EntitySpawnNMSPacket(entityID, EntityTypeID.ARMOR_STAND, positionX, positionY, positionZ));
add(EntityMetadataNMSPacket.builder(entityID)
.setArmorStandMarker()
.build()
);
}
@Override
public void addArmorStandSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ, String customName) {
add(new EntitySpawnNMSPacket(entityID, EntityTypeID.ARMOR_STAND, positionX, positionY, positionZ));
add(EntityMetadataNMSPacket.builder(entityID)
.setArmorStandMarker()
.setCustomName(customName)
.build()
);
}
@Override
public void addArmorStandSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ, IndividualCustomName individualCustomName) {
add(new EntitySpawnNMSPacket(entityID, EntityTypeID.ARMOR_STAND, positionX, positionY, positionZ));
add(new IndividualNMSPacket(player -> EntityMetadataNMSPacket.builder(entityID)
.setArmorStandMarker()
.setCustomName(individualCustomName.get(player))
.build()
));
}
@Override
public void addArmorStandNameChangePackets(EntityID entityID, String customName) {
add(EntityMetadataNMSPacket.builder(entityID)
.setCustomName(customName)
.build()
);
}
@Override
public void addArmorStandNameChangePackets(EntityID entityID, IndividualCustomName individualCustomName) {
add(new IndividualNMSPacket(player -> EntityMetadataNMSPacket.builder(entityID)
.setCustomName(individualCustomName.get(player))
.build()
));
}
@Override
public void addItemSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ, ItemStack itemStack) {
add(new EntitySpawnNMSPacket(entityID, EntityTypeID.ITEM, positionX, positionY, positionZ));
add(EntityMetadataNMSPacket.builder(entityID)
.setItemStack(itemStack)
.build()
);
}
@Override
public void addItemStackChangePackets(EntityID entityID, ItemStack itemStack) {
add(EntityMetadataNMSPacket.builder(entityID)
.setItemStack(itemStack)
.build()
);
}
@Override
public void addSlimeSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ) {
add(EntityLivingSpawnNMSPacket.builder(entityID, EntityTypeID.SLIME, positionX, positionY, positionZ)
.setInvisible()
.setSlimeSmall() // Required for a correct client-side collision box
.build()
);
}
@Override
public void addEntityDestroyPackets(EntityID... entityIDs) {
add(new EntityDestroyNMSPacket(entityIDs));
}
@Override
public void addTeleportPackets(EntityID entityID, double positionX, double positionY, double positionZ) {
add(new EntityTeleportNMSPacket(entityID, positionX, positionY, positionZ));
}
@Override
public void addMountPackets(EntityID vehicleEntityID, EntityID passengerEntityID) {
add(new EntityMountNMSPacket(vehicleEntityID, passengerEntityID));
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_12_R1;
import io.netty.handler.codec.EncoderException;
import net.minecraft.server.v1_12_R1.DataWatcherRegistry;
import net.minecraft.server.v1_12_R1.DataWatcherSerializer;
import net.minecraft.server.v1_12_R1.ItemStack;
class DataWatcherKey<T> {
private static final DataWatcherSerializer<Byte> BYTE_SERIALIZER = DataWatcherRegistry.a;
private static final DataWatcherSerializer<Integer> INT_SERIALIZER = DataWatcherRegistry.b;
private static final DataWatcherSerializer<Boolean> BOOLEAN_SERIALIZER = DataWatcherRegistry.h;
private static final DataWatcherSerializer<ItemStack> ITEM_STACK_SERIALIZER = DataWatcherRegistry.f;
private static final DataWatcherSerializer<String> STRING_SERIALIZER = DataWatcherRegistry.d;
static final DataWatcherKey<Byte> ENTITY_STATUS = new DataWatcherKey<>(0, BYTE_SERIALIZER);
static final DataWatcherKey<String> CUSTOM_NAME = new DataWatcherKey<>(2, STRING_SERIALIZER);
static final DataWatcherKey<Boolean> CUSTOM_NAME_VISIBILITY = new DataWatcherKey<>(3, BOOLEAN_SERIALIZER);
static final DataWatcherKey<ItemStack> ITEM_STACK = new DataWatcherKey<>(6, ITEM_STACK_SERIALIZER);
static final DataWatcherKey<Byte> ARMOR_STAND_STATUS = new DataWatcherKey<>(11, BYTE_SERIALIZER);
static final DataWatcherKey<Integer> SLIME_SIZE = new DataWatcherKey<>(12, INT_SERIALIZER);
private final int index;
private final DataWatcherSerializer<T> serializer;
private final int serializerTypeID;
private DataWatcherKey(int index, DataWatcherSerializer<T> 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<T> getSerializer() {
return serializer;
}
int getSerializerTypeID() {
return serializerTypeID;
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_12_R1;
import me.filoghost.fcommons.Strings;
import org.bukkit.craftbukkit.v1_12_R1.inventory.CraftItemStack;
import org.bukkit.inventory.ItemStack;
abstract class DataWatcherPacketBuilder<T> {
private final PacketByteBuffer packetByteBuffer;
DataWatcherPacketBuilder(PacketByteBuffer packetByteBuffer) {
this.packetByteBuffer = packetByteBuffer;
}
DataWatcherPacketBuilder<T> setInvisible() {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.ENTITY_STATUS, (byte) 0x20); // Invisible
return this;
}
DataWatcherPacketBuilder<T> setArmorStandMarker() {
setInvisible();
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.ARMOR_STAND_STATUS, (byte) (0x01 | 0x08 | 0x10)); // Small, no base plate, marker
return this;
}
DataWatcherPacketBuilder<T> setCustomName(String customName) {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.CUSTOM_NAME, Strings.truncate(customName, 300));
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.CUSTOM_NAME_VISIBILITY, !Strings.isEmpty(customName));
return this;
}
DataWatcherPacketBuilder<T> setItemStack(ItemStack itemStack) {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.ITEM_STACK, CraftItemStack.asNMSCopy(itemStack));
return this;
}
DataWatcherPacketBuilder<T> setSlimeSmall() {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.SLIME_SIZE, 1);
return this;
}
T build() {
packetByteBuffer.writeDataWatcherEntriesEnd();
return createPacket(packetByteBuffer);
}
abstract T createPacket(PacketByteBuffer packetByteBuffer);
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_12_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_12_R1.Packet;
import net.minecraft.server.v1_12_R1.PacketPlayOutEntityDestroy;
class EntityDestroyNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntityDestroyNMSPacket(EntityID... entityIDs) {
int[] entityIDsArray = new int[entityIDs.length];
for (int i = 0; i < entityIDs.length; i++) {
entityIDsArray[i] = entityIDs[i].getNumericID();
}
this.rawPacket = new PacketPlayOutEntityDestroy(entityIDsArray);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_12_R1;
import me.filoghost.fcommons.reflection.ReflectField;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_12_R1.DataWatcher;
import net.minecraft.server.v1_12_R1.Packet;
import net.minecraft.server.v1_12_R1.PacketDataSerializer;
import net.minecraft.server.v1_12_R1.PacketPlayOutSpawnEntityLiving;
class EntityLivingSpawnNMSPacket extends VersionNMSPacket {
private static final ReflectField<DataWatcher> DATA_WATCHER_FIELD =
ReflectField.lookup(DataWatcher.class, PacketPlayOutSpawnEntityLiving.class, "m");
private final Packet<?> rawPacket;
private EntityLivingSpawnNMSPacket(Packet<?> rawPacket, DataWatcher dataWatcher) {
this.rawPacket = rawPacket;
try {
DATA_WATCHER_FIELD.set(rawPacket, dataWatcher);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
public static DataWatcherPacketBuilder<EntityLivingSpawnNMSPacket> builder(
EntityID entityID, int entityTypeID, double positionX, double positionY, double positionZ) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(entityID.getNumericID());
packetByteBuffer.writeUUID(entityID.getUUID());
packetByteBuffer.writeVarInt(entityTypeID);
// Position
packetByteBuffer.writeDouble(positionX);
packetByteBuffer.writeDouble(positionY);
packetByteBuffer.writeDouble(positionZ);
// Rotation
packetByteBuffer.writeByte(0);
packetByteBuffer.writeByte(0);
// Head rotation
packetByteBuffer.writeByte(0);
// Velocity
packetByteBuffer.writeShort(0);
packetByteBuffer.writeShort(0);
packetByteBuffer.writeShort(0);
// Entries are deserialized and saved into a field which is not used during the re-serialization, set as empty
packetByteBuffer.writeDataWatcherEntriesEnd();
Packet<?> rawPacket = writeData(new PacketPlayOutSpawnEntityLiving(), packetByteBuffer);
return new Builder(rawPacket, PacketByteBuffer.get());
}
private static class Builder extends DataWatcherPacketBuilder<EntityLivingSpawnNMSPacket> {
private final Packet<?> rawPacket;
private Builder(Packet<?> rawPacket, PacketByteBuffer packetByteBuffer) {
super(packetByteBuffer);
this.rawPacket = rawPacket;
}
@Override
EntityLivingSpawnNMSPacket createPacket(PacketByteBuffer packetByteBuffer) {
return new EntityLivingSpawnNMSPacket(rawPacket, new SerializedDataWatcher(packetByteBuffer));
}
}
private static class SerializedDataWatcher extends DataWatcher {
private final byte[] serializedValue;
SerializedDataWatcher(PacketByteBuffer dataByteBuffer) {
super(null);
serializedValue = new byte[dataByteBuffer.readableBytes()];
dataByteBuffer.readBytes(serializedValue);
}
@Override
public void a(PacketDataSerializer packetDataSerializer) {
packetDataSerializer.writeBytes(serializedValue);
}
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_12_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_12_R1.Packet;
import net.minecraft.server.v1_12_R1.PacketPlayOutEntityMetadata;
class EntityMetadataNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
private EntityMetadataNMSPacket(PacketByteBuffer packetByteBuffer) {
this.rawPacket = writeData(new PacketPlayOutEntityMetadata(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
public static DataWatcherPacketBuilder<EntityMetadataNMSPacket> builder(EntityID entityID) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(entityID.getNumericID());
return new Builder(packetByteBuffer);
}
private static class Builder extends DataWatcherPacketBuilder<EntityMetadataNMSPacket> {
private Builder(PacketByteBuffer packetByteBuffer) {
super(packetByteBuffer);
}
@Override
EntityMetadataNMSPacket createPacket(PacketByteBuffer packetByteBuffer) {
return new EntityMetadataNMSPacket(packetByteBuffer);
}
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_12_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_12_R1.Packet;
import net.minecraft.server.v1_12_R1.PacketPlayOutMount;
class EntityMountNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntityMountNMSPacket(EntityID vehicleEntityID, EntityID passengerEntityID) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(vehicleEntityID.getNumericID());
packetByteBuffer.writeIntArray(passengerEntityID.getNumericID());
this.rawPacket = writeData(new PacketPlayOutMount(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_12_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_12_R1.Packet;
import net.minecraft.server.v1_12_R1.PacketPlayOutSpawnEntity;
class EntitySpawnNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntitySpawnNMSPacket(EntityID entityID, int entityTypeID, double positionX, double positionY, double positionZ) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(entityID.getNumericID());
packetByteBuffer.writeUUID(entityID.getUUID());
packetByteBuffer.writeByte(entityTypeID);
// Position
packetByteBuffer.writeDouble(positionX);
packetByteBuffer.writeDouble(positionY);
packetByteBuffer.writeDouble(positionZ);
// 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 = writeData(new PacketPlayOutSpawnEntity(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_12_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_12_R1.Packet;
import net.minecraft.server.v1_12_R1.PacketPlayOutEntityTeleport;
class EntityTeleportNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntityTeleportNMSPacket(EntityID entityID, double positionX, double positionY, double positionZ) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(entityID.getNumericID());
// Position
packetByteBuffer.writeDouble(positionX);
packetByteBuffer.writeDouble(positionY);
packetByteBuffer.writeDouble(positionZ);
// Rotation
packetByteBuffer.writeByte(0);
packetByteBuffer.writeByte(0);
// On ground
packetByteBuffer.writeBoolean(false);
this.rawPacket = writeData(new PacketPlayOutEntityTeleport(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,14 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_12_R1;
class EntityTypeID {
static final int ARMOR_STAND = 78;
static final int ITEM = 2;
static final int SLIME = 55;
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_12_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.common.nms.NMSErrors;
import me.filoghost.holographicdisplays.common.nms.PacketListener;
import net.minecraft.server.v1_12_R1.PacketPlayInUseEntity;
import org.bukkit.entity.Player;
public class InboundPacketHandler extends ChannelInboundHandlerAdapter {
public static final String HANDLER_NAME = "holographic_displays_listener";
private static final ReflectField<Integer> ENTITY_ID_FIELD = ReflectField.lookup(int.class, PacketPlayInUseEntity.class, "a");
private final Player player;
private final PacketListener packetListener;
public 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);
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_12_R1;
import io.netty.buffer.Unpooled;
import net.minecraft.server.v1_12_R1.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 writeUUID(UUID uuid) {
super.a(uuid);
}
void writeIntArray(int... array) {
super.a(array);
}
<T> void writeDataWatcherEntry(DataWatcherKey<T> key, T value) {
writeByte(key.getIndex());
writeVarInt(key.getSerializerTypeID());
key.getSerializer().a(this, value);
}
void writeDataWatcherEntriesEnd() {
writeByte(255);
}
}

View File

@ -5,32 +5,113 @@
*/
package me.filoghost.holographicdisplays.nms.v1_12_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.common.nms.EntityID;
import me.filoghost.holographicdisplays.common.nms.FallbackEntityIDGenerator;
import me.filoghost.holographicdisplays.common.nms.NMSErrors;
import me.filoghost.holographicdisplays.common.nms.NMSManager;
import me.filoghost.holographicdisplays.common.nms.NMSPacketList;
import me.filoghost.holographicdisplays.common.nms.PacketListener;
import net.minecraft.server.v1_12_R1.Entity;
import net.minecraft.server.v1_12_R1.NetworkManager;
import net.minecraft.server.v1_12_R1.PlayerConnection;
import org.bukkit.craftbukkit.v1_12_R1.entity.CraftPlayer;
import org.bukkit.entity.Player;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class VersionNMSManager implements NMSManager {
private static final ReflectField<Integer> ENTITY_ID_COUNTER_FIELD = ReflectField.lookup(int.class, Entity.class, "entityCount");
private final Supplier<Integer> fallbackEntityIDGenerator;
private final Supplier<Integer> entityIDGenerator;
public VersionNMSManager(ErrorCollector errorCollector) {
this.fallbackEntityIDGenerator = new FallbackEntityIDGenerator();
this.entityIDGenerator = getEntityIDGenerator(errorCollector);
// Force initialization of class to eventually throw exceptions early
DataWatcherKey.ENTITY_STATUS.getIndex();
}
private Supplier<Integer> getEntityIDGenerator(ErrorCollector errorCollector) {
try {
testStaticFieldReadWrite(ENTITY_ID_COUNTER_FIELD);
return () -> {
try {
int nmsEntityIDCounter = ENTITY_ID_COUNTER_FIELD.getStatic();
ENTITY_ID_COUNTER_FIELD.setStatic(nmsEntityIDCounter + 1);
return nmsEntityIDCounter;
} catch (ReflectiveOperationException e) {
// Should not happen, access is tested beforehand
return fallbackEntityIDGenerator.get();
}
};
} catch (ReflectiveOperationException e) {
errorCollector.add(e, NMSErrors.EXCEPTION_GETTING_ENTITY_ID_GENERATOR);
return fallbackEntityIDGenerator;
}
}
private <T> void testStaticFieldReadWrite(ReflectField<T> field) throws ReflectiveOperationException {
T value = field.getStatic();
field.setStatic(value);
}
@Override
public EntityID newEntityID() {
return null;
return new EntityID(entityIDGenerator);
}
@Override
public NMSPacketList createPacketList() {
return null;
return new VersionNMSPacketList();
}
@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<ChannelPipeline> pipelineModifierTask) {
PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection;
NetworkManager networkManager = playerConnection.a();
Channel channel = networkManager.channel;
channel.eventLoop().execute(() -> {
try {
pipelineModifierTask.accept(channel.pipeline());
} catch (Exception e) {
Log.warning(NMSErrors.EXCEPTION_MODIFYING_CHANNEL_PIPELINE, e);
}
});
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_12_R1;
import me.filoghost.holographicdisplays.common.nms.NMSPacket;
import net.minecraft.server.v1_12_R1.Packet;
import org.bukkit.craftbukkit.v1_12_R1.entity.CraftPlayer;
import org.bukkit.entity.Player;
import java.io.IOException;
abstract class VersionNMSPacket implements NMSPacket {
@Override
public void sendTo(Player player) {
((CraftPlayer) player).getHandle().playerConnection.sendPacket(getRawPacket());
}
abstract Packet<?> getRawPacket();
protected static <T extends Packet<?>> T writeData(T packet, PacketByteBuffer packetByteBuffer) {
try {
packet.a(packetByteBuffer);
return packet;
} catch (IOException e) {
// Never thrown by the implementations
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,102 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_12_R1;
import me.filoghost.holographicdisplays.common.nms.AbstractNMSPacketList;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import me.filoghost.holographicdisplays.common.nms.IndividualCustomName;
import me.filoghost.holographicdisplays.common.nms.IndividualNMSPacket;
import org.bukkit.inventory.ItemStack;
class VersionNMSPacketList extends AbstractNMSPacketList {
@Override
public void addArmorStandSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ) {
add(new EntitySpawnNMSPacket(entityID, EntityTypeID.ARMOR_STAND, positionX, positionY, positionZ));
add(EntityMetadataNMSPacket.builder(entityID)
.setArmorStandMarker()
.build()
);
}
@Override
public void addArmorStandSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ, String customName) {
add(new EntitySpawnNMSPacket(entityID, EntityTypeID.ARMOR_STAND, positionX, positionY, positionZ));
add(EntityMetadataNMSPacket.builder(entityID)
.setArmorStandMarker()
.setCustomName(customName)
.build()
);
}
@Override
public void addArmorStandSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ, IndividualCustomName individualCustomName) {
add(new EntitySpawnNMSPacket(entityID, EntityTypeID.ARMOR_STAND, positionX, positionY, positionZ));
add(new IndividualNMSPacket(player -> EntityMetadataNMSPacket.builder(entityID)
.setArmorStandMarker()
.setCustomName(individualCustomName.get(player))
.build()
));
}
@Override
public void addArmorStandNameChangePackets(EntityID entityID, String customName) {
add(EntityMetadataNMSPacket.builder(entityID)
.setCustomName(customName)
.build()
);
}
@Override
public void addArmorStandNameChangePackets(EntityID entityID, IndividualCustomName individualCustomName) {
add(new IndividualNMSPacket(player -> EntityMetadataNMSPacket.builder(entityID)
.setCustomName(individualCustomName.get(player))
.build()
));
}
@Override
public void addItemSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ, ItemStack itemStack) {
add(new EntitySpawnNMSPacket(entityID, EntityTypeID.ITEM, positionX, positionY, positionZ));
add(EntityMetadataNMSPacket.builder(entityID)
.setItemStack(itemStack)
.build()
);
}
@Override
public void addItemStackChangePackets(EntityID entityID, ItemStack itemStack) {
add(EntityMetadataNMSPacket.builder(entityID)
.setItemStack(itemStack)
.build()
);
}
@Override
public void addSlimeSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ) {
add(EntityLivingSpawnNMSPacket.builder(entityID, EntityTypeID.SLIME, positionX, positionY, positionZ)
.setInvisible()
.setSlimeSmall() // Required for a correct client-side collision box
.build()
);
}
@Override
public void addEntityDestroyPackets(EntityID... entityIDs) {
add(new EntityDestroyNMSPacket(entityIDs));
}
@Override
public void addTeleportPackets(EntityID entityID, double positionX, double positionY, double positionZ) {
add(new EntityTeleportNMSPacket(entityID, positionX, positionY, positionZ));
}
@Override
public void addMountPackets(EntityID vehicleEntityID, EntityID passengerEntityID) {
add(new EntityMountNMSPacket(vehicleEntityID, passengerEntityID));
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_13_R2;
import io.netty.handler.codec.EncoderException;
import net.minecraft.server.v1_13_R2.DataWatcherRegistry;
import net.minecraft.server.v1_13_R2.DataWatcherSerializer;
import net.minecraft.server.v1_13_R2.IChatBaseComponent;
import net.minecraft.server.v1_13_R2.ItemStack;
import java.util.Optional;
class DataWatcherKey<T> {
private static final DataWatcherSerializer<Byte> BYTE_SERIALIZER = DataWatcherRegistry.a;
private static final DataWatcherSerializer<Integer> INT_SERIALIZER = DataWatcherRegistry.b;
private static final DataWatcherSerializer<Boolean> BOOLEAN_SERIALIZER = DataWatcherRegistry.i;
private static final DataWatcherSerializer<ItemStack> ITEM_STACK_SERIALIZER = DataWatcherRegistry.g;
private static final DataWatcherSerializer<Optional<IChatBaseComponent>> OPTIONAL_CHAT_COMPONENT_SERIALIZER = DataWatcherRegistry.f;
static final DataWatcherKey<Byte> ENTITY_STATUS = new DataWatcherKey<>(0, BYTE_SERIALIZER);
static final DataWatcherKey<Optional<IChatBaseComponent>> CUSTOM_NAME = new DataWatcherKey<>(2, OPTIONAL_CHAT_COMPONENT_SERIALIZER);
static final DataWatcherKey<Boolean> CUSTOM_NAME_VISIBILITY = new DataWatcherKey<>(3, BOOLEAN_SERIALIZER);
static final DataWatcherKey<ItemStack> ITEM_STACK = new DataWatcherKey<>(6, ITEM_STACK_SERIALIZER);
static final DataWatcherKey<Byte> ARMOR_STAND_STATUS = new DataWatcherKey<>(11, BYTE_SERIALIZER);
static final DataWatcherKey<Integer> SLIME_SIZE = new DataWatcherKey<>(12, INT_SERIALIZER);
private final int index;
private final DataWatcherSerializer<T> serializer;
private final int serializerTypeID;
private DataWatcherKey(int index, DataWatcherSerializer<T> 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<T> getSerializer() {
return serializer;
}
int getSerializerTypeID() {
return serializerTypeID;
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_13_R2;
import me.filoghost.fcommons.Strings;
import net.minecraft.server.v1_13_R2.IChatBaseComponent;
import org.bukkit.craftbukkit.v1_13_R2.inventory.CraftItemStack;
import org.bukkit.craftbukkit.v1_13_R2.util.CraftChatMessage;
import org.bukkit.inventory.ItemStack;
import java.util.Optional;
abstract class DataWatcherPacketBuilder<T> {
private final PacketByteBuffer packetByteBuffer;
DataWatcherPacketBuilder(PacketByteBuffer packetByteBuffer) {
this.packetByteBuffer = packetByteBuffer;
}
DataWatcherPacketBuilder<T> setInvisible() {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.ENTITY_STATUS, (byte) 0x20); // Invisible
return this;
}
DataWatcherPacketBuilder<T> setArmorStandMarker() {
setInvisible();
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.ARMOR_STAND_STATUS, (byte) (0x01 | 0x08 | 0x10)); // Small, no base plate, marker
return this;
}
DataWatcherPacketBuilder<T> setCustomName(String customName) {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.CUSTOM_NAME, getCustomNameDataWatcherValue(customName));
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.CUSTOM_NAME_VISIBILITY, !Strings.isEmpty(customName));
return this;
}
private Optional<IChatBaseComponent> getCustomNameDataWatcherValue(String customName) {
customName = Strings.truncate(customName, 300);
if (!Strings.isEmpty(customName)) {
return Optional.of(CraftChatMessage.fromString(customName, false)[0]);
} else {
return Optional.empty();
}
}
DataWatcherPacketBuilder<T> setItemStack(ItemStack itemStack) {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.ITEM_STACK, CraftItemStack.asNMSCopy(itemStack));
return this;
}
DataWatcherPacketBuilder<T> setSlimeSmall() {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.SLIME_SIZE, 1);
return this;
}
T build() {
packetByteBuffer.writeDataWatcherEntriesEnd();
return createPacket(packetByteBuffer);
}
abstract T createPacket(PacketByteBuffer packetByteBuffer);
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_13_R2;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_13_R2.Packet;
import net.minecraft.server.v1_13_R2.PacketPlayOutEntityDestroy;
class EntityDestroyNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntityDestroyNMSPacket(EntityID... entityIDs) {
int[] entityIDsArray = new int[entityIDs.length];
for (int i = 0; i < entityIDs.length; i++) {
entityIDsArray[i] = entityIDs[i].getNumericID();
}
this.rawPacket = new PacketPlayOutEntityDestroy(entityIDsArray);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_13_R2;
import me.filoghost.fcommons.reflection.ReflectField;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_13_R2.DataWatcher;
import net.minecraft.server.v1_13_R2.Packet;
import net.minecraft.server.v1_13_R2.PacketDataSerializer;
import net.minecraft.server.v1_13_R2.PacketPlayOutSpawnEntityLiving;
class EntityLivingSpawnNMSPacket extends VersionNMSPacket {
private static final ReflectField<DataWatcher> DATA_WATCHER_FIELD =
ReflectField.lookup(DataWatcher.class, PacketPlayOutSpawnEntityLiving.class, "m");
private final Packet<?> rawPacket;
private EntityLivingSpawnNMSPacket(Packet<?> rawPacket, DataWatcher dataWatcher) {
this.rawPacket = rawPacket;
try {
DATA_WATCHER_FIELD.set(rawPacket, dataWatcher);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
public static DataWatcherPacketBuilder<EntityLivingSpawnNMSPacket> builder(
EntityID entityID, int entityTypeID, double positionX, double positionY, double positionZ) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(entityID.getNumericID());
packetByteBuffer.writeUUID(entityID.getUUID());
packetByteBuffer.writeVarInt(entityTypeID);
// Position
packetByteBuffer.writeDouble(positionX);
packetByteBuffer.writeDouble(positionY);
packetByteBuffer.writeDouble(positionZ);
// Rotation
packetByteBuffer.writeByte(0);
packetByteBuffer.writeByte(0);
// Head rotation
packetByteBuffer.writeByte(0);
// Velocity
packetByteBuffer.writeShort(0);
packetByteBuffer.writeShort(0);
packetByteBuffer.writeShort(0);
// Entries are deserialized and saved into a field which is not used during the re-serialization, set as empty
packetByteBuffer.writeDataWatcherEntriesEnd();
Packet<?> rawPacket = writeData(new PacketPlayOutSpawnEntityLiving(), packetByteBuffer);
return new Builder(rawPacket, PacketByteBuffer.get());
}
private static class Builder extends DataWatcherPacketBuilder<EntityLivingSpawnNMSPacket> {
private final Packet<?> rawPacket;
private Builder(Packet<?> rawPacket, PacketByteBuffer packetByteBuffer) {
super(packetByteBuffer);
this.rawPacket = rawPacket;
}
@Override
EntityLivingSpawnNMSPacket createPacket(PacketByteBuffer packetByteBuffer) {
return new EntityLivingSpawnNMSPacket(rawPacket, new SerializedDataWatcher(packetByteBuffer));
}
}
private static class SerializedDataWatcher extends DataWatcher {
private final byte[] serializedValue;
SerializedDataWatcher(PacketByteBuffer dataByteBuffer) {
super(null);
serializedValue = new byte[dataByteBuffer.readableBytes()];
dataByteBuffer.readBytes(serializedValue);
}
@Override
public void a(PacketDataSerializer packetDataSerializer) {
packetDataSerializer.writeBytes(serializedValue);
}
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_13_R2;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_13_R2.Packet;
import net.minecraft.server.v1_13_R2.PacketPlayOutEntityMetadata;
class EntityMetadataNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
private EntityMetadataNMSPacket(PacketByteBuffer packetByteBuffer) {
this.rawPacket = writeData(new PacketPlayOutEntityMetadata(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
public static DataWatcherPacketBuilder<EntityMetadataNMSPacket> builder(EntityID entityID) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(entityID.getNumericID());
return new Builder(packetByteBuffer);
}
private static class Builder extends DataWatcherPacketBuilder<EntityMetadataNMSPacket> {
private Builder(PacketByteBuffer packetByteBuffer) {
super(packetByteBuffer);
}
@Override
EntityMetadataNMSPacket createPacket(PacketByteBuffer packetByteBuffer) {
return new EntityMetadataNMSPacket(packetByteBuffer);
}
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_13_R2;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_13_R2.Packet;
import net.minecraft.server.v1_13_R2.PacketPlayOutMount;
class EntityMountNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntityMountNMSPacket(EntityID vehicleEntityID, EntityID passengerEntityID) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(vehicleEntityID.getNumericID());
packetByteBuffer.writeIntArray(passengerEntityID.getNumericID());
this.rawPacket = writeData(new PacketPlayOutMount(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_13_R2;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_13_R2.Packet;
import net.minecraft.server.v1_13_R2.PacketPlayOutSpawnEntity;
class EntitySpawnNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntitySpawnNMSPacket(EntityID entityID, int entityTypeID, double positionX, double positionY, double positionZ) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(entityID.getNumericID());
packetByteBuffer.writeUUID(entityID.getUUID());
packetByteBuffer.writeByte(entityTypeID);
// Position
packetByteBuffer.writeDouble(positionX);
packetByteBuffer.writeDouble(positionY);
packetByteBuffer.writeDouble(positionZ);
// 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 = writeData(new PacketPlayOutSpawnEntity(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_13_R2;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_13_R2.Packet;
import net.minecraft.server.v1_13_R2.PacketPlayOutEntityTeleport;
class EntityTeleportNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntityTeleportNMSPacket(EntityID entityID, double positionX, double positionY, double positionZ) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(entityID.getNumericID());
// Position
packetByteBuffer.writeDouble(positionX);
packetByteBuffer.writeDouble(positionY);
packetByteBuffer.writeDouble(positionZ);
// Rotation
packetByteBuffer.writeByte(0);
packetByteBuffer.writeByte(0);
// On ground
packetByteBuffer.writeBoolean(false);
this.rawPacket = writeData(new PacketPlayOutEntityTeleport(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,14 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_13_R2;
class EntityTypeID {
static final int ARMOR_STAND = 78;
static final int ITEM = 2;
static final int SLIME = 64;
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_13_R2;
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.common.nms.NMSErrors;
import me.filoghost.holographicdisplays.common.nms.PacketListener;
import net.minecraft.server.v1_13_R2.PacketPlayInUseEntity;
import org.bukkit.entity.Player;
public class InboundPacketHandler extends ChannelInboundHandlerAdapter {
public static final String HANDLER_NAME = "holographic_displays_listener";
private static final ReflectField<Integer> ENTITY_ID_FIELD = ReflectField.lookup(int.class, PacketPlayInUseEntity.class, "a");
private final Player player;
private final PacketListener packetListener;
public 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);
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_13_R2;
import io.netty.buffer.Unpooled;
import net.minecraft.server.v1_13_R2.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 writeUUID(UUID uuid) {
super.a(uuid);
}
void writeIntArray(int... array) {
super.a(array);
}
<T> void writeDataWatcherEntry(DataWatcherKey<T> key, T value) {
writeByte(key.getIndex());
writeVarInt(key.getSerializerTypeID());
key.getSerializer().a(this, value);
}
void writeDataWatcherEntriesEnd() {
writeByte(255);
}
}

View File

@ -5,32 +5,113 @@
*/
package me.filoghost.holographicdisplays.nms.v1_13_R2;
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.common.nms.EntityID;
import me.filoghost.holographicdisplays.common.nms.FallbackEntityIDGenerator;
import me.filoghost.holographicdisplays.common.nms.NMSErrors;
import me.filoghost.holographicdisplays.common.nms.NMSManager;
import me.filoghost.holographicdisplays.common.nms.NMSPacketList;
import me.filoghost.holographicdisplays.common.nms.PacketListener;
import net.minecraft.server.v1_13_R2.Entity;
import net.minecraft.server.v1_13_R2.NetworkManager;
import net.minecraft.server.v1_13_R2.PlayerConnection;
import org.bukkit.craftbukkit.v1_13_R2.entity.CraftPlayer;
import org.bukkit.entity.Player;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class VersionNMSManager implements NMSManager {
private static final ReflectField<Integer> ENTITY_ID_COUNTER_FIELD = ReflectField.lookup(int.class, Entity.class, "entityCount");
private final Supplier<Integer> fallbackEntityIDGenerator;
private final Supplier<Integer> entityIDGenerator;
public VersionNMSManager(ErrorCollector errorCollector) {
this.fallbackEntityIDGenerator = new FallbackEntityIDGenerator();
this.entityIDGenerator = getEntityIDGenerator(errorCollector);
// Force initialization of class to eventually throw exceptions early
DataWatcherKey.ENTITY_STATUS.getIndex();
}
private Supplier<Integer> getEntityIDGenerator(ErrorCollector errorCollector) {
try {
testStaticFieldReadWrite(ENTITY_ID_COUNTER_FIELD);
return () -> {
try {
int nmsEntityIDCounter = ENTITY_ID_COUNTER_FIELD.getStatic();
ENTITY_ID_COUNTER_FIELD.setStatic(nmsEntityIDCounter + 1);
return nmsEntityIDCounter;
} catch (ReflectiveOperationException e) {
// Should not happen, access is tested beforehand
return fallbackEntityIDGenerator.get();
}
};
} catch (ReflectiveOperationException e) {
errorCollector.add(e, NMSErrors.EXCEPTION_GETTING_ENTITY_ID_GENERATOR);
return fallbackEntityIDGenerator;
}
}
private <T> void testStaticFieldReadWrite(ReflectField<T> field) throws ReflectiveOperationException {
T value = field.getStatic();
field.setStatic(value);
}
@Override
public EntityID newEntityID() {
return null;
return new EntityID(entityIDGenerator);
}
@Override
public NMSPacketList createPacketList() {
return null;
return new VersionNMSPacketList();
}
@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<ChannelPipeline> pipelineModifierTask) {
PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection;
NetworkManager networkManager = playerConnection.a();
Channel channel = networkManager.channel;
channel.eventLoop().execute(() -> {
try {
pipelineModifierTask.accept(channel.pipeline());
} catch (Exception e) {
Log.warning(NMSErrors.EXCEPTION_MODIFYING_CHANNEL_PIPELINE, e);
}
});
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_13_R2;
import me.filoghost.holographicdisplays.common.nms.NMSPacket;
import net.minecraft.server.v1_13_R2.Packet;
import org.bukkit.craftbukkit.v1_13_R2.entity.CraftPlayer;
import org.bukkit.entity.Player;
import java.io.IOException;
abstract class VersionNMSPacket implements NMSPacket {
@Override
public void sendTo(Player player) {
((CraftPlayer) player).getHandle().playerConnection.sendPacket(getRawPacket());
}
abstract Packet<?> getRawPacket();
protected static <T extends Packet<?>> T writeData(T packet, PacketByteBuffer packetByteBuffer) {
try {
packet.a(packetByteBuffer);
return packet;
} catch (IOException e) {
// Never thrown by the implementations
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,102 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_13_R2;
import me.filoghost.holographicdisplays.common.nms.AbstractNMSPacketList;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import me.filoghost.holographicdisplays.common.nms.IndividualCustomName;
import me.filoghost.holographicdisplays.common.nms.IndividualNMSPacket;
import org.bukkit.inventory.ItemStack;
class VersionNMSPacketList extends AbstractNMSPacketList {
@Override
public void addArmorStandSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ) {
add(new EntitySpawnNMSPacket(entityID, EntityTypeID.ARMOR_STAND, positionX, positionY, positionZ));
add(EntityMetadataNMSPacket.builder(entityID)
.setArmorStandMarker()
.build()
);
}
@Override
public void addArmorStandSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ, String customName) {
add(new EntitySpawnNMSPacket(entityID, EntityTypeID.ARMOR_STAND, positionX, positionY, positionZ));
add(EntityMetadataNMSPacket.builder(entityID)
.setArmorStandMarker()
.setCustomName(customName)
.build()
);
}
@Override
public void addArmorStandSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ, IndividualCustomName individualCustomName) {
add(new EntitySpawnNMSPacket(entityID, EntityTypeID.ARMOR_STAND, positionX, positionY, positionZ));
add(new IndividualNMSPacket(player -> EntityMetadataNMSPacket.builder(entityID)
.setArmorStandMarker()
.setCustomName(individualCustomName.get(player))
.build()
));
}
@Override
public void addArmorStandNameChangePackets(EntityID entityID, String customName) {
add(EntityMetadataNMSPacket.builder(entityID)
.setCustomName(customName)
.build()
);
}
@Override
public void addArmorStandNameChangePackets(EntityID entityID, IndividualCustomName individualCustomName) {
add(new IndividualNMSPacket(player -> EntityMetadataNMSPacket.builder(entityID)
.setCustomName(individualCustomName.get(player))
.build()
));
}
@Override
public void addItemSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ, ItemStack itemStack) {
add(new EntitySpawnNMSPacket(entityID, EntityTypeID.ITEM, positionX, positionY, positionZ));
add(EntityMetadataNMSPacket.builder(entityID)
.setItemStack(itemStack)
.build()
);
}
@Override
public void addItemStackChangePackets(EntityID entityID, ItemStack itemStack) {
add(EntityMetadataNMSPacket.builder(entityID)
.setItemStack(itemStack)
.build()
);
}
@Override
public void addSlimeSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ) {
add(EntityLivingSpawnNMSPacket.builder(entityID, EntityTypeID.SLIME, positionX, positionY, positionZ)
.setInvisible()
.setSlimeSmall() // Required for a correct client-side collision box
.build()
);
}
@Override
public void addEntityDestroyPackets(EntityID... entityIDs) {
add(new EntityDestroyNMSPacket(entityIDs));
}
@Override
public void addTeleportPackets(EntityID entityID, double positionX, double positionY, double positionZ) {
add(new EntityTeleportNMSPacket(entityID, positionX, positionY, positionZ));
}
@Override
public void addMountPackets(EntityID vehicleEntityID, EntityID passengerEntityID) {
add(new EntityMountNMSPacket(vehicleEntityID, passengerEntityID));
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_14_R1;
import io.netty.handler.codec.EncoderException;
import net.minecraft.server.v1_14_R1.DataWatcherRegistry;
import net.minecraft.server.v1_14_R1.DataWatcherSerializer;
import net.minecraft.server.v1_14_R1.IChatBaseComponent;
import net.minecraft.server.v1_14_R1.ItemStack;
import java.util.Optional;
class DataWatcherKey<T> {
private static final DataWatcherSerializer<Byte> BYTE_SERIALIZER = DataWatcherRegistry.a;
private static final DataWatcherSerializer<Integer> INT_SERIALIZER = DataWatcherRegistry.b;
private static final DataWatcherSerializer<Boolean> BOOLEAN_SERIALIZER = DataWatcherRegistry.i;
private static final DataWatcherSerializer<ItemStack> ITEM_STACK_SERIALIZER = DataWatcherRegistry.g;
private static final DataWatcherSerializer<Optional<IChatBaseComponent>> OPTIONAL_CHAT_COMPONENT_SERIALIZER = DataWatcherRegistry.f;
static final DataWatcherKey<Byte> ENTITY_STATUS = new DataWatcherKey<>(0, BYTE_SERIALIZER);
static final DataWatcherKey<Optional<IChatBaseComponent>> CUSTOM_NAME = new DataWatcherKey<>(2, OPTIONAL_CHAT_COMPONENT_SERIALIZER);
static final DataWatcherKey<Boolean> CUSTOM_NAME_VISIBILITY = new DataWatcherKey<>(3, BOOLEAN_SERIALIZER);
static final DataWatcherKey<ItemStack> ITEM_STACK = new DataWatcherKey<>(7, ITEM_STACK_SERIALIZER);
static final DataWatcherKey<Byte> ARMOR_STAND_STATUS = new DataWatcherKey<>(13, BYTE_SERIALIZER);
static final DataWatcherKey<Integer> SLIME_SIZE = new DataWatcherKey<>(14, INT_SERIALIZER);
private final int index;
private final DataWatcherSerializer<T> serializer;
private final int serializerTypeID;
private DataWatcherKey(int index, DataWatcherSerializer<T> 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<T> getSerializer() {
return serializer;
}
int getSerializerTypeID() {
return serializerTypeID;
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_14_R1;
import me.filoghost.fcommons.Strings;
import net.minecraft.server.v1_14_R1.IChatBaseComponent;
import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftItemStack;
import org.bukkit.craftbukkit.v1_14_R1.util.CraftChatMessage;
import org.bukkit.inventory.ItemStack;
import java.util.Optional;
abstract class DataWatcherPacketBuilder<T> {
private final PacketByteBuffer packetByteBuffer;
DataWatcherPacketBuilder(PacketByteBuffer packetByteBuffer) {
this.packetByteBuffer = packetByteBuffer;
}
DataWatcherPacketBuilder<T> setInvisible() {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.ENTITY_STATUS, (byte) 0x20); // Invisible
return this;
}
DataWatcherPacketBuilder<T> setArmorStandMarker() {
setInvisible();
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.ARMOR_STAND_STATUS, (byte) (0x01 | 0x08 | 0x10)); // Small, no base plate, marker
return this;
}
DataWatcherPacketBuilder<T> setCustomName(String customName) {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.CUSTOM_NAME, getCustomNameDataWatcherValue(customName));
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.CUSTOM_NAME_VISIBILITY, !Strings.isEmpty(customName));
return this;
}
private Optional<IChatBaseComponent> getCustomNameDataWatcherValue(String customName) {
customName = Strings.truncate(customName, 300);
if (!Strings.isEmpty(customName)) {
return Optional.of(CraftChatMessage.fromString(customName, false)[0]);
} else {
return Optional.empty();
}
}
DataWatcherPacketBuilder<T> setItemStack(ItemStack itemStack) {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.ITEM_STACK, CraftItemStack.asNMSCopy(itemStack));
return this;
}
DataWatcherPacketBuilder<T> setSlimeSmall() {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.SLIME_SIZE, 1);
return this;
}
T build() {
packetByteBuffer.writeDataWatcherEntriesEnd();
return createPacket(packetByteBuffer);
}
abstract T createPacket(PacketByteBuffer packetByteBuffer);
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_14_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_14_R1.Packet;
import net.minecraft.server.v1_14_R1.PacketPlayOutEntityDestroy;
class EntityDestroyNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntityDestroyNMSPacket(EntityID... entityIDs) {
int[] entityIDsArray = new int[entityIDs.length];
for (int i = 0; i < entityIDs.length; i++) {
entityIDsArray[i] = entityIDs[i].getNumericID();
}
this.rawPacket = new PacketPlayOutEntityDestroy(entityIDsArray);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_14_R1;
import me.filoghost.fcommons.reflection.ReflectField;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_14_R1.DataWatcher;
import net.minecraft.server.v1_14_R1.Packet;
import net.minecraft.server.v1_14_R1.PacketDataSerializer;
import net.minecraft.server.v1_14_R1.PacketPlayOutSpawnEntityLiving;
class EntityLivingSpawnNMSPacket extends VersionNMSPacket {
private static final ReflectField<DataWatcher> DATA_WATCHER_FIELD =
ReflectField.lookup(DataWatcher.class, PacketPlayOutSpawnEntityLiving.class, "m");
private final Packet<?> rawPacket;
private EntityLivingSpawnNMSPacket(Packet<?> rawPacket, DataWatcher dataWatcher) {
this.rawPacket = rawPacket;
try {
DATA_WATCHER_FIELD.set(rawPacket, dataWatcher);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
public static DataWatcherPacketBuilder<EntityLivingSpawnNMSPacket> builder(
EntityID entityID, int entityTypeID, double positionX, double positionY, double positionZ) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(entityID.getNumericID());
packetByteBuffer.writeUUID(entityID.getUUID());
packetByteBuffer.writeVarInt(entityTypeID);
// Position
packetByteBuffer.writeDouble(positionX);
packetByteBuffer.writeDouble(positionY);
packetByteBuffer.writeDouble(positionZ);
// Rotation
packetByteBuffer.writeByte(0);
packetByteBuffer.writeByte(0);
// Head rotation
packetByteBuffer.writeByte(0);
// Velocity
packetByteBuffer.writeShort(0);
packetByteBuffer.writeShort(0);
packetByteBuffer.writeShort(0);
// Entries are deserialized and saved into a field which is not used during the re-serialization, set as empty
packetByteBuffer.writeDataWatcherEntriesEnd();
Packet<?> rawPacket = writeData(new PacketPlayOutSpawnEntityLiving(), packetByteBuffer);
return new Builder(rawPacket, PacketByteBuffer.get());
}
private static class Builder extends DataWatcherPacketBuilder<EntityLivingSpawnNMSPacket> {
private final Packet<?> rawPacket;
private Builder(Packet<?> rawPacket, PacketByteBuffer packetByteBuffer) {
super(packetByteBuffer);
this.rawPacket = rawPacket;
}
@Override
EntityLivingSpawnNMSPacket createPacket(PacketByteBuffer packetByteBuffer) {
return new EntityLivingSpawnNMSPacket(rawPacket, new SerializedDataWatcher(packetByteBuffer));
}
}
private static class SerializedDataWatcher extends DataWatcher {
private final byte[] serializedValue;
SerializedDataWatcher(PacketByteBuffer dataByteBuffer) {
super(null);
serializedValue = new byte[dataByteBuffer.readableBytes()];
dataByteBuffer.readBytes(serializedValue);
}
@Override
public void a(PacketDataSerializer packetDataSerializer) {
packetDataSerializer.writeBytes(serializedValue);
}
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_14_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_14_R1.Packet;
import net.minecraft.server.v1_14_R1.PacketPlayOutEntityMetadata;
class EntityMetadataNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
private EntityMetadataNMSPacket(PacketByteBuffer packetByteBuffer) {
this.rawPacket = writeData(new PacketPlayOutEntityMetadata(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
public static DataWatcherPacketBuilder<EntityMetadataNMSPacket> builder(EntityID entityID) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(entityID.getNumericID());
return new Builder(packetByteBuffer);
}
private static class Builder extends DataWatcherPacketBuilder<EntityMetadataNMSPacket> {
private Builder(PacketByteBuffer packetByteBuffer) {
super(packetByteBuffer);
}
@Override
EntityMetadataNMSPacket createPacket(PacketByteBuffer packetByteBuffer) {
return new EntityMetadataNMSPacket(packetByteBuffer);
}
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_14_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_14_R1.Packet;
import net.minecraft.server.v1_14_R1.PacketPlayOutMount;
class EntityMountNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntityMountNMSPacket(EntityID vehicleEntityID, EntityID passengerEntityID) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(vehicleEntityID.getNumericID());
packetByteBuffer.writeIntArray(passengerEntityID.getNumericID());
this.rawPacket = writeData(new PacketPlayOutMount(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_14_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_14_R1.Packet;
import net.minecraft.server.v1_14_R1.PacketPlayOutSpawnEntity;
class EntitySpawnNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntitySpawnNMSPacket(EntityID entityID, int entityTypeID, double positionX, double positionY, double positionZ) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(entityID.getNumericID());
packetByteBuffer.writeUUID(entityID.getUUID());
packetByteBuffer.writeVarInt(entityTypeID);
// Position
packetByteBuffer.writeDouble(positionX);
packetByteBuffer.writeDouble(positionY);
packetByteBuffer.writeDouble(positionZ);
// 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 = writeData(new PacketPlayOutSpawnEntity(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_14_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_14_R1.Packet;
import net.minecraft.server.v1_14_R1.PacketPlayOutEntityTeleport;
class EntityTeleportNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntityTeleportNMSPacket(EntityID entityID, double positionX, double positionY, double positionZ) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(entityID.getNumericID());
// Position
packetByteBuffer.writeDouble(positionX);
packetByteBuffer.writeDouble(positionY);
packetByteBuffer.writeDouble(positionZ);
// Rotation
packetByteBuffer.writeByte(0);
packetByteBuffer.writeByte(0);
// On ground
packetByteBuffer.writeBoolean(false);
this.rawPacket = writeData(new PacketPlayOutEntityTeleport(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,14 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_14_R1;
class EntityTypeID {
static final int ARMOR_STAND = 1;
static final int ITEM = 34;
static final int SLIME = 67;
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_14_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.common.nms.NMSErrors;
import me.filoghost.holographicdisplays.common.nms.PacketListener;
import net.minecraft.server.v1_14_R1.PacketPlayInUseEntity;
import org.bukkit.entity.Player;
public class InboundPacketHandler extends ChannelInboundHandlerAdapter {
public static final String HANDLER_NAME = "holographic_displays_listener";
private static final ReflectField<Integer> ENTITY_ID_FIELD = ReflectField.lookup(int.class, PacketPlayInUseEntity.class, "a");
private final Player player;
private final PacketListener packetListener;
public 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);
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_14_R1;
import io.netty.buffer.Unpooled;
import net.minecraft.server.v1_14_R1.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 writeUUID(UUID uuid) {
super.a(uuid);
}
void writeIntArray(int... array) {
super.a(array);
}
<T> void writeDataWatcherEntry(DataWatcherKey<T> key, T value) {
writeByte(key.getIndex());
writeVarInt(key.getSerializerTypeID());
key.getSerializer().a(this, value);
}
void writeDataWatcherEntriesEnd() {
writeByte(255);
}
}

View File

@ -5,32 +5,97 @@
*/
package me.filoghost.holographicdisplays.nms.v1_14_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.common.nms.EntityID;
import me.filoghost.holographicdisplays.common.nms.FallbackEntityIDGenerator;
import me.filoghost.holographicdisplays.common.nms.NMSErrors;
import me.filoghost.holographicdisplays.common.nms.NMSManager;
import me.filoghost.holographicdisplays.common.nms.NMSPacketList;
import me.filoghost.holographicdisplays.common.nms.PacketListener;
import net.minecraft.server.v1_14_R1.Entity;
import net.minecraft.server.v1_14_R1.NetworkManager;
import net.minecraft.server.v1_14_R1.PlayerConnection;
import org.bukkit.craftbukkit.v1_14_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<AtomicInteger> ENTITY_ID_COUNTER_FIELD = ReflectField.lookup(AtomicInteger.class, Entity.class, "entityCount");
private final Supplier<Integer> entityIDGenerator;
public VersionNMSManager(ErrorCollector errorCollector) {
this.entityIDGenerator = getEntityIDGenerator(errorCollector);
// Force initialization of class to eventually throw exceptions early
DataWatcherKey.ENTITY_STATUS.getIndex();
}
private Supplier<Integer> 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();
}
}
@Override
public EntityID newEntityID() {
return null;
return new EntityID(entityIDGenerator);
}
@Override
public NMSPacketList createPacketList() {
return null;
return new VersionNMSPacketList();
}
@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<ChannelPipeline> pipelineModifierTask) {
PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection;
NetworkManager networkManager = playerConnection.a();
Channel channel = networkManager.channel;
channel.eventLoop().execute(() -> {
try {
pipelineModifierTask.accept(channel.pipeline());
} catch (Exception e) {
Log.warning(NMSErrors.EXCEPTION_MODIFYING_CHANNEL_PIPELINE, e);
}
});
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_14_R1;
import me.filoghost.holographicdisplays.common.nms.NMSPacket;
import net.minecraft.server.v1_14_R1.Packet;
import org.bukkit.craftbukkit.v1_14_R1.entity.CraftPlayer;
import org.bukkit.entity.Player;
import java.io.IOException;
abstract class VersionNMSPacket implements NMSPacket {
@Override
public void sendTo(Player player) {
((CraftPlayer) player).getHandle().playerConnection.sendPacket(getRawPacket());
}
abstract Packet<?> getRawPacket();
protected static <T extends Packet<?>> T writeData(T packet, PacketByteBuffer packetByteBuffer) {
try {
packet.a(packetByteBuffer);
return packet;
} catch (IOException e) {
// Never thrown by the implementations
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,100 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_14_R1;
import me.filoghost.holographicdisplays.common.nms.AbstractNMSPacketList;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import me.filoghost.holographicdisplays.common.nms.IndividualCustomName;
import me.filoghost.holographicdisplays.common.nms.IndividualNMSPacket;
import org.bukkit.inventory.ItemStack;
class VersionNMSPacketList extends AbstractNMSPacketList {
@Override
public void addArmorStandSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ) {
add(EntityLivingSpawnNMSPacket.builder(entityID, EntityTypeID.ARMOR_STAND, positionX, positionY, positionZ)
.setArmorStandMarker()
.build()
);
}
@Override
public void addArmorStandSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ, String customName) {
add(EntityLivingSpawnNMSPacket.builder(entityID, EntityTypeID.ARMOR_STAND, positionX, positionY, positionZ)
.setArmorStandMarker()
.setCustomName(customName)
.build()
);
}
@Override
public void addArmorStandSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ, IndividualCustomName individualCustomName) {
add(new IndividualNMSPacket(player -> EntityLivingSpawnNMSPacket.builder(
entityID, EntityTypeID.ARMOR_STAND, positionX, positionY, positionZ)
.setArmorStandMarker()
.setCustomName(individualCustomName.get(player))
.build()
));
}
@Override
public void addArmorStandNameChangePackets(EntityID entityID, String customName) {
add(EntityMetadataNMSPacket.builder(entityID)
.setCustomName(customName)
.build()
);
}
@Override
public void addArmorStandNameChangePackets(EntityID entityID, IndividualCustomName individualCustomName) {
add(new IndividualNMSPacket(player -> EntityMetadataNMSPacket.builder(entityID)
.setCustomName(individualCustomName.get(player))
.build()
));
}
@Override
public void addItemSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ, ItemStack itemStack) {
add(new EntitySpawnNMSPacket(entityID, EntityTypeID.ITEM, positionX, positionY, positionZ));
add(EntityMetadataNMSPacket.builder(entityID)
.setItemStack(itemStack)
.build()
);
}
@Override
public void addItemStackChangePackets(EntityID entityID, ItemStack itemStack) {
add(EntityMetadataNMSPacket.builder(entityID)
.setItemStack(itemStack)
.build()
);
}
@Override
public void addSlimeSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ) {
add(EntityLivingSpawnNMSPacket.builder(entityID, EntityTypeID.SLIME, positionX, positionY, positionZ)
.setInvisible()
.setSlimeSmall() // Required for a correct client-side collision box
.build()
);
}
@Override
public void addEntityDestroyPackets(EntityID... entityIDs) {
add(new EntityDestroyNMSPacket(entityIDs));
}
@Override
public void addTeleportPackets(EntityID entityID, double positionX, double positionY, double positionZ) {
add(new EntityTeleportNMSPacket(entityID, positionX, positionY, positionZ));
}
@Override
public void addMountPackets(EntityID vehicleEntityID, EntityID passengerEntityID) {
add(new EntityMountNMSPacket(vehicleEntityID, passengerEntityID));
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_15_R1;
import io.netty.handler.codec.EncoderException;
import net.minecraft.server.v1_15_R1.DataWatcherRegistry;
import net.minecraft.server.v1_15_R1.DataWatcherSerializer;
import net.minecraft.server.v1_15_R1.IChatBaseComponent;
import net.minecraft.server.v1_15_R1.ItemStack;
import java.util.Optional;
class DataWatcherKey<T> {
private static final DataWatcherSerializer<Byte> BYTE_SERIALIZER = DataWatcherRegistry.a;
private static final DataWatcherSerializer<Integer> INT_SERIALIZER = DataWatcherRegistry.b;
private static final DataWatcherSerializer<Boolean> BOOLEAN_SERIALIZER = DataWatcherRegistry.i;
private static final DataWatcherSerializer<ItemStack> ITEM_STACK_SERIALIZER = DataWatcherRegistry.g;
private static final DataWatcherSerializer<Optional<IChatBaseComponent>> OPTIONAL_CHAT_COMPONENT_SERIALIZER = DataWatcherRegistry.f;
static final DataWatcherKey<Byte> ENTITY_STATUS = new DataWatcherKey<>(0, BYTE_SERIALIZER);
static final DataWatcherKey<Optional<IChatBaseComponent>> CUSTOM_NAME = new DataWatcherKey<>(2, OPTIONAL_CHAT_COMPONENT_SERIALIZER);
static final DataWatcherKey<Boolean> CUSTOM_NAME_VISIBILITY = new DataWatcherKey<>(3, BOOLEAN_SERIALIZER);
static final DataWatcherKey<ItemStack> ITEM_STACK = new DataWatcherKey<>(7, ITEM_STACK_SERIALIZER);
static final DataWatcherKey<Byte> ARMOR_STAND_STATUS = new DataWatcherKey<>(14, BYTE_SERIALIZER);
static final DataWatcherKey<Integer> SLIME_SIZE = new DataWatcherKey<>(15, INT_SERIALIZER);
private final int index;
private final DataWatcherSerializer<T> serializer;
private final int serializerTypeID;
private DataWatcherKey(int index, DataWatcherSerializer<T> 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<T> getSerializer() {
return serializer;
}
int getSerializerTypeID() {
return serializerTypeID;
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_15_R1;
import me.filoghost.fcommons.Strings;
import net.minecraft.server.v1_15_R1.IChatBaseComponent;
import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftItemStack;
import org.bukkit.craftbukkit.v1_15_R1.util.CraftChatMessage;
import org.bukkit.inventory.ItemStack;
import java.util.Optional;
abstract class DataWatcherPacketBuilder<T> {
private final PacketByteBuffer packetByteBuffer;
DataWatcherPacketBuilder(PacketByteBuffer packetByteBuffer) {
this.packetByteBuffer = packetByteBuffer;
}
DataWatcherPacketBuilder<T> setInvisible() {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.ENTITY_STATUS, (byte) 0x20); // Invisible
return this;
}
DataWatcherPacketBuilder<T> setArmorStandMarker() {
setInvisible();
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.ARMOR_STAND_STATUS, (byte) (0x01 | 0x08 | 0x10)); // Small, no base plate, marker
return this;
}
DataWatcherPacketBuilder<T> setCustomName(String customName) {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.CUSTOM_NAME, getCustomNameDataWatcherValue(customName));
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.CUSTOM_NAME_VISIBILITY, !Strings.isEmpty(customName));
return this;
}
private Optional<IChatBaseComponent> getCustomNameDataWatcherValue(String customName) {
customName = Strings.truncate(customName, 300);
if (!Strings.isEmpty(customName)) {
return Optional.of(CraftChatMessage.fromString(customName, false)[0]);
} else {
return Optional.empty();
}
}
DataWatcherPacketBuilder<T> setItemStack(ItemStack itemStack) {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.ITEM_STACK, CraftItemStack.asNMSCopy(itemStack));
return this;
}
DataWatcherPacketBuilder<T> setSlimeSmall() {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.SLIME_SIZE, 1);
return this;
}
T build() {
packetByteBuffer.writeDataWatcherEntriesEnd();
return createPacket(packetByteBuffer);
}
abstract T createPacket(PacketByteBuffer packetByteBuffer);
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_15_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_15_R1.Packet;
import net.minecraft.server.v1_15_R1.PacketPlayOutEntityDestroy;
class EntityDestroyNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntityDestroyNMSPacket(EntityID... entityIDs) {
int[] entityIDsArray = new int[entityIDs.length];
for (int i = 0; i < entityIDs.length; i++) {
entityIDsArray[i] = entityIDs[i].getNumericID();
}
this.rawPacket = new PacketPlayOutEntityDestroy(entityIDsArray);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_15_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_15_R1.Packet;
import net.minecraft.server.v1_15_R1.PacketPlayOutSpawnEntityLiving;
class EntityLivingSpawnNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntityLivingSpawnNMSPacket(EntityID entityID, int entityTypeID, double positionX, double positionY, double positionZ) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(entityID.getNumericID());
packetByteBuffer.writeUUID(entityID.getUUID());
packetByteBuffer.writeVarInt(entityTypeID);
// Position
packetByteBuffer.writeDouble(positionX);
packetByteBuffer.writeDouble(positionY);
packetByteBuffer.writeDouble(positionZ);
// Rotation
packetByteBuffer.writeByte(0);
packetByteBuffer.writeByte(0);
// Head rotation
packetByteBuffer.writeByte(0);
// Velocity
packetByteBuffer.writeShort(0);
packetByteBuffer.writeShort(0);
packetByteBuffer.writeShort(0);
this.rawPacket = writeData(new PacketPlayOutSpawnEntityLiving(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_15_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_15_R1.Packet;
import net.minecraft.server.v1_15_R1.PacketPlayOutEntityMetadata;
class EntityMetadataNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
private EntityMetadataNMSPacket(PacketByteBuffer packetByteBuffer) {
this.rawPacket = writeData(new PacketPlayOutEntityMetadata(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
public static DataWatcherPacketBuilder<EntityMetadataNMSPacket> builder(EntityID entityID) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(entityID.getNumericID());
return new Builder(packetByteBuffer);
}
private static class Builder extends DataWatcherPacketBuilder<EntityMetadataNMSPacket> {
private Builder(PacketByteBuffer packetByteBuffer) {
super(packetByteBuffer);
}
@Override
EntityMetadataNMSPacket createPacket(PacketByteBuffer packetByteBuffer) {
return new EntityMetadataNMSPacket(packetByteBuffer);
}
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_15_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_15_R1.Packet;
import net.minecraft.server.v1_15_R1.PacketPlayOutMount;
class EntityMountNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntityMountNMSPacket(EntityID vehicleEntityID, EntityID passengerEntityID) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(vehicleEntityID.getNumericID());
packetByteBuffer.writeIntArray(passengerEntityID.getNumericID());
this.rawPacket = writeData(new PacketPlayOutMount(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_15_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_15_R1.Packet;
import net.minecraft.server.v1_15_R1.PacketPlayOutSpawnEntity;
class EntitySpawnNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntitySpawnNMSPacket(EntityID entityID, int entityTypeID, double positionX, double positionY, double positionZ) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(entityID.getNumericID());
packetByteBuffer.writeUUID(entityID.getUUID());
packetByteBuffer.writeVarInt(entityTypeID);
// Position
packetByteBuffer.writeDouble(positionX);
packetByteBuffer.writeDouble(positionY);
packetByteBuffer.writeDouble(positionZ);
// 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 = writeData(new PacketPlayOutSpawnEntity(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_15_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_15_R1.Packet;
import net.minecraft.server.v1_15_R1.PacketPlayOutEntityTeleport;
class EntityTeleportNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntityTeleportNMSPacket(EntityID entityID, double positionX, double positionY, double positionZ) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(entityID.getNumericID());
// Position
packetByteBuffer.writeDouble(positionX);
packetByteBuffer.writeDouble(positionY);
packetByteBuffer.writeDouble(positionZ);
// Rotation
packetByteBuffer.writeByte(0);
packetByteBuffer.writeByte(0);
// On ground
packetByteBuffer.writeBoolean(false);
this.rawPacket = writeData(new PacketPlayOutEntityTeleport(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,14 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_15_R1;
class EntityTypeID {
static final int ARMOR_STAND = 1;
static final int ITEM = 35;
static final int SLIME = 68;
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_15_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.common.nms.NMSErrors;
import me.filoghost.holographicdisplays.common.nms.PacketListener;
import net.minecraft.server.v1_15_R1.PacketPlayInUseEntity;
import org.bukkit.entity.Player;
public class InboundPacketHandler extends ChannelInboundHandlerAdapter {
public static final String HANDLER_NAME = "holographic_displays_listener";
private static final ReflectField<Integer> ENTITY_ID_FIELD = ReflectField.lookup(int.class, PacketPlayInUseEntity.class, "a");
private final Player player;
private final PacketListener packetListener;
public 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);
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_15_R1;
import io.netty.buffer.Unpooled;
import net.minecraft.server.v1_15_R1.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 writeUUID(UUID uuid) {
super.a(uuid);
}
void writeIntArray(int... array) {
super.a(array);
}
<T> void writeDataWatcherEntry(DataWatcherKey<T> key, T value) {
writeByte(key.getIndex());
writeVarInt(key.getSerializerTypeID());
key.getSerializer().a(this, value);
}
void writeDataWatcherEntriesEnd() {
writeByte(255);
}
}

View File

@ -5,32 +5,97 @@
*/
package me.filoghost.holographicdisplays.nms.v1_15_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.common.nms.EntityID;
import me.filoghost.holographicdisplays.common.nms.FallbackEntityIDGenerator;
import me.filoghost.holographicdisplays.common.nms.NMSErrors;
import me.filoghost.holographicdisplays.common.nms.NMSManager;
import me.filoghost.holographicdisplays.common.nms.NMSPacketList;
import me.filoghost.holographicdisplays.common.nms.PacketListener;
import net.minecraft.server.v1_15_R1.Entity;
import net.minecraft.server.v1_15_R1.NetworkManager;
import net.minecraft.server.v1_15_R1.PlayerConnection;
import org.bukkit.craftbukkit.v1_15_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<AtomicInteger> ENTITY_ID_COUNTER_FIELD = ReflectField.lookup(AtomicInteger.class, Entity.class, "entityCount");
private final Supplier<Integer> entityIDGenerator;
public VersionNMSManager(ErrorCollector errorCollector) {
this.entityIDGenerator = getEntityIDGenerator(errorCollector);
// Force initialization of class to eventually throw exceptions early
DataWatcherKey.ENTITY_STATUS.getIndex();
}
private Supplier<Integer> 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();
}
}
@Override
public EntityID newEntityID() {
return null;
return new EntityID(entityIDGenerator);
}
@Override
public NMSPacketList createPacketList() {
return null;
return new VersionNMSPacketList();
}
@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<ChannelPipeline> pipelineModifierTask) {
PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection;
NetworkManager networkManager = playerConnection.a();
Channel channel = networkManager.channel;
channel.eventLoop().execute(() -> {
try {
pipelineModifierTask.accept(channel.pipeline());
} catch (Exception e) {
Log.warning(NMSErrors.EXCEPTION_MODIFYING_CHANNEL_PIPELINE, e);
}
});
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_15_R1;
import me.filoghost.holographicdisplays.common.nms.NMSPacket;
import net.minecraft.server.v1_15_R1.Packet;
import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer;
import org.bukkit.entity.Player;
import java.io.IOException;
abstract class VersionNMSPacket implements NMSPacket {
@Override
public void sendTo(Player player) {
((CraftPlayer) player).getHandle().playerConnection.sendPacket(getRawPacket());
}
abstract Packet<?> getRawPacket();
protected static <T extends Packet<?>> T writeData(T packet, PacketByteBuffer packetByteBuffer) {
try {
packet.a(packetByteBuffer);
return packet;
} catch (IOException e) {
// Never thrown by the implementations
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_15_R1;
import me.filoghost.holographicdisplays.common.nms.AbstractNMSPacketList;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import me.filoghost.holographicdisplays.common.nms.IndividualCustomName;
import me.filoghost.holographicdisplays.common.nms.IndividualNMSPacket;
import org.bukkit.inventory.ItemStack;
class VersionNMSPacketList extends AbstractNMSPacketList {
@Override
public void addArmorStandSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ) {
add(new EntityLivingSpawnNMSPacket(entityID, EntityTypeID.ARMOR_STAND, positionX, positionY, positionZ));
add(EntityMetadataNMSPacket.builder(entityID)
.setArmorStandMarker()
.build()
);
}
@Override
public void addArmorStandSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ, String customName) {
add(new EntityLivingSpawnNMSPacket(entityID, EntityTypeID.ARMOR_STAND, positionX, positionY, positionZ));
add(EntityMetadataNMSPacket.builder(entityID)
.setArmorStandMarker()
.setCustomName(customName)
.build()
);
}
@Override
public void addArmorStandSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ, IndividualCustomName individualCustomName) {
add(new EntityLivingSpawnNMSPacket(entityID, EntityTypeID.ARMOR_STAND, positionX, positionY, positionZ));
add(new IndividualNMSPacket(player -> EntityMetadataNMSPacket.builder(entityID)
.setArmorStandMarker()
.setCustomName(individualCustomName.get(player))
.build()
));
}
@Override
public void addArmorStandNameChangePackets(EntityID entityID, String customName) {
add(EntityMetadataNMSPacket.builder(entityID)
.setCustomName(customName)
.build()
);
}
@Override
public void addArmorStandNameChangePackets(EntityID entityID, IndividualCustomName individualCustomName) {
add(new IndividualNMSPacket(player -> EntityMetadataNMSPacket.builder(entityID)
.setCustomName(individualCustomName.get(player))
.build()
));
}
@Override
public void addItemSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ, ItemStack itemStack) {
add(new EntitySpawnNMSPacket(entityID, EntityTypeID.ITEM, positionX, positionY, positionZ));
add(EntityMetadataNMSPacket.builder(entityID)
.setItemStack(itemStack)
.build()
);
}
@Override
public void addItemStackChangePackets(EntityID entityID, ItemStack itemStack) {
add(EntityMetadataNMSPacket.builder(entityID)
.setItemStack(itemStack)
.build()
);
}
@Override
public void addSlimeSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ) {
add(new EntityLivingSpawnNMSPacket(entityID, EntityTypeID.SLIME, positionX, positionY, positionZ));
add(EntityMetadataNMSPacket.builder(entityID)
.setInvisible()
.setSlimeSmall() // Required for a correct client-side collision box
.build()
);
}
@Override
public void addEntityDestroyPackets(EntityID... entityIDs) {
add(new EntityDestroyNMSPacket(entityIDs));
}
@Override
public void addTeleportPackets(EntityID entityID, double positionX, double positionY, double positionZ) {
add(new EntityTeleportNMSPacket(entityID, positionX, positionY, positionZ));
}
@Override
public void addMountPackets(EntityID vehicleEntityID, EntityID passengerEntityID) {
add(new EntityMountNMSPacket(vehicleEntityID, passengerEntityID));
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_16_R1;
import io.netty.handler.codec.EncoderException;
import net.minecraft.server.v1_16_R1.DataWatcherRegistry;
import net.minecraft.server.v1_16_R1.DataWatcherSerializer;
import net.minecraft.server.v1_16_R1.IChatBaseComponent;
import net.minecraft.server.v1_16_R1.ItemStack;
import java.util.Optional;
class DataWatcherKey<T> {
private static final DataWatcherSerializer<Byte> BYTE_SERIALIZER = DataWatcherRegistry.a;
private static final DataWatcherSerializer<Integer> INT_SERIALIZER = DataWatcherRegistry.b;
private static final DataWatcherSerializer<Boolean> BOOLEAN_SERIALIZER = DataWatcherRegistry.i;
private static final DataWatcherSerializer<ItemStack> ITEM_STACK_SERIALIZER = DataWatcherRegistry.g;
private static final DataWatcherSerializer<Optional<IChatBaseComponent>> OPTIONAL_CHAT_COMPONENT_SERIALIZER = DataWatcherRegistry.f;
static final DataWatcherKey<Byte> ENTITY_STATUS = new DataWatcherKey<>(0, BYTE_SERIALIZER);
static final DataWatcherKey<Optional<IChatBaseComponent>> CUSTOM_NAME = new DataWatcherKey<>(2, OPTIONAL_CHAT_COMPONENT_SERIALIZER);
static final DataWatcherKey<Boolean> CUSTOM_NAME_VISIBILITY = new DataWatcherKey<>(3, BOOLEAN_SERIALIZER);
static final DataWatcherKey<ItemStack> ITEM_STACK = new DataWatcherKey<>(7, ITEM_STACK_SERIALIZER);
static final DataWatcherKey<Byte> ARMOR_STAND_STATUS = new DataWatcherKey<>(14, BYTE_SERIALIZER);
static final DataWatcherKey<Integer> SLIME_SIZE = new DataWatcherKey<>(15, INT_SERIALIZER);
private final int index;
private final DataWatcherSerializer<T> serializer;
private final int serializerTypeID;
private DataWatcherKey(int index, DataWatcherSerializer<T> 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<T> getSerializer() {
return serializer;
}
int getSerializerTypeID() {
return serializerTypeID;
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_16_R1;
import me.filoghost.fcommons.Strings;
import net.minecraft.server.v1_16_R1.IChatBaseComponent;
import org.bukkit.craftbukkit.v1_16_R1.inventory.CraftItemStack;
import org.bukkit.craftbukkit.v1_16_R1.util.CraftChatMessage;
import org.bukkit.inventory.ItemStack;
import java.util.Optional;
abstract class DataWatcherPacketBuilder<T> {
private final PacketByteBuffer packetByteBuffer;
DataWatcherPacketBuilder(PacketByteBuffer packetByteBuffer) {
this.packetByteBuffer = packetByteBuffer;
}
DataWatcherPacketBuilder<T> setInvisible() {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.ENTITY_STATUS, (byte) 0x20); // Invisible
return this;
}
DataWatcherPacketBuilder<T> setArmorStandMarker() {
setInvisible();
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.ARMOR_STAND_STATUS, (byte) (0x01 | 0x08 | 0x10)); // Small, no base plate, marker
return this;
}
DataWatcherPacketBuilder<T> setCustomName(String customName) {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.CUSTOM_NAME, getCustomNameDataWatcherValue(customName));
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.CUSTOM_NAME_VISIBILITY, !Strings.isEmpty(customName));
return this;
}
private Optional<IChatBaseComponent> getCustomNameDataWatcherValue(String customName) {
customName = Strings.truncate(customName, 300);
if (!Strings.isEmpty(customName)) {
return Optional.of(CraftChatMessage.fromString(customName, false)[0]);
} else {
return Optional.empty();
}
}
DataWatcherPacketBuilder<T> setItemStack(ItemStack itemStack) {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.ITEM_STACK, CraftItemStack.asNMSCopy(itemStack));
return this;
}
DataWatcherPacketBuilder<T> setSlimeSmall() {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.SLIME_SIZE, 1);
return this;
}
T build() {
packetByteBuffer.writeDataWatcherEntriesEnd();
return createPacket(packetByteBuffer);
}
abstract T createPacket(PacketByteBuffer packetByteBuffer);
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_16_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_16_R1.Packet;
import net.minecraft.server.v1_16_R1.PacketPlayOutEntityDestroy;
class EntityDestroyNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntityDestroyNMSPacket(EntityID... entityIDs) {
int[] entityIDsArray = new int[entityIDs.length];
for (int i = 0; i < entityIDs.length; i++) {
entityIDsArray[i] = entityIDs[i].getNumericID();
}
this.rawPacket = new PacketPlayOutEntityDestroy(entityIDsArray);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_16_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_16_R1.Packet;
import net.minecraft.server.v1_16_R1.PacketPlayOutSpawnEntityLiving;
class EntityLivingSpawnNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntityLivingSpawnNMSPacket(EntityID entityID, int entityTypeID, double positionX, double positionY, double positionZ) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(entityID.getNumericID());
packetByteBuffer.writeUUID(entityID.getUUID());
packetByteBuffer.writeVarInt(entityTypeID);
// Position
packetByteBuffer.writeDouble(positionX);
packetByteBuffer.writeDouble(positionY);
packetByteBuffer.writeDouble(positionZ);
// Rotation
packetByteBuffer.writeByte(0);
packetByteBuffer.writeByte(0);
// Head rotation
packetByteBuffer.writeByte(0);
// Velocity
packetByteBuffer.writeShort(0);
packetByteBuffer.writeShort(0);
packetByteBuffer.writeShort(0);
this.rawPacket = writeData(new PacketPlayOutSpawnEntityLiving(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_16_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_16_R1.Packet;
import net.minecraft.server.v1_16_R1.PacketPlayOutEntityMetadata;
class EntityMetadataNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
private EntityMetadataNMSPacket(PacketByteBuffer packetByteBuffer) {
this.rawPacket = writeData(new PacketPlayOutEntityMetadata(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
public static DataWatcherPacketBuilder<EntityMetadataNMSPacket> builder(EntityID entityID) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(entityID.getNumericID());
return new Builder(packetByteBuffer);
}
private static class Builder extends DataWatcherPacketBuilder<EntityMetadataNMSPacket> {
private Builder(PacketByteBuffer packetByteBuffer) {
super(packetByteBuffer);
}
@Override
EntityMetadataNMSPacket createPacket(PacketByteBuffer packetByteBuffer) {
return new EntityMetadataNMSPacket(packetByteBuffer);
}
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_16_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_16_R1.Packet;
import net.minecraft.server.v1_16_R1.PacketPlayOutMount;
class EntityMountNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntityMountNMSPacket(EntityID vehicleEntityID, EntityID passengerEntityID) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(vehicleEntityID.getNumericID());
packetByteBuffer.writeIntArray(passengerEntityID.getNumericID());
this.rawPacket = writeData(new PacketPlayOutMount(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_16_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_16_R1.Packet;
import net.minecraft.server.v1_16_R1.PacketPlayOutSpawnEntity;
class EntitySpawnNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntitySpawnNMSPacket(EntityID entityID, int entityTypeID, double positionX, double positionY, double positionZ) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(entityID.getNumericID());
packetByteBuffer.writeUUID(entityID.getUUID());
packetByteBuffer.writeVarInt(entityTypeID);
// Position
packetByteBuffer.writeDouble(positionX);
packetByteBuffer.writeDouble(positionY);
packetByteBuffer.writeDouble(positionZ);
// 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 = writeData(new PacketPlayOutSpawnEntity(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_16_R1;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import net.minecraft.server.v1_16_R1.Packet;
import net.minecraft.server.v1_16_R1.PacketPlayOutEntityTeleport;
class EntityTeleportNMSPacket extends VersionNMSPacket {
private final Packet<?> rawPacket;
EntityTeleportNMSPacket(EntityID entityID, double positionX, double positionY, double positionZ) {
PacketByteBuffer packetByteBuffer = PacketByteBuffer.get();
packetByteBuffer.writeVarInt(entityID.getNumericID());
// Position
packetByteBuffer.writeDouble(positionX);
packetByteBuffer.writeDouble(positionY);
packetByteBuffer.writeDouble(positionZ);
// Rotation
packetByteBuffer.writeByte(0);
packetByteBuffer.writeByte(0);
// On ground
packetByteBuffer.writeBoolean(false);
this.rawPacket = writeData(new PacketPlayOutEntityTeleport(), packetByteBuffer);
}
@Override
Packet<?> getRawPacket() {
return rawPacket;
}
}

View File

@ -0,0 +1,14 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_16_R1;
class EntityTypeID {
static final int ARMOR_STAND = 1;
static final int ITEM = 37;
static final int SLIME = 75;
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_16_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.common.nms.NMSErrors;
import me.filoghost.holographicdisplays.common.nms.PacketListener;
import net.minecraft.server.v1_16_R1.PacketPlayInUseEntity;
import org.bukkit.entity.Player;
public class InboundPacketHandler extends ChannelInboundHandlerAdapter {
public static final String HANDLER_NAME = "holographic_displays_listener";
private static final ReflectField<Integer> ENTITY_ID_FIELD = ReflectField.lookup(int.class, PacketPlayInUseEntity.class, "a");
private final Player player;
private final PacketListener packetListener;
public 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);
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_16_R1;
import io.netty.buffer.Unpooled;
import net.minecraft.server.v1_16_R1.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 writeUUID(UUID uuid) {
super.a(uuid);
}
void writeIntArray(int... array) {
super.a(array);
}
<T> void writeDataWatcherEntry(DataWatcherKey<T> key, T value) {
writeByte(key.getIndex());
writeVarInt(key.getSerializerTypeID());
key.getSerializer().a(this, value);
}
void writeDataWatcherEntriesEnd() {
writeByte(255);
}
}

View File

@ -5,32 +5,97 @@
*/
package me.filoghost.holographicdisplays.nms.v1_16_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.common.nms.EntityID;
import me.filoghost.holographicdisplays.common.nms.FallbackEntityIDGenerator;
import me.filoghost.holographicdisplays.common.nms.NMSErrors;
import me.filoghost.holographicdisplays.common.nms.NMSManager;
import me.filoghost.holographicdisplays.common.nms.NMSPacketList;
import me.filoghost.holographicdisplays.common.nms.PacketListener;
import net.minecraft.server.v1_16_R1.Entity;
import net.minecraft.server.v1_16_R1.NetworkManager;
import net.minecraft.server.v1_16_R1.PlayerConnection;
import org.bukkit.craftbukkit.v1_16_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<AtomicInteger> ENTITY_ID_COUNTER_FIELD = ReflectField.lookup(AtomicInteger.class, Entity.class, "entityCount");
private final Supplier<Integer> entityIDGenerator;
public VersionNMSManager(ErrorCollector errorCollector) {
this.entityIDGenerator = getEntityIDGenerator(errorCollector);
// Force initialization of class to eventually throw exceptions early
DataWatcherKey.ENTITY_STATUS.getIndex();
}
private Supplier<Integer> 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();
}
}
@Override
public EntityID newEntityID() {
return null;
return new EntityID(entityIDGenerator);
}
@Override
public NMSPacketList createPacketList() {
return null;
return new VersionNMSPacketList();
}
@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<ChannelPipeline> pipelineModifierTask) {
PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection;
NetworkManager networkManager = playerConnection.a();
Channel channel = networkManager.channel;
channel.eventLoop().execute(() -> {
try {
pipelineModifierTask.accept(channel.pipeline());
} catch (Exception e) {
Log.warning(NMSErrors.EXCEPTION_MODIFYING_CHANNEL_PIPELINE, e);
}
});
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_16_R1;
import me.filoghost.holographicdisplays.common.nms.NMSPacket;
import net.minecraft.server.v1_16_R1.Packet;
import org.bukkit.craftbukkit.v1_16_R1.entity.CraftPlayer;
import org.bukkit.entity.Player;
import java.io.IOException;
abstract class VersionNMSPacket implements NMSPacket {
@Override
public void sendTo(Player player) {
((CraftPlayer) player).getHandle().playerConnection.sendPacket(getRawPacket());
}
abstract Packet<?> getRawPacket();
protected static <T extends Packet<?>> T writeData(T packet, PacketByteBuffer packetByteBuffer) {
try {
packet.a(packetByteBuffer);
return packet;
} catch (IOException e) {
// Never thrown by the implementations
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_16_R1;
import me.filoghost.holographicdisplays.common.nms.AbstractNMSPacketList;
import me.filoghost.holographicdisplays.common.nms.EntityID;
import me.filoghost.holographicdisplays.common.nms.IndividualCustomName;
import me.filoghost.holographicdisplays.common.nms.IndividualNMSPacket;
import org.bukkit.inventory.ItemStack;
class VersionNMSPacketList extends AbstractNMSPacketList {
@Override
public void addArmorStandSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ) {
add(new EntityLivingSpawnNMSPacket(entityID, EntityTypeID.ARMOR_STAND, positionX, positionY, positionZ));
add(EntityMetadataNMSPacket.builder(entityID)
.setArmorStandMarker()
.build()
);
}
@Override
public void addArmorStandSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ, String customName) {
add(new EntityLivingSpawnNMSPacket(entityID, EntityTypeID.ARMOR_STAND, positionX, positionY, positionZ));
add(EntityMetadataNMSPacket.builder(entityID)
.setArmorStandMarker()
.setCustomName(customName)
.build()
);
}
@Override
public void addArmorStandSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ, IndividualCustomName individualCustomName) {
add(new EntityLivingSpawnNMSPacket(entityID, EntityTypeID.ARMOR_STAND, positionX, positionY, positionZ));
add(new IndividualNMSPacket(player -> EntityMetadataNMSPacket.builder(entityID)
.setArmorStandMarker()
.setCustomName(individualCustomName.get(player))
.build()
));
}
@Override
public void addArmorStandNameChangePackets(EntityID entityID, String customName) {
add(EntityMetadataNMSPacket.builder(entityID)
.setCustomName(customName)
.build()
);
}
@Override
public void addArmorStandNameChangePackets(EntityID entityID, IndividualCustomName individualCustomName) {
add(new IndividualNMSPacket(player -> EntityMetadataNMSPacket.builder(entityID)
.setCustomName(individualCustomName.get(player))
.build()
));
}
@Override
public void addItemSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ, ItemStack itemStack) {
add(new EntitySpawnNMSPacket(entityID, EntityTypeID.ITEM, positionX, positionY, positionZ));
add(EntityMetadataNMSPacket.builder(entityID)
.setItemStack(itemStack)
.build()
);
}
@Override
public void addItemStackChangePackets(EntityID entityID, ItemStack itemStack) {
add(EntityMetadataNMSPacket.builder(entityID)
.setItemStack(itemStack)
.build()
);
}
@Override
public void addSlimeSpawnPackets(EntityID entityID, double positionX, double positionY, double positionZ) {
add(new EntityLivingSpawnNMSPacket(entityID, EntityTypeID.SLIME, positionX, positionY, positionZ));
add(EntityMetadataNMSPacket.builder(entityID)
.setInvisible()
.setSlimeSmall() // Required for a correct client-side collision box
.build()
);
}
@Override
public void addEntityDestroyPackets(EntityID... entityIDs) {
add(new EntityDestroyNMSPacket(entityIDs));
}
@Override
public void addTeleportPackets(EntityID entityID, double positionX, double positionY, double positionZ) {
add(new EntityTeleportNMSPacket(entityID, positionX, positionY, positionZ));
}
@Override
public void addMountPackets(EntityID vehicleEntityID, EntityID passengerEntityID) {
add(new EntityMountNMSPacket(vehicleEntityID, passengerEntityID));
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_16_R2;
import io.netty.handler.codec.EncoderException;
import net.minecraft.server.v1_16_R2.DataWatcherRegistry;
import net.minecraft.server.v1_16_R2.DataWatcherSerializer;
import net.minecraft.server.v1_16_R2.IChatBaseComponent;
import net.minecraft.server.v1_16_R2.ItemStack;
import java.util.Optional;
class DataWatcherKey<T> {
private static final DataWatcherSerializer<Byte> BYTE_SERIALIZER = DataWatcherRegistry.a;
private static final DataWatcherSerializer<Integer> INT_SERIALIZER = DataWatcherRegistry.b;
private static final DataWatcherSerializer<Boolean> BOOLEAN_SERIALIZER = DataWatcherRegistry.i;
private static final DataWatcherSerializer<ItemStack> ITEM_STACK_SERIALIZER = DataWatcherRegistry.g;
private static final DataWatcherSerializer<Optional<IChatBaseComponent>> OPTIONAL_CHAT_COMPONENT_SERIALIZER = DataWatcherRegistry.f;
static final DataWatcherKey<Byte> ENTITY_STATUS = new DataWatcherKey<>(0, BYTE_SERIALIZER);
static final DataWatcherKey<Optional<IChatBaseComponent>> CUSTOM_NAME = new DataWatcherKey<>(2, OPTIONAL_CHAT_COMPONENT_SERIALIZER);
static final DataWatcherKey<Boolean> CUSTOM_NAME_VISIBILITY = new DataWatcherKey<>(3, BOOLEAN_SERIALIZER);
static final DataWatcherKey<ItemStack> ITEM_STACK = new DataWatcherKey<>(7, ITEM_STACK_SERIALIZER);
static final DataWatcherKey<Byte> ARMOR_STAND_STATUS = new DataWatcherKey<>(14, BYTE_SERIALIZER);
static final DataWatcherKey<Integer> SLIME_SIZE = new DataWatcherKey<>(15, INT_SERIALIZER);
private final int index;
private final DataWatcherSerializer<T> serializer;
private final int serializerTypeID;
private DataWatcherKey(int index, DataWatcherSerializer<T> 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<T> getSerializer() {
return serializer;
}
int getSerializerTypeID() {
return serializerTypeID;
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.holographicdisplays.nms.v1_16_R2;
import me.filoghost.fcommons.Strings;
import net.minecraft.server.v1_16_R2.IChatBaseComponent;
import org.bukkit.craftbukkit.v1_16_R2.inventory.CraftItemStack;
import org.bukkit.craftbukkit.v1_16_R2.util.CraftChatMessage;
import org.bukkit.inventory.ItemStack;
import java.util.Optional;
abstract class DataWatcherPacketBuilder<T> {
private final PacketByteBuffer packetByteBuffer;
DataWatcherPacketBuilder(PacketByteBuffer packetByteBuffer) {
this.packetByteBuffer = packetByteBuffer;
}
DataWatcherPacketBuilder<T> setInvisible() {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.ENTITY_STATUS, (byte) 0x20); // Invisible
return this;
}
DataWatcherPacketBuilder<T> setArmorStandMarker() {
setInvisible();
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.ARMOR_STAND_STATUS, (byte) (0x01 | 0x08 | 0x10)); // Small, no base plate, marker
return this;
}
DataWatcherPacketBuilder<T> setCustomName(String customName) {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.CUSTOM_NAME, getCustomNameDataWatcherValue(customName));
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.CUSTOM_NAME_VISIBILITY, !Strings.isEmpty(customName));
return this;
}
private Optional<IChatBaseComponent> getCustomNameDataWatcherValue(String customName) {
customName = Strings.truncate(customName, 300);
if (!Strings.isEmpty(customName)) {
return Optional.of(CraftChatMessage.fromString(customName, false)[0]);
} else {
return Optional.empty();
}
}
DataWatcherPacketBuilder<T> setItemStack(ItemStack itemStack) {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.ITEM_STACK, CraftItemStack.asNMSCopy(itemStack));
return this;
}
DataWatcherPacketBuilder<T> setSlimeSmall() {
packetByteBuffer.writeDataWatcherEntry(DataWatcherKey.SLIME_SIZE, 1);
return this;
}
T build() {
packetByteBuffer.writeDataWatcherEntriesEnd();
return createPacket(packetByteBuffer);
}
abstract T createPacket(PacketByteBuffer packetByteBuffer);
}

Some files were not shown because too many files have changed in this diff Show More