diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftEffect.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftEffect.java index 71733f918e..c856384019 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftEffect.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftEffect.java @@ -15,6 +15,14 @@ public class CraftEffect { public static <T> int getDataValue(Effect effect, T data) { int datavalue; switch (effect) { + // Paper start - add missing effects + case PARTICLES_SCULK_CHARGE: + case TRIAL_SPAWNER_DETECT_PLAYER: + case BEE_GROWTH: + case TURTLE_EGG_PLACEMENT: + case SMASH_ATTACK: + case TRIAL_SPAWNER_DETECT_PLAYER_OMINOUS: + // Paper end - add missing effects case VILLAGER_PLANT_GROW: datavalue = (Integer) data; break; @@ -26,6 +34,13 @@ public class CraftEffect { Preconditions.checkArgument(data == Material.AIR || ((Material) data).isRecord(), "Invalid record type for Material %s!", data); datavalue = Item.getId(CraftItemType.bukkitToMinecraft((Material) data)); break; + // Paper start - handle shoot white smoke event + case SHOOT_WHITE_SMOKE: + final BlockFace face = (BlockFace) data; + Preconditions.checkArgument(face.isCartesian(), face + " isn't cartesian"); + datavalue = org.bukkit.craftbukkit.block.CraftBlock.blockFaceToNotch(face).get3DDataValue(); + break; + // Paper end - handle shoot white smoke event case SMOKE: switch ((BlockFace) data) { case DOWN: @@ -57,10 +72,25 @@ public class CraftEffect { } break; case STEP_SOUND: + if (data instanceof Material) { // Paper - support BlockData Preconditions.checkArgument(((Material) data).isBlock(), "Material %s is not a block!", data); datavalue = Block.getId(CraftBlockType.bukkitToMinecraft((Material) data).defaultBlockState()); + // Paper start - support BlockData + break; + } + case PARTICLES_AND_SOUND_BRUSH_BLOCK_COMPLETE: + datavalue = Block.getId(((org.bukkit.craftbukkit.block.data.CraftBlockData) data).getState()); + // Paper end break; case COMPOSTER_FILL_ATTEMPT: + // Paper start - add missing effects + case TRIAL_SPAWNER_SPAWN: + case TRIAL_SPAWNER_SPAWN_MOB_AT: + case VAULT_ACTIVATE: + case VAULT_DEACTIVATE: + case TRIAL_SPAWNER_BECOME_OMINOUS: + case TRIAL_SPAWNER_SPAWN_ITEM: + // Paper end - add missing effects datavalue = ((Boolean) data) ? 1 : 0; break; case BONE_MEAL_USE: diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index 5379931b24..231089eab8 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -1363,7 +1363,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { public <T> void playEffect(Location loc, Effect effect, T data, int radius) { if (data != null) { Preconditions.checkArgument(effect.getData() != null, "Effect.%s does not have a valid Data", effect); - Preconditions.checkArgument(effect.getData().isAssignableFrom(data.getClass()), "%s data cannot be used for the %s effect", data.getClass().getName(), effect); + Preconditions.checkArgument(effect.isApplicable(data), "%s data cannot be used for the %s effect", data.getClass().getName(), effect); // Paper } else { // Special case: the axis is optional for ELECTRIC_SPARK Preconditions.checkArgument(effect.getData() == null || effect == Effect.ELECTRIC_SPARK, "Wrong kind of data for the %s effect", effect); diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index d47b44adb0..1f188f80e4 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -924,7 +924,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { Preconditions.checkArgument(effect != null, "Effect cannot be null"); if (data != null) { Preconditions.checkArgument(effect.getData() != null, "Effect.%s does not have a valid Data", effect); - Preconditions.checkArgument(effect.getData().isAssignableFrom(data.getClass()), "%s data cannot be used for the %s effect", data.getClass().getName(), effect); + Preconditions.checkArgument(effect.isApplicable(data), "%s data cannot be used for the %s effect", data.getClass().getName(), effect); // Paper } else { // Special case: the axis is optional for ELECTRIC_SPARK Preconditions.checkArgument(effect.getData() == null || effect == Effect.ELECTRIC_SPARK, "Wrong kind of data for the %s effect", effect); diff --git a/paper-server/src/test/java/org/bukkit/EffectTest.java b/paper-server/src/test/java/org/bukkit/EffectTest.java new file mode 100644 index 0000000000..3129212415 --- /dev/null +++ b/paper-server/src/test/java/org/bukkit/EffectTest.java @@ -0,0 +1,78 @@ +package org.bukkit; + +import com.google.common.base.Joiner; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import net.minecraft.world.level.block.LevelEvent; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +public class EffectTest { + + private static List<Integer> collectNmsLevelEvents() throws ReflectiveOperationException { + final List<Integer> events = new ArrayList<>(); + for (final Field field : LevelEvent.class.getFields()) { + if (Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers()) && field.getType() == int.class) { + events.add((int) field.get(null)); + } + } + return events; + } + + private static boolean isNotDeprecated(Effect effect) throws ReflectiveOperationException { + return !Effect.class.getDeclaredField(effect.name()).isAnnotationPresent(Deprecated.class); + } + + @Test + public void checkAllApiExists() throws ReflectiveOperationException { + Map<Integer, Effect> toId = new HashMap<>(); + for (final Effect effect : Effect.values()) { + if (isNotDeprecated(effect)) { + final Effect put = toId.put(effect.getId(), effect); + assertNull(put, "duplicate API effect: " + put); + } + } + + final Set<Integer> missingEvents = new HashSet<>(); + for (final Integer event : collectNmsLevelEvents()) { + if (toId.get(event) == null) { + missingEvents.add(event); + } + } + if (!missingEvents.isEmpty()) { + fail("Missing API Effects:\n" + Joiner.on("\n").join(missingEvents)); + } + } + + @Test + public void checkNoExtraApi() throws ReflectiveOperationException { + Map<Integer, Effect> toId = new HashMap<>(); + for (final Effect effect : Effect.values()) { + if (isNotDeprecated(effect)) { + final Effect put = toId.put(effect.getId(), effect); + assertNull(put, "duplicate API effect: " + put); + } + } + + final List<Integer> nmsEvents = collectNmsLevelEvents(); + final Set<Effect> extraApiEffects = new HashSet<>(); + for (final Map.Entry<Integer, Effect> entry : toId.entrySet()) { + if (!nmsEvents.contains(entry.getKey())) { + extraApiEffects.add(entry.getValue()); + } + } + if (!extraApiEffects.isEmpty()) { + fail("Extra API Effects:\n" + Joiner.on("\n").join(extraApiEffects)); + } + } +}