From 4c3a0afd7e2dfb25c0ab2cbc0a27345a17d5d9f2 Mon Sep 17 00:00:00 2001 From: Dan Mulloy Date: Wed, 1 Aug 2018 17:38:56 -0400 Subject: [PATCH] Shiny new 1.13 particle support Hopefully this doesn't break 1.8-1.12 --- .../protocol/events/PacketContainer.java | 24 ++- .../protocol/wrappers/BukkitConverters.java | 13 +- .../protocol/wrappers/EnumWrappers.java | 1 + .../protocol/wrappers/WrappedParticle.java | 151 ++++++++++++++++++ .../wrappers/WrappedParticleTest.java | 61 +++++++ 5 files changed, 240 insertions(+), 10 deletions(-) create mode 100644 modules/API/src/main/java/com/comphenix/protocol/wrappers/WrappedParticle.java create mode 100644 modules/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/WrappedParticleTest.java diff --git a/modules/API/src/main/java/com/comphenix/protocol/events/PacketContainer.java b/modules/API/src/main/java/com/comphenix/protocol/events/PacketContainer.java index 1e5debe5..b7be62e0 100644 --- a/modules/API/src/main/java/com/comphenix/protocol/events/PacketContainer.java +++ b/modules/API/src/main/java/com/comphenix/protocol/events/PacketContainer.java @@ -42,6 +42,8 @@ import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.StreamSerializer; import com.comphenix.protocol.wrappers.*; import com.comphenix.protocol.wrappers.EnumWrappers.*; +import com.comphenix.protocol.wrappers.EnumWrappers.Difficulty; +import com.comphenix.protocol.wrappers.EnumWrappers.SoundCategory; import com.comphenix.protocol.wrappers.nbt.NbtBase; import com.comphenix.protocol.wrappers.nbt.NbtCompound; import com.comphenix.protocol.wrappers.nbt.NbtFactory; @@ -53,10 +55,8 @@ import com.google.common.collect.Sets; import io.netty.buffer.ByteBuf; import io.netty.buffer.UnpooledByteBufAllocator; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.World; -import org.bukkit.WorldType; +import org.bukkit.*; +import org.bukkit.Particle; import org.bukkit.entity.Entity; import org.bukkit.inventory.ItemStack; import org.bukkit.potion.PotionEffectType; @@ -831,16 +831,28 @@ public class PacketContainer implements Serializable { } /** - * Retrieve a read/write structure for the Particle enum in 1.8. + * Retrieve a read/write structure for the Particle enum in 1.8-1.12. + * NOTE: This will produce undesirable results in 1.13 * @return A modifier for Particle enum fields. */ - public StructureModifier getParticles() { + public StructureModifier getParticles() { // Convert to and from the wrapper return structureModifier.withType( EnumWrappers.getParticleClass(), EnumWrappers.getParticleConverter()); } + /** + * Retrieve a read/write structure for ParticleParams in 1.13 + * @return A modifier for ParticleParam fields. + */ + public StructureModifier getNewParticles() { + return structureModifier.withType( + MinecraftReflection.getMinecraftClass("ParticleParam"), + BukkitConverters.getParticleConverter() + ); + } + /** * Retrieve a read/write structure for the MobEffectList class in 1.9. * @return A modifier for MobEffectList fields. diff --git a/modules/API/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java b/modules/API/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java index 1c939e07..5339bf31 100644 --- a/modules/API/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java +++ b/modules/API/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java @@ -47,11 +47,12 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.World; -import org.bukkit.WorldType; +import net.minecraft.server.v1_13_R1.ParticleParam; +import net.minecraft.server.v1_13_R1.ParticleParamBlock; + +import org.bukkit.*; import org.bukkit.advancement.Advancement; +import org.bukkit.craftbukkit.v1_13_R1.CraftParticle; import org.bukkit.entity.Entity; import org.bukkit.inventory.ItemStack; import org.bukkit.potion.PotionEffect; @@ -929,6 +930,10 @@ public class BukkitConverters { }); } + public static EquivalentConverter getParticleConverter() { + return ignoreNull(handle(WrappedParticle::getHandle, WrappedParticle::fromHandle)); + } + public static EquivalentConverter getAdvancementConverter() { return ignoreNull(new EquivalentConverter() { @Override diff --git a/modules/API/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java b/modules/API/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java index d8a5d320..7b7114c6 100644 --- a/modules/API/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java +++ b/modules/API/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java @@ -12,6 +12,7 @@ import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.utility.MinecraftReflection; import com.google.common.collect.Maps; +import org.bukkit.EntityEffect; import org.bukkit.GameMode; /** diff --git a/modules/API/src/main/java/com/comphenix/protocol/wrappers/WrappedParticle.java b/modules/API/src/main/java/com/comphenix/protocol/wrappers/WrappedParticle.java new file mode 100644 index 00000000..0095365d --- /dev/null +++ b/modules/API/src/main/java/com/comphenix/protocol/wrappers/WrappedParticle.java @@ -0,0 +1,151 @@ +package com.comphenix.protocol.wrappers; + +import java.lang.reflect.Modifier; + +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.MethodAccessor; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; +import com.comphenix.protocol.utility.MinecraftReflection; + +import org.bukkit.Color; +import org.bukkit.Particle; +import org.bukkit.inventory.ItemStack; + +/** + * Represents an immutable wrapped ParticleParam in 1.13 + */ +public class WrappedParticle { + private static MethodAccessor toBukkit; + private static MethodAccessor toNMS; + private static MethodAccessor toCraftData; + + private static void ensureMethods() { + if (toBukkit != null && toNMS != null) { + return; + } + + FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getCraftBukkitClass("CraftParticle")); + FuzzyMethodContract contract = FuzzyMethodContract + .newBuilder() + .requireModifier(Modifier.STATIC) + .returnTypeExact(Particle.class) + .parameterExactType(MinecraftReflection.getMinecraftClass("ParticleParam")) + .build(); + toBukkit = Accessors.getMethodAccessor(fuzzy.getMethod(contract)); + + contract = FuzzyMethodContract + .newBuilder() + .requireModifier(Modifier.STATIC) + .returnTypeExact(MinecraftReflection.getMinecraftClass("ParticleParam")) + .parameterCount(2) + .build(); + toNMS = Accessors.getMethodAccessor(fuzzy.getMethod(contract)); + + Class cbData = MinecraftReflection.getCraftBukkitClass("block.data.CraftBlockData"); + fuzzy = FuzzyReflection.fromClass(cbData); + contract = FuzzyMethodContract + .newBuilder() + .requireModifier(Modifier.STATIC) + .returnTypeExact(cbData) + .parameterExactArray(MinecraftReflection.getIBlockDataClass()) + .build(); + toCraftData = Accessors.getMethodAccessor(fuzzy.getMethod(contract)); + } + + private final Particle particle; + private final T data; + private final Object handle; + + private WrappedParticle(Object handle, Particle particle, T data) { + this.handle = handle; + this.particle = particle; + this.data = data; + } + + /** + * @return This particle's Bukkit type + */ + public Particle getParticle() { + return particle; + } + + /** + * Gets this Particle's Bukkit/ProtocolLib data. The type of this data depends on the + * {@link #getParticle() Particle type}. For Block particles it will be {@link WrappedBlockData}, + * for Item crack particles, it will be an {@link ItemStack}, and for redstone particles it will + * be {@link Particle.DustOptions} + * + * @return The particle data + */ + public T getData() { + return data; + } + + /** + * @return NMS handle + */ + public Object getHandle() { + return handle; + } + + public static WrappedParticle fromHandle(Object handle) { + Particle bukkit = (Particle) toBukkit.invoke(null, handle); + Object data = null; + + switch (bukkit) { + case BLOCK_CRACK: + case BLOCK_DUST: + case FALLING_DUST: + data = getBlockData(handle); + break; + case ITEM_CRACK: + data = getItem(handle); + break; + case REDSTONE: + data = getRedstone(handle); + break; + default: + break; + } + + return new WrappedParticle<>(handle, bukkit, data); + } + + private static WrappedBlockData getBlockData(Object handle) { + return new StructureModifier<>(handle.getClass()) + .withTarget(handle) + .withType(MinecraftReflection.getIBlockDataClass(), BukkitConverters.getWrappedBlockDataConverter()) + .read(0); + } + + private static Object getItem(Object handle) { + return new StructureModifier<>(handle.getClass()) + .withTarget(handle) + .withType(MinecraftReflection.getItemStackClass(), BukkitConverters.getItemStackConverter()) + .read(0); + } + + private static Object getRedstone(Object handle) { + StructureModifier modifier = new StructureModifier<>(handle.getClass()).withTarget(handle).withType(float.class); + return new Particle.DustOptions(Color.fromRGB( + (int) (modifier.read(0) * 255), + (int) (modifier.read(1) * 255), + (int) (modifier.read(2) * 255)), + modifier.read(3)); + } + + public static WrappedParticle create(Particle particle, T data) { + ensureMethods(); + + Object bukkitData = data; + if (data instanceof WrappedBlockData) { + WrappedBlockData blockData = (WrappedBlockData) data; + bukkitData = toCraftData.invoke(null, blockData.getHandle()); + } + + Object handle = toNMS.invoke(null, particle, bukkitData); + return new WrappedParticle<>(handle, particle, data); + } +} diff --git a/modules/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/WrappedParticleTest.java b/modules/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/WrappedParticleTest.java new file mode 100644 index 00000000..0cfa979e --- /dev/null +++ b/modules/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/WrappedParticleTest.java @@ -0,0 +1,61 @@ +package com.comphenix.protocol.wrappers; + +import com.comphenix.protocol.BukkitInitialization; +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.events.PacketContainer; + +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Particle.DustOptions; +import org.bukkit.inventory.ItemStack; +import org.junit.BeforeClass; +import org.junit.Test; + +import static com.comphenix.protocol.utility.TestUtils.assertItemsEqual; +import static org.junit.Assert.assertEquals; + +public class WrappedParticleTest { + @BeforeClass + public static void beforeClass() { + BukkitInitialization.initializeItemMeta(); + } + + @Test + public void testBlockData() { + PacketContainer packet = new PacketContainer(PacketType.Play.Server.WORLD_PARTICLES); + WrappedParticle before = WrappedParticle.create(Particle.BLOCK_CRACK, + WrappedBlockData.createData(Material.LAPIS_BLOCK)); + packet.getNewParticles().write(0, before); + + WrappedParticle after = packet.getNewParticles().read(0); + assertEquals(before.getParticle(), after.getParticle()); + assertEquals(before.getData(), after.getData()); + } + + @Test + public void testItemStacks() { + PacketContainer packet = new PacketContainer(PacketType.Play.Server.WORLD_PARTICLES); + WrappedParticle before = WrappedParticle.create(Particle.ITEM_CRACK, new ItemStack(Material.FLINT_AND_STEEL)); + packet.getNewParticles().write(0, before); + + WrappedParticle after = packet.getNewParticles().read(0); + assertEquals(before.getParticle(), after.getParticle()); + assertItemsEqual((ItemStack) before.getData(), (ItemStack) after.getData()); + } + + @Test + public void testRedstone() { + PacketContainer packet = new PacketContainer(PacketType.Play.Server.WORLD_PARTICLES); + WrappedParticle before = WrappedParticle.create(Particle.REDSTONE, new DustOptions(Color.BLUE, 1)); + packet.getNewParticles().write(0, before); + + WrappedParticle after = packet.getNewParticles().read(0); + assertEquals(before.getParticle(), after.getParticle()); + + DustOptions beforeDust = (DustOptions) before.getData(); + DustOptions afterDust = (DustOptions) after.getData(); + assertEquals(beforeDust.getColor(), afterDust.getColor()); + assertEquals(beforeDust.getSize(), afterDust.getSize(), 0); + } +}