mirror of
https://github.com/PaperMC/Paper.git
synced 2024-12-27 23:10:16 +01:00
619 lines
34 KiB
Diff
619 lines
34 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Jake Potrebic <jake.m.potrebic@gmail.com>
|
|
Date: Wed, 8 Dec 2021 16:49:37 -0800
|
|
Subject: [PATCH] BlockProperty API
|
|
|
|
|
|
diff --git a/src/main/java/io/papermc/paper/block/fluid/PaperFluidData.java b/src/main/java/io/papermc/paper/block/fluid/PaperFluidData.java
|
|
index 479bc32241ebadf8bbc1080b601f61391ad37fa4..b66f1a6e896ae4c5242620fbdc804a40ab5cb8e2 100644
|
|
--- a/src/main/java/io/papermc/paper/block/fluid/PaperFluidData.java
|
|
+++ b/src/main/java/io/papermc/paper/block/fluid/PaperFluidData.java
|
|
@@ -3,10 +3,14 @@ package io.papermc.paper.block.fluid;
|
|
import com.google.common.base.Preconditions;
|
|
import io.papermc.paper.block.fluid.type.PaperFallingFluidData;
|
|
import io.papermc.paper.block.fluid.type.PaperFlowingFluidData;
|
|
+import io.papermc.paper.block.property.PaperBlockPropertyHolder;
|
|
import io.papermc.paper.util.MCUtil;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
import java.util.function.Function;
|
|
+import net.minecraft.world.level.block.state.StateDefinition;
|
|
+import net.minecraft.world.level.block.state.properties.EnumProperty;
|
|
+import net.minecraft.world.level.block.state.properties.Property;
|
|
import net.minecraft.world.level.material.FluidState;
|
|
import net.minecraft.world.level.material.LavaFluid;
|
|
import net.minecraft.world.level.material.WaterFluid;
|
|
@@ -14,11 +18,12 @@ import org.bukkit.Fluid;
|
|
import org.bukkit.Location;
|
|
import org.bukkit.craftbukkit.CraftFluid;
|
|
import org.bukkit.craftbukkit.CraftWorld;
|
|
+import org.bukkit.craftbukkit.block.data.CraftBlockData;
|
|
import org.bukkit.craftbukkit.util.CraftVector;
|
|
import org.bukkit.util.Vector;
|
|
import org.jetbrains.annotations.NotNull;
|
|
|
|
-public class PaperFluidData implements FluidData {
|
|
+public class PaperFluidData implements FluidData, PaperBlockPropertyHolder<net.minecraft.world.level.material.Fluid, FluidState> {
|
|
|
|
private final FluidState state;
|
|
|
|
@@ -99,6 +104,21 @@ public class PaperFluidData implements FluidData {
|
|
//</editor-fold>
|
|
}
|
|
|
|
+ @Override
|
|
+ public StateDefinition<net.minecraft.world.level.material.Fluid, FluidState> getStateDefinition() {
|
|
+ return this.state.getType().getStateDefinition();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public <T extends Comparable<T>> T get(final Property<T> ibs) {
|
|
+ return this.state.getValue(ibs);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public <B extends Enum<B>> B get(final EnumProperty<?> nms, final Class<B> bukkit) {
|
|
+ return CraftBlockData.toBukkit(this.state.getValue(nms), bukkit);
|
|
+ }
|
|
+
|
|
static void register(final Class<? extends net.minecraft.world.level.material.Fluid> fluid, final Function<FluidState, PaperFluidData> creator) {
|
|
Preconditions.checkState(MAP.put(fluid, creator) == null, "Duplicate mapping %s->%s", fluid, creator);
|
|
MAP.put(fluid, creator);
|
|
diff --git a/src/main/java/io/papermc/paper/block/property/PaperBlockProperties.java b/src/main/java/io/papermc/paper/block/property/PaperBlockProperties.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..ad1e17fe4fa8ab410c5443f0912b63f89e67d8ef
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/block/property/PaperBlockProperties.java
|
|
@@ -0,0 +1,82 @@
|
|
+package io.papermc.paper.block.property;
|
|
+
|
|
+import java.util.Collection;
|
|
+import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
|
+import net.minecraft.world.level.block.state.properties.Property;
|
|
+import org.bukkit.craftbukkit.block.data.CraftBlockData;
|
|
+
|
|
+public final class PaperBlockProperties {
|
|
+
|
|
+ public static void setup() {
|
|
+ //<editor-fold desc="Paper API Properties Registration" defaultstate="collapsed">
|
|
+ registerProp(BlockStateProperties.DOUBLE_BLOCK_HALF, BlockProperties.DOUBLE_BLOCK_HALF);
|
|
+ registerProp(BlockStateProperties.HALF, BlockProperties.HALF);
|
|
+ registerProp(BlockStateProperties.HORIZONTAL_AXIS, BlockProperties.HORIZONTAL_AXIS);
|
|
+ registerProp(BlockStateProperties.AXIS, BlockProperties.AXIS);
|
|
+ registerProp(BlockStateProperties.CHEST_TYPE, BlockProperties.CHEST_TYPE);
|
|
+ registerProp(BlockStateProperties.PISTON_TYPE, BlockProperties.PISTON_TYPE);
|
|
+ registerProp(BlockStateProperties.SLAB_TYPE, BlockProperties.SLAB_TYPE);
|
|
+ registerProp(BlockStateProperties.MODE_COMPARATOR, BlockProperties.MODE_COMPARATOR);
|
|
+ registerProp(BlockStateProperties.STRUCTUREBLOCK_MODE, BlockProperties.STRUCTUREBLOCK_MODE);
|
|
+ registerProp(BlockStateProperties.WEST, BlockProperties.WEST);
|
|
+ registerProp(BlockStateProperties.WEST_WALL, BlockProperties.WEST_WALL);
|
|
+ registerProp(BlockStateProperties.WEST_REDSTONE, BlockProperties.WEST_REDSTONE);
|
|
+ registerProp(BlockStateProperties.EAST, BlockProperties.EAST);
|
|
+ registerProp(BlockStateProperties.EAST_WALL, BlockProperties.EAST_WALL);
|
|
+ registerProp(BlockStateProperties.EAST_REDSTONE, BlockProperties.EAST_REDSTONE);
|
|
+ registerProp(BlockStateProperties.NORTH, BlockProperties.NORTH);
|
|
+ registerProp(BlockStateProperties.NORTH_WALL, BlockProperties.NORTH_WALL);
|
|
+ registerProp(BlockStateProperties.NORTH_REDSTONE, BlockProperties.NORTH_REDSTONE);
|
|
+ registerProp(BlockStateProperties.SOUTH, BlockProperties.SOUTH);
|
|
+ registerProp(BlockStateProperties.SOUTH_WALL, BlockProperties.SOUTH_WALL);
|
|
+ registerProp(BlockStateProperties.SOUTH_REDSTONE, BlockProperties.SOUTH_REDSTONE);
|
|
+ registerProp(BlockStateProperties.RAIL_SHAPE, BlockProperties.RAIL_SHAPE);
|
|
+ registerProp(BlockStateProperties.RAIL_SHAPE_STRAIGHT, BlockProperties.RAIL_SHAPE_STRAIGHT);
|
|
+ registerProp(BlockStateProperties.STAIRS_SHAPE, BlockProperties.STAIRS_SHAPE);
|
|
+ registerProp(BlockStateProperties.LEVEL_CAULDRON, BlockProperties.LEVEL_CAULDRON);
|
|
+ registerProp(BlockStateProperties.LEVEL_COMPOSTER, BlockProperties.LEVEL_COMPOSTER);
|
|
+ registerProp(BlockStateProperties.LEVEL_FLOWING, BlockProperties.LEVEL_FLOWING);
|
|
+ registerProp(BlockStateProperties.LEVEL, BlockProperties.LEVEL);
|
|
+ registerProp(BlockStateProperties.DISTANCE, BlockProperties.DISTANCE);
|
|
+ registerProp(BlockStateProperties.STABILITY_DISTANCE, BlockProperties.STABILITY_DISTANCE);
|
|
+ registerProp(BlockStateProperties.FACING, BlockProperties.FACING);
|
|
+ registerProp(BlockStateProperties.FACING_HOPPER, BlockProperties.FACING_HOPPER);
|
|
+ registerProp(BlockStateProperties.HORIZONTAL_FACING, BlockProperties.HORIZONTAL_FACING);
|
|
+ registerProp(BlockStateProperties.AGE_1, BlockProperties.AGE_1);
|
|
+ registerProp(BlockStateProperties.AGE_2, BlockProperties.AGE_2);
|
|
+ registerProp(BlockStateProperties.AGE_3, BlockProperties.AGE_3);
|
|
+ registerProp(BlockStateProperties.AGE_4, BlockProperties.AGE_4);
|
|
+ registerProp(BlockStateProperties.AGE_5, BlockProperties.AGE_5);
|
|
+ registerProp(BlockStateProperties.AGE_7, BlockProperties.AGE_7);
|
|
+ registerProp(BlockStateProperties.AGE_15, BlockProperties.AGE_15);
|
|
+ registerProp(BlockStateProperties.AGE_25, BlockProperties.AGE_25);
|
|
+ //</editor-fold>
|
|
+ }
|
|
+
|
|
+ private static void registerProp(final Property<?> nmsProperty, final BlockProperty<?> paperProperty) {
|
|
+ CraftBlockData.DATA_PROPERTY_CACHE_MAP.put(nmsProperty, paperProperty);
|
|
+ }
|
|
+
|
|
+
|
|
+ public static BlockProperty<?> convertToPaperProperty(final Property<?> nmsProperty) {
|
|
+ return CraftBlockData.DATA_PROPERTY_CACHE_MAP.computeIfAbsent(nmsProperty, prop -> {
|
|
+ final Collection<BlockProperty<?>> properties = BlockProperties.PROPERTIES.get(prop.getName());
|
|
+ if (properties.size() == 1) {
|
|
+ return properties.iterator().next();
|
|
+ } else {
|
|
+ throw new IllegalArgumentException(nmsProperty + " should already be present in DATA_PROPERTY_CACHE_MAP");
|
|
+ }
|
|
+ });
|
|
+ }
|
|
+
|
|
+ public static Property<?> convertToNmsProperty(final BlockProperty<?> paperProperty) {
|
|
+ return CraftBlockData.DATA_PROPERTY_CACHE_MAP.inverse().computeIfAbsent(paperProperty, prop -> {
|
|
+ final Collection<Property<?>> properties = Property.PROPERTY_MULTIMAP.get(prop.name());
|
|
+ if (properties.size() == 1) {
|
|
+ return properties.iterator().next();
|
|
+ } else {
|
|
+ throw new IllegalArgumentException(paperProperty + " should already be present in DATA_PROPERTY_CACHE_MAP");
|
|
+ }
|
|
+ });
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/block/property/PaperBlockPropertyHolder.java b/src/main/java/io/papermc/paper/block/property/PaperBlockPropertyHolder.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..2d06aa85b261c964908f5d778a429e28ebcc34dd
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/block/property/PaperBlockPropertyHolder.java
|
|
@@ -0,0 +1,107 @@
|
|
+package io.papermc.paper.block.property;
|
|
+
|
|
+import com.google.common.base.Preconditions;
|
|
+import java.util.Collection;
|
|
+import java.util.Collections;
|
|
+import net.minecraft.util.StringRepresentable;
|
|
+import net.minecraft.world.level.block.state.StateDefinition;
|
|
+import net.minecraft.world.level.block.state.StateHolder;
|
|
+import net.minecraft.world.level.block.state.properties.BooleanProperty;
|
|
+import net.minecraft.world.level.block.state.properties.EnumProperty;
|
|
+import net.minecraft.world.level.block.state.properties.IntegerProperty;
|
|
+import net.minecraft.world.level.block.state.properties.Property;
|
|
+import org.jspecify.annotations.Nullable;
|
|
+
|
|
+public interface PaperBlockPropertyHolder<O, S extends StateHolder<O, S>> extends BlockPropertyHolder {
|
|
+
|
|
+ StateHolder<O, S> getState();
|
|
+
|
|
+ StateDefinition<O, S> getStateDefinition();
|
|
+
|
|
+ <T extends Comparable<T>> T get(Property<T> ibs);
|
|
+
|
|
+ <B extends Enum<B>> B get(EnumProperty<?> nms, Class<B> bukkit);
|
|
+
|
|
+ @SuppressWarnings("unchecked")
|
|
+ @Override
|
|
+ default <T extends Comparable<T>> @Nullable BlockProperty<T> getProperty(final String propertyName) {
|
|
+ final Property<?> nmsProperty = this.getStateDefinition().getProperty(propertyName);
|
|
+ if (nmsProperty != null) {
|
|
+ return (BlockProperty<T>) PaperBlockProperties.convertToPaperProperty(nmsProperty);
|
|
+ }
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ default <T extends Comparable<T>> boolean hasProperty(final BlockProperty<T> property) {
|
|
+ return this.getState().hasProperty(PaperBlockProperties.convertToNmsProperty(property));
|
|
+ }
|
|
+
|
|
+ @SuppressWarnings("unchecked")
|
|
+ @Override
|
|
+ default <T extends Comparable<T>> T getValue(final BlockProperty<T> property) {
|
|
+ final Property<?> nmsProperty = PaperBlockProperties.convertToNmsProperty(property);
|
|
+ Preconditions.checkArgument(this.getState().hasProperty(nmsProperty), property.name() + " is not present on " + this);
|
|
+ switch (nmsProperty) {
|
|
+ case final IntegerProperty nmsIntProperty -> {
|
|
+ final T value;
|
|
+ if (property instanceof final AsIntegerProperty<?> intRepresented) {
|
|
+ value = ((AsIntegerProperty<T>) intRepresented).fromIntValue(this.get(nmsIntProperty));
|
|
+ } else {
|
|
+ value = this.get((Property<T>) nmsIntProperty);
|
|
+ }
|
|
+ return value;
|
|
+ }
|
|
+ case final BooleanProperty ignored -> {
|
|
+ return this.get((Property<T>) nmsProperty);
|
|
+ }
|
|
+ case final EnumProperty<?> enumProperty when property instanceof final EnumBlockProperty<?> enumDataProperty -> {
|
|
+ return (T) this.get(enumProperty, enumDataProperty.type());
|
|
+ }
|
|
+ default -> throw new IllegalArgumentException("Did not recognize " + property + " and " + nmsProperty);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ default <T extends Comparable<T>> java.util.Optional<T> getOptionalValue(final BlockProperty<T> property) {
|
|
+ if (!this.hasProperty(property)) {
|
|
+ return java.util.Optional.empty();
|
|
+ } else {
|
|
+ return java.util.Optional.of(this.getValue(property));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ default Collection<BlockProperty<?>> getProperties() {
|
|
+ return Collections.unmodifiableCollection(com.google.common.collect.Collections2.transform(this.getState().getProperties(), PaperBlockProperties::convertToPaperProperty));
|
|
+ }
|
|
+
|
|
+ interface PaperMutable<O, S extends StateHolder<O, S>> extends PaperBlockPropertyHolder<O, S>, BlockPropertyHolder.Mutable {
|
|
+
|
|
+ <T extends Comparable<T>, V extends T> void set(Property<T> nmsProperty, V value);
|
|
+
|
|
+ <B extends Enum<B>, N extends Enum<N> & StringRepresentable> void set(EnumProperty<N> nms, Enum<B> bukkit);
|
|
+
|
|
+ @Override
|
|
+ default <T extends Comparable<T>> void setValue(final BlockProperty<T> property, final T value) {
|
|
+ Preconditions.checkNotNull(value, "Cannot set a data property to null");
|
|
+ final Property<?> nmsProperty = PaperBlockProperties.convertToNmsProperty(property);
|
|
+ Preconditions.checkArgument(this.getState().hasProperty(nmsProperty), property.name() + " is not present on " + this);
|
|
+ switch (nmsProperty) {
|
|
+ case final IntegerProperty nmsIntProperty -> {
|
|
+ final int intValue;
|
|
+ if (property instanceof final AsIntegerProperty<T> intRepresented) {
|
|
+ intValue = intRepresented.toIntValue(value);
|
|
+ } else {
|
|
+ intValue = (Integer) value;
|
|
+ }
|
|
+ this.set(nmsIntProperty, intValue);
|
|
+ }
|
|
+ case final BooleanProperty nmsBoolProperty -> this.set(nmsBoolProperty, (Boolean) value);
|
|
+ case final EnumProperty<?> enumProperty when value instanceof final Enum<?> enumValue -> this.set(enumProperty, enumValue);
|
|
+ default ->
|
|
+ throw new IllegalArgumentException("Did not recognize " + property + " with value " + value + " (" + value.getClass().getSimpleName() + ") for " + nmsProperty);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/block/property/package-info.java b/src/main/java/io/papermc/paper/block/property/package-info.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..359cc04d2e8d6f7eb2ea373e19aff4339f0a431c
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/block/property/package-info.java
|
|
@@ -0,0 +1,4 @@
|
|
+@NullMarked
|
|
+package io.papermc.paper.block.property;
|
|
+
|
|
+import org.jspecify.annotations.NullMarked;
|
|
diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java
|
|
index 0b116160924300a9d62ad5948bfaf276f0386e4d..50c34f79ab957830dfadb6bc98956471701e21d1 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java
|
|
@@ -51,11 +51,13 @@ public abstract class Property<T extends Comparable<T>> implements ca.spottedlea
|
|
@Override
|
|
public abstract int moonrise$getIdFor(final T value);
|
|
// Paper end - optimise blockstate property access
|
|
+ public static final com.google.common.collect.Multimap<String, Property<?>> PROPERTY_MULTIMAP = com.google.common.collect.Multimaps.newSetMultimap(new java.util.HashMap<>(), com.google.common.collect.Sets::newIdentityHashSet); // Paper - property API
|
|
|
|
protected Property(String name, Class<T> type) {
|
|
this.clazz = type;
|
|
this.name = name;
|
|
this.id = ID_GENERATOR.getAndIncrement(); // Paper - optimise blockstate property access
|
|
+ PROPERTY_MULTIMAP.put(this.name, this); // Paper - property API
|
|
}
|
|
|
|
public Property.Value<T> value(T value) {
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java
|
|
index 50fb7edd25c1b38f5c463b78d21d4583bdc89229..62f514601de2d0d103a5e9766d8e504857cd8086 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java
|
|
@@ -48,7 +48,7 @@ import org.bukkit.craftbukkit.util.CraftLocation;
|
|
import org.bukkit.inventory.ItemStack;
|
|
import org.jetbrains.annotations.NotNull;
|
|
|
|
-public class CraftBlockData implements BlockData {
|
|
+public class CraftBlockData implements BlockData, io.papermc.paper.block.property.PaperBlockPropertyHolder.PaperMutable<Block, net.minecraft.world.level.block.state.BlockState> { // Paper
|
|
|
|
private net.minecraft.world.level.block.state.BlockState state;
|
|
private Map<Property<?>, Comparable<?>> parsedStates;
|
|
@@ -78,7 +78,7 @@ public class CraftBlockData implements BlockData {
|
|
* @param <B> the type
|
|
* @return the matching Bukkit type
|
|
*/
|
|
- protected <B extends Enum<B>> B get(EnumProperty<?> nms, Class<B> bukkit) {
|
|
+ public <B extends Enum<B>> B get(EnumProperty<?> nms, Class<B> bukkit) { // Paper
|
|
return CraftBlockData.toBukkit(this.state.getValue(nms), bukkit);
|
|
}
|
|
|
|
@@ -110,7 +110,7 @@ public class CraftBlockData implements BlockData {
|
|
* @param <B> the Bukkit type
|
|
* @param <N> the NMS type
|
|
*/
|
|
- protected <B extends Enum<B>, N extends Enum<N> & StringRepresentable> void set(EnumProperty<N> nms, Enum<B> bukkit) {
|
|
+ public <B extends Enum<B>, N extends Enum<N> & StringRepresentable> void set(EnumProperty<N> nms, Enum<B> bukkit) { // Paper
|
|
this.parsedStates = null;
|
|
this.state = this.state.setValue(nms, CraftBlockData.toNMS(bukkit, nms.getValueClass()));
|
|
}
|
|
@@ -165,7 +165,7 @@ public class CraftBlockData implements BlockData {
|
|
* @throws IllegalStateException if the Enum could not be converted
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
- private static <B extends Enum<B>> B toBukkit(Enum<?> nms, Class<B> bukkit) {
|
|
+ public static <B extends Enum<B>> B toBukkit(Enum<?> nms, Class<B> bukkit) { // Paper - private -> public
|
|
if (nms instanceof Direction) {
|
|
return (B) CraftBlock.notchToBlockFace((Direction) nms);
|
|
}
|
|
@@ -195,7 +195,7 @@ public class CraftBlockData implements BlockData {
|
|
* @param <T> the type
|
|
* @return the current value of the given state
|
|
*/
|
|
- protected <T extends Comparable<T>> T get(Property<T> ibs) {
|
|
+ public <T extends Comparable<T>> T get(Property<T> ibs) { // Paper
|
|
// Straight integer or boolean getter
|
|
return this.state.getValue(ibs);
|
|
}
|
|
@@ -358,6 +358,7 @@ public class CraftBlockData implements BlockData {
|
|
|
|
//
|
|
private static final Map<Class<? extends Block>, Function<net.minecraft.world.level.block.state.BlockState, CraftBlockData>> MAP = new HashMap<>();
|
|
+ public static final com.google.common.collect.BiMap<Property<?>, io.papermc.paper.block.property.BlockProperty<?>> DATA_PROPERTY_CACHE_MAP = com.google.common.collect.HashBiMap.create(); // Paper
|
|
|
|
static {
|
|
//<editor-fold desc="CraftBlockData Registration" defaultstate="collapsed">
|
|
@@ -537,8 +538,16 @@ public class CraftBlockData implements BlockData {
|
|
register(net.minecraft.world.level.block.piston.PistonHeadBlock.class, org.bukkit.craftbukkit.block.impl.CraftPistonExtension::new);
|
|
register(net.minecraft.world.level.block.piston.MovingPistonBlock.class, org.bukkit.craftbukkit.block.impl.CraftPistonMoving::new);
|
|
//</editor-fold>
|
|
+ io.papermc.paper.block.property.PaperBlockProperties.setup(); // Paper
|
|
}
|
|
|
|
+ // Paper start - block property API
|
|
+ @Override
|
|
+ public net.minecraft.world.level.block.state.StateDefinition<Block, net.minecraft.world.level.block.state.BlockState> getStateDefinition() {
|
|
+ return this.getState().getBlock().getStateDefinition();
|
|
+ }
|
|
+ // Paper end - block property API
|
|
+
|
|
private static void register(Class<? extends Block> nms, Function<net.minecraft.world.level.block.state.BlockState, CraftBlockData> bukkit) {
|
|
Preconditions.checkState(CraftBlockData.MAP.put(nms, bukkit) == null, "Duplicate mapping %s->%s", nms, bukkit);
|
|
}
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
|
|
index 507f908916cbeb592496f963b46e4c2121a7b5e3..cfebd80f87ef5a52bd5b6af36ef428a92538015f 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
|
|
@@ -702,6 +702,18 @@ public final class CraftMagicNumbers implements UnsafeValues {
|
|
}
|
|
// Paper end - proxy ItemStack
|
|
|
|
+ // Paper start - block property API
|
|
+ @SuppressWarnings({"unchecked", "rawtypes"})
|
|
+ @Override
|
|
+ public <B extends Enum<B>> String getPropertyEnumName(io.papermc.paper.block.property.EnumBlockProperty<B> enumProperty, B bukkitEnum) {
|
|
+ final net.minecraft.world.level.block.state.properties.Property<?> nmsProperty = io.papermc.paper.block.property.PaperBlockProperties.convertToNmsProperty(enumProperty);
|
|
+ if (!(nmsProperty instanceof net.minecraft.world.level.block.state.properties.EnumProperty nmsEnumProperty)) {
|
|
+ throw new IllegalArgumentException("Could not convert " + enumProperty + " to an nms EnumProperty");
|
|
+ }
|
|
+ return nmsEnumProperty.getName(CraftBlockData.toNMS(bukkitEnum, nmsEnumProperty.getValueClass()));
|
|
+ }
|
|
+ // Paper end - block property API
|
|
+
|
|
/**
|
|
* This helper class represents the different NBT Tags.
|
|
* <p>
|
|
diff --git a/src/test/java/io/papermc/paper/block/property/BlockPropertyTest.java b/src/test/java/io/papermc/paper/block/property/BlockPropertyTest.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..22d1c3f2815c4091432a72654daca7fde3c45c55
|
|
--- /dev/null
|
|
+++ b/src/test/java/io/papermc/paper/block/property/BlockPropertyTest.java
|
|
@@ -0,0 +1,226 @@
|
|
+package io.papermc.paper.block.property;
|
|
+
|
|
+import com.google.common.collect.ImmutableMap;
|
|
+import java.io.PrintStream;
|
|
+import java.lang.reflect.Field;
|
|
+import java.lang.reflect.Modifier;
|
|
+import java.util.ArrayList;
|
|
+import java.util.LinkedHashMap;
|
|
+import java.util.List;
|
|
+import java.util.Map;
|
|
+import net.minecraft.core.Direction;
|
|
+import net.minecraft.core.FrontAndTop;
|
|
+import net.minecraft.util.StringRepresentable;
|
|
+import net.minecraft.world.level.block.CreakingHeartBlock;
|
|
+import net.minecraft.world.level.block.entity.trialspawner.TrialSpawnerState;
|
|
+import net.minecraft.world.level.block.entity.vault.VaultState;
|
|
+import net.minecraft.world.level.block.state.properties.AttachFace;
|
|
+import net.minecraft.world.level.block.state.properties.BambooLeaves;
|
|
+import net.minecraft.world.level.block.state.properties.BedPart;
|
|
+import net.minecraft.world.level.block.state.properties.BellAttachType;
|
|
+import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
|
+import net.minecraft.world.level.block.state.properties.BooleanProperty;
|
|
+import net.minecraft.world.level.block.state.properties.ChestType;
|
|
+import net.minecraft.world.level.block.state.properties.ComparatorMode;
|
|
+import net.minecraft.world.level.block.state.properties.DoorHingeSide;
|
|
+import net.minecraft.world.level.block.state.properties.DoubleBlockHalf;
|
|
+import net.minecraft.world.level.block.state.properties.DripstoneThickness;
|
|
+import net.minecraft.world.level.block.state.properties.EnumProperty;
|
|
+import net.minecraft.world.level.block.state.properties.Half;
|
|
+import net.minecraft.world.level.block.state.properties.IntegerProperty;
|
|
+import net.minecraft.world.level.block.state.properties.NoteBlockInstrument;
|
|
+import net.minecraft.world.level.block.state.properties.PistonType;
|
|
+import net.minecraft.world.level.block.state.properties.Property;
|
|
+import net.minecraft.world.level.block.state.properties.RailShape;
|
|
+import net.minecraft.world.level.block.state.properties.RedstoneSide;
|
|
+import net.minecraft.world.level.block.state.properties.SculkSensorPhase;
|
|
+import net.minecraft.world.level.block.state.properties.SlabType;
|
|
+import net.minecraft.world.level.block.state.properties.StairsShape;
|
|
+import net.minecraft.world.level.block.state.properties.StructureMode;
|
|
+import net.minecraft.world.level.block.state.properties.Tilt;
|
|
+import net.minecraft.world.level.block.state.properties.WallSide;
|
|
+import org.bukkit.Axis;
|
|
+import org.bukkit.Instrument;
|
|
+import org.bukkit.block.BlockFace;
|
|
+import org.bukkit.block.data.Bisected;
|
|
+import org.bukkit.block.data.FaceAttachable;
|
|
+import org.bukkit.block.data.Rail;
|
|
+import org.bukkit.block.data.type.Bamboo;
|
|
+import org.bukkit.block.data.type.Bed;
|
|
+import org.bukkit.block.data.type.Bell;
|
|
+import org.bukkit.block.data.type.BigDripleaf;
|
|
+import org.bukkit.block.data.type.Chest;
|
|
+import org.bukkit.block.data.type.Comparator;
|
|
+import org.bukkit.block.data.type.CreakingHeart;
|
|
+import org.bukkit.block.data.type.Door;
|
|
+import org.bukkit.block.data.type.Jigsaw;
|
|
+import org.bukkit.block.data.type.PointedDripstone;
|
|
+import org.bukkit.block.data.type.RedstoneWire;
|
|
+import org.bukkit.block.data.type.SculkSensor;
|
|
+import org.bukkit.block.data.type.Slab;
|
|
+import org.bukkit.block.data.type.Stairs;
|
|
+import org.bukkit.block.data.type.StructureBlock;
|
|
+import org.bukkit.block.data.type.TechnicalPiston;
|
|
+import org.bukkit.block.data.type.TrialSpawner;
|
|
+import org.bukkit.block.data.type.Vault;
|
|
+import org.bukkit.block.data.type.Wall;
|
|
+import org.bukkit.support.environment.AllFeatures;
|
|
+import org.junit.jupiter.api.AfterEach;
|
|
+import org.junit.jupiter.api.BeforeEach;
|
|
+import org.junit.jupiter.api.Test;
|
|
+
|
|
+import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
+import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
+import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
+import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
+import static org.junit.jupiter.api.Assertions.fail;
|
|
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
|
+
|
|
+@AllFeatures
|
|
+public class BlockPropertyTest {
|
|
+
|
|
+ private static final Map<Class<? extends Enum<? extends StringRepresentable>>, Class<? extends Enum<?>>> ENUM_MAPPING = ImmutableMap.<Class<? extends Enum<? extends StringRepresentable>>, Class<? extends Enum<?>>>builder()
|
|
+ .put(DoorHingeSide.class, Door.Hinge.class)
|
|
+ .put(SlabType.class, Slab.Type.class)
|
|
+ .put(StructureMode.class, StructureBlock.Mode.class)
|
|
+ .put(FrontAndTop.class, Jigsaw.Orientation.class)
|
|
+ .put(DripstoneThickness.class, PointedDripstone.Thickness.class)
|
|
+ .put(WallSide.class, Wall.Height.class)
|
|
+ .put(BellAttachType.class, Bell.Attachment.class)
|
|
+ .put(NoteBlockInstrument.class, Instrument.class)
|
|
+ .put(StairsShape.class, Stairs.Shape.class)
|
|
+ .put(Direction.class, BlockFace.class)
|
|
+ .put(ComparatorMode.class, Comparator.Mode.class)
|
|
+ .put(PistonType.class, TechnicalPiston.Type.class)
|
|
+ .put(BedPart.class, Bed.Part.class)
|
|
+ .put(Half.class, Bisected.Half.class)
|
|
+ .put(AttachFace.class, FaceAttachable.AttachedFace.class)
|
|
+ .put(RailShape.class, Rail.Shape.class)
|
|
+ .put(SculkSensorPhase.class, SculkSensor.Phase.class)
|
|
+ .put(DoubleBlockHalf.class, Bisected.Half.class)
|
|
+ .put(Tilt.class, BigDripleaf.Tilt.class)
|
|
+ .put(ChestType.class, Chest.Type.class)
|
|
+ .put(RedstoneSide.class, RedstoneWire.Connection.class)
|
|
+ .put(Direction.Axis.class, Axis.class)
|
|
+ .put(BambooLeaves.class, Bamboo.Leaves.class)
|
|
+ .put(TrialSpawnerState.class, TrialSpawner.State.class)
|
|
+ .put(VaultState.class, Vault.State.class)
|
|
+ .put(CreakingHeartBlock.CreakingHeartState.class, CreakingHeart.Creaking.class)
|
|
+ .build();
|
|
+
|
|
+ private PrintStream old;
|
|
+ @BeforeEach
|
|
+ public void beforeEach() {
|
|
+ old = System.out;
|
|
+ }
|
|
+
|
|
+ @AfterEach
|
|
+ public void afterEach() {
|
|
+ System.setOut(this.old);
|
|
+ }
|
|
+
|
|
+ @SuppressWarnings("rawtypes")
|
|
+ @Test
|
|
+ public void testEnumMapping() throws IllegalAccessException {
|
|
+ final Map<String, EnumProperty> nmsPropertyMap = collectProperties(BlockStateProperties.class, EnumProperty.class);
|
|
+ for (final EnumProperty value : nmsPropertyMap.values()) {
|
|
+ assertNotNull(ENUM_MAPPING.get(value.getValueClass()), "Missing enum mapping for " + value.getValueClass());
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @SuppressWarnings("rawtypes")
|
|
+ @Test
|
|
+ public void validateProperties() throws IllegalAccessException {
|
|
+ final Map<String, Property> nmsPropertyMap = collectProperties(BlockStateProperties.class, Property.class);
|
|
+ final Map<String, BlockProperty> paperPropertyMap = collectProperties(BlockProperties.class, BlockProperty.class);
|
|
+ final List<String> missing = new ArrayList<>();
|
|
+ final List<String> invalid = new ArrayList<>();
|
|
+ nmsPropertyMap.forEach((name, prop) -> {
|
|
+ if (paperPropertyMap.containsKey(name)) {
|
|
+ if (!isEqual(prop, paperPropertyMap.get(name))) {
|
|
+ invalid.add(name + ": \n\t" + paperPropertyMap.get(name) + "\n\t" + prop);
|
|
+ }
|
|
+ paperPropertyMap.remove(name);
|
|
+ } else {
|
|
+ missing.add(stringifyPropertyField(name, prop));
|
|
+ }
|
|
+ });
|
|
+
|
|
+ assertEquals(0, invalid.size(), "Invalid Property: \n" + String.join("\n", invalid) + "\n");
|
|
+ assertEquals(0, paperPropertyMap.size(), "Extra Property: \n" + String.join("\n", paperPropertyMap.keySet()) + "\n");
|
|
+ assertEquals(0, missing.size(), "Missing Property: \n" + String.join("\n", missing) + "\n");
|
|
+ }
|
|
+
|
|
+
|
|
+ @Test
|
|
+ public void testToNmsPropertyConversion() {
|
|
+ assertFalse(BlockProperties.PROPERTIES.isEmpty(), "no paper properties found");
|
|
+ for (final BlockProperty<?> property : BlockProperties.PROPERTIES.values()) {
|
|
+ final Property<?> nmsProperty = PaperBlockProperties.convertToNmsProperty(property);
|
|
+ assertNotNull(nmsProperty, "Could not convert " + property + " to its nms counterpart");
|
|
+ assertTrue(isEqual(nmsProperty, property), property.name() + " is not equal to " + nmsProperty.getName());
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Test
|
|
+ public void testToPaperPropertyConversion() {
|
|
+ assertFalse(Property.PROPERTY_MULTIMAP.isEmpty(), "no nms properties found");
|
|
+ for (final Property<?> nmsProp : Property.PROPERTY_MULTIMAP.values()) {
|
|
+ final BlockProperty<?> paperProp = PaperBlockProperties.convertToPaperProperty(nmsProp);
|
|
+ assertNotNull(paperProp, "Could not convert " + nmsProp + " to its paper counterpart");
|
|
+ assertTrue(isEqual(nmsProp, paperProp), nmsProp.getName() + " is not equal to " + paperProp.name());
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private static boolean isEqual(final Property<?> nmsProperty, final BlockProperty<?> property) {
|
|
+ return switch (property) {
|
|
+ // special cases
|
|
+ case final RotationBlockProperty rotation when nmsProperty instanceof IntegerProperty && "rotation".equals(nmsProperty.getName()) ->
|
|
+ nmsProperty.getPossibleValues().size() == rotation.values().size();
|
|
+ case final NoteBlockProperty note when nmsProperty instanceof IntegerProperty && "note".equals(nmsProperty.getName()) ->
|
|
+ nmsProperty.getPossibleValues().size() == note.values().size();
|
|
+ // end special cases
|
|
+ case final BooleanBlockProperty ignored when nmsProperty instanceof BooleanProperty -> true;
|
|
+ case final IntegerBlockProperty apiIntProp when nmsProperty instanceof final IntegerProperty nmsIntProp ->
|
|
+ nmsIntProp.min == apiIntProp.min() && nmsIntProp.max == apiIntProp.max() && nmsIntProp.getPossibleValues().size() == apiIntProp.values().size();
|
|
+ case final EnumBlockProperty<?> apiEnumProp when nmsProperty instanceof final EnumProperty<?> nmsEnumProp ->
|
|
+ ENUM_MAPPING.get(nmsEnumProp.getValueClass()) == apiEnumProp.type() && nmsEnumProp.getPossibleValues().size() == apiEnumProp.values().size();
|
|
+ default -> false;
|
|
+ };
|
|
+ }
|
|
+
|
|
+ private static String stringifyPropertyField(final String fieldName, final Property<?> property) {
|
|
+ if (property instanceof final IntegerProperty intProp) {
|
|
+ // special cases
|
|
+ if (property == BlockStateProperties.ROTATION_16) { /** see {@link RotationBlockProperty} */
|
|
+ return "public static final EnumBlockProperty<BlockFace> " + fieldName + " = new RotationBlockProperty(\"" + property.getName() + "\");";
|
|
+ } else if (property == BlockStateProperties.NOTE) { /** see {@link NoteBlockProperty} */
|
|
+ return "public static final BlockProperty<Note> " + fieldName + " = new NoteBlockProperty(\"" + property.getName() + "\");";
|
|
+ }
|
|
+ // end special cases
|
|
+ return "public static final IntegerBlockProperty " + fieldName + " = integer(\"" + property.getName() + "\", " + intProp.min + ", " + intProp.max + ");";
|
|
+ } else if (property instanceof net.minecraft.world.level.block.state.properties.BooleanProperty) {
|
|
+ return "public static final BooleanBlockProperty " + fieldName + " = bool(\"" + property.getName() + "\");";
|
|
+ } else if (property instanceof final net.minecraft.world.level.block.state.properties.EnumProperty<?> enumProp) {
|
|
+ final String value;
|
|
+ assumeTrue(ENUM_MAPPING.containsKey(enumProp.getValueClass()), "Missing enum mappings!");
|
|
+ final Class<? extends Enum<?>> bukkitEnum = ENUM_MAPPING.get(enumProp.getValueClass());
|
|
+ final String name = (bukkitEnum.isMemberClass() ? bukkitEnum.getEnclosingClass().getSimpleName() + "." : "") + bukkitEnum.getSimpleName();
|
|
+ value = "public static final EnumBlockProperty<" + name + "> " + fieldName + " = enumeration(\"" + property.getName() + "\", " + name + ".class);";
|
|
+ return value;
|
|
+ }
|
|
+ fail(property + " is not a recognized property type");
|
|
+ return "";
|
|
+ }
|
|
+
|
|
+ private static <P> Map<String, P> collectProperties(final Class<?> containerClass, final Class<P> propertyClass) throws IllegalAccessException {
|
|
+ final Map<String, P> propertyMap = new LinkedHashMap<>();
|
|
+ for (final Field field : containerClass.getFields()) {
|
|
+ if (!Modifier.isStatic(field.getModifiers()) || !propertyClass.isAssignableFrom(field.getType())) {
|
|
+ continue;
|
|
+ }
|
|
+ propertyMap.put(field.getName(), propertyClass.cast(field.get(null)));
|
|
+ }
|
|
+ return propertyMap;
|
|
+ }
|
|
+}
|