From 67a52a64854b21060cfd6eb7ca8bb7c2cdf02d1b Mon Sep 17 00:00:00 2001 From: DerFrZocker Date: Sat, 21 Oct 2023 13:42:09 +1100 Subject: [PATCH] #1279: Back Particle by a minecraft registry --- .../org/bukkit/craftbukkit/CraftParticle.java | 376 +++++++++--------- .../org/bukkit/craftbukkit/CraftRegistry.java | 4 +- .../org/bukkit/craftbukkit/CraftWorld.java | 4 +- .../entity/CraftAreaEffectCloud.java | 7 +- .../craftbukkit/entity/CraftPlayer.java | 4 +- src/test/java/org/bukkit/ParticleTest.java | 291 ++++++++++++-- 6 files changed, 456 insertions(+), 230 deletions(-) diff --git a/src/main/java/org/bukkit/craftbukkit/CraftParticle.java b/src/main/java/org/bukkit/craftbukkit/CraftParticle.java index 87692150d..54033bc4d 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftParticle.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftParticle.java @@ -1,11 +1,10 @@ package org.bukkit.craftbukkit; import com.google.common.base.Preconditions; -import com.google.common.collect.BiMap; -import com.google.common.collect.HashBiMap; import java.util.HashMap; import java.util.Map; -import net.minecraft.core.BlockPosition; +import java.util.function.BiFunction; +import net.minecraft.core.IRegistry; import net.minecraft.core.particles.DustColorTransitionOptions; import net.minecraft.core.particles.ParticleParam; import net.minecraft.core.particles.ParticleParamBlock; @@ -15,15 +14,17 @@ import net.minecraft.core.particles.ParticleType; import net.minecraft.core.particles.SculkChargeParticleOptions; import net.minecraft.core.particles.ShriekParticleOption; import net.minecraft.core.particles.VibrationParticleOption; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.resources.MinecraftKey; +import net.minecraft.core.registries.Registries; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.gameevent.BlockPositionSource; import net.minecraft.world.level.gameevent.EntityPositionSource; import net.minecraft.world.level.gameevent.PositionSource; import org.bukkit.Color; +import org.bukkit.Keyed; import org.bukkit.Location; +import org.bukkit.NamespacedKey; import org.bukkit.Particle; +import org.bukkit.Registry; import org.bukkit.Vibration; import org.bukkit.block.data.BlockData; import org.bukkit.craftbukkit.block.data.CraftBlockData; @@ -31,205 +32,184 @@ import org.bukkit.craftbukkit.entity.CraftEntity; import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.craftbukkit.util.CraftLocation; import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; import org.bukkit.inventory.ItemStack; import org.bukkit.material.MaterialData; import org.joml.Vector3f; -public enum CraftParticle { +public abstract class CraftParticle implements Keyed { - EXPLOSION_NORMAL("poof"), - EXPLOSION_LARGE("explosion"), - EXPLOSION_HUGE("explosion_emitter"), - FIREWORKS_SPARK("firework"), - WATER_BUBBLE("bubble"), - WATER_SPLASH("splash"), - WATER_WAKE("fishing"), - SUSPENDED("underwater"), - SUSPENDED_DEPTH("underwater"), - CRIT("crit"), - CRIT_MAGIC("enchanted_hit"), - SMOKE_NORMAL("smoke"), - SMOKE_LARGE("large_smoke"), - SPELL("effect"), - SPELL_INSTANT("instant_effect"), - SPELL_MOB("entity_effect"), - SPELL_MOB_AMBIENT("ambient_entity_effect"), - SPELL_WITCH("witch"), - DRIP_WATER("dripping_water"), - DRIP_LAVA("dripping_lava"), - VILLAGER_ANGRY("angry_villager"), - VILLAGER_HAPPY("happy_villager"), - TOWN_AURA("mycelium"), - NOTE("note"), - PORTAL("portal"), - ENCHANTMENT_TABLE("enchant"), - FLAME("flame"), - LAVA("lava"), - CLOUD("cloud"), - REDSTONE("dust"), - SNOWBALL("item_snowball"), - SNOW_SHOVEL("item_snowball"), - SLIME("item_slime"), - HEART("heart"), - ITEM_CRACK("item"), - BLOCK_CRACK("block"), - BLOCK_DUST("block"), - WATER_DROP("rain"), - MOB_APPEARANCE("elder_guardian"), - DRAGON_BREATH("dragon_breath"), - END_ROD("end_rod"), - DAMAGE_INDICATOR("damage_indicator"), - SWEEP_ATTACK("sweep_attack"), - FALLING_DUST("falling_dust"), - TOTEM("totem_of_undying"), - SPIT("spit"), - SQUID_INK("squid_ink"), - BUBBLE_POP("bubble_pop"), - CURRENT_DOWN("current_down"), - BUBBLE_COLUMN_UP("bubble_column_up"), - NAUTILUS("nautilus"), - DOLPHIN("dolphin"), - SNEEZE("sneeze"), - CAMPFIRE_COSY_SMOKE("campfire_cosy_smoke"), - CAMPFIRE_SIGNAL_SMOKE("campfire_signal_smoke"), - COMPOSTER("composter"), - FLASH("flash"), - FALLING_LAVA("falling_lava"), - LANDING_LAVA("landing_lava"), - FALLING_WATER("falling_water"), - DRIPPING_HONEY("dripping_honey"), - FALLING_HONEY("falling_honey"), - LANDING_HONEY("landing_honey"), - FALLING_NECTAR("falling_nectar"), - SOUL_FIRE_FLAME("soul_fire_flame"), - ASH("ash"), - CRIMSON_SPORE("crimson_spore"), - WARPED_SPORE("warped_spore"), - SOUL("soul"), - DRIPPING_OBSIDIAN_TEAR("dripping_obsidian_tear"), - FALLING_OBSIDIAN_TEAR("falling_obsidian_tear"), - LANDING_OBSIDIAN_TEAR("landing_obsidian_tear"), - REVERSE_PORTAL("reverse_portal"), - WHITE_ASH("white_ash"), - DUST_COLOR_TRANSITION("dust_color_transition"), - VIBRATION("vibration"), - FALLING_SPORE_BLOSSOM("falling_spore_blossom"), - SPORE_BLOSSOM_AIR("spore_blossom_air"), - SMALL_FLAME("small_flame"), - SNOWFLAKE("snowflake"), - DRIPPING_DRIPSTONE_LAVA("dripping_dripstone_lava"), - FALLING_DRIPSTONE_LAVA("falling_dripstone_lava"), - DRIPPING_DRIPSTONE_WATER("dripping_dripstone_water"), - FALLING_DRIPSTONE_WATER("falling_dripstone_water"), - GLOW_SQUID_INK("glow_squid_ink"), - GLOW("glow"), - WAX_ON("wax_on"), - WAX_OFF("wax_off"), - ELECTRIC_SPARK("electric_spark"), - SCRAPE("scrape"), - BLOCK_MARKER("block_marker"), - SONIC_BOOM("sonic_boom"), - SCULK_SOUL("sculk_soul"), - SCULK_CHARGE("sculk_charge"), - SCULK_CHARGE_POP("sculk_charge_pop"), - SHRIEK("shriek"), - CHERRY_LEAVES("cherry_leaves"), - EGG_CRACK("egg_crack"), - // ----- Legacy Separator ----- - LEGACY_BLOCK_CRACK("block"), - LEGACY_BLOCK_DUST("block"), - LEGACY_FALLING_DUST("falling_dust"); - private final MinecraftKey minecraftKey; - private final Particle bukkit; - private static final BiMap particles; - private static final Map aliases; - - static { - particles = HashBiMap.create(); - aliases = new HashMap<>(); - - for (CraftParticle particle : CraftParticle.values()) { - if (particles.containsValue(particle.minecraftKey)) { - aliases.put(particle.bukkit, particles.inverse().get(particle.minecraftKey)); - } else { - particles.put(particle.bukkit, particle.minecraftKey); - } - } - } - - private CraftParticle(String minecraftKey) { - this.minecraftKey = new MinecraftKey(minecraftKey); - - this.bukkit = Particle.valueOf(this.name()); - Preconditions.checkState(bukkit != null, "Bukkit particle %s does not exist", this.name()); - } - - public static ParticleParam toNMS(Particle bukkit) { - return toNMS(bukkit, null); - } - - public static ParticleParam toNMS(Particle particle, T obj) { - Particle canonical = particle; - if (aliases.containsKey(particle)) { - canonical = aliases.get(particle); - } - - net.minecraft.core.particles.Particle nms = BuiltInRegistries.PARTICLE_TYPE.get(particles.get(canonical)); - Preconditions.checkArgument(nms != null, "No NMS particle %s", particle); - - if (particle.getDataType().equals(Void.class)) { - return (ParticleType) nms; - } - Preconditions.checkArgument(obj != null, "Particle %s requires data, null provided", particle); - if (particle.getDataType().equals(ItemStack.class)) { - ItemStack itemStack = (ItemStack) obj; - return new ParticleParamItem((net.minecraft.core.particles.Particle) nms, CraftItemStack.asNMSCopy(itemStack)); - } - if (particle.getDataType() == MaterialData.class) { - MaterialData data = (MaterialData) obj; - return new ParticleParamBlock((net.minecraft.core.particles.Particle) nms, CraftMagicNumbers.getBlock(data)); - } - if (particle.getDataType() == BlockData.class) { - BlockData data = (BlockData) obj; - return new ParticleParamBlock((net.minecraft.core.particles.Particle) nms, ((CraftBlockData) data).getState()); - } - if (particle.getDataType() == Particle.DustOptions.class) { - Particle.DustOptions data = (Particle.DustOptions) obj; - Color color = data.getColor(); - return new ParticleParamRedstone(new Vector3f(color.getRed() / 255.0f, color.getGreen() / 255.0f, color.getBlue() / 255.0f), data.getSize()); - } - if (particle.getDataType() == Particle.DustTransition.class) { - Particle.DustTransition data = (Particle.DustTransition) obj; - Color from = data.getColor(); - Color to = data.getToColor(); - return new DustColorTransitionOptions(new Vector3f(from.getRed() / 255.0f, from.getGreen() / 255.0f, from.getBlue() / 255.0f), new Vector3f(to.getRed() / 255.0f, to.getGreen() / 255.0f, to.getBlue() / 255.0f), data.getSize()); - } - if (particle.getDataType() == Vibration.class) { - Vibration vibration = (Vibration) obj; - - PositionSource source; - if (vibration.getDestination() instanceof Vibration.Destination.BlockDestination) { - Location destination = ((Vibration.Destination.BlockDestination) vibration.getDestination()).getLocation(); - source = new BlockPositionSource(CraftLocation.toBlockPosition(destination)); - } else if (vibration.getDestination() instanceof Vibration.Destination.EntityDestination) { - Entity destination = ((CraftEntity) ((Vibration.Destination.EntityDestination) vibration.getDestination()).getEntity()).getHandle(); - source = new EntityPositionSource(destination, destination.getEyeHeight()); - } else { - throw new IllegalArgumentException("Unknown vibration destination " + vibration.getDestination()); - } - - return new VibrationParticleOption(source, vibration.getArrivalTime()); - } - if (particle.getDataType() == Float.class) { - return new SculkChargeParticleOptions((Float) obj); - } - if (particle.getDataType() == Integer.class) { - return new ShriekParticleOption((Integer) obj); - } - throw new IllegalArgumentException(particle.getDataType().toString()); - } + private static final Registry> CRAFT_PARTICLE_REGISTRY = new CraftParticleRegistry(CraftRegistry.getMinecraftRegistry(Registries.PARTICLE_TYPE)); public static Particle minecraftToBukkit(net.minecraft.core.particles.Particle minecraft) { - return particles.inverse().get(BuiltInRegistries.PARTICLE_TYPE.getKey(minecraft)); + Preconditions.checkArgument(minecraft != null); + + IRegistry> registry = CraftRegistry.getMinecraftRegistry(Registries.PARTICLE_TYPE); + Particle bukkit = Registry.PARTICLE_TYPE.get(CraftNamespacedKey.fromMinecraft(registry.getResourceKey(minecraft).orElseThrow().location())); + + Preconditions.checkArgument(bukkit != null); + + return bukkit; + } + + public static net.minecraft.core.particles.Particle bukkitToMinecraft(Particle bukkit) { + Preconditions.checkArgument(bukkit != null); + + return CraftRegistry.getMinecraftRegistry(Registries.PARTICLE_TYPE) + .getOptional(CraftNamespacedKey.toMinecraft(bukkit.getKey())).orElseThrow(); + } + + public static ParticleParam createParticleParam(Particle particle, D data) { + Preconditions.checkArgument(particle != null); + + CraftParticle craftParticle = (CraftParticle) CRAFT_PARTICLE_REGISTRY.get(particle.getKey()); + + Preconditions.checkArgument(craftParticle != null); + + return craftParticle.createParticleParam(data); + } + + public static T convertLegacy(T object) { + if (object instanceof MaterialData mat) { + return (T) CraftBlockData.fromData(CraftMagicNumbers.getBlock(mat)); + } + + return object; + } + + public static Particle convertLegacy(Particle particle) { + return switch (particle) { + case LEGACY_BLOCK_DUST -> Particle.BLOCK_DUST; + case LEGACY_FALLING_DUST -> Particle.FALLING_DUST; + case LEGACY_BLOCK_CRACK -> Particle.BLOCK_CRACK; + default -> particle; + }; + } + + private final NamespacedKey key; + private final net.minecraft.core.particles.Particle particle; + private final Class clazz; + + public CraftParticle(NamespacedKey key, net.minecraft.core.particles.Particle particle, Class clazz) { + this.key = key; + this.particle = particle; + this.clazz = clazz; + } + + public net.minecraft.core.particles.Particle getHandle() { + return particle; + } + + public abstract ParticleParam createParticleParam(D data); + + @Override + public NamespacedKey getKey() { + return key; + } + + public static class CraftParticleRegistry extends CraftRegistry, net.minecraft.core.particles.Particle> { + + private static final Map, CraftParticle>> PARTICLE_MAP = new HashMap<>(); + + private static final BiFunction, CraftParticle> VOID_FUNCTION = (name, particle) -> new CraftParticle<>(name, particle, Void.class) { + @Override + public ParticleParam createParticleParam(Void data) { + return (ParticleType) getHandle(); + } + }; + + static { + BiFunction, CraftParticle> dustOptionsFunction = (name, particle) -> new CraftParticle<>(name, particle, Particle.DustOptions.class) { + @Override + public ParticleParam createParticleParam(Particle.DustOptions data) { + Color color = data.getColor(); + return new ParticleParamRedstone(new Vector3f(color.getRed() / 255.0f, color.getGreen() / 255.0f, color.getBlue() / 255.0f), data.getSize()); + } + }; + + BiFunction, CraftParticle> itemStackFunction = (name, particle) -> new CraftParticle<>(name, particle, ItemStack.class) { + @Override + public ParticleParam createParticleParam(ItemStack data) { + return new ParticleParamItem((net.minecraft.core.particles.Particle) getHandle(), CraftItemStack.asNMSCopy(data)); + } + }; + + BiFunction, CraftParticle> blockDataFunction = (name, particle) -> new CraftParticle<>(name, particle, BlockData.class) { + @Override + public ParticleParam createParticleParam(BlockData data) { + return new ParticleParamBlock((net.minecraft.core.particles.Particle) getHandle(), ((CraftBlockData) data).getState()); + } + }; + + BiFunction, CraftParticle> dustTransitionFunction = (name, particle) -> new CraftParticle<>(name, particle, Particle.DustTransition.class) { + @Override + public ParticleParam createParticleParam(Particle.DustTransition data) { + Color from = data.getColor(); + Color to = data.getToColor(); + return new DustColorTransitionOptions(new Vector3f(from.getRed() / 255.0f, from.getGreen() / 255.0f, from.getBlue() / 255.0f), new Vector3f(to.getRed() / 255.0f, to.getGreen() / 255.0f, to.getBlue() / 255.0f), data.getSize()); + } + }; + + BiFunction, CraftParticle> vibrationFunction = (name, particle) -> new CraftParticle<>(name, particle, Vibration.class) { + @Override + public ParticleParam createParticleParam(Vibration data) { + PositionSource source; + if (data.getDestination() instanceof Vibration.Destination.BlockDestination) { + Location destination = ((Vibration.Destination.BlockDestination) data.getDestination()).getLocation(); + source = new BlockPositionSource(CraftLocation.toBlockPosition(destination)); + } else if (data.getDestination() instanceof Vibration.Destination.EntityDestination) { + Entity destination = ((CraftEntity) ((Vibration.Destination.EntityDestination) data.getDestination()).getEntity()).getHandle(); + source = new EntityPositionSource(destination, destination.getEyeHeight()); + } else { + throw new IllegalArgumentException("Unknown vibration destination " + data.getDestination()); + } + + return new VibrationParticleOption(source, data.getArrivalTime()); + } + }; + + BiFunction, CraftParticle> floatFunction = (name, particle) -> new CraftParticle<>(name, particle, Float.class) { + @Override + public ParticleParam createParticleParam(Float data) { + return new SculkChargeParticleOptions(data); + } + }; + + BiFunction, CraftParticle> integerFunction = (name, particle) -> new CraftParticle<>(name, particle, Integer.class) { + @Override + public ParticleParam createParticleParam(Integer data) { + return new ShriekParticleOption(data); + } + }; + + add("dust", dustOptionsFunction); + add("item", itemStackFunction); + add("block", blockDataFunction); + add("falling_dust", blockDataFunction); + add("dust_color_transition", dustTransitionFunction); + add("vibration", vibrationFunction); + add("sculk_charge", floatFunction); + add("shriek", integerFunction); + add("block_marker", blockDataFunction); + } + + private static void add(String name, BiFunction, CraftParticle> function) { + PARTICLE_MAP.put(NamespacedKey.fromString(name), function); + } + + public CraftParticleRegistry(IRegistry> minecraftRegistry) { + super(CraftParticle.class, minecraftRegistry, null); + } + + @Override + public CraftParticle createBukkit(NamespacedKey namespacedKey, net.minecraft.core.particles.Particle particle) { + if (particle == null) { + return null; + } + + BiFunction, CraftParticle> function = PARTICLE_MAP.getOrDefault(namespacedKey, VOID_FUNCTION); + + return function.apply(namespacedKey, particle); + } } } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java index 98067400f..51c72722c 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java @@ -67,13 +67,13 @@ public class CraftRegistry implements Registry { return null; } - private final Class bukkitClass; + private final Class bukkitClass; private final Map cache = new HashMap<>(); private final IRegistry minecraftRegistry; private final BiFunction minecraftToBukkit; private boolean init; - public CraftRegistry(Class bukkitClass, IRegistry minecraftRegistry, BiFunction minecraftToBukkit) { + public CraftRegistry(Class bukkitClass, IRegistry minecraftRegistry, BiFunction minecraftToBukkit) { this.bukkitClass = bukkitClass; this.minecraftRegistry = minecraftRegistry; this.minecraftToBukkit = minecraftToBukkit; diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index 5955306f4..49305e511 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -1788,12 +1788,14 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data, boolean force) { + particle = CraftParticle.convertLegacy(particle); + data = CraftParticle.convertLegacy(data); if (data != null) { Preconditions.checkArgument(particle.getDataType().isInstance(data), "data (%s) should be %s", data.getClass(), particle.getDataType()); } getHandle().sendParticles( null, // Sender - CraftParticle.toNMS(particle, data), // Particle + CraftParticle.createParticleParam(particle, data), // Particle x, y, z, // Position count, // Count offsetX, offsetY, offsetZ, // Random offset diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java index c981b091c..90a094d69 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java @@ -121,7 +121,12 @@ public class CraftAreaEffectCloud extends CraftEntity implements AreaEffectCloud @Override public void setParticle(Particle particle, T data) { - getHandle().setParticle(CraftParticle.toNMS(particle, data)); + particle = CraftParticle.convertLegacy(particle); + data = CraftParticle.convertLegacy(data); + if (data != null) { + Preconditions.checkArgument(particle.getDataType().isInstance(data), "data (%s) should be %s", data.getClass(), particle.getDataType()); + } + getHandle().setParticle(CraftParticle.createParticleParam(particle, data)); } @Override diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index 97df91de6..273b6e397 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -2080,10 +2080,12 @@ public class CraftPlayer extends CraftHumanEntity implements Player { @Override public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data) { + particle = CraftParticle.convertLegacy(particle); + data = CraftParticle.convertLegacy(data); if (data != null) { Preconditions.checkArgument(particle.getDataType().isInstance(data), "data (%s) should be %s", data.getClass(), particle.getDataType()); } - PacketPlayOutWorldParticles packetplayoutworldparticles = new PacketPlayOutWorldParticles(CraftParticle.toNMS(particle, data), true, (float) x, (float) y, (float) z, (float) offsetX, (float) offsetY, (float) offsetZ, (float) extra, count); + PacketPlayOutWorldParticles packetplayoutworldparticles = new PacketPlayOutWorldParticles(CraftParticle.createParticleParam(particle, data), true, (float) x, (float) y, (float) z, (float) offsetX, (float) offsetY, (float) offsetZ, (float) extra, count); getHandle().connection.send(packetplayoutworldparticles); } diff --git a/src/test/java/org/bukkit/ParticleTest.java b/src/test/java/org/bukkit/ParticleTest.java index 100197ba3..8239813fe 100644 --- a/src/test/java/org/bukkit/ParticleTest.java +++ b/src/test/java/org/bukkit/ParticleTest.java @@ -1,43 +1,280 @@ package org.bukkit; import static org.junit.jupiter.api.Assertions.*; -import net.minecraft.core.registries.BuiltInRegistries; +import com.mojang.serialization.DataResult; +import java.util.Optional; +import java.util.stream.Stream; +import net.minecraft.core.particles.DustColorTransitionOptions; +import net.minecraft.core.particles.ParticleParam; +import net.minecraft.core.particles.ParticleParamBlock; +import net.minecraft.core.particles.ParticleParamItem; +import net.minecraft.core.particles.ParticleParamRedstone; +import net.minecraft.core.particles.ParticleType; +import net.minecraft.core.particles.SculkChargeParticleOptions; +import net.minecraft.core.particles.ShriekParticleOption; +import net.minecraft.core.particles.VibrationParticleOption; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.DynamicOpsNBT; +import net.minecraft.nbt.NBTBase; +import net.minecraft.resources.MinecraftKey; +import net.minecraft.world.phys.Vec3D; import org.bukkit.block.data.BlockData; import org.bukkit.craftbukkit.CraftParticle; +import org.bukkit.craftbukkit.CraftRegistry; import org.bukkit.craftbukkit.block.data.CraftBlockData; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; import org.bukkit.inventory.ItemStack; -import org.bukkit.material.MaterialData; import org.bukkit.support.AbstractTestingBase; -import org.junit.jupiter.api.Test; +import org.joml.Vector3f; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.MethodSource; public class ParticleTest extends AbstractTestingBase { - @Test - public void verifyMapping() { - for (Particle bukkit : Particle.values()) { - Object data = null; - if (bukkit.getDataType().equals(ItemStack.class)) { - data = new ItemStack(Material.STONE); - } else if (bukkit.getDataType() == MaterialData.class) { - data = new MaterialData(Material.LEGACY_STONE); - } else if (bukkit.getDataType() == Particle.DustOptions.class) { - data = new Particle.DustOptions(Color.BLACK, 0); - } else if (bukkit.getDataType() == Particle.DustTransition.class) { - data = new Particle.DustTransition(Color.BLACK, Color.WHITE, 0); - } else if (bukkit.getDataType() == Vibration.class) { - data = new Vibration(new Location(null, 0, 0, 0), new Vibration.Destination.BlockDestination(new Location(null, 0, 0, 0)), 0); - } else if (bukkit.getDataType() == BlockData.class) { - data = CraftBlockData.newData(Material.STONE, ""); - } else if (bukkit.getDataType() == Float.class) { - data = 1.0F; - } else if (bukkit.getDataType() == Integer.class) { - data = 0; - } + public static Stream data() { + return CraftRegistry.getMinecraftRegistry(Registries.PARTICLE_TYPE).keySet().stream().map(Arguments::of); + } - assertNotNull(CraftParticle.toNMS(bukkit, data), "Missing Bukkit->NMS particle mapping for " + bukkit); + @ParameterizedTest + @MethodSource("data") + public void testBukkitValuesPresent(MinecraftKey minecraft) { + // TODO: 10/19/23 Remove with enum PR, it is then no longer needed, since the enum PR has a extra test for this + assertNotNull(Registry.PARTICLE_TYPE.get(CraftNamespacedKey.fromMinecraft(minecraft)), String.format(""" + No bukkit particle found for minecraft particle %s. + Please add the particle to bukkit. + """, minecraft)); + } + + @ParameterizedTest + @EnumSource(Particle.class) + public void testMinecraftValuesPresent(Particle bukkit) { + // TODO: 10/19/23 Remove with enum PR, it is then no longer needed, since the enum PR has a extra test for this + bukkit = CraftParticle.convertLegacy(bukkit); + Particle finalBukkit = bukkit; + assertDoesNotThrow(() -> CraftParticle.bukkitToMinecraft(finalBukkit), String.format(""" + No minecraft particle found for bukkit particle %s. + Please map the bukkit particle to a minecraft particle. + """, bukkit.getKey())); + } + + @ParameterizedTest + @EnumSource(Particle.class) + public void testRightParticleParamCreation(Particle bukkit) { + bukkit = CraftParticle.convertLegacy(bukkit); + net.minecraft.core.particles.Particle minecraft = CraftParticle.bukkitToMinecraft(bukkit); + + if (bukkit.getDataType().equals(Void.class)) { + testEmptyData(bukkit, minecraft); + return; } - for (net.minecraft.core.particles.Particle nms : BuiltInRegistries.PARTICLE_TYPE) { - assertNotNull(CraftParticle.minecraftToBukkit(nms), "Missing NMS->Bukkit particle mapping for " + BuiltInRegistries.PARTICLE_TYPE.getKey(nms)); + + if (bukkit.getDataType().equals(Particle.DustOptions.class)) { + testDustOption(bukkit, minecraft); + return; } + + if (bukkit.getDataType().equals(ItemStack.class)) { + testItemStack(bukkit, minecraft); + return; + } + + if (bukkit.getDataType().equals(BlockData.class)) { + testBlockData(bukkit, minecraft); + return; + } + + if (bukkit.getDataType().equals(Particle.DustTransition.class)) { + testDustTransition(bukkit, minecraft); + return; + } + + if (bukkit.getDataType().equals(Vibration.class)) { + testVibration(bukkit, minecraft); + return; + } + + if (bukkit.getDataType().equals(Float.class)) { + testFloat(bukkit, minecraft); + return; + } + + if (bukkit.getDataType().equals(Integer.class)) { + testInteger(bukkit, minecraft); + return; + } + + fail(String.format(""" + No test found for particle %s. + Please add a test case for it here. + """, bukkit.getKey())); + } + + private void testEmptyData(Particle bukkit, net.minecraft.core.particles.Particle minecraft) { + createAndTest(bukkit, minecraft, null, ParticleType.class); + } + + private void testDustOption(Particle bukkit, net.minecraft.core.particles.Particle minecraft) { + Particle.DustOptions dustOptions = new Particle.DustOptions(Color.fromRGB(236, 28, 36), 0.1205f); + ParticleParamRedstone param = createAndTest(bukkit, minecraft, dustOptions, ParticleParamRedstone.class); + + assertEquals(0.1205f, param.getScale(), 0.001, String.format(""" + Dust option scale for particle %s do not match. + Did something change in the implementation or minecraft? + """, bukkit.getKey())); + + Vector3f expectedColor = new Vector3f(0.92549f, 0.1098f, 0.14117647f); + assertTrue(expectedColor.equals(param.getColor(), 0.001f), String.format(""" + Dust option color for particle %s do not match. + Did something change in the implementation or minecraft? + Expected: %s. + Got: %s. + """, bukkit.getKey(), expectedColor, param.getColor())); // Print expected and got since we use assert true + } + + private void testItemStack(Particle bukkit, net.minecraft.core.particles.Particle minecraft) { + ItemStack itemStack = new ItemStack(Material.STONE); + ParticleParamItem param = createAndTest(bukkit, minecraft, itemStack, ParticleParamItem.class); + + assertEquals(itemStack, CraftItemStack.asBukkitCopy(param.getItem()), String.format(""" + ItemStack for particle %s do not match. + Did something change in the implementation or minecraft? + """, bukkit.getKey())); + } + + private void testBlockData(Particle bukkit, net.minecraft.core.particles.Particle minecraft) { + BlockData blockData = Bukkit.createBlockData(Material.STONE); + ParticleParamBlock param = createAndTest(bukkit, minecraft, blockData, ParticleParamBlock.class); + + assertEquals(blockData, CraftBlockData.fromData(param.getState()), String.format(""" + Block data for particle %s do not match. + Did something change in the implementation or minecraft? + """, bukkit.getKey())); + } + + private void testDustTransition(Particle bukkit, net.minecraft.core.particles.Particle minecraft) { + Particle.DustTransition dustTransition = new Particle.DustTransition(Color.fromRGB(236, 28, 36), Color.fromRGB(107, 159, 181), 0.1205f); + DustColorTransitionOptions param = createAndTest(bukkit, minecraft, dustTransition, DustColorTransitionOptions.class); + + assertEquals(0.1205f, param.getScale(), 0.001, String.format(""" + Dust transition scale for particle %s do not match. + Did something change in the implementation or minecraft? + """, bukkit.getKey())); + + Vector3f expectedFrom = new Vector3f(0.92549f, 0.1098f, 0.14117647f); + assertTrue(expectedFrom.equals(param.getFromColor(), 0.001f), String.format(""" + Dust transition from color for particle %s do not match. + Did something change in the implementation or minecraft? + Expected: %s. + Got: %s. + """, bukkit.getKey(), expectedFrom, param.getColor())); // Print expected and got since we use assert true + + Vector3f expectedTo = new Vector3f(0.4196f, 0.6235294f, 0.7098f); + assertTrue(expectedTo.equals(param.getToColor(), 0.001f), String.format(""" + Dust transition to color for particle %s do not match. + Did something change in the implementation or minecraft? + Expected: %s. + Got: %s. + """, bukkit.getKey(), expectedTo, param.getColor())); // Print expected and got since we use assert true + } + + private void testVibration(Particle bukkit, net.minecraft.core.particles.Particle minecraft) { + Vibration vibration = new Vibration(new Location(null, 3, 1, 4), new Vibration.Destination.BlockDestination(new Location(null, 1, 5, 9)), 265); + VibrationParticleOption param = createAndTest(bukkit, minecraft, vibration, VibrationParticleOption.class); + + assertEquals(265, param.getArrivalInTicks(), String.format(""" + Vibration ticks for particle %s do not match. + Did something change in the implementation or minecraft? + """, bukkit.getKey())); + + Optional pos = param.getDestination().getPosition(null); + assertTrue(pos.isPresent(), String.format(""" + Vibration position for particle %s is not present. + Did something change in the implementation or minecraft? + """, bukkit.getKey())); + + // Add 0.5 since it gets centered to the block + assertEquals(new Vec3D(1.5, 5.5, 9.5), pos.get(), String.format(""" + Vibration position for particle %s do not match. + Did something change in the implementation or minecraft? + """, bukkit.getKey())); + } + + private void testFloat(Particle bukkit, net.minecraft.core.particles.Particle minecraft) { + float role = 0.1205f; + SculkChargeParticleOptions param = createAndTest(bukkit, minecraft, role, SculkChargeParticleOptions.class); + + assertEquals(role, param.roll(), 0.001, String.format(""" + Float role for particle %s do not match. + Did something change in the implementation or minecraft? + """, bukkit.getKey())); + } + + private void testInteger(Particle bukkit, net.minecraft.core.particles.Particle minecraft) { + int delay = 1205; + ShriekParticleOption param = createAndTest(bukkit, minecraft, delay, ShriekParticleOption.class); + + assertEquals(delay, param.getDelay(), String.format(""" + Integer delay for particle %s do not match. + Did something change in the implementation or minecraft? + """, bukkit.getKey())); + } + + private D createAndTest(Particle bukkit, net.minecraft.core.particles.Particle minecraft, Object data, Class paramClass) { + @SuppressWarnings("unchecked") + T particleParam = (T) assertDoesNotThrow(() -> CraftParticle.createParticleParam(bukkit, data), String.format(""" + Could not create particle param for particle %s. + This can indicated, that the default particle param is used, but the particle requires extra data. + Or that something is wrong with the logic which creates the particle param with extra data. + Check in CraftParticle if the conversion is still correct. + """, bukkit.getKey())); + + DataResult encoded = assertDoesNotThrow(() -> minecraft.codec().encodeStart(DynamicOpsNBT.INSTANCE, particleParam), + String.format(""" + Could not encoded particle param for particle %s. + This can indicated, that the wrong particle param is created in CraftParticle. + Particle param is of type %s. + """, bukkit.getKey(), particleParam.getClass())); + + Optional> encodeError = encoded.error(); + assertTrue(encodeError.isEmpty(), () -> String.format(""" + Could not encoded particle param for particle %s. + This is possible because the wrong particle param is created in CraftParticle. + Particle param is of type %s. + Error message: %s. + """, bukkit.getKey(), particleParam.getClass(), encoded.error().get().message())); + + Optional encodeResult = encoded.result(); + assertTrue(encodeResult.isPresent(), String.format(""" + Result is not present for particle %s. + Even though there is also no error, this should not happen. + Particle param is of type %s. + """, bukkit.getKey(), particleParam.getClass())); + + DataResult decoded = minecraft.codec().parse(DynamicOpsNBT.INSTANCE, encodeResult.get()); + + Optional> decodeError = decoded.error(); + assertTrue(decodeError.isEmpty(), () -> String.format(""" + Could not decoded particle param for particle %s. + This is possible because the wrong particle param is created in CraftParticle. + Particle param is of type %s. + Error message: %s. + + + NBT data: %s. + """, bukkit.getKey(), particleParam.getClass(), decodeError.get().message(), encodeResult.get())); + + Optional decodeResult = decoded.result(); + assertTrue(decodeResult.isPresent(), String.format(""" + Result is not present for particle %s. + Even though there is also no error, this should not happen. + Particle param is of type %s. + """, bukkit.getKey(), particleParam.getClass())); + + return assertInstanceOf(paramClass, decodeResult.get(), String.format(""" + Result is not of the right type for particle %s. + """, bukkit.getKey())); } }