#1405: Add a tool component to ItemMeta

By: 2008Choco <hawkeboyz2@hotmail.com>
Also-by: md_5 <git@md-5.net>
This commit is contained in:
CraftBukkit/Spigot 2024-05-29 06:53:59 +10:00
parent 3433d3f5b7
commit 4691102616
5 changed files with 406 additions and 2 deletions

View File

@ -19,6 +19,7 @@ import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.entity.CraftEntity;
import org.bukkit.craftbukkit.entity.CraftEntityType;
import org.bukkit.craftbukkit.inventory.components.CraftFoodComponent;
import org.bukkit.craftbukkit.inventory.components.CraftToolComponent;
import org.bukkit.craftbukkit.util.CraftLegacy;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
@ -36,6 +37,8 @@ public final class CraftItemFactory implements ItemFactory {
ConfigurationSerialization.registerClass(SerializableMeta.class);
ConfigurationSerialization.registerClass(CraftFoodComponent.class);
ConfigurationSerialization.registerClass(CraftFoodComponent.CraftFoodEffect.class);
ConfigurationSerialization.registerClass(CraftToolComponent.class);
ConfigurationSerialization.registerClass(CraftToolComponent.CraftToolRule.class);
}
private CraftItemFactory() {

View File

@ -67,6 +67,7 @@ import net.minecraft.world.item.component.CustomData;
import net.minecraft.world.item.component.CustomModelData;
import net.minecraft.world.item.component.ItemAttributeModifiers;
import net.minecraft.world.item.component.ItemLore;
import net.minecraft.world.item.component.Tool;
import net.minecraft.world.item.component.Unbreakable;
import net.minecraft.world.item.enchantment.ItemEnchantments;
import net.minecraft.world.level.block.state.IBlockData;
@ -85,6 +86,7 @@ import org.bukkit.craftbukkit.block.data.CraftBlockData;
import org.bukkit.craftbukkit.enchantments.CraftEnchantment;
import org.bukkit.craftbukkit.inventory.CraftMetaItem.ItemMetaKey.Specific;
import org.bukkit.craftbukkit.inventory.components.CraftFoodComponent;
import org.bukkit.craftbukkit.inventory.components.CraftToolComponent;
import org.bukkit.craftbukkit.inventory.tags.DeprecatedCustomTagContainer;
import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
import org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry;
@ -100,6 +102,7 @@ import org.bukkit.inventory.meta.Damageable;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.Repairable;
import org.bukkit.inventory.meta.components.FoodComponent;
import org.bukkit.inventory.meta.components.ToolComponent;
import org.bukkit.inventory.meta.tags.CustomItemTagContainer;
import org.bukkit.persistence.PersistentDataContainer;
@ -221,6 +224,8 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
@Specific(Specific.To.NBT)
static final ItemMetaKeyType<FoodInfo> FOOD = new ItemMetaKeyType<>(DataComponents.FOOD, "food");
@Specific(Specific.To.NBT)
static final ItemMetaKeyType<Tool> TOOL = new ItemMetaKeyType<>(DataComponents.TOOL, "tool");
@Specific(Specific.To.NBT)
static final ItemMetaKeyType<Integer> DAMAGE = new ItemMetaKeyType<>(DataComponents.DAMAGE, "Damage");
@Specific(Specific.To.NBT)
static final ItemMetaKeyType<Integer> MAX_DAMAGE = new ItemMetaKeyType<>(DataComponents.MAX_DAMAGE, "max-damage");
@ -249,6 +254,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
private Integer maxStackSize;
private ItemRarity rarity;
private CraftFoodComponent food;
private CraftToolComponent tool;
private int damage;
private Integer maxDamage;
@ -295,6 +301,9 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
if (meta.hasFood()) {
this.food = new CraftFoodComponent(meta.food);
}
if (meta.hasTool()) {
this.tool = new CraftToolComponent(meta.tool);
}
this.damage = meta.damage;
this.maxDamage = meta.maxDamage;
this.unhandledTags = meta.unhandledTags;
@ -373,6 +382,9 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
getOrEmpty(tag, FOOD).ifPresent((foodInfo) -> {
food = new CraftFoodComponent(foodInfo);
});
getOrEmpty(tag, TOOL).ifPresent((toolInfo) -> {
tool = new CraftToolComponent(toolInfo);
});
getOrEmpty(tag, DAMAGE).ifPresent((i) -> {
damage = i;
});
@ -551,6 +563,11 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
setFood(food);
}
CraftToolComponent tool = SerializableMeta.getObject(CraftToolComponent.class, map, TOOL.BUKKIT, true);
if (tool != null) {
setTool(tool);
}
Integer damage = SerializableMeta.getObject(Integer.class, map, DAMAGE.BUKKIT, true);
if (damage != null) {
setDamage(damage);
@ -779,6 +796,10 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
itemTag.put(FOOD, food.getHandle());
}
if (hasTool()) {
itemTag.put(TOOL, tool.getHandle());
}
if (hasDamage()) {
itemTag.put(DAMAGE, damage);
}
@ -861,7 +882,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
@Overridden
boolean isEmpty() {
return !(hasDisplayName() || hasItemName() || hasLocalizedName() || hasEnchants() || (lore != null) || hasCustomModelData() || hasBlockData() || hasRepairCost() || !unhandledTags.build().isEmpty() || !persistentDataContainer.isEmpty() || hideFlag != 0 || isHideTooltip() || isUnbreakable() || hasEnchantmentGlintOverride() || isFireResistant() || hasMaxStackSize() || hasRarity() || hasFood() || hasDamage() || hasMaxDamage() || hasAttributeModifiers() || customTag != null);
return !(hasDisplayName() || hasItemName() || hasLocalizedName() || hasEnchants() || (lore != null) || hasCustomModelData() || hasBlockData() || hasRepairCost() || !unhandledTags.build().isEmpty() || !persistentDataContainer.isEmpty() || hideFlag != 0 || isHideTooltip() || isUnbreakable() || hasEnchantmentGlintOverride() || isFireResistant() || hasMaxStackSize() || hasRarity() || hasFood() || hasTool() || hasDamage() || hasMaxDamage() || hasAttributeModifiers() || customTag != null);
}
@Override
@ -1174,6 +1195,21 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
this.food = (food == null) ? null : new CraftFoodComponent((CraftFoodComponent) food);
}
@Override
public boolean hasTool() {
return this.tool != null;
}
@Override
public ToolComponent getTool() {
return (this.hasTool()) ? new CraftToolComponent(this.tool) : new CraftToolComponent(new Tool(Collections.emptyList(), 1.0F, 0));
}
@Override
public void setTool(ToolComponent tool) {
this.tool = (tool == null) ? null : new CraftToolComponent((CraftToolComponent) tool);
}
@Override
public boolean hasAttributeModifiers() {
return attributeModifiers != null && !attributeModifiers.isEmpty();
@ -1425,6 +1461,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
&& (this.hasMaxStackSize() ? that.hasMaxStackSize() && this.maxStackSize.equals(that.maxStackSize) : !that.hasMaxStackSize())
&& (this.rarity == that.rarity)
&& (this.hasFood() ? that.hasFood() && this.food.equals(that.food) : !that.hasFood())
&& (this.hasTool() ? that.hasTool() && this.tool.equals(that.tool) : !that.hasTool())
&& (this.hasDamage() ? that.hasDamage() && this.damage == that.damage : !that.hasDamage())
&& (this.hasMaxDamage() ? that.hasMaxDamage() && this.maxDamage.equals(that.maxDamage) : !that.hasMaxDamage())
&& (this.version == that.version);
@ -1466,6 +1503,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
hash = 61 * hash + (hasMaxStackSize() ? this.maxStackSize.hashCode() : 0);
hash = 61 * hash + (hasRarity() ? this.rarity.hashCode() : 0);
hash = 61 * hash + (hasFood() ? this.food.hashCode() : 0);
hash = 61 * hash + (hasTool() ? this.tool.hashCode() : 0);
hash = 61 * hash + (hasDamage() ? this.damage : 0);
hash = 61 * hash + (hasMaxDamage() ? 1231 : 1237);
hash = 61 * hash + (hasAttributeModifiers() ? this.attributeModifiers.hashCode() : 0);
@ -1503,6 +1541,9 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
if (this.hasFood()) {
clone.food = new CraftFoodComponent(food);
}
if (this.hasTool()) {
clone.tool = new CraftToolComponent(tool);
}
clone.damage = this.damage;
clone.maxDamage = this.maxDamage;
clone.version = this.version;
@ -1591,6 +1632,10 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
builder.put(FOOD.BUKKIT, food);
}
if (hasTool()) {
builder.put(TOOL.BUKKIT, tool);
}
if (hasDamage()) {
builder.put(DAMAGE.BUKKIT, damage);
}
@ -1757,6 +1802,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
MAX_STACK_SIZE.TYPE,
RARITY.TYPE,
FOOD.TYPE,
TOOL.TYPE,
DAMAGE.TYPE,
MAX_DAMAGE.TYPE,
CUSTOM_DATA.TYPE,

