From 8f55ed539f35138fe54f2ca5e3c7278be57495d2 Mon Sep 17 00:00:00 2001 From: CraftBukkit/Spigot Date: Sun, 5 May 2024 10:08:54 +1000 Subject: [PATCH] #1275: Add internal ItemType and BlockType, delegate Material methods to them By: Jishuna Also-by: Bjarne Koll Also-by: DerFrZocker Also-by: md_5 --- .../org/bukkit/craftbukkit/CraftRegistry.java | 12 +- .../org/bukkit/craftbukkit/CraftServer.java | 5 +- .../craftbukkit/block/CraftBlockType.java | 214 ++++++++++++++++- .../block/data/CraftBlockData.java | 7 +- .../craftbukkit/inventory/CraftItemType.java | 215 +++++++++++++++++- .../craftbukkit/legacy/CraftLegacy.java | 2 +- .../packs/CraftDataPackManager.java | 20 ++ .../test/java/org/bukkit/BlockDataTest.java | 12 +- .../craftbukkit/block/BlockTypeTest.java | 22 ++ .../craftbukkit/inventory/ItemMetaTest.java | 25 ++ .../craftbukkit/inventory/ItemTypeTest.java | 76 +++++++ .../registry/RegistryConversionTest.java | 41 ++-- .../java/org/bukkit/support/DummyServer.java | 2 +- .../provider/RegistriesArgumentProvider.java | 36 ++- 14 files changed, 642 insertions(+), 47 deletions(-) create mode 100644 paper-server/src/test/java/org/bukkit/craftbukkit/block/BlockTypeTest.java create mode 100644 paper-server/src/test/java/org/bukkit/craftbukkit/inventory/ItemTypeTest.java diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java index 3ce32ab75e..4ecf846081 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java @@ -20,11 +20,14 @@ import org.bukkit.Particle; import org.bukkit.Registry; import org.bukkit.attribute.Attribute; import org.bukkit.block.Biome; +import org.bukkit.block.BlockType; +import org.bukkit.craftbukkit.block.CraftBlockType; import org.bukkit.craftbukkit.damage.CraftDamageType; import org.bukkit.craftbukkit.enchantments.CraftEnchantment; import org.bukkit.craftbukkit.entity.CraftWolf; import org.bukkit.craftbukkit.generator.structure.CraftStructure; import org.bukkit.craftbukkit.generator.structure.CraftStructureType; +import org.bukkit.craftbukkit.inventory.CraftItemType; import org.bukkit.craftbukkit.inventory.trim.CraftTrimMaterial; import org.bukkit.craftbukkit.inventory.trim.CraftTrimPattern; import org.bukkit.craftbukkit.legacy.FieldRename; @@ -38,6 +41,7 @@ import org.bukkit.entity.EntityType; import org.bukkit.entity.Wolf; import org.bukkit.generator.structure.Structure; import org.bukkit.generator.structure.StructureType; +import org.bukkit.inventory.ItemType; import org.bukkit.inventory.meta.trim.TrimMaterial; import org.bukkit.inventory.meta.trim.TrimPattern; import org.bukkit.potion.PotionEffectType; @@ -115,7 +119,7 @@ public class CraftRegistry implements Registry { * @param registryHolder the minecraft registry holder * @return the bukkit registry of the provided class */ - public static Registry createRegistry(Class bukkitClass, IRegistryCustom registryHolder) { + public static Registry createRegistry(Class bukkitClass, IRegistryCustom registryHolder) { if (bukkitClass == Enchantment.class) { return new CraftRegistry<>(Enchantment.class, registryHolder.registryOrThrow(Registries.ENCHANTMENT), CraftEnchantment::new, FieldRename.ENCHANTMENT_RENAME); } @@ -146,6 +150,12 @@ public class CraftRegistry implements Registry { if (bukkitClass == Wolf.Variant.class) { return new CraftRegistry<>(Wolf.Variant.class, registryHolder.registryOrThrow(Registries.WOLF_VARIANT), CraftWolf.CraftVariant::new, NONE); } + if (bukkitClass == BlockType.class) { + return new CraftRegistry<>(BlockType.class, registryHolder.registryOrThrow(Registries.BLOCK), CraftBlockType::new, NONE); + } + if (bukkitClass == ItemType.class) { + return new CraftRegistry<>(ItemType.class, registryHolder.registryOrThrow(Registries.ITEM), CraftItemType::new, NONE); + } return null; } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java index d1d13683a4..8a2bcf7123 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -121,6 +121,7 @@ import org.bukkit.ChatColor; import org.bukkit.GameMode; import org.bukkit.Keyed; import org.bukkit.Location; +import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.OfflinePlayer; import org.bukkit.Registry; @@ -2364,14 +2365,14 @@ public final class CraftServer implements Server { public BlockData createBlockData(String data) throws IllegalArgumentException { Preconditions.checkArgument(data != null, "data cannot be null"); - return createBlockData(null, data); + return createBlockData((Material) null, data); } @Override public BlockData createBlockData(org.bukkit.Material material, String data) { Preconditions.checkArgument(material != null || data != null, "Must provide one of material or data"); - return CraftBlockData.newData(material, data); + return CraftBlockData.newData((material != null) ? material.asBlockType() : null, data); } @Override diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlockType.java b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlockType.java index 22f8b29e7a..b335d07068 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlockType.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlockType.java @@ -1,10 +1,42 @@ package org.bukkit.craftbukkit.block; +import com.google.common.base.Preconditions; +import java.util.function.Consumer; +import net.minecraft.core.BlockPosition; +import net.minecraft.core.registries.Registries; +import net.minecraft.world.EnumHand; +import net.minecraft.world.entity.player.EntityHuman; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.BlockAccessAir; import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.BlockFalling; +import net.minecraft.world.level.block.BlockFire; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockBase; +import net.minecraft.world.level.block.state.IBlockData; +import net.minecraft.world.phys.MovingObjectPositionBlock; import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; +import org.bukkit.World; +import org.bukkit.block.BlockType; +import org.bukkit.block.data.BlockData; +import org.bukkit.craftbukkit.CraftRegistry; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.block.data.CraftBlockData; +import org.bukkit.craftbukkit.inventory.CraftItemType; import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.craftbukkit.util.Handleable; +import org.bukkit.inventory.ItemType; +import org.jetbrains.annotations.NotNull; -public class CraftBlockType { +public class CraftBlockType implements BlockType.Typed, Handleable { + + private final NamespacedKey key; + private final Block block; + private final Class blockDataClass; + private final boolean interactable; public static Material minecraftToBukkit(Block block) { return CraftMagicNumbers.getMaterial(block); @@ -13,4 +45,184 @@ public class CraftBlockType { public static Block bukkitToMinecraft(Material material) { return CraftMagicNumbers.getBlock(material); } + + public static BlockType minecraftToBukkitNew(Block minecraft) { + return CraftRegistry.minecraftToBukkit(minecraft, Registries.BLOCK, Registry.BLOCK); + } + + public static Block bukkitToMinecraftNew(BlockType bukkit) { + return CraftRegistry.bukkitToMinecraft(bukkit); + } + + private static boolean hasMethod(Class clazz, String methodName, Class... params) { + boolean hasMethod; + try { + hasMethod = clazz.getDeclaredMethod(methodName, params) != null; + } catch (NoSuchMethodException ex) { + hasMethod = false; + } + + return hasMethod; + } + + private static boolean isInteractable(Block block) { + Class clazz = block.getClass(); + + boolean hasMethod = hasMethod(clazz, "useWithoutItem", IBlockData.class, net.minecraft.world.level.World.class, BlockPosition.class, EntityHuman.class, MovingObjectPositionBlock.class) + || hasMethod(clazz, "useItemOn", net.minecraft.world.item.ItemStack.class, IBlockData.class, net.minecraft.world.level.World.class, BlockPosition.class, EntityHuman.class, EnumHand.class, MovingObjectPositionBlock.class); + + if (!hasMethod && clazz.getSuperclass() != BlockBase.class) { + clazz = clazz.getSuperclass(); + + hasMethod = hasMethod(clazz, "useWithoutItem", IBlockData.class, net.minecraft.world.level.World.class, BlockPosition.class, EntityHuman.class, MovingObjectPositionBlock.class) + || hasMethod(clazz, "useItemOn", net.minecraft.world.item.ItemStack.class, IBlockData.class, net.minecraft.world.level.World.class, BlockPosition.class, EntityHuman.class, EnumHand.class, MovingObjectPositionBlock.class); + } + + return hasMethod; + } + + public CraftBlockType(NamespacedKey key, Block block) { + this.key = key; + this.block = block; + this.blockDataClass = (Class) CraftBlockData.fromData(block.defaultBlockState()).getClass().getInterfaces()[0]; + this.interactable = isInteractable(block); + } + + @Override + public Block getHandle() { + return block; + } + + @NotNull + @Override + public Typed typed() { + return this.typed(BlockData.class); + } + + @NotNull + @Override + @SuppressWarnings("unchecked") + public Typed typed(@NotNull Class blockDataType) { + if (blockDataType.isAssignableFrom(this.blockDataClass)) return (Typed) this; + throw new IllegalArgumentException("Cannot type block type " + this.key.toString() + " to blockdata type " + blockDataType.getSimpleName()); + } + + @Override + public boolean hasItemType() { + if (this == AIR) { + return true; + } + + return block.asItem() != Items.AIR; + } + + @NotNull + @Override + public ItemType getItemType() { + if (this == AIR) { + return ItemType.AIR; + } + + Item item = block.asItem(); + Preconditions.checkArgument(item != Items.AIR, "The block type %s has no corresponding item type", getKey()); + return CraftItemType.minecraftToBukkitNew(item); + } + + @Override + public Class getBlockDataClass() { + return blockDataClass; + } + + @Override + public B createBlockData() { + return createBlockData((String) null); + } + + @Override + public B createBlockData(Consumer consumer) { + B data = createBlockData(); + + if (consumer != null) { + consumer.accept(data); + } + + return data; + } + + @Override + public B createBlockData(String data) { + return (B) CraftBlockData.newData(this, data); + } + + @Override + public boolean isSolid() { + return block.defaultBlockState().blocksMotion(); + } + + @Override + public boolean isAir() { + return block.defaultBlockState().isAir(); + } + + @Override + public boolean isEnabledByFeature(@NotNull World world) { + Preconditions.checkNotNull(world, "World cannot be null"); + return getHandle().isEnabled(((CraftWorld) world).getHandle().enabledFeatures()); + } + + @Override + public boolean isFlammable() { + return block.defaultBlockState().ignitedByLava(); + } + + @Override + public boolean isBurnable() { + return ((BlockFire) Blocks.FIRE).igniteOdds.getOrDefault(block, 0) > 0; + } + + @Override + public boolean isOccluding() { + return block.defaultBlockState().isRedstoneConductor(BlockAccessAir.INSTANCE, BlockPosition.ZERO); + } + + @Override + public boolean hasGravity() { + return block instanceof BlockFalling; + } + + @Override + public boolean isInteractable() { + return interactable; + } + + @Override + public float getHardness() { + return block.defaultBlockState().destroySpeed; + } + + @Override + public float getBlastResistance() { + return block.getExplosionResistance(); + } + + @Override + public float getSlipperiness() { + return block.getFriction(); + } + + @NotNull + @Override + public String getTranslationKey() { + return block.getDescriptionId(); + } + + @Override + public NamespacedKey getKey() { + return key; + } + + @Override + public Material asMaterial() { + return Registry.MATERIAL.get(this.key); + } } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java b/paper-server/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java index 34a8e54fc0..75c3c1214b 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java @@ -31,6 +31,7 @@ import org.bukkit.SoundGroup; import org.bukkit.block.BlockFace; import org.bukkit.block.BlockState; import org.bukkit.block.BlockSupport; +import org.bukkit.block.BlockType; import org.bukkit.block.PistonMoveReaction; import org.bukkit.block.data.BlockData; import org.bukkit.block.structure.Mirror; @@ -540,11 +541,9 @@ public class CraftBlockData implements BlockData { Preconditions.checkState(MAP.put(nms, bukkit) == null, "Duplicate mapping %s->%s", nms, bukkit); } - public static CraftBlockData newData(Material material, String data) { - Preconditions.checkArgument(material == null || material.isBlock(), "Cannot get data for not block %s", material); - + public static CraftBlockData newData(BlockType blockType, String data) { IBlockData blockData; - Block block = CraftBlockType.bukkitToMinecraft(material); + Block block = blockType == null ? null : ((CraftBlockType) blockType).getHandle(); Map, Comparable> parsed = null; // Data provided, use it diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java index d2ef2a7218..f4d5b624f9 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java @@ -1,10 +1,44 @@ package org.bukkit.craftbukkit.inventory; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Multimap; +import java.util.function.Consumer; +import net.minecraft.core.component.DataComponents; +import net.minecraft.core.registries.Registries; import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemBlock; +import net.minecraft.world.item.ItemRecord; +import net.minecraft.world.item.component.ItemAttributeModifiers; +import net.minecraft.world.level.block.entity.TileEntityFurnace; import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; +import org.bukkit.World; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeModifier; +import org.bukkit.block.BlockType; +import org.bukkit.craftbukkit.CraftEquipmentSlot; +import org.bukkit.craftbukkit.CraftRegistry; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.attribute.CraftAttribute; +import org.bukkit.craftbukkit.attribute.CraftAttributeInstance; +import org.bukkit.craftbukkit.block.CraftBlockType; import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.craftbukkit.util.Handleable; +import org.bukkit.inventory.CreativeCategory; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.ItemType; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -public class CraftItemType { +public class CraftItemType implements ItemType.Typed, Handleable { + + private final NamespacedKey key; + private final Item item; + private final Class itemMetaClass; public static Material minecraftToBukkit(Item item) { return CraftMagicNumbers.getMaterial(item); @@ -13,4 +47,183 @@ public class CraftItemType { public static Item bukkitToMinecraft(Material material) { return CraftMagicNumbers.getItem(material); } + + public static ItemType minecraftToBukkitNew(Item minecraft) { + return CraftRegistry.minecraftToBukkit(minecraft, Registries.ITEM, Registry.ITEM); + } + + public static Item bukkitToMinecraftNew(ItemType bukkit) { + return CraftRegistry.bukkitToMinecraft(bukkit); + } + + public CraftItemType(NamespacedKey key, Item item) { + this.key = key; + this.item = item; + this.itemMetaClass = getItemMetaClass(item); + } + + // Cursed, this should be refactored when possible + private Class getItemMetaClass(Item item) { + ItemMeta meta = new ItemStack(asMaterial()).getItemMeta(); + if (meta != null) { + if (CraftMetaEntityTag.class != meta.getClass() && CraftMetaArmorStand.class != meta.getClass()) { + return (Class) meta.getClass().getInterfaces()[0]; + } + } + return (Class) ItemMeta.class; + } + + @NotNull + @Override + public Typed typed() { + return this.typed(ItemMeta.class); + } + + @NotNull + @Override + @SuppressWarnings("unchecked") + public Typed typed(@NotNull final Class itemMetaType) { + if (itemMetaType.isAssignableFrom(this.itemMetaClass)) return (Typed) this; + + throw new IllegalArgumentException("Cannot type item type " + this.key.toString() + " to meta type " + itemMetaType.getSimpleName()); + } + + @NotNull + @Override + public ItemStack createItemStack() { + return this.createItemStack(1, null); + } + + @NotNull + @Override + public ItemStack createItemStack(final int amount) { + return this.createItemStack(amount, null); + } + + @NotNull + @Override + public ItemStack createItemStack(Consumer metaConfigurator) { + return this.createItemStack(1, metaConfigurator); + } + + @NotNull + @Override + public ItemStack createItemStack(final int amount, @Nullable final Consumer metaConfigurator) { + final ItemStack itemStack = new ItemStack(this.asMaterial(), amount); + if (metaConfigurator != null) { + final ItemMeta itemMeta = itemStack.getItemMeta(); + metaConfigurator.accept((M) itemMeta); + itemStack.setItemMeta(itemMeta); + } + return itemStack; + } + + @Override + public Item getHandle() { + return item; + } + + @Override + public boolean hasBlockType() { + return item instanceof ItemBlock; + } + + @NotNull + @Override + public BlockType getBlockType() { + if (!(item instanceof ItemBlock block)) { + throw new IllegalStateException("The item type " + getKey() + " has no corresponding block type"); + } + + return CraftBlockType.minecraftToBukkitNew(block.getBlock()); + } + + @Override + public Class getItemMetaClass() { + if (this == ItemType.AIR) { + throw new UnsupportedOperationException("Air does not have ItemMeta"); + } + return itemMetaClass; + } + + @Override + public int getMaxStackSize() { + // Based of the material enum air is only 0, in PerMaterialTest it is also set as special case + // the item info itself would return 64 + if (this == AIR) { + return 0; + } + return item.components().getOrDefault(DataComponents.MAX_STACK_SIZE, 64); + } + + @Override + public short getMaxDurability() { + return item.components().getOrDefault(DataComponents.MAX_DAMAGE, 0).shortValue(); + } + + @Override + public boolean isEdible() { + return item.components().has(DataComponents.FOOD); + } + + @Override + public boolean isRecord() { + return item instanceof ItemRecord; + } + + @Override + public boolean isFuel() { + return TileEntityFurnace.isFuel(new net.minecraft.world.item.ItemStack(item)); + } + + @Override + public ItemType getCraftingRemainingItem() { + Item expectedItem = item.getCraftingRemainingItem(); + return expectedItem == null ? null : minecraftToBukkitNew(expectedItem); + } + +// @Override +// public EquipmentSlot getEquipmentSlot() { +// return CraftEquipmentSlot.getSlot(EntityInsentient.getEquipmentSlotForItem(CraftItemStack.asNMSCopy(ItemStack.of(this)))); +// } + + @Override + public Multimap getDefaultAttributeModifiers(EquipmentSlot slot) { + ImmutableMultimap.Builder defaultAttributes = ImmutableMultimap.builder(); + + ItemAttributeModifiers nmsDefaultAttributes = item.getDefaultAttributeModifiers(); + nmsDefaultAttributes.forEach(CraftEquipmentSlot.getNMS(slot), (key, value) -> { + Attribute attribute = CraftAttribute.minecraftToBukkit(key.value()); + defaultAttributes.put(attribute, CraftAttributeInstance.convert(value, slot)); + }); + + return defaultAttributes.build(); + } + + @Override + public CreativeCategory getCreativeCategory() { + return CreativeCategory.BUILDING_BLOCKS; + } + + @Override + public boolean isEnabledByFeature(@NotNull World world) { + Preconditions.checkNotNull(world, "World cannot be null"); + return getHandle().isEnabled(((CraftWorld) world).getHandle().enabledFeatures()); + } + + @NotNull + @Override + public String getTranslationKey() { + return item.getDescriptionId(); + } + + @Override + public NamespacedKey getKey() { + return key; + } + + @Override + public Material asMaterial() { + return Registry.MATERIAL.get(this.key); + } } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java b/paper-server/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java index 4a318cb3d1..e9172ec6b0 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java @@ -192,7 +192,7 @@ public final class CraftLegacy { } } - if (mappedData == null && material.isBlock()) { + if (mappedData == null) { // Try exact match first IBlockData iblock = materialToData.get(materialData); if (iblock != null) { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/packs/CraftDataPackManager.java b/paper-server/src/main/java/org/bukkit/craftbukkit/packs/CraftDataPackManager.java index dad32e0987..77e3f54c17 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/packs/CraftDataPackManager.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/packs/CraftDataPackManager.java @@ -10,11 +10,13 @@ import net.minecraft.world.entity.EntityTypes; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.World; +import org.bukkit.block.BlockType; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.block.CraftBlockType; import org.bukkit.craftbukkit.entity.CraftEntityType; import org.bukkit.craftbukkit.inventory.CraftItemType; import org.bukkit.entity.EntityType; +import org.bukkit.inventory.ItemType; import org.bukkit.packs.DataPack; import org.bukkit.packs.DataPackManager; @@ -89,6 +91,24 @@ public class CraftDataPackManager implements DataPackManager { return false; } + @Override + public boolean isEnabledByFeature(ItemType itemType, World world) { + Preconditions.checkArgument(itemType != null, "itemType cannot be null"); + Preconditions.checkArgument(world != null, "world cannot be null"); + + CraftWorld craftWorld = ((CraftWorld) world); + return CraftItemType.bukkitToMinecraftNew(itemType.typed()).isEnabled(craftWorld.getHandle().enabledFeatures()); + } + + @Override + public boolean isEnabledByFeature(BlockType blockType, World world) { + Preconditions.checkArgument(blockType != null, "blockType cannot be null"); + Preconditions.checkArgument(world != null, "world cannot be null"); + + CraftWorld craftWorld = ((CraftWorld) world); + return CraftBlockType.bukkitToMinecraftNew(blockType.typed()).isEnabled(craftWorld.getHandle().enabledFeatures()); + } + @Override public boolean isEnabledByFeature(EntityType entityType, World world) { Preconditions.checkArgument(entityType != null, "entityType cannot be null"); diff --git a/paper-server/src/test/java/org/bukkit/BlockDataTest.java b/paper-server/src/test/java/org/bukkit/BlockDataTest.java index 982cf4b68a..988e916576 100644 --- a/paper-server/src/test/java/org/bukkit/BlockDataTest.java +++ b/paper-server/src/test/java/org/bukkit/BlockDataTest.java @@ -8,6 +8,7 @@ import net.minecraft.world.level.block.BlockCake; import net.minecraft.world.level.block.BlockChest; import net.minecraft.world.level.block.Blocks; import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockType; import org.bukkit.block.data.BlockData; import org.bukkit.block.data.type.Cake; import org.bukkit.block.data.type.Chest; @@ -21,7 +22,7 @@ public class BlockDataTest extends AbstractTestingBase { public void testParsing() { BlockData cakeTest = CraftBlockData.fromData(Blocks.CAKE.defaultBlockState().setValue(BlockCake.BITES, 3)); - BlockData materialString = CraftBlockData.newData(Material.CAKE, "[bites=3]"); + BlockData materialString = CraftBlockData.newData(BlockType.CAKE, "[bites=3]"); assertThat(materialString, is(cakeTest)); BlockData combined = CraftBlockData.newData(null, "cake[bites=3]"); @@ -46,19 +47,14 @@ public class BlockDataTest extends AbstractTestingBase { @Test public void testDoubleMaterial() { - assertThrows(IllegalArgumentException.class, () -> CraftBlockData.newData(Material.CAKE, "minecraft:cake[bites=3]")); + assertThrows(IllegalArgumentException.class, () -> CraftBlockData.newData(BlockType.CAKE, "minecraft:cake[bites=3]")); } @Test public void testMistake() { BlockData cakeTest = CraftBlockData.fromData(Blocks.CAKE.defaultBlockState().setValue(BlockCake.BITES, 3)); - assertThrows(IllegalArgumentException.class, () -> CraftBlockData.newData(Material.CAKE, cakeTest.toString())); - } - - @Test - public void testItem() { - assertThrows(IllegalArgumentException.class, () -> CraftBlockData.newData(Material.DIAMOND_AXE, null)); + assertThrows(IllegalArgumentException.class, () -> CraftBlockData.newData(BlockType.CAKE, cakeTest.toString())); } @Test diff --git a/paper-server/src/test/java/org/bukkit/craftbukkit/block/BlockTypeTest.java b/paper-server/src/test/java/org/bukkit/craftbukkit/block/BlockTypeTest.java new file mode 100644 index 0000000000..d67c0fb1b5 --- /dev/null +++ b/paper-server/src/test/java/org/bukkit/craftbukkit/block/BlockTypeTest.java @@ -0,0 +1,22 @@ +package org.bukkit.craftbukkit.block; + +import static org.bukkit.support.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import java.lang.reflect.Field; +import org.bukkit.block.BlockType; +import org.junit.jupiter.api.Test; + +public class BlockTypeTest { + + // Ensures all BlockType constants have the correct generics + @Test + public void testBlockDataClasses() throws Exception { + for (Field f : BlockType.class.getDeclaredFields()) { + BlockType type = (BlockType) f.get(null); + + Class expected = type.getBlockDataClass(); + Class actual = type.typed().createBlockData().getClass().getInterfaces()[0]; + assertThat(actual, is(expected)); + } + } +} diff --git a/paper-server/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java b/paper-server/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java index c491b9933e..675a1838c5 100644 --- a/paper-server/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java +++ b/paper-server/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java @@ -22,6 +22,7 @@ import org.bukkit.FireworkEffect.Type; import org.bukkit.Material; import org.bukkit.MusicInstrument; import org.bukkit.NamespacedKey; +import org.bukkit.Registry; import org.bukkit.attribute.Attribute; import org.bukkit.attribute.AttributeModifier; import org.bukkit.block.banner.Pattern; @@ -35,6 +36,7 @@ import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Axolotl; import org.bukkit.entity.TropicalFish; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.ItemType; import org.bukkit.inventory.meta.ArmorMeta; import org.bukkit.inventory.meta.AxolotlBucketMeta; import org.bukkit.inventory.meta.BannerMeta; @@ -452,6 +454,29 @@ public class ItemMetaTest extends AbstractTestingBase { assertThat(itemMeta.getBlockData(Material.CHEST), is(CraftBlockData.newData(null, "minecraft:chest[waterlogged=true]"))); } + @Test + public void testMetaClasses() { + Registry.ITEM.forEach(itemType -> { + if (itemType == ItemType.AIR) { + return; + } + + ItemMeta meta = new ItemStack(itemType.asMaterial()).getItemMeta(); + Class internal = meta == null ? CraftMetaItem.class : meta.getClass(); + Class[] interfaces = internal.getInterfaces(); + Class expected; + if (interfaces.length > 0) { + expected = interfaces[0]; + } else { + expected = ItemMeta.class; + } + + // Currently the expected and actual for AIR are ItemMeta rather than null + Class actual = itemType.getItemMetaClass(); + assertThat(actual, is(expected)); + }); + } + private void downCastTest(final StackWrapper provider) { final String name = provider.toString(); final ItemStack blank = new ItemStack(Material.STONE); diff --git a/paper-server/src/test/java/org/bukkit/craftbukkit/inventory/ItemTypeTest.java b/paper-server/src/test/java/org/bukkit/craftbukkit/inventory/ItemTypeTest.java new file mode 100644 index 0000000000..be1be8d33f --- /dev/null +++ b/paper-server/src/test/java/org/bukkit/craftbukkit/inventory/ItemTypeTest.java @@ -0,0 +1,76 @@ +package org.bukkit.craftbukkit.inventory; + +import static org.bukkit.support.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertThrows; +import java.lang.reflect.Field; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.ItemType; +import org.bukkit.inventory.meta.ArmorMeta; +import org.bukkit.inventory.meta.EnchantmentStorageMeta; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.trim.ArmorTrim; +import org.bukkit.inventory.meta.trim.TrimMaterial; +import org.bukkit.inventory.meta.trim.TrimPattern; +import org.bukkit.support.AbstractTestingBase; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class ItemTypeTest extends AbstractTestingBase { + + // Ensures all ItemType constants have the correct generics + @Test + public void testItemMetaClasses() throws Exception { + for (Field f : ItemType.class.getDeclaredFields()) { + ItemType type = (ItemType) f.get(null); + if (type == ItemType.AIR) { + continue; + } + + ItemMeta meta = new ItemStack(type.asMaterial()).getItemMeta(); + Class internal = meta == null ? CraftMetaItem.class : meta.getClass(); + Class[] interfaces = internal.getInterfaces(); + Class expected; + if (interfaces.length > 0) { + expected = interfaces[0]; + } else { + expected = ItemMeta.class; + } + + Class actual = type.getItemMetaClass(); + assertThat(actual, is(expected)); + } + + assertThrows(UnsupportedOperationException.class, () -> ItemType.AIR.getItemMetaClass()); + } + + @Test + public void testStaticItemTypeUsage() { + final ItemStack itemStack = ItemType.DIAMOND.createItemStack(); + Assertions.assertEquals(itemStack.getType(), Material.DIAMOND); + Assertions.assertEquals(itemStack.getType().asItemType(), ItemType.DIAMOND); + } + + @Test + public void testStaticItemTypeUsageBuilder() { + final ItemStack armor = ItemType.DIAMOND_LEGGINGS.createItemStack(a -> + a.setTrim(new ArmorTrim(TrimMaterial.EMERALD, TrimPattern.COAST)) + ); + + final ItemMeta itemMeta = armor.getItemMeta(); + Assertions.assertInstanceOf(ArmorMeta.class, itemMeta); + + final ArmorTrim trim = ((ArmorMeta) itemMeta).getTrim(); + Assertions.assertEquals(trim.getMaterial(), TrimMaterial.EMERALD); + Assertions.assertEquals(trim.getPattern(), TrimPattern.COAST); + } + + @Test + public void testRetyping() { + final ItemType itemType = ItemType.ENCHANTED_BOOK; + Assertions.assertDoesNotThrow(() -> itemType.typed()); + Assertions.assertDoesNotThrow(() -> itemType.typed(EnchantmentStorageMeta.class)); + Assertions.assertThrows(IllegalArgumentException.class, () -> itemType.typed(ArmorMeta.class)); + } +} diff --git a/paper-server/src/test/java/org/bukkit/registry/RegistryConversionTest.java b/paper-server/src/test/java/org/bukkit/registry/RegistryConversionTest.java index 69dc9bbdf4..292cf337e4 100644 --- a/paper-server/src/test/java/org/bukkit/registry/RegistryConversionTest.java +++ b/paper-server/src/test/java/org/bukkit/registry/RegistryConversionTest.java @@ -31,6 +31,9 @@ public class RegistryConversionTest extends AbstractTestingBase { private static final String MINECRAFT_TO_BUKKIT = "minecraftToBukkit"; private static final String BUKKIT_TO_MINECRAFT = "bukkitToMinecraft"; + private static final String MINECRAFT_TO_BUKKIT_NEW = "minecraftToBukkitNew"; + private static final String BUKKIT_TO_MINECRAFT_NEW = "bukkitToMinecraftNew"; + private static final Map, Method> MINECRAFT_TO_BUKKIT_METHODS = new HashMap<>(); private static final Map, Method> BUKKIT_TO_MINECRAFT_METHODS = new HashMap<>(); @@ -61,17 +64,18 @@ public class RegistryConversionTest extends AbstractTestingBase { @Order(2) @RegistriesTest public void testMinecraftToBukkitPresent(Class clazz, ResourceKey> registryKey, - Class craftClazz, Class minecraftClazz) { + Class craftClazz, Class minecraftClazz, boolean newMethod) { + String methodName = (newMethod) ? MINECRAFT_TO_BUKKIT_NEW : MINECRAFT_TO_BUKKIT; Method method = null; try { - method = craftClazz.getDeclaredMethod(MINECRAFT_TO_BUKKIT, minecraftClazz); + method = craftClazz.getDeclaredMethod(methodName, minecraftClazz); } catch (NoSuchMethodException e) { fail(String.format(""" The class %s does not have a public static method to convert a minecraft value to a bukkit value. Following method should be add which, returns the bukkit value based on the minecraft value. %s - """, craftClazz, buildMinecraftToBukkitMethod(clazz, minecraftClazz))); + """, craftClazz, buildMinecraftToBukkitMethod(clazz, methodName, minecraftClazz))); } assertTrue(Modifier.isPublic(method.getModifiers()), String.format(""" @@ -79,47 +83,48 @@ public class RegistryConversionTest extends AbstractTestingBase { The method should be made public, method structure: %s - """, MINECRAFT_TO_BUKKIT, craftClazz, buildMinecraftToBukkitMethod(clazz, minecraftClazz))); + """, methodName, craftClazz, buildMinecraftToBukkitMethod(clazz, methodName, minecraftClazz))); assertTrue(Modifier.isStatic(method.getModifiers()), String.format(""" The method %s in class %s is not static. The method should be made static, method structure: %s - """, MINECRAFT_TO_BUKKIT, craftClazz, buildMinecraftToBukkitMethod(clazz, minecraftClazz))); + """, methodName, craftClazz, buildMinecraftToBukkitMethod(clazz, methodName, minecraftClazz))); assertSame(clazz, method.getReturnType(), String.format(""" The method %s in class %s has the wrong return value. The method should have the correct return value, method structure: %s - """, MINECRAFT_TO_BUKKIT, craftClazz, buildMinecraftToBukkitMethod(clazz, minecraftClazz))); + """, methodName, craftClazz, buildMinecraftToBukkitMethod(clazz, methodName, minecraftClazz))); MINECRAFT_TO_BUKKIT_METHODS.put(clazz, method); } - private String buildMinecraftToBukkitMethod(Class clazz, Class minecraftClazz) { + private String buildMinecraftToBukkitMethod(Class clazz, String methodName, Class minecraftClazz) { return String.format(""" - public static %s minecraftToBukkit(%s minecraft) { + public static %s %s(%s minecraft) { [...] } - """, clazz.getSimpleName(), minecraftClazz.getName()); + """, clazz.getSimpleName(), methodName, minecraftClazz.getName()); } @Order(2) @RegistriesTest public void testBukkitToMinecraftPresent(Class clazz, ResourceKey> registryKey, - Class craftClazz, Class minecraftClazz) { + Class craftClazz, Class minecraftClazz, boolean newMethod) { + String methodName = (newMethod) ? BUKKIT_TO_MINECRAFT_NEW : BUKKIT_TO_MINECRAFT; Method method = null; try { - method = craftClazz.getDeclaredMethod(BUKKIT_TO_MINECRAFT, clazz); + method = craftClazz.getDeclaredMethod(methodName, clazz); } catch (NoSuchMethodException e) { fail(String.format(""" The class %s does not have a public static method to convert a bukkit value to a minecraft value. Following method should be add which, returns the minecraft value based on the bukkit value. %s - """, craftClazz, buildBukkitToMinecraftMethod(clazz, minecraftClazz))); + """, craftClazz, buildBukkitToMinecraftMethod(clazz, methodName, minecraftClazz))); } assertTrue(Modifier.isPublic(method.getModifiers()), String.format(""" @@ -127,31 +132,31 @@ public class RegistryConversionTest extends AbstractTestingBase { The method should be made public, method structure: %s - """, BUKKIT_TO_MINECRAFT, craftClazz, buildBukkitToMinecraftMethod(clazz, minecraftClazz))); + """, methodName, craftClazz, buildBukkitToMinecraftMethod(clazz, methodName, minecraftClazz))); assertTrue(Modifier.isStatic(method.getModifiers()), String.format(""" The method %s in class %s is not static. The method should be made static, method structure: %s - """, BUKKIT_TO_MINECRAFT, craftClazz, buildBukkitToMinecraftMethod(clazz, minecraftClazz))); + """, methodName, craftClazz, buildBukkitToMinecraftMethod(clazz, methodName, minecraftClazz))); assertSame(minecraftClazz, method.getReturnType(), String.format(""" The method %s in class %s has the wrong return value. The method should have the correct return value, method structure: %s - """, BUKKIT_TO_MINECRAFT, craftClazz, buildBukkitToMinecraftMethod(clazz, minecraftClazz))); + """, methodName, craftClazz, buildBukkitToMinecraftMethod(clazz, methodName, minecraftClazz))); BUKKIT_TO_MINECRAFT_METHODS.put(clazz, method); } - private String buildBukkitToMinecraftMethod(Class clazz, Class minecraftClazz) { + private String buildBukkitToMinecraftMethod(Class clazz, String methodName, Class minecraftClazz) { return String.format(""" - public static %s bukkitToMinecraft(%s bukkit) { + public static %s %s(%s bukkit) { [...] } - """, minecraftClazz.getName(), clazz.getSimpleName()); + """, minecraftClazz.getName(), methodName, clazz.getSimpleName()); } @Order(2) diff --git a/paper-server/src/test/java/org/bukkit/support/DummyServer.java b/paper-server/src/test/java/org/bukkit/support/DummyServer.java index ee0cff8437..4153866f3e 100644 --- a/paper-server/src/test/java/org/bukkit/support/DummyServer.java +++ b/paper-server/src/test/java/org/bukkit/support/DummyServer.java @@ -40,7 +40,7 @@ public final class DummyServer { when(instance.getUnsafe()).then(mock -> CraftMagicNumbers.INSTANCE); - when(instance.createBlockData(any(Material.class))).then(mock -> CraftBlockData.newData(mock.getArgument(0), null)); + when(instance.createBlockData(any(Material.class))).then(mock -> CraftBlockData.newData(((Material) mock.getArgument(0)).asBlockType(), null)); when(instance.getLootTable(any())).then(mock -> new CraftLootTable(mock.getArgument(0), AbstractTestingBase.DATA_PACK.fullRegistries().getLootTable(ResourceKey.create(Registries.LOOT_TABLE, CraftNamespacedKey.toMinecraft(mock.getArgument(0)))))); diff --git a/paper-server/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java b/paper-server/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java index d770268353..22c2efb665 100644 --- a/paper-server/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java +++ b/paper-server/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java @@ -4,18 +4,22 @@ import com.google.common.collect.Lists; import java.util.List; import java.util.stream.Stream; import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; import net.minecraft.world.effect.MobEffectList; import net.minecraft.world.entity.animal.WolfVariant; import net.minecraft.world.item.Instrument; import org.bukkit.GameEvent; import org.bukkit.MusicInstrument; +import org.bukkit.block.BlockType; import org.bukkit.craftbukkit.CraftGameEvent; import org.bukkit.craftbukkit.CraftMusicInstrument; +import org.bukkit.craftbukkit.block.CraftBlockType; import org.bukkit.craftbukkit.damage.CraftDamageType; import org.bukkit.craftbukkit.enchantments.CraftEnchantment; import org.bukkit.craftbukkit.entity.CraftWolf; import org.bukkit.craftbukkit.generator.structure.CraftStructure; import org.bukkit.craftbukkit.generator.structure.CraftStructureType; +import org.bukkit.craftbukkit.inventory.CraftItemType; import org.bukkit.craftbukkit.inventory.trim.CraftTrimMaterial; import org.bukkit.craftbukkit.inventory.trim.CraftTrimPattern; import org.bukkit.craftbukkit.potion.CraftPotionEffectType; @@ -24,6 +28,7 @@ import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Wolf; import org.bukkit.generator.structure.Structure; import org.bukkit.generator.structure.StructureType; +import org.bukkit.inventory.ItemType; import org.bukkit.inventory.meta.trim.TrimMaterial; import org.bukkit.inventory.meta.trim.TrimPattern; import org.bukkit.potion.PotionEffectType; @@ -37,16 +42,27 @@ public class RegistriesArgumentProvider implements ArgumentsProvider { static { // Order: Bukkit class, Minecraft Registry key, CraftBukkit class, Minecraft class - DATA.add(Arguments.of(Enchantment.class, Registries.ENCHANTMENT, CraftEnchantment.class, net.minecraft.world.item.enchantment.Enchantment.class)); - DATA.add(Arguments.of(GameEvent.class, Registries.GAME_EVENT, CraftGameEvent.class, net.minecraft.world.level.gameevent.GameEvent.class)); - DATA.add(Arguments.of(MusicInstrument.class, Registries.INSTRUMENT, CraftMusicInstrument.class, Instrument.class)); - DATA.add(Arguments.of(PotionEffectType.class, Registries.MOB_EFFECT, CraftPotionEffectType.class, MobEffectList.class)); - DATA.add(Arguments.of(Structure.class, Registries.STRUCTURE, CraftStructure.class, net.minecraft.world.level.levelgen.structure.Structure.class)); - DATA.add(Arguments.of(StructureType.class, Registries.STRUCTURE_TYPE, CraftStructureType.class, net.minecraft.world.level.levelgen.structure.StructureType.class)); - DATA.add(Arguments.of(TrimMaterial.class, Registries.TRIM_MATERIAL, CraftTrimMaterial.class, net.minecraft.world.item.armortrim.TrimMaterial.class)); - DATA.add(Arguments.of(TrimPattern.class, Registries.TRIM_PATTERN, CraftTrimPattern.class, net.minecraft.world.item.armortrim.TrimPattern.class)); - DATA.add(Arguments.of(DamageType.class, Registries.DAMAGE_TYPE, CraftDamageType.class, net.minecraft.world.damagesource.DamageType.class)); - DATA.add(Arguments.of(Wolf.Variant.class, Registries.WOLF_VARIANT, CraftWolf.CraftVariant.class, WolfVariant.class)); + register(Enchantment.class, Registries.ENCHANTMENT, CraftEnchantment.class, net.minecraft.world.item.enchantment.Enchantment.class); + register(GameEvent.class, Registries.GAME_EVENT, CraftGameEvent.class, net.minecraft.world.level.gameevent.GameEvent.class); + register(MusicInstrument.class, Registries.INSTRUMENT, CraftMusicInstrument.class, Instrument.class); + register(PotionEffectType.class, Registries.MOB_EFFECT, CraftPotionEffectType.class, MobEffectList.class); + register(Structure.class, Registries.STRUCTURE, CraftStructure.class, net.minecraft.world.level.levelgen.structure.Structure.class); + register(StructureType.class, Registries.STRUCTURE_TYPE, CraftStructureType.class, net.minecraft.world.level.levelgen.structure.StructureType.class); + register(TrimMaterial.class, Registries.TRIM_MATERIAL, CraftTrimMaterial.class, net.minecraft.world.item.armortrim.TrimMaterial.class); + register(TrimPattern.class, Registries.TRIM_PATTERN, CraftTrimPattern.class, net.minecraft.world.item.armortrim.TrimPattern.class); + register(DamageType.class, Registries.DAMAGE_TYPE, CraftDamageType.class, net.minecraft.world.damagesource.DamageType.class); + register(Wolf.Variant.class, Registries.WOLF_VARIANT, CraftWolf.CraftVariant.class, WolfVariant.class); + register(ItemType.class, Registries.ITEM, CraftItemType.class, net.minecraft.world.item.Item.class, true); + register(BlockType.class, Registries.BLOCK, CraftBlockType.class, net.minecraft.world.level.block.Block.class, true); + + } + + private static void register(Class bukkit, ResourceKey registry, Class craft, Class minecraft) { + register(bukkit, registry, craft, minecraft, false); + } + + private static void register(Class bukkit, ResourceKey registry, Class craft, Class minecraft, boolean newClass) { + DATA.add(Arguments.of(bukkit, registry, craft, minecraft, newClass)); } @Override