diff --git a/src/main/java/com/comphenix/protocol/events/AbstractStructure.java b/src/main/java/com/comphenix/protocol/events/AbstractStructure.java index 47c87caf2..6b3703152 100644 --- a/src/main/java/com/comphenix/protocol/events/AbstractStructure.java +++ b/src/main/java/com/comphenix/protocol/events/AbstractStructure.java @@ -55,6 +55,7 @@ import com.comphenix.protocol.wrappers.WrappedMessageSignature; import com.comphenix.protocol.wrappers.WrappedNumberFormat; import com.comphenix.protocol.wrappers.WrappedParticle; +import com.comphenix.protocol.wrappers.WrappedPositionMoveRotation; import com.comphenix.protocol.wrappers.WrappedProfilePublicKey; import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData; import com.comphenix.protocol.wrappers.WrappedRegistrable; @@ -1297,6 +1298,20 @@ public StructureModifier> getPacketBundles() { )); } + /** + * Retrieve a read/write structure for {@link WrappedPositionMoveRotation} (available since Minecraft 1.21.2). + *

+ * This is used in packets such as {@code ENTITY_TELEPORT} and {@code ENTITY_POSITION_SYNC}. + * + * @return A modifier for PositionMoveRotation fields. + */ + public StructureModifier getPositionMoveRotation() { + return structureModifier.withType( + MinecraftReflection.getPositionMoveRotationClass(), + WrappedPositionMoveRotation.getConverter() + ); + } + /** * Represents an equivalent converter for ItemStack arrays. * @author Kristian diff --git a/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java index 9f474921e..3486265fe 100644 --- a/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -1903,4 +1903,13 @@ public static boolean isMojangMapped() { return isMojangMapped; } + + /** + * Retrieves the PositionMoveRotation class (introduced in 1.21.2). + * + * @return The PositionMoveRotation class. + */ + public static Class getPositionMoveRotationClass() { + return getMinecraftClass("world.entity.PositionMoveRotation"); + } } diff --git a/src/main/java/com/comphenix/protocol/wrappers/WrappedPositionMoveRotation.java b/src/main/java/com/comphenix/protocol/wrappers/WrappedPositionMoveRotation.java new file mode 100644 index 000000000..7bce1b2d5 --- /dev/null +++ b/src/main/java/com/comphenix/protocol/wrappers/WrappedPositionMoveRotation.java @@ -0,0 +1,183 @@ +package com.comphenix.protocol.wrappers; + +import com.comphenix.protocol.reflect.EquivalentConverter; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; +import com.comphenix.protocol.utility.MinecraftReflection; + +import org.bukkit.util.Vector; + +/** + * Wrapper around the NMS {@code PositionMoveRotation} record, introduced in Minecraft 1.21.2. + *

+ * This type is used in packets such as {@code ENTITY_TELEPORT} and {@code ENTITY_POSITION_SYNC} + * to represent combined position, movement delta and rotation. + *

+ * Example usage: + *

{@code
+ * WrappedPositionMoveRotation posRot = WrappedPositionMoveRotation.create(
+ *         new Vector(x, y, z),   // position
+ *         new Vector(0, 0, 0),   // deltaMovement
+ *         yaw,                   // yRot
+ *         pitch                  // xRot
+ * );
+ * packet.getPositionMoveRotation().write(0, posRot);
+ * }
+ */ +public class WrappedPositionMoveRotation extends AbstractWrapper { + + private static Class NMS_CLASS; + private static ConstructorAccessor CONSTRUCTOR; + private static StructureModifier BASE_MODIFIER; + + private final StructureModifier modifier; + + private WrappedPositionMoveRotation(Object handle) { + super(getNmsClass()); + setHandle(handle); + this.modifier = getBaseModifier().withTarget(handle); + } + + private static Class getNmsClass() { + if (NMS_CLASS == null) { + NMS_CLASS = MinecraftReflection.getPositionMoveRotationClass(); + } + return NMS_CLASS; + } + + private static StructureModifier getBaseModifier() { + if (BASE_MODIFIER == null) { + BASE_MODIFIER = new StructureModifier<>(getNmsClass()); + } + return BASE_MODIFIER; + } + + private static ConstructorAccessor getConstructor() { + if (CONSTRUCTOR == null) { + CONSTRUCTOR = Accessors.getConstructorAccessor( + getNmsClass(), + MinecraftReflection.getVec3DClass(), + MinecraftReflection.getVec3DClass(), + float.class, + float.class + ); + } + return CONSTRUCTOR; + } + + /** + * Creates a new {@code WrappedPositionMoveRotation} from the given NMS handle. + * + * @param handle - the NMS {@code PositionMoveRotation} instance. + * @return the wrapper. + */ + public static WrappedPositionMoveRotation fromHandle(Object handle) { + return new WrappedPositionMoveRotation(handle); + } + + /** + * Creates a new {@code WrappedPositionMoveRotation} with the given values. + * + * @param position - the absolute position. + * @param deltaMovement - the movement delta (velocity). + * @param yRot - the yaw rotation. + * @param xRot - the pitch rotation. + * @return a new wrapper instance. + */ + public static WrappedPositionMoveRotation create(Vector position, Vector deltaMovement, float yRot, float xRot) { + EquivalentConverter conv = BukkitConverters.getVectorConverter(); + Object handle = getConstructor().invoke( + conv.getGeneric(position), + conv.getGeneric(deltaMovement), + yRot, + xRot + ); + return new WrappedPositionMoveRotation(handle); + } + + /** + * Retrieves the absolute position component. + * + * @return the position as a Bukkit {@link Vector}. + */ + public Vector getPosition() { + return modifier.withType(MinecraftReflection.getVec3DClass(), BukkitConverters.getVectorConverter()).read(0); + } + + /** + * Sets the absolute position component. + * + * @param position - the new position. + */ + public void setPosition(Vector position) { + modifier.withType(MinecraftReflection.getVec3DClass(), BukkitConverters.getVectorConverter()).write(0, position); + } + + /** + * Retrieves the movement delta (velocity) component. + * + * @return the delta movement as a Bukkit {@link Vector}. + */ + public Vector getDeltaMovement() { + return modifier.withType(MinecraftReflection.getVec3DClass(), BukkitConverters.getVectorConverter()).read(1); + } + + /** + * Sets the movement delta (velocity) component. + * + * @param deltaMovement - the new delta movement. + */ + public void setDeltaMovement(Vector deltaMovement) { + modifier.withType(MinecraftReflection.getVec3DClass(), BukkitConverters.getVectorConverter()).write(1, deltaMovement); + } + + /** + * Retrieves the yaw rotation (y-axis rotation). + * + * @return the yaw as a float. + */ + public float getYRot() { + StructureModifier floats = modifier.withType(float.class); + return floats.read(0); + } + + /** + * Sets the yaw rotation (y-axis rotation). + * + * @param yRot - the new yaw value. + */ + public void setYRot(float yRot) { + modifier.withType(float.class).write(0, yRot); + } + + /** + * Retrieves the pitch rotation (x-axis rotation). + * + * @return the pitch as a float. + */ + public float getXRot() { + StructureModifier floats = modifier.withType(float.class); + return floats.read(1); + } + + /** + * Sets the pitch rotation (x-axis rotation). + * + * @param xRot - the new pitch value. + */ + public void setXRot(float xRot) { + modifier.withType(float.class).write(1, xRot); + } + + /** + * Returns an {@link EquivalentConverter} that converts between {@code WrappedPositionMoveRotation} + * and the underlying NMS handle. + * + * @return the converter. + */ + public static EquivalentConverter getConverter() { + return Converters.handle(AbstractWrapper::getHandle, + WrappedPositionMoveRotation::fromHandle, WrappedPositionMoveRotation.class); + } +} diff --git a/src/test/java/com/comphenix/protocol/wrappers/WrappedPositionMoveRotationTest.java b/src/test/java/com/comphenix/protocol/wrappers/WrappedPositionMoveRotationTest.java new file mode 100644 index 000000000..f5962c366 --- /dev/null +++ b/src/test/java/com/comphenix/protocol/wrappers/WrappedPositionMoveRotationTest.java @@ -0,0 +1,120 @@ +package com.comphenix.protocol.wrappers; + +import com.comphenix.protocol.BukkitInitialization; +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.events.PacketContainer; + +import net.minecraft.world.entity.PositionMoveRotation; +import net.minecraft.world.phys.Vec3; +import org.bukkit.util.Vector; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class WrappedPositionMoveRotationTest { + + @BeforeAll + public static void beforeClass() { + BukkitInitialization.initializeAll(); + } + + @Test + public void testCreateAndRead() { + Vector position = new Vector(1.5, 64.0, -3.5); + Vector delta = new Vector(0.1, -0.05, 0.2); + float yRot = 45.0f; + float xRot = -10.0f; + + WrappedPositionMoveRotation created = WrappedPositionMoveRotation.create(position, delta, yRot, xRot); + + assertEquals(position.getX(), created.getPosition().getX(), 1e-6); + assertEquals(position.getY(), created.getPosition().getY(), 1e-6); + assertEquals(position.getZ(), created.getPosition().getZ(), 1e-6); + + assertEquals(delta.getX(), created.getDeltaMovement().getX(), 1e-6); + assertEquals(delta.getY(), created.getDeltaMovement().getY(), 1e-6); + assertEquals(delta.getZ(), created.getDeltaMovement().getZ(), 1e-6); + + assertEquals(yRot, created.getYRot(), 1e-6f); + assertEquals(xRot, created.getXRot(), 1e-6f); + } + + @Test + public void testEntityPositionSyncPacket() { + PacketContainer packet = new PacketContainer(PacketType.Play.Server.ENTITY_POSITION_SYNC); + + Vector position = new Vector(10.0, 70.0, -5.0); + Vector delta = new Vector(0.0, 0.0, 0.0); + float yRot = 90.0f; + float xRot = 0.0f; + + packet.getPositionMoveRotation().write(0, + WrappedPositionMoveRotation.create(position, delta, yRot, xRot)); + + WrappedPositionMoveRotation result = packet.getPositionMoveRotation().read(0); + + assertNotNull(result); + + assertEquals(position.getX(), result.getPosition().getX(), 1e-6); + assertEquals(position.getY(), result.getPosition().getY(), 1e-6); + assertEquals(position.getZ(), result.getPosition().getZ(), 1e-6); + + assertEquals(delta.getX(), result.getDeltaMovement().getX(), 1e-6); + assertEquals(delta.getY(), result.getDeltaMovement().getY(), 1e-6); + assertEquals(delta.getZ(), result.getDeltaMovement().getZ(), 1e-6); + + assertEquals(yRot, result.getYRot(), 1e-6f); + assertEquals(xRot, result.getXRot(), 1e-6f); + } + + @Test + public void testEntityTeleportPacket() { + PacketContainer packet = new PacketContainer(PacketType.Play.Server.ENTITY_TELEPORT); + + Vector position = new Vector(-100.5, 200.0, 300.75); + Vector delta = new Vector(0.0, 0.0, 0.0); + float yRot = 180.0f; + float xRot = 30.0f; + + packet.getPositionMoveRotation().write(0, + WrappedPositionMoveRotation.create(position, delta, yRot, xRot)); + + WrappedPositionMoveRotation result = packet.getPositionMoveRotation().read(0); + + assertNotNull(result); + + assertEquals(position.getX(), result.getPosition().getX(), 1e-6); + assertEquals(position.getY(), result.getPosition().getY(), 1e-6); + assertEquals(position.getZ(), result.getPosition().getZ(), 1e-6); + + assertEquals(delta.getX(), result.getDeltaMovement().getX(), 1e-6); + assertEquals(delta.getY(), result.getDeltaMovement().getY(), 1e-6); + assertEquals(delta.getZ(), result.getDeltaMovement().getZ(), 1e-6); + + assertEquals(yRot, result.getYRot(), 1e-6f); + assertEquals(xRot, result.getXRot(), 1e-6f); + } + + @Test + public void testFromHandle() { + PositionMoveRotation nmsHandle = new PositionMoveRotation( + new Vec3(5.0, 65.0, 5.0), + new Vec3(0.0, -0.1, 0.0), + 270.0f, + -45.0f); + + WrappedPositionMoveRotation wrapper = WrappedPositionMoveRotation.fromHandle(nmsHandle); + + assertEquals(5.0, wrapper.getPosition().getX(), 1e-6); + assertEquals(65.0, wrapper.getPosition().getY(), 1e-6); + assertEquals(5.0, wrapper.getPosition().getZ(), 1e-6); + + assertEquals(0.0, wrapper.getDeltaMovement().getX(), 1e-6); + assertEquals(-0.1, wrapper.getDeltaMovement().getY(), 1e-6); + assertEquals(0.0, wrapper.getDeltaMovement().getZ(), 1e-6); + + assertEquals(270.0f, wrapper.getYRot(), 1e-6f); + assertEquals(-45.0f, wrapper.getXRot(), 1e-6f); + } +}