View File

@ -0,0 +1,333 @@
package org.bukkit.craftbukkit.inventory.components;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.MinecraftKey;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.component.Tool;
import net.minecraft.world.level.block.Block;
import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.configuration.serialization.SerializableAs;
import org.bukkit.craftbukkit.block.CraftBlockType;
import org.bukkit.craftbukkit.inventory.SerializableMeta;
import org.bukkit.craftbukkit.tag.CraftBlockTag;
import org.bukkit.inventory.meta.components.ToolComponent;
@SerializableAs("Tool")
public final class CraftToolComponent implements ToolComponent {
private Tool handle;
public CraftToolComponent(Tool tool) {
this.handle = tool;
}
public CraftToolComponent(CraftToolComponent tool) {
this.handle = tool.handle;
}
public CraftToolComponent(Map<String, Object> map) {
Float speed = SerializableMeta.getObject(Float.class, map, "default-mining-speed", false);
Integer damage = SerializableMeta.getObject(Integer.class, map, "damage-per-block", false);
ImmutableList.Builder<ToolRule> rules = ImmutableList.builder();
Iterable<?> rawRuleList = SerializableMeta.getObject(Iterable.class, map, "rules", true);
if (rawRuleList != null) {
for (Object obj : rawRuleList) {
Preconditions.checkArgument(obj instanceof ToolRule, "Object (%s) in rule list is not valid", obj.getClass());
CraftToolRule rule = new CraftToolRule((ToolRule) obj);
if (rule.handle.blocks().size() > 0) {
rules.add(rule);
}
}
}
this.handle = new Tool(rules.build().stream().map(CraftToolRule::new).map(CraftToolRule::getHandle).toList(), speed, damage);
}
@Override
public Map<String, Object> serialize() {
Map<String, Object> result = new LinkedHashMap<>();
result.put("default-mining-speed", getDefaultMiningSpeed());
result.put("damage-per-block", getDamagePerBlock());
result.put("rules", getRules());
return result;
}
public Tool getHandle() {
return handle;
}
@Override
public float getDefaultMiningSpeed() {
return handle.defaultMiningSpeed();
}
@Override
public void setDefaultMiningSpeed(float speed) {
handle = new Tool(handle.rules(), speed, handle.damagePerBlock());
}
@Override
public int getDamagePerBlock() {
return handle.damagePerBlock();
}
@Override
public void setDamagePerBlock(int damage) {
Preconditions.checkArgument(damage >= 0, "damage must be >= 0, was %d", damage);
handle = new Tool(handle.rules(), handle.defaultMiningSpeed(), damage);
}
@Override
public List<ToolRule> getRules() {
return handle.rules().stream().map(CraftToolRule::new).collect(Collectors.toList());
}
@Override
public void setRules(List<ToolRule> rules) {
Preconditions.checkArgument(rules != null, "rules must not be null");
handle = new Tool(rules.stream().map(CraftToolRule::new).map(CraftToolRule::getHandle).toList(), handle.defaultMiningSpeed(), handle.damagePerBlock());
}
@Override
public ToolRule addRule(Material block, Float speed, Boolean correctForDrops) {
Preconditions.checkArgument(block != null, "block must not be null");
Preconditions.checkArgument(block.isBlock(), "block must be a block type, given %s", block.getKey());
Holder.c<Block> nmsBlock = CraftBlockType.bukkitToMinecraft(block).builtInRegistryHolder();
return addRule(HolderSet.direct(nmsBlock), speed, correctForDrops);
}
@Override
public ToolRule addRule(Collection<Material> blocks, Float speed, Boolean correctForDrops) {
List<Holder.c<Block>> nmsBlocks = new ArrayList<>(blocks.size());
for (Material material : blocks) {
Preconditions.checkArgument(material.isBlock(), "blocks contains non-block type: %s", material.getKey());
nmsBlocks.add(CraftBlockType.bukkitToMinecraft(material).builtInRegistryHolder());
}
return addRule(HolderSet.direct(nmsBlocks), speed, correctForDrops);
}
@Override
public ToolRule addRule(Tag<Material> tag, Float speed, Boolean correctForDrops) {
Preconditions.checkArgument(tag instanceof CraftBlockTag, "tag must be a block tag");
return addRule(((CraftBlockTag) tag).getHandle(), speed, correctForDrops);
}
private ToolRule addRule(HolderSet<Block> blocks, Float speed, Boolean correctForDrops) {
Tool.a rule = new Tool.a(blocks, Optional.ofNullable(speed), Optional.ofNullable(correctForDrops));
List<Tool.a> rules = new ArrayList<>(handle.rules().size() + 1);
rules.addAll(handle.rules());
rules.add(rule);
handle = new Tool(rules, handle.defaultMiningSpeed(), handle.damagePerBlock());
return new CraftToolRule(rule);
}
@Override
public boolean removeRule(ToolRule rule) {
Preconditions.checkArgument(rule != null, "rule must not be null");
List<Tool.a> rules = new ArrayList<>(handle.rules());
boolean removed = rules.remove(((CraftToolRule) rule).handle);
handle = new Tool(rules, handle.defaultMiningSpeed(), handle.damagePerBlock());
return removed;
}
@Override
public int hashCode() {
int hash = 7;
hash = 73 * hash + Objects.hashCode(this.handle);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final CraftToolComponent other = (CraftToolComponent) obj;
return Objects.equals(this.handle, other.handle);
}
@Override
public String toString() {
return "CraftToolComponent{" + "handle=" + handle + '}';
}
@SerializableAs("ToolRule")
public static class CraftToolRule implements ToolRule {
private Tool.a handle;
public CraftToolRule(Tool.a handle) {
this.handle = handle;
}
public CraftToolRule(ToolRule bukkit) {
Tool.a toCopy = ((CraftToolRule) bukkit).handle;
this.handle = new Tool.a(toCopy.blocks(), toCopy.speed(), toCopy.correctForDrops());
}
public CraftToolRule(Map<String, Object> map) {
Float speed = SerializableMeta.getObject(Float.class, map, "speed", false);
Boolean correct = SerializableMeta.getObject(Boolean.class, map, "correct-for-drops", false);
HolderSet<Block> blocks = null;
Object blocksObject = SerializableMeta.getObject(Object.class, map, "blocks", false);
if (blocksObject instanceof String blocksString && blocksString.startsWith("#")) { // Tag
blocksString = blocksString.substring(1);
MinecraftKey key = MinecraftKey.tryParse(blocksString);
if (key != null) {
blocks = BuiltInRegistries.BLOCK.getTag(TagKey.create(Registries.BLOCK, key)).orElse(null);
}
} else if (blocksObject instanceof List blocksList) { // List of blocks
List<Holder.c<Block>> blockHolders = new ArrayList<>(blocksList.size());
for (Object entry : blocksList) {
MinecraftKey key = MinecraftKey.tryParse(entry.toString());
if (key == null) {
continue;
}
BuiltInRegistries.BLOCK.getHolder(key).ifPresent(blockHolders::add);
}
blocks = HolderSet.direct(blockHolders);
} else {
throw new IllegalArgumentException("blocks" + "(" + blocksObject + ") is not a valid String or List");
}
if (blocks == null) {
blocks = HolderSet.empty();
}
this.handle = new Tool.a(blocks, Optional.ofNullable(speed), Optional.ofNullable(correct));
}
@Override
public Map<String, Object> serialize() {
Map<String, Object> result = new LinkedHashMap<>();
handle.blocks().unwrap()
.ifLeft(key -> result.put("blocks", "#" + key.location().toString())) // Tag
.ifRight(blocks -> result.put("blocks", blocks.stream().map((block) -> block.unwrapKey().orElseThrow().location().toString()).toList())); // List of blocks
Float speed = getSpeed();
if (speed != null) {
result.put("speed", speed);
}
Boolean correct = isCorrectForDrops();
if (correct != null) {
result.put("correct-for-drops", correct);
}
return result;
}
public Tool.a getHandle() {
return handle;
}
@Override
public Collection<Material> getBlocks() {
return handle.blocks().stream().map(Holder::value).map(CraftBlockType::minecraftToBukkit).collect(Collectors.toList());
}
@Override
public void setBlocks(Material block) {
Preconditions.checkArgument(block != null, "block must not be null");
Preconditions.checkArgument(block.isBlock(), "block must be a block type, given %s", block.getKey());
handle = new Tool.a(HolderSet.direct(CraftBlockType.bukkitToMinecraft(block).builtInRegistryHolder()), handle.speed(), handle.correctForDrops());
}
@Override
public void setBlocks(Collection<Material> blocks) {
Preconditions.checkArgument(blocks != null, "blocks must not be null");
for (Material material : blocks) {
Preconditions.checkArgument(material.isBlock(), "blocks contains non-block type: %s", material.getKey());
}
handle = new Tool.a(HolderSet.direct((List) blocks.stream().map(CraftBlockType::bukkitToMinecraft).map(Block::builtInRegistryHolder).collect(Collectors.toList())), handle.speed(), handle.correctForDrops());
}
@Override
public void setBlocks(Tag<Material> tag) {
Preconditions.checkArgument(tag instanceof CraftBlockTag, "tag must be a block tag");
handle = new Tool.a(((CraftBlockTag) tag).getHandle(), handle.speed(), handle.correctForDrops());
}
@Override
public Float getSpeed() {
return handle.speed().orElse(null);
}
@Override
public void setSpeed(Float speed) {
handle = new Tool.a(handle.blocks(), Optional.ofNullable(speed), handle.correctForDrops());
}
@Override
public Boolean isCorrectForDrops() {
return handle.correctForDrops().orElse(null);
}
@Override
public void setCorrectForDrops(Boolean correct) {
handle = new Tool.a(handle.blocks(), handle.speed(), Optional.ofNullable(correct));
}
@Override
public int hashCode() {
int hash = 5;
hash = 97 * hash + Objects.hashCode(this.handle);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final CraftToolRule other = (CraftToolRule) obj;
return Objects.equals(this.handle, other.handle);
}
@Override
public String toString() {
return "CraftToolRule{" + "handle=" + handle + '}';
}
}
}

