diff --git a/build.gradle.kts b/build.gradle.kts index 4330e72..c407124 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,7 @@ plugins { `java-library` + id("xyz.jpenilla.run-paper") version "3.0.2" + id("io.papermc.paperweight.userdev") version "2.0.0-beta.21" //Access to Minecraft NMS + Paper API packages } group = "xyz.holocons.mc" @@ -16,8 +18,8 @@ repositories { } dependencies { - compileOnly("io.papermc.paper:paper-api:1.21.1-R0.1-SNAPSHOT") - compileOnly("com.comphenix.protocol:ProtocolLib:5.3.0") + paperweight.paperDevBundle("1.21.11-R0.1-SNAPSHOT") + compileOnly("net.dmulloy2:ProtocolLib:5.4.0") } tasks { @@ -50,4 +52,10 @@ tasks { expand(pluginProperties) } } + runServer { + // Configure the Minecraft version for our task. + // This is the only required configuration besides applying the plugin. + // Your plugin's jar (or shadowJar if present) will be used automatically. + minecraftVersion("1.21.11") + } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 9bbc975..8bdaf60 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37f853b..c61a118 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index faf9300..ef07e01 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -114,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9d21a21..db3a6ac 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/settings.gradle.kts b/settings.gradle.kts index c314ecb..d908fcc 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,6 +1,7 @@ pluginManagement { repositories { gradlePluginPortal() + maven("https://repo.papermc.io/repository/maven-public/") } } diff --git a/src/main/java/xyz/holocons/mc/waypoints/Hologram.java b/src/main/java/xyz/holocons/mc/waypoints/Hologram.java index 8695d58..4f5fa4d 100644 --- a/src/main/java/xyz/holocons/mc/waypoints/Hologram.java +++ b/src/main/java/xyz/holocons/mc/waypoints/Hologram.java @@ -1,81 +1,111 @@ package xyz.holocons.mc.waypoints; +import java.lang.reflect.Field; +import java.util.List; import java.util.Optional; import java.util.UUID; -import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.util.Vector; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.events.PacketContainer; -import com.comphenix.protocol.wrappers.WrappedChatComponent; -import com.comphenix.protocol.wrappers.WrappedDataValue; -import com.comphenix.protocol.wrappers.WrappedDataWatcher.Registry; +import com.google.gson.JsonParser; +import com.mojang.serialization.JsonOps; import it.unimi.dsi.fastutil.ints.IntList; -import it.unimi.dsi.fastutil.objects.ObjectList; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.ComponentSerialization; +import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; +import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket; +import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.decoration.ArmorStand; +import net.minecraft.world.phys.Vec3; public record Hologram(long chunkKey, UUID uniqueId) { + // These fields are private in NMS + private static final EntityDataAccessor DATA_SHARED_FLAGS_ID = getAccessor(Entity.class, "DATA_SHARED_FLAGS_ID"); + private static final EntityDataAccessor> DATA_CUSTOM_NAME = getAccessor(Entity.class, "DATA_CUSTOM_NAME"); + private static final EntityDataAccessor DATA_CUSTOM_NAME_VISIBLE = getAccessor(Entity.class, "DATA_CUSTOM_NAME_VISIBLE"); + private static final EntityDataAccessor DATA_CLIENT_FLAGS = getAccessor(ArmorStand.class, "DATA_CLIENT_FLAGS"); + + @SuppressWarnings("unchecked") + private static EntityDataAccessor getAccessor(Class clazz, String name) { + try { + Field field = clazz.getDeclaredField(name); + field.setAccessible(true); + return (EntityDataAccessor) field.get(null); + } catch (Exception e) { + throw new RuntimeException("Failed to access " + name, e); + } + } + public Hologram(Waypoint waypoint, Player player) { this(waypoint.getChunkKey(), player.getUniqueId()); } private static final Vector HOLOGRAM_POSITION_OFFSET = new Vector(0.5, 1.6, 0.5); - // https://nms.screamingsandals.org/1.19.3/net/minecraft/network/protocol/game/ClientboundAddEntityPacket.html - // https://wiki.vg/Protocol#Spawn_Entity + // https://nms.screamingsandals.org/1.21.11/net/minecraft/network/protocol/game/ClientboundAddEntityPacket.html + // https://minecraft.wiki/w/Java_Edition_protocol/Packets#Spawn_Entity public static PacketContainer getSpawnPacket(int entityId, UUID uniqueId, Waypoint waypoint) { var location = waypoint.getLocation().add(HOLOGRAM_POSITION_OFFSET); - var packet = new PacketContainer(PacketType.Play.Server.SPAWN_ENTITY); - packet.getIntegers() - .write(0, entityId) // id - .write(1, 0) // xa - .write(2, 0) // ya - .write(3, 0) // za - .write(4, 0); // data - packet.getUUIDs() - .write(0, uniqueId); // uuid - packet.getEntityTypeModifier() - .write(0, EntityType.ARMOR_STAND); // type - packet.getDoubles() - .write(0, location.getX()) // x - .write(1, location.getY()) // y - .write(2, location.getZ()); // z - packet.getBytes() - .write(0, (byte) 0) // xRot - .write(1, (byte) 0) // yRot - .write(2, (byte) 0); // yHeadRot + + // NMS packet + var vanillaPacket = new ClientboundAddEntityPacket( + entityId, + uniqueId, + location.getX(), location.getY(), location.getZ(), + 0F, 0F, + EntityType.ARMOR_STAND, + 0, + Vec3.ZERO, + 0D + ); + + var packet = new PacketContainer(PacketType.Play.Server.SPAWN_ENTITY, vanillaPacket); return packet; } - // https://nms.screamingsandals.org/1.19.3/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.html - // https://wiki.vg/Protocol#Set_Entity_Metadata - // https://wiki.vg/Entity_metadata#Entity_Metadata_Format + // https://nms.screamingsandals.org/1.21.11/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.html + // https://minecraft.wiki/w/Java_Edition_protocol/Packets#Set_Entity_Metadata + // https://minecraft.wiki/w/Java_Edition_protocol/Entity_metadata#Entity_Metadata_Format public static PacketContainer getMetadataPacket(int entityId, Waypoint waypoint) { - var name = WrappedChatComponent.fromJson(GsonComponentSerializer.gson().serialize(waypoint.getDisplayName())); - var metadata = ObjectList.of( - new WrappedDataValue(0, Registry.get(Byte.class), (byte) 0x20), - new WrappedDataValue(2, Registry.getChatComponentSerializer(true), Optional.of(name.getHandle())), - new WrappedDataValue(3, Registry.get(Boolean.class), true), - new WrappedDataValue(15, Registry.get(Byte.class), (byte) (0x08 | 0x10))); - var packet = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA); - packet.getIntegers() - .write(0, entityId); // id - packet.getDataValueCollectionModifier() - .write(0, metadata); // packedItems + + // Convert Adventure Component to Minecraft Component + var json = GsonComponentSerializer.gson().serialize(waypoint.getDisplayName()); + var result = ComponentSerialization.CODEC.parse(JsonOps.INSTANCE, JsonParser.parseString(json)); + Optional optComponent = result.result(); + + // NMS metadata + List> metadata = List.of( + SynchedEntityData.DataValue.create(DATA_SHARED_FLAGS_ID, (byte) 0x20), + SynchedEntityData.DataValue.>create(DATA_CUSTOM_NAME, optComponent), + SynchedEntityData.DataValue.create(DATA_CUSTOM_NAME_VISIBLE, true), + SynchedEntityData.DataValue.create(DATA_CLIENT_FLAGS, (byte) (0x08 | 0x10)) + ); + + // NMS packet + var vanillaPacket = new ClientboundSetEntityDataPacket(entityId, metadata); + + var packet = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA, vanillaPacket); return packet; } - // https://nms.screamingsandals.org/1.19.3/net/minecraft/network/protocol/game/ClientboundRemoveEntitiesPacket.html - // https://wiki.vg/Protocol#Remove_Entities + // https://nms.screamingsandals.org/1.21.11/net/minecraft/network/protocol/game/ClientboundRemoveEntitiesPacket.html + // https://minecraft.wiki/w/Java_Edition_protocol/Packets#Remove_Entities public static PacketContainer getDestroyPacket(int... entityId) { - var entityIds = IntList.of(entityId); - var packet = new PacketContainer(PacketType.Play.Server.ENTITY_DESTROY); - packet.getIntLists() - .write(0, entityIds); // entityIds + + // NMS packet + var vanillaPacket = new ClientboundRemoveEntitiesPacket(IntList.of(entityId)); + + var packet = new PacketContainer(PacketType.Play.Server.ENTITY_DESTROY, vanillaPacket); return packet; } }