diff --git a/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundSetEntityDataPacket.java b/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundSetEntityDataPacket.java new file mode 100644 index 0000000..b52ffef --- /dev/null +++ b/api/src/main/java/de/oliver/fancysitula/api/packets/FS_ClientboundSetEntityDataPacket.java @@ -0,0 +1,44 @@ +package de.oliver.fancysitula.api.packets; + +import java.util.List; + +public abstract class FS_ClientboundSetEntityDataPacket implements FS_ClientboundPacket { + + protected int entityId; + protected List entityData; + + public FS_ClientboundSetEntityDataPacket(int entityId, List entityData) { + this.entityId = entityId; + this.entityData = entityData; + } + + public int getEntityId() { + return entityId; + } + + public void setEntityId(int entityId) { + this.entityId = entityId; + } + + public List getEntityData() { + return entityData; + } + + public void setEntityData(List entityData) { + this.entityData = entityData; + } + + /** + * @param accessor can be found in {@link de.oliver.fancysitula.api.utils.entityData} + * @param value must be the correct type for the accessor (see accessor javadoc) + */ + public record EntityData(EntityDataAccessor accessor, Object value) { + } + + /** + * @param entityClassName the class name of the entity (e.g. "net.minecraft.world.entity.Display$TextDisplay") + * @param accessorFieldName the field name of the accessor (typically starts with "DATA_" and ends with "_ID") + */ + public record EntityDataAccessor(String entityClassName, String accessorFieldName) { + } +} diff --git a/api/src/main/java/de/oliver/fancysitula/api/utils/entityData/TextDisplayData.java b/api/src/main/java/de/oliver/fancysitula/api/utils/entityData/TextDisplayData.java new file mode 100644 index 0000000..c270824 --- /dev/null +++ b/api/src/main/java/de/oliver/fancysitula/api/utils/entityData/TextDisplayData.java @@ -0,0 +1,41 @@ +package de.oliver.fancysitula.api.utils.entityData; + +import de.oliver.fancysitula.api.packets.FS_ClientboundSetEntityDataPacket; + +public enum TextDisplayData { + /** + * Use {@link net.kyori.adventure.text.Component} as value + */ + TEXT(new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Display$TextDisplay", "DATA_TEXT_ID")), + + /** + * Use {@link Integer} as value + */ + LINE_WIDTH(new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Display$TextDisplay", "DATA_LINE_WIDTH_ID")), + + /** + * Use {@link Integer} as value + */ + BACKGROUND(new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Display$TextDisplay", "DATA_BACKGROUND_COLOR_ID")), + + /** + * Use {@link Byte} as value + */ + TEXT_OPACITY(new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Display$TextDisplay", "DATA_TEXT_OPACITY_ID")), + + /** + * Use {@link Byte} as value + */ + STYLE_FLAGS(new FS_ClientboundSetEntityDataPacket.EntityDataAccessor("net.minecraft.world.entity.Display$TextDisplay", "DATA_STYLE_FLAGS_ID")), + ; + + private final FS_ClientboundSetEntityDataPacket.EntityDataAccessor accessor; + + TextDisplayData(FS_ClientboundSetEntityDataPacket.EntityDataAccessor accessor) { + this.accessor = accessor; + } + + public FS_ClientboundSetEntityDataPacket.EntityDataAccessor get() { + return accessor; + } +} diff --git a/api/src/main/java/de/oliver/fancysitula/api/utils/reflections/ReflectionUtils.java b/api/src/main/java/de/oliver/fancysitula/api/utils/reflections/ReflectionUtils.java index b852752..a708eda 100644 --- a/api/src/main/java/de/oliver/fancysitula/api/utils/reflections/ReflectionUtils.java +++ b/api/src/main/java/de/oliver/fancysitula/api/utils/reflections/ReflectionUtils.java @@ -63,4 +63,10 @@ public static void setFinalField(Object target, String fieldName, Object value) unsafe.putObject(target, offset, value); } } + + public static T getStaticField(Class clazz, String fieldName) throws NoSuchFieldException, IllegalAccessException { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + return (T) field.get(null); + } } diff --git a/factories/src/main/java/de/oliver/fancysitula/factories/PacketFactory.java b/factories/src/main/java/de/oliver/fancysitula/factories/PacketFactory.java index 8ba7243..3b09ba7 100644 --- a/factories/src/main/java/de/oliver/fancysitula/factories/PacketFactory.java +++ b/factories/src/main/java/de/oliver/fancysitula/factories/PacketFactory.java @@ -236,4 +236,30 @@ public FS_ClientboundRotateHeadPacket createRotateHeadPacket(ServerVersion serve public FS_ClientboundRotateHeadPacket createRotateHeadPacket(int entityId, float headYaw) { return createRotateHeadPacket(ServerVersion.getCurrentVersion(), entityId, headYaw); } + + /** + * Creates a new FS_ClientboundSetEntityDataPacket instance based on the server version + * + * @param entityId ID of the entity to set the data of + * @param entityData List of {@link FS_ClientboundSetEntityDataPacket.EntityData} to set + */ + public FS_ClientboundSetEntityDataPacket createSetEntityDataPacket( + ServerVersion serverVersion, int entityId, List entityData) { + switch (serverVersion) { + case v1_20_6 -> { + return new de.oliver.fancysitula.versions.v1_20_6.packets.ClientboundSetEntityDataPacketImpl(entityId, entityData); + } + default -> throw new IllegalArgumentException("Unsupported server version: " + serverVersion.getVersion()); + } + } + + /** + * Creates a new FS_ClientboundSetEntityDataPacket instance based on the current server version + * + * @param entityId ID of the entity to set the data of + * @param entityData List of {@link FS_ClientboundSetEntityDataPacket.EntityData} to set + */ + public FS_ClientboundSetEntityDataPacket createSetEntityDataPacket(int entityId, List entityData) { + return createSetEntityDataPacket(ServerVersion.getCurrentVersion(), entityId, entityData); + } } diff --git a/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundSetEntityDataPacketImpl.java b/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundSetEntityDataPacketImpl.java new file mode 100644 index 0000000..e14f73c --- /dev/null +++ b/implementations/1_20_6/src/main/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundSetEntityDataPacketImpl.java @@ -0,0 +1,52 @@ +package de.oliver.fancysitula.versions.v1_20_6.packets; + +import de.oliver.fancysitula.api.entities.FS_RealPlayer; +import de.oliver.fancysitula.api.packets.FS_ClientboundSetEntityDataPacket; +import de.oliver.fancysitula.api.utils.reflections.ReflectionUtils; +import de.oliver.fancysitula.versions.v1_20_6.utils.VanillaPlayerAdapter; +import io.papermc.paper.adventure.PaperAdventure; +import net.kyori.adventure.text.Component; +import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.server.level.ServerPlayer; + +import java.util.ArrayList; +import java.util.List; + +public class ClientboundSetEntityDataPacketImpl extends FS_ClientboundSetEntityDataPacket { + + public ClientboundSetEntityDataPacketImpl(int entityId, List entityData) { + super(entityId, entityData); + } + + @Override + public Object createPacket() { + List> dataValues = new ArrayList<>(); + for (EntityData data : entityData) { + try { + Class entityClass = Class.forName(data.accessor().entityClassName()); + net.minecraft.network.syncher.EntityDataAccessor accessor = ReflectionUtils.getStaticField(entityClass, data.accessor().accessorFieldName()); + + Object vanillaValue = data.value(); + + if (data.value() instanceof Component c) { + vanillaValue = PaperAdventure.asVanilla(c); + } + + dataValues.add(SynchedEntityData.DataValue.create(accessor, vanillaValue)); + } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + } + } + + return new ClientboundSetEntityDataPacket(entityId, dataValues); + } + + @Override + public void send(FS_RealPlayer player) { + ClientboundSetEntityDataPacket packet = (ClientboundSetEntityDataPacket) createPacket(); + + ServerPlayer vanillaPlayer = VanillaPlayerAdapter.asVanilla(player.getBukkitPlayer()); + vanillaPlayer.connection.send(packet); + } +} diff --git a/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundSetEntityDataPacketImplTest.java b/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundSetEntityDataPacketImplTest.java new file mode 100644 index 0000000..793f3ad --- /dev/null +++ b/implementations/1_20_6/src/test/java/de/oliver/fancysitula/versions/v1_20_6/packets/ClientboundSetEntityDataPacketImplTest.java @@ -0,0 +1,28 @@ +package de.oliver.fancysitula.versions.v1_20_6.packets; + +import de.oliver.fancysitula.api.packets.FS_ClientboundSetEntityDataPacket; +import de.oliver.fancysitula.api.utils.entityData.TextDisplayData; +import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; + +import java.util.List; + +class ClientboundSetEntityDataPacketImplTest { + + //TODO: Fix this test (using registry) +// @Test + void createPacket() { + int entityId = 712; + List entityData = List.of( + new FS_ClientboundSetEntityDataPacket.EntityData( + TextDisplayData.TEXT.get(), + "Hello, World!" + ) + ); + + ClientboundSetEntityDataPacketImpl packet = new ClientboundSetEntityDataPacketImpl(entityId, entityData); + ClientboundSetEntityDataPacket createdPacket = (ClientboundSetEntityDataPacket) packet.createPacket(); + + assert createdPacket.id() == entityId; + assert createdPacket.packedItems().size() == 1; + } +} \ No newline at end of file diff --git a/test_plugin/src/main/java/de/oliver/fancysitula/commands/FancySitulaCMD.java b/test_plugin/src/main/java/de/oliver/fancysitula/commands/FancySitulaCMD.java index 09499af..67a6077 100644 --- a/test_plugin/src/main/java/de/oliver/fancysitula/commands/FancySitulaCMD.java +++ b/test_plugin/src/main/java/de/oliver/fancysitula/commands/FancySitulaCMD.java @@ -1,9 +1,9 @@ package de.oliver.fancysitula.commands; import de.oliver.fancysitula.api.entities.FS_RealPlayer; -import de.oliver.fancysitula.api.packets.FS_ClientboundPlayerInfoUpdatePacket; +import de.oliver.fancysitula.api.packets.FS_ClientboundSetEntityDataPacket; import de.oliver.fancysitula.api.utils.FS_GameProfile; -import de.oliver.fancysitula.api.utils.FS_GameType; +import de.oliver.fancysitula.api.utils.entityData.TextDisplayData; import de.oliver.fancysitula.factories.FancySitula; import net.kyori.adventure.text.Component; import org.bukkit.command.Command; @@ -12,7 +12,6 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -import java.util.EnumSet; import java.util.List; import java.util.UUID; @@ -36,29 +35,29 @@ public boolean execute(@NotNull CommandSender sender, @NotNull String s, @NotNul FS_GameProfile fakeProfile = new FS_GameProfile(UUID.randomUUID(), "FakePlayer"); // PlayerInfoUpdatePacket - FS_ClientboundPlayerInfoUpdatePacket.Entry entry = new FS_ClientboundPlayerInfoUpdatePacket.Entry( - fakeProfile.getUUID(), - fakeProfile, - true, - 42, - FS_GameType.SURVIVAL, - Component.text("FakePlayer1123") - ); - - EnumSet actions = EnumSet.of(FS_ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER); - actions.add(FS_ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME); - actions.add(FS_ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED); - - FancySitula.PACKET_FACTORY - .createPlayerInfoUpdatePacket(actions, List.of(entry)) - .send(fsPlayer); +// FS_ClientboundPlayerInfoUpdatePacket.Entry entry = new FS_ClientboundPlayerInfoUpdatePacket.Entry( +// fakeProfile.getUUID(), +// fakeProfile, +// true, +// 42, +// FS_GameType.SURVIVAL, +// Component.text("FakePlayer1123") +// ); +// +// EnumSet actions = EnumSet.of(FS_ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER); +// actions.add(FS_ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME); +// actions.add(FS_ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED); +// +// FancySitula.PACKET_FACTORY +// .createPlayerInfoUpdatePacket(actions, List.of(entry)) +// .send(fsPlayer); // AddEntityPacket FancySitula.PACKET_FACTORY .createAddEntityPacket( 420, fakeProfile.getUUID(), - EntityType.PLAYER, + EntityType.TEXT_DISPLAY, p.getLocation().getX(), p.getLocation().getY(), p.getLocation().getZ(), @@ -72,6 +71,22 @@ public boolean execute(@NotNull CommandSender sender, @NotNull String s, @NotNul ) .send(fsPlayer); + + FancySitula.PACKET_FACTORY + .createSetEntityDataPacket( + 420, + List.of( + new FS_ClientboundSetEntityDataPacket.EntityData( + TextDisplayData.TEXT.get(), + Component.text("Hello world") + ), + new FS_ClientboundSetEntityDataPacket.EntityData( + TextDisplayData.BACKGROUND.get(), + 0xFF00FF00 + ) + ) + ) + .send(fsPlayer); return true; } }