View File

@ -1,5 +1,6 @@
package org.bukkit.craftbukkit.legacy;
import java.util.Collection;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
@ -60,6 +61,7 @@ import org.bukkit.inventory.ShapelessRecipe;
import org.bukkit.inventory.StonecuttingRecipe;
import org.bukkit.inventory.meta.BlockDataMeta;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.components.ToolComponent;
import org.bukkit.material.MaterialData;
import org.bukkit.packs.DataPackManager;
import org.bukkit.scoreboard.Criteria;
@ -578,4 +580,24 @@ public class MaterialRerouting {
public static FallingBlock spawnFallingBlock(World world, Location location, Material material, byte data) {
return world.spawnFallingBlock(location, CraftBlockData.fromData(CraftMagicNumbers.getBlock(material, data)));
}
public static ToolComponent.ToolRule addRule(ToolComponent toolComponent, Material block, Float speed, Boolean correctForDrops) {
return toolComponent.addRule(transformToBlockType(block), speed, correctForDrops);
}
public static ToolComponent.ToolRule addRule(ToolComponent toolComponent, Collection<Material> blocks, Float speed, Boolean correctForDrops) {
return toolComponent.addRule(blocks.stream().map(MaterialRerouting::transformToBlockType).collect(Collectors.toList()), speed, correctForDrops);
}
public static Collection<Material> getBlocks(ToolComponent.ToolRule toolRule, @InjectPluginVersion ApiVersion version) {
return toolRule.getBlocks().stream().map(val -> transformFromBlockType(val, version)).toList();
}
public static void setBlocks(ToolComponent.ToolRule toolRule, Material block) {
toolRule.setBlocks(transformToBlockType(block));
}
public static void setBlocks(ToolComponent.ToolRule toolRule, Collection<Material> blocks) {
toolRule.setBlocks(blocks.stream().map(MaterialRerouting::transformToBlockType).toList());
}
}

View File

@ -21,7 +21,7 @@ public abstract class CraftTag<N, B extends Keyed> implements Tag<B> {
this.handle = registry.getTag(this.tag).orElseThrow();
}
protected HolderSet.Named<N> getHandle() {
public HolderSet.Named<N> getHandle() {
return handle;
}