#1279: Back Particle by a minecraft registry

This commit is contained in:
DerFrZocker 2023-10-21 13:42:09 +11:00 committed by md_5
parent f4d977e794
commit 67a52a6485
No known key found for this signature in database
GPG Key ID: E8E901AC7C617C11
6 changed files with 456 additions and 230 deletions

View File

@ -1,11 +1,10 @@
package org.bukkit.craftbukkit; package org.bukkit.craftbukkit;
import com.google.common.base.Preconditions; 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.HashMap;
import java.util.Map; 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.DustColorTransitionOptions;
import net.minecraft.core.particles.ParticleParam; import net.minecraft.core.particles.ParticleParam;
import net.minecraft.core.particles.ParticleParamBlock; 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.SculkChargeParticleOptions;
import net.minecraft.core.particles.ShriekParticleOption; import net.minecraft.core.particles.ShriekParticleOption;
import net.minecraft.core.particles.VibrationParticleOption; import net.minecraft.core.particles.VibrationParticleOption;
import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries;
import net.minecraft.resources.MinecraftKey;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.gameevent.BlockPositionSource; import net.minecraft.world.level.gameevent.BlockPositionSource;
import net.minecraft.world.level.gameevent.EntityPositionSource; import net.minecraft.world.level.gameevent.EntityPositionSource;
import net.minecraft.world.level.gameevent.PositionSource; import net.minecraft.world.level.gameevent.PositionSource;
import org.bukkit.Color; import org.bukkit.Color;
import org.bukkit.Keyed;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.NamespacedKey;
import org.bukkit.Particle; import org.bukkit.Particle;
import org.bukkit.Registry;
import org.bukkit.Vibration; import org.bukkit.Vibration;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
import org.bukkit.craftbukkit.block.data.CraftBlockData; 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.inventory.CraftItemStack;
import org.bukkit.craftbukkit.util.CraftLocation; import org.bukkit.craftbukkit.util.CraftLocation;
import org.bukkit.craftbukkit.util.CraftMagicNumbers; import org.bukkit.craftbukkit.util.CraftMagicNumbers;
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.material.MaterialData; import org.bukkit.material.MaterialData;
import org.joml.Vector3f; import org.joml.Vector3f;
public enum CraftParticle { public abstract class CraftParticle<D> implements Keyed {
EXPLOSION_NORMAL("poof"), private static final Registry<CraftParticle<?>> CRAFT_PARTICLE_REGISTRY = new CraftParticleRegistry(CraftRegistry.getMinecraftRegistry(Registries.PARTICLE_TYPE));
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<Particle, MinecraftKey> particles;
private static final Map<Particle, Particle> 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 <T> 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<ParticleParamItem>) nms, CraftItemStack.asNMSCopy(itemStack));
}
if (particle.getDataType() == MaterialData.class) {
MaterialData data = (MaterialData) obj;
return new ParticleParamBlock((net.minecraft.core.particles.Particle<ParticleParamBlock>) nms, CraftMagicNumbers.getBlock(data));
}
if (particle.getDataType() == BlockData.class) {
BlockData data = (BlockData) obj;
return new ParticleParamBlock((net.minecraft.core.particles.Particle<ParticleParamBlock>) 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());
}
public static Particle minecraftToBukkit(net.minecraft.core.particles.Particle<?> minecraft) { public static Particle minecraftToBukkit(net.minecraft.core.particles.Particle<?> minecraft) {
return particles.inverse().get(BuiltInRegistries.PARTICLE_TYPE.getKey(minecraft)); Preconditions.checkArgument(minecraft != null);
IRegistry<net.minecraft.core.particles.Particle<?>> 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 <D> ParticleParam createParticleParam(Particle particle, D data) {
Preconditions.checkArgument(particle != null);
CraftParticle<D> craftParticle = (CraftParticle<D>) CRAFT_PARTICLE_REGISTRY.get(particle.getKey());
Preconditions.checkArgument(craftParticle != null);
return craftParticle.createParticleParam(data);
}
public static <T> 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<D> clazz;
public CraftParticle(NamespacedKey key, net.minecraft.core.particles.Particle<?> particle, Class<D> 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<CraftParticle<?>, net.minecraft.core.particles.Particle<?>> {
private static final Map<NamespacedKey, BiFunction<NamespacedKey, net.minecraft.core.particles.Particle<?>, CraftParticle<?>>> PARTICLE_MAP = new HashMap<>();
private static final BiFunction<NamespacedKey, net.minecraft.core.particles.Particle<?>, CraftParticle<?>> VOID_FUNCTION = (name, particle) -> new CraftParticle<>(name, particle, Void.class) {
@Override
public ParticleParam createParticleParam(Void data) {
return (ParticleType) getHandle();
}
};
static {
BiFunction<NamespacedKey, net.minecraft.core.particles.Particle<?>, 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<NamespacedKey, net.minecraft.core.particles.Particle<?>, CraftParticle<?>> itemStackFunction = (name, particle) -> new CraftParticle<>(name, particle, ItemStack.class) {
@Override
public ParticleParam createParticleParam(ItemStack data) {
return new ParticleParamItem((net.minecraft.core.particles.Particle<ParticleParamItem>) getHandle(), CraftItemStack.asNMSCopy(data));
}
};
BiFunction<NamespacedKey, net.minecraft.core.particles.Particle<?>, CraftParticle<?>> blockDataFunction = (name, particle) -> new CraftParticle<>(name, particle, BlockData.class) {
@Override
public ParticleParam createParticleParam(BlockData data) {
return new ParticleParamBlock((net.minecraft.core.particles.Particle<ParticleParamBlock>) getHandle(), ((CraftBlockData) data).getState());
}
};
BiFunction<NamespacedKey, net.minecraft.core.particles.Particle<?>, 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<NamespacedKey, net.minecraft.core.particles.Particle<?>, 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<NamespacedKey, net.minecraft.core.particles.Particle<?>, CraftParticle<?>> floatFunction = (name, particle) -> new CraftParticle<>(name, particle, Float.class) {
@Override
public ParticleParam createParticleParam(Float data) {
return new SculkChargeParticleOptions(data);
}
};
BiFunction<NamespacedKey, net.minecraft.core.particles.Particle<?>, 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<NamespacedKey, net.minecraft.core.particles.Particle<?>, CraftParticle<?>> function) {
PARTICLE_MAP.put(NamespacedKey.fromString(name), function);
}
public CraftParticleRegistry(IRegistry<net.minecraft.core.particles.Particle<?>> minecraftRegistry) {
super(CraftParticle.class, minecraftRegistry, null);
}
@Override
public CraftParticle<?> createBukkit(NamespacedKey namespacedKey, net.minecraft.core.particles.Particle<?> particle) {
if (particle == null) {
return null;
}
BiFunction<NamespacedKey, net.minecraft.core.particles.Particle<?>, CraftParticle<?>> function = PARTICLE_MAP.getOrDefault(namespacedKey, VOID_FUNCTION);
return function.apply(namespacedKey, particle);
}
} }
} }

