From 60eec22bd37865c9ba82b1b486f23bdcb1e4ac67 Mon Sep 17 00:00:00 2001 From: CraftBukkit/Spigot Date: Sat, 20 Jul 2024 10:15:22 +1000 Subject: [PATCH] SPIGOT-7809: Add ShieldMeta and fix setting shield base colours By: Doc Also-by: md_5 --- .../craftbukkit/inventory/CraftItemMetas.java | 12 +- .../inventory/CraftMetaBlockState.java | 21 +- .../craftbukkit/inventory/CraftMetaItem.java | 1 + .../inventory/CraftMetaShield.java | 302 ++++++++++++++++++ .../inventory/SerializableMeta.java | 16 +- .../craftbukkit/inventory/ItemMetaTest.java | 8 + 6 files changed, 350 insertions(+), 10 deletions(-) create mode 100644 paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaShield.java diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemMetas.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemMetas.java index 4b078b4f32..9d5903cb67 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemMetas.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemMetas.java @@ -24,6 +24,7 @@ import org.bukkit.inventory.meta.MapMeta; import org.bukkit.inventory.meta.MusicInstrumentMeta; import org.bukkit.inventory.meta.OminousBottleMeta; import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.inventory.meta.ShieldMeta; import org.bukkit.inventory.meta.SkullMeta; import org.bukkit.inventory.meta.SpawnEggMeta; import org.bukkit.inventory.meta.SuspiciousStewMeta; @@ -107,6 +108,10 @@ public final class CraftItemMetas { item -> new CraftMetaBlockState(item.getComponentsPatch(), CraftItemType.minecraftToBukkit(item.getItem())), (type, meta) -> new CraftMetaBlockState(meta, type.asMaterial())); + private static final ItemMetaData SHIELD_META_DATA = new ItemMetaData<>(ShieldMeta.class, + item -> new CraftMetaShield(item.getComponentsPatch()), + (type, meta) -> new CraftMetaShield(meta)); + private static final ItemMetaData TROPICAL_FISH_BUCKET_META_DATA = new ItemMetaData<>(TropicalFishBucketMeta.class, item -> new CraftMetaTropicalFishBucket(item.getComponentsPatch()), (type, meta) -> meta instanceof CraftMetaTropicalFishBucket tropicalFishBucket ? tropicalFishBucket : new CraftMetaTropicalFishBucket(meta)); @@ -258,8 +263,8 @@ public final class CraftItemMetas { || itemType == ItemType.COMMAND_BLOCK || itemType == ItemType.REPEATING_COMMAND_BLOCK || itemType == ItemType.CHAIN_COMMAND_BLOCK || itemType == ItemType.BEACON || itemType == ItemType.DAYLIGHT_DETECTOR || itemType == ItemType.HOPPER - || itemType == ItemType.COMPARATOR || itemType == ItemType.SHIELD - || itemType == ItemType.STRUCTURE_BLOCK || (itemType.hasBlockType() && Tag.SHULKER_BOXES.isTagged(itemType.getBlockType().asMaterial())) + || itemType == ItemType.COMPARATOR || itemType == ItemType.STRUCTURE_BLOCK + || (itemType.hasBlockType() && Tag.SHULKER_BOXES.isTagged(itemType.getBlockType().asMaterial())) || itemType == ItemType.ENDER_CHEST || itemType == ItemType.BARREL || itemType == ItemType.BELL || itemType == ItemType.BLAST_FURNACE || itemType == ItemType.CAMPFIRE || itemType == ItemType.SOUL_CAMPFIRE @@ -273,6 +278,9 @@ public final class CraftItemMetas { || itemType == ItemType.TRIAL_SPAWNER || itemType == ItemType.VAULT) { return asType(BLOCK_STATE_META_DATA); } + if (itemType == ItemType.SHIELD) { + return asType(SHIELD_META_DATA); + } if (itemType == ItemType.TROPICAL_FISH_BUCKET) { return asType(TROPICAL_FISH_BUCKET_META_DATA); } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java index 1390e51d8c..ca58207928 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBlockState.java @@ -15,8 +15,10 @@ import net.minecraft.core.component.PatchedDataComponentMap; import net.minecraft.core.component.TypedDataComponent; import net.minecraft.nbt.NBTBase; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.item.EnumColor; import net.minecraft.world.item.component.CustomData; import net.minecraft.world.level.block.entity.TileEntity; +import org.bukkit.DyeColor; import org.bukkit.Material; import org.bukkit.block.BlockState; import org.bukkit.configuration.serialization.DelegateDeserialization; @@ -202,7 +204,7 @@ public class CraftMetaBlockState extends CraftMetaItem implements BlockStateMeta private static CraftBlockEntityState getBlockState(Material material, NBTTagCompound blockEntityTag) { BlockPosition pos = BlockPosition.ZERO; - Material stateMaterial = (material != Material.SHIELD) ? material : shieldToBannerHack(); // Only actually used for jigsaws + Material stateMaterial = (material != Material.SHIELD) ? material : shieldToBannerHack(blockEntityTag); // Only actually used for jigsaws if (blockEntityTag != null) { if (material == Material.SHIELD) { blockEntityTag.putString("id", "minecraft:banner"); @@ -223,14 +225,25 @@ public class CraftMetaBlockState extends CraftMetaItem implements BlockStateMeta public void setBlockState(BlockState blockState) { Preconditions.checkArgument(blockState != null, "blockState must not be null"); - Material stateMaterial = (material != Material.SHIELD) ? material : shieldToBannerHack(); + Material stateMaterial = (material != Material.SHIELD) ? material : shieldToBannerHack(null); Class blockStateType = CraftBlockStates.getBlockStateType(stateMaterial); - Preconditions.checkArgument(blockStateType == blockState.getClass() && blockState instanceof CraftBlockEntityState, "Invalid blockState for " + material); + Preconditions.checkArgument(blockStateType == blockState.getClass() && blockState instanceof CraftBlockEntityState, "Invalid blockState for %s", material); this.blockEntityTag = (CraftBlockEntityState) blockState; } - private static Material shieldToBannerHack() { + private static Material shieldToBannerHack(NBTTagCompound tag) { + if (tag != null) { + if (tag.contains("components", CraftMagicNumbers.NBT.TAG_COMPOUND)) { + NBTTagCompound components = tag.getCompound("components"); + if (components.contains("minecraft:base_color", CraftMagicNumbers.NBT.TAG_STRING)) { + DyeColor color = DyeColor.getByWoolData((byte) EnumColor.byName(components.getString("minecraft:base_color"), EnumColor.WHITE).getId()); + + return CraftMetaShield.shieldToBannerHack(color); + } + } + } + return Material.WHITE_BANNER; } } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java index 875ededd74..519b586792 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java @@ -1923,6 +1923,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { CraftMetaMap.MAP_COLOR.TYPE, CraftMetaMap.MAP_ID.TYPE, CraftMetaPotion.POTION_CONTENTS.TYPE, + CraftMetaShield.BASE_COLOR.TYPE, CraftMetaSkull.SKULL_PROFILE.TYPE, CraftMetaSkull.NOTE_BLOCK_SOUND.TYPE, CraftMetaSpawnEgg.ENTITY_TAG.TYPE, diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaShield.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaShield.java new file mode 100644 index 0000000000..64ffa1849f --- /dev/null +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaShield.java @@ -0,0 +1,302 @@ +package org.bukkit.craftbukkit.inventory; + +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import net.minecraft.core.BlockPosition; +import net.minecraft.core.component.DataComponentPatch; +import net.minecraft.core.component.DataComponents; +import net.minecraft.world.item.EnumColor; +import net.minecraft.world.level.block.entity.BannerPatternLayers; +import org.bukkit.DyeColor; +import org.bukkit.Material; +import org.bukkit.block.Banner; +import org.bukkit.block.BlockState; +import org.bukkit.block.banner.Pattern; +import org.bukkit.block.banner.PatternType; +import org.bukkit.configuration.serialization.DelegateDeserialization; +import org.bukkit.craftbukkit.block.CraftBlockStates; +import org.bukkit.craftbukkit.block.banner.CraftPatternType; +import org.bukkit.inventory.meta.BlockStateMeta; +import org.bukkit.inventory.meta.ShieldMeta; + +@DelegateDeserialization(SerializableMeta.class) +public class CraftMetaShield extends CraftMetaItem implements ShieldMeta, BlockStateMeta { + + static final ItemMetaKeyType BASE_COLOR = new ItemMetaKeyType<>(DataComponents.BASE_COLOR, "Base", "base-color"); + + private Banner banner; + + CraftMetaShield(CraftMetaItem meta) { + super(meta); + + if (meta instanceof CraftMetaShield craftMetaShield) { + if (craftMetaShield.banner != null) { + this.banner = (Banner) craftMetaShield.banner.copy(); + } + } else if (meta instanceof CraftMetaBlockState state && state.hasBlockState() && state.getBlockState() instanceof Banner banner) { + this.banner = (Banner) banner.copy(); + } + } + + CraftMetaShield(DataComponentPatch tag) { + super(tag); + + getOrEmpty(tag, BASE_COLOR).ifPresent((color) -> { + banner = getBlockState(DyeColor.getByWoolData((byte) color.getId())); + }); + + getOrEmpty(tag, CraftMetaBanner.PATTERNS).ifPresent((entityTag) -> { + List patterns = entityTag.layers(); + for (int i = 0; i < Math.min(patterns.size(), 20); i++) { + BannerPatternLayers.b p = patterns.get(i); + DyeColor color = DyeColor.getByWoolData((byte) p.color().getId()); + PatternType pattern = CraftPatternType.minecraftHolderToBukkit(p.pattern()); + + if (color != null && pattern != null) { + addPattern(new Pattern(color, pattern)); + } + } + }); + } + + CraftMetaShield(Map map) { + super(map); + + String baseColor = SerializableMeta.getString(map, BASE_COLOR.BUKKIT, true); + if (baseColor != null) { + banner = getBlockState(DyeColor.valueOf(baseColor)); + } + + Iterable rawPatternList = SerializableMeta.getObject(Iterable.class, map, CraftMetaBanner.PATTERNS.BUKKIT, true); + if (rawPatternList == null) { + return; + } + + for (Object obj : rawPatternList) { + Preconditions.checkArgument(obj instanceof Pattern, "Object (%s) in pattern list is not valid", obj.getClass()); + addPattern((Pattern) obj); + } + } + + @Override + void applyToItem(CraftMetaItem.Applicator tag) { + super.applyToItem(tag); + + if (banner != null) { + tag.put(BASE_COLOR, EnumColor.byId(banner.getBaseColor().getWoolData())); + + if (banner.numberOfPatterns() > 0) { + List newPatterns = new ArrayList<>(); + + for (Pattern p : banner.getPatterns()) { + newPatterns.add(new BannerPatternLayers.b(CraftPatternType.bukkitToMinecraftHolder(p.getPattern()), EnumColor.byId(p.getColor().getWoolData()))); + } + + tag.put(CraftMetaBanner.PATTERNS, new BannerPatternLayers(newPatterns)); + } + } + } + + @Override + public List getPatterns() { + if (banner == null) { + return new ArrayList<>(); + } + + return banner.getPatterns(); + } + + @Override + public void setPatterns(List patterns) { + if (banner == null) { + if (patterns.isEmpty()) { + return; + } + + banner = getBlockState(null); + } + + banner.setPatterns(patterns); + } + + @Override + public void addPattern(Pattern pattern) { + if (banner == null) { + banner = getBlockState(null); + } + + banner.addPattern(pattern); + } + + @Override + public Pattern getPattern(int i) { + if (banner == null) { + throw new IndexOutOfBoundsException(i); + } + + return banner.getPattern(i); + } + + @Override + public Pattern removePattern(int i) { + if (banner == null) { + throw new IndexOutOfBoundsException(i); + } + + return banner.removePattern(i); + } + + @Override + public void setPattern(int i, Pattern pattern) { + if (banner == null) { + throw new IndexOutOfBoundsException(i); + } + + banner.setPattern(i, pattern); + } + + @Override + public int numberOfPatterns() { + if (banner == null) { + return 0; + } + + return banner.numberOfPatterns(); + } + + @Override + public DyeColor getBaseColor() { + if (banner == null) { + return null; + } + + return banner.getBaseColor(); + } + + @Override + public void setBaseColor(DyeColor baseColor) { + if (baseColor == null) { + if (banner.numberOfPatterns() > 0) { + banner.setBaseColor(DyeColor.WHITE); + } else { + banner = null; + } + } else { + if (banner == null) { + banner = getBlockState(baseColor); + } + + banner.setBaseColor(baseColor); + } + } + + @Override + ImmutableMap.Builder serialize(ImmutableMap.Builder builder) { + super.serialize(builder); + + if (banner != null) { + builder.put(BASE_COLOR.BUKKIT, banner.getBaseColor().toString()); + + if (banner.numberOfPatterns() > 0) { + builder.put(CraftMetaBanner.PATTERNS.BUKKIT, banner.getPatterns()); + } + } + + return builder; + } + + @Override + int applyHash() { + final int original; + int hash = original = super.applyHash(); + if (banner != null) { + hash = 61 * hash + banner.hashCode(); + } + return original != hash ? CraftMetaShield.class.hashCode() ^ hash : hash; + } + + @Override + boolean equalsCommon(CraftMetaItem meta) { + if (!super.equalsCommon(meta)) { + return false; + } + if (meta instanceof CraftMetaShield that) { + return Objects.equal(this.banner, that.banner); + } + return true; + } + + @Override + boolean notUncommon(CraftMetaItem meta) { + return super.notUncommon(meta) && (meta instanceof CraftMetaShield || this.banner == null); + } + + @Override + boolean isEmpty() { + return super.isEmpty() && this.banner == null; + } + + @Override + public boolean hasBlockState() { + return banner != null; + } + + @Override + public BlockState getBlockState() { + return (banner != null) ? banner.copy() : getBlockState(null); + } + + @Override + public void setBlockState(BlockState blockState) { + Preconditions.checkArgument(blockState != null, "blockState must not be null"); + Preconditions.checkArgument(blockState instanceof Banner, "Invalid blockState"); + + this.banner = (Banner) blockState; + } + + private static Banner getBlockState(DyeColor color) { + BlockPosition pos = BlockPosition.ZERO; + Material stateMaterial = shieldToBannerHack(color); + + return (Banner) CraftBlockStates.getBlockState(pos, stateMaterial, null); + } + + @Override + public CraftMetaShield clone() { + CraftMetaShield meta = (CraftMetaShield) super.clone(); + if (this.banner != null) { + meta.banner = (Banner) banner.copy(); + } + return meta; + } + + static Material shieldToBannerHack(DyeColor color) { + if (color == null) { + return Material.WHITE_BANNER; + } + + return switch (color) { + case WHITE -> Material.WHITE_BANNER; + case ORANGE -> Material.ORANGE_BANNER; + case MAGENTA -> Material.MAGENTA_BANNER; + case LIGHT_BLUE -> Material.LIGHT_BLUE_BANNER; + case YELLOW -> Material.YELLOW_BANNER; + case LIME -> Material.LIME_BANNER; + case PINK -> Material.PINK_BANNER; + case GRAY -> Material.GRAY_BANNER; + case LIGHT_GRAY -> Material.LIGHT_GRAY_BANNER; + case CYAN -> Material.CYAN_BANNER; + case PURPLE -> Material.PURPLE_BANNER; + case BLUE -> Material.BLUE_BANNER; + case BROWN -> Material.BROWN_BANNER; + case GREEN -> Material.GREEN_BANNER; + case RED -> Material.RED_BANNER; + case BLACK -> Material.BLACK_BANNER; + default -> throw new IllegalArgumentException("Unknown banner colour"); + }; + } +} diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/SerializableMeta.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/SerializableMeta.java index 585dac7f68..f5c9449543 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/SerializableMeta.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/SerializableMeta.java @@ -6,6 +6,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Map; import java.util.NoSuchElementException; +import org.bukkit.block.Banner; import org.bukkit.configuration.serialization.ConfigurationSerializable; import org.bukkit.configuration.serialization.SerializableAs; import org.bukkit.inventory.meta.ItemMeta; @@ -30,6 +31,7 @@ public final class SerializableMeta implements ConfigurationSerializable { .put(CraftMetaColorableArmor.class, "COLORABLE_ARMOR") .put(CraftMetaMap.class, "MAP") .put(CraftMetaPotion.class, "POTION") + .put(CraftMetaShield.class, "SHIELD") .put(CraftMetaSpawnEgg.class, "SPAWN_EGG") .put(CraftMetaEnchantedBook.class, "ENCHANTED") .put(CraftMetaFirework.class, "FIREWORK") @@ -72,10 +74,16 @@ public final class SerializableMeta implements ConfigurationSerializable { } try { - return constructor.newInstance(map); - } catch (final InstantiationException e) { - throw new AssertionError(e); - } catch (final IllegalAccessException e) { + CraftMetaItem meta = constructor.newInstance(map); + + // Convert Shield CraftMetaBlockState to CraftMetaShield + if (meta instanceof CraftMetaBlockState state && state.hasBlockState() && state.getBlockState() instanceof Banner) { + meta = new CraftMetaShield(meta); + meta.unhandledTags.clear(CraftMetaShield.BASE_COLOR.TYPE); + } + + return meta; + } catch (final InstantiationException | IllegalAccessException e) { throw new AssertionError(e); } catch (final InvocationTargetException e) { throw e.getCause(); 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 675a1838c5..022b726af5 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 @@ -416,6 +416,14 @@ public class ItemMetaTest extends AbstractTestingBase { cleanStack.setItemMeta(meta); return cleanStack; } + }, + new StackProvider(Material.SHIELD) { + @Override ItemStack operate(ItemStack cleanStack) { + final CraftMetaShield meta = (CraftMetaShield) cleanStack.getItemMeta(); + meta.setBaseColor(DyeColor.ORANGE); + cleanStack.setItemMeta(meta); + return cleanStack; + } } );