package org.bukkit; import static org.junit.jupiter.api.Assertions.*; 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.support.AbstractTestingBase; 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 { public static Stream data() { return CraftRegistry.getMinecraftRegistry(Registries.PARTICLE_TYPE).keySet().stream().map(Arguments::of); } @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; } 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())); } }