View File

@ -67,13 +67,13 @@ public class CraftRegistry<B extends Keyed, M> implements Registry<B> {
return null; return null;
} }
private final Class<B> bukkitClass; private final Class<? super B> bukkitClass;
private final Map<NamespacedKey, B> cache = new HashMap<>(); private final Map<NamespacedKey, B> cache = new HashMap<>();
private final IRegistry<M> minecraftRegistry; private final IRegistry<M> minecraftRegistry;
private final BiFunction<NamespacedKey, M, B> minecraftToBukkit; private final BiFunction<NamespacedKey, M, B> minecraftToBukkit;
private boolean init; private boolean init;
public CraftRegistry(Class<B> bukkitClass, IRegistry<M> minecraftRegistry, BiFunction<NamespacedKey, M, B> minecraftToBukkit) { public CraftRegistry(Class<? super B> bukkitClass, IRegistry<M> minecraftRegistry, BiFunction<NamespacedKey, M, B> minecraftToBukkit) {
this.bukkitClass = bukkitClass; this.bukkitClass = bukkitClass;
this.minecraftRegistry = minecraftRegistry; this.minecraftRegistry = minecraftRegistry;
this.minecraftToBukkit = minecraftToBukkit; this.minecraftToBukkit = minecraftToBukkit;

View File

@ -1788,12 +1788,14 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override @Override
public <T> void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data, boolean force) { public <T> 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) { if (data != null) {
Preconditions.checkArgument(particle.getDataType().isInstance(data), "data (%s) should be %s", data.getClass(), particle.getDataType()); Preconditions.checkArgument(particle.getDataType().isInstance(data), "data (%s) should be %s", data.getClass(), particle.getDataType());
} }
getHandle().sendParticles( getHandle().sendParticles(
null, // Sender null, // Sender
CraftParticle.toNMS(particle, data), // Particle CraftParticle.createParticleParam(particle, data), // Particle
x, y, z, // Position x, y, z, // Position
count, // Count count, // Count
offsetX, offsetY, offsetZ, // Random offset offsetX, offsetY, offsetZ, // Random offset

View File

@ -121,7 +121,12 @@ public class CraftAreaEffectCloud extends CraftEntity implements AreaEffectCloud
@Override @Override
public <T> void setParticle(Particle particle, T data) { public <T> 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 @Override

View File

@ -2080,10 +2080,12 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
@Override @Override
public <T> void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data) { public <T> 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) { if (data != null) {
Preconditions.checkArgument(particle.getDataType().isInstance(data), "data (%s) should be %s", data.getClass(), particle.getDataType()); 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); getHandle().connection.send(packetplayoutworldparticles);
} }

View File

@ -1,43 +1,280 @@
package org.bukkit; package org.bukkit;
import static org.junit.jupiter.api.Assertions.*; 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.block.data.BlockData;
import org.bukkit.craftbukkit.CraftParticle; import org.bukkit.craftbukkit.CraftParticle;
import org.bukkit.craftbukkit.CraftRegistry;
import org.bukkit.craftbukkit.block.data.CraftBlockData; 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.inventory.ItemStack;
import org.bukkit.material.MaterialData;
import org.bukkit.support.AbstractTestingBase; 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 { public class ParticleTest extends AbstractTestingBase {
@Test public static Stream<Arguments> data() {
public void verifyMapping() { return CraftRegistry.getMinecraftRegistry(Registries.PARTICLE_TYPE).keySet().stream().map(Arguments::of);
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;
}
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 <T extends ParticleParam> void testEmptyData(Particle bukkit, net.minecraft.core.particles.Particle<T> minecraft) {
createAndTest(bukkit, minecraft, null, ParticleType.class);
}
private <T extends ParticleParam> void testDustOption(Particle bukkit, net.minecraft.core.particles.Particle<T> 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 <T extends ParticleParam> void testItemStack(Particle bukkit, net.minecraft.core.particles.Particle<T> 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 <T extends ParticleParam> void testBlockData(Particle bukkit, net.minecraft.core.particles.Particle<T> 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 <T extends ParticleParam> void testDustTransition(Particle bukkit, net.minecraft.core.particles.Particle<T> 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 <T extends ParticleParam> void testVibration(Particle bukkit, net.minecraft.core.particles.Particle<T> 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<Vec3D> 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 <T extends ParticleParam> void testFloat(Particle bukkit, net.minecraft.core.particles.Particle<T> 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 <T extends ParticleParam> void testInteger(Particle bukkit, net.minecraft.core.particles.Particle<T> 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 extends ParticleParam, T extends ParticleParam> D createAndTest(Particle bukkit, net.minecraft.core.particles.Particle<T> minecraft, Object data, Class<D> 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<NBTBase> 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<DataResult.PartialResult<NBTBase>> 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<NBTBase> 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<T> decoded = minecraft.codec().parse(DynamicOpsNBT.INSTANCE, encodeResult.get());
Optional<DataResult.PartialResult<T>> 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<T> 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()));
} }
} }