3
0
Mirror von https://github.com/PaperMC/Paper.git synchronisiert 2024-12-23 14:50:08 +01:00

SPIGOT-1916: Attribute modifiers for ItemStacks

Dieser Commit ist enthalten in:
Senmori 2018-09-21 20:47:37 +10:00 committet von md_5
Ursprung 26c89277cb
Commit 8164f4b25b
3 geänderte Dateien mit 317 neuen und 55 gelöschten Zeilen

Datei anzeigen

@ -65,11 +65,11 @@ public class CraftAttributeInstance implements AttributeInstance {
return handle.getAttribute().getDefault(); return handle.getAttribute().getDefault();
} }
private static net.minecraft.server.AttributeModifier convert(AttributeModifier bukkit) { public static net.minecraft.server.AttributeModifier convert(AttributeModifier bukkit) {
return new net.minecraft.server.AttributeModifier(bukkit.getUniqueId(), bukkit.getName(), bukkit.getAmount(), bukkit.getOperation().ordinal()); return new net.minecraft.server.AttributeModifier(bukkit.getUniqueId(), bukkit.getName(), bukkit.getAmount(), bukkit.getOperation().ordinal());
} }
private static AttributeModifier convert(net.minecraft.server.AttributeModifier nms) { public static AttributeModifier convert(net.minecraft.server.AttributeModifier nms) {
return new AttributeModifier(nms.a(), nms.b(), nms.d(), AttributeModifier.Operation.values()[nms.c()]); return new AttributeModifier(nms.a(), nms.b(), nms.d(), AttributeModifier.Operation.values()[nms.c()]);
} }
} }

Datei anzeigen

@ -1,7 +1,10 @@
package org.bukkit.craftbukkit.attribute; package org.bukkit.craftbukkit.attribute;
import com.google.common.base.CaseFormat;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import java.util.Locale;
import net.minecraft.server.AttributeMapBase; import net.minecraft.server.AttributeMapBase;
import org.apache.commons.lang3.EnumUtils;
import org.bukkit.attribute.Attributable; import org.bukkit.attribute.Attributable;
import org.bukkit.attribute.Attribute; import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance; import org.bukkit.attribute.AttributeInstance;
@ -22,7 +25,7 @@ public class CraftAttributeMap implements Attributable {
return (nms == null) ? null : new CraftAttributeInstance(nms, attribute); return (nms == null) ? null : new CraftAttributeInstance(nms, attribute);
} }
static String toMinecraft(String bukkit) { public static String toMinecraft(String bukkit) {
int first = bukkit.indexOf('_'); int first = bukkit.indexOf('_');
int second = bukkit.indexOf('_', first + 1); int second = bukkit.indexOf('_', first + 1);
@ -36,4 +39,17 @@ public class CraftAttributeMap implements Attributable {
return sb.toString(); return sb.toString();
} }
public static String toMinecraft(Attribute attribute) {
return toMinecraft(attribute.name());
}
public static Attribute fromMinecraft(String nms) {
String[] split = nms.split("\\.", 2);
String generic = split[0];
String descriptor = CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, split[1]); // movementSpeed -> MOVEMENT_SPEED
String fin = generic + "_" + descriptor;
return EnumUtils.getEnum(Attribute.class, fin.toUpperCase(Locale.ROOT)); // so we can return null without throwing exceptions
}
} }

Datei anzeigen

@ -9,10 +9,20 @@ import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.SetMultimap;
import net.minecraft.server.EnumItemSlot;
import net.minecraft.server.GenericAttributes;
import net.minecraft.server.IChatBaseComponent; import net.minecraft.server.IChatBaseComponent;
import net.minecraft.server.NBTBase; import net.minecraft.server.NBTBase;
import net.minecraft.server.NBTTagCompound; import net.minecraft.server.NBTTagCompound;
@ -20,22 +30,28 @@ import net.minecraft.server.NBTTagList;
import net.minecraft.server.NBTTagString; import net.minecraft.server.NBTTagString;
import org.apache.commons.lang.Validate; import org.apache.commons.lang.Validate;
import org.apache.commons.lang3.EnumUtils;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeModifier;
import org.bukkit.configuration.serialization.ConfigurationSerializable; import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.configuration.serialization.DelegateDeserialization; import org.bukkit.configuration.serialization.DelegateDeserialization;
import org.bukkit.configuration.serialization.SerializableAs; import org.bukkit.configuration.serialization.SerializableAs;
import org.bukkit.craftbukkit.CraftEquipmentSlot;
import org.bukkit.craftbukkit.Overridden; import org.bukkit.craftbukkit.Overridden;
import org.bukkit.craftbukkit.attribute.CraftAttributeInstance;
import org.bukkit.craftbukkit.attribute.CraftAttributeMap;
import org.bukkit.craftbukkit.inventory.CraftMetaItem.ItemMetaKey.Specific; import org.bukkit.craftbukkit.inventory.CraftMetaItem.ItemMetaKey.Specific;
import org.bukkit.craftbukkit.util.CraftChatMessage; import org.bukkit.craftbukkit.util.CraftChatMessage;
import org.bukkit.craftbukkit.util.CraftMagicNumbers; import org.bukkit.craftbukkit.util.CraftMagicNumbers;
import org.bukkit.craftbukkit.util.CraftNamespacedKey; import org.bukkit.craftbukkit.util.CraftNamespacedKey;
import org.bukkit.enchantments.Enchantment; import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.meta.Damageable; import org.bukkit.inventory.meta.Damageable;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.Repairable; import org.bukkit.inventory.meta.Repairable;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
@ -45,15 +61,16 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import net.minecraft.server.ChatComponentText;
import net.minecraft.server.EnumChatFormat; import net.minecraft.server.EnumChatFormat;
import net.minecraft.server.NBTCompressedStreamTools; import net.minecraft.server.NBTCompressedStreamTools;
import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Base64;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/** /**
* Children must include the following: * Children must include the following:
* *
@ -206,8 +223,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable {
@Specific(Specific.To.NBT) @Specific(Specific.To.NBT)
static final ItemMetaKey ENCHANTMENTS_LVL = new ItemMetaKey("lvl"); static final ItemMetaKey ENCHANTMENTS_LVL = new ItemMetaKey("lvl");
static final ItemMetaKey REPAIR = new ItemMetaKey("RepairCost", "repair-cost"); static final ItemMetaKey REPAIR = new ItemMetaKey("RepairCost", "repair-cost");
@Specific(Specific.To.NBT) static final ItemMetaKey ATTRIBUTES = new ItemMetaKey("AttributeModifiers", "attribute-modifiers");
static final ItemMetaKey ATTRIBUTES = new ItemMetaKey("AttributeModifiers");
@Specific(Specific.To.NBT) @Specific(Specific.To.NBT)
static final ItemMetaKey ATTRIBUTES_IDENTIFIER = new ItemMetaKey("AttributeName"); static final ItemMetaKey ATTRIBUTES_IDENTIFIER = new ItemMetaKey("AttributeName");
@Specific(Specific.To.NBT) @Specific(Specific.To.NBT)
@ -221,6 +237,8 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable {
@Specific(Specific.To.NBT) @Specific(Specific.To.NBT)
static final ItemMetaKey ATTRIBUTES_UUID_LOW = new ItemMetaKey("UUIDLeast"); static final ItemMetaKey ATTRIBUTES_UUID_LOW = new ItemMetaKey("UUIDLeast");
@Specific(Specific.To.NBT) @Specific(Specific.To.NBT)
static final ItemMetaKey ATTRIBUTES_SLOT = new ItemMetaKey("Slot");
@Specific(Specific.To.NBT)
static final ItemMetaKey HIDEFLAGS = new ItemMetaKey("HideFlags", "ItemFlags"); static final ItemMetaKey HIDEFLAGS = new ItemMetaKey("HideFlags", "ItemFlags");
@Specific(Specific.To.NBT) @Specific(Specific.To.NBT)
static final ItemMetaKey UNBREAKABLE = new ItemMetaKey("Unbreakable"); static final ItemMetaKey UNBREAKABLE = new ItemMetaKey("Unbreakable");
@ -231,6 +249,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable {
private IChatBaseComponent locName; private IChatBaseComponent locName;
private List<String> lore; private List<String> lore;
private Map<Enchantment, Integer> enchantments; private Map<Enchantment, Integer> enchantments;
private Multimap<Attribute, AttributeModifier> attributeModifiers;
private int repairCost; private int repairCost;
private int hideFlag; private int hideFlag;
private boolean unbreakable; private boolean unbreakable;
@ -257,6 +276,10 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable {
this.enchantments = new HashMap<Enchantment, Integer>(meta.enchantments); this.enchantments = new HashMap<Enchantment, Integer>(meta.enchantments);
} }
if (meta.hasAttributeModifiers()) {
this.attributeModifiers = HashMultimap.create(meta.attributeModifiers);
}
this.repairCost = meta.repairCost; this.repairCost = meta.repairCost;
this.hideFlag = meta.hideFlag; this.hideFlag = meta.hideFlag;
this.unbreakable = meta.unbreakable; this.unbreakable = meta.unbreakable;
@ -301,6 +324,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable {
} }
this.enchantments = buildEnchantments(tag, ENCHANTMENTS); this.enchantments = buildEnchantments(tag, ENCHANTMENTS);
this.attributeModifiers = buildModifiers(tag, ATTRIBUTES);
if (tag.hasKey(REPAIR.NBT)) { if (tag.hasKey(REPAIR.NBT)) {
repairCost = tag.getInt(REPAIR.NBT); repairCost = tag.getInt(REPAIR.NBT);
@ -316,52 +340,6 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable {
damage = tag.getInt(DAMAGE.NBT); damage = tag.getInt(DAMAGE.NBT);
} }
if (tag.get(ATTRIBUTES.NBT) instanceof NBTTagList) {
NBTTagList save = null;
NBTTagList nbttaglist = tag.getList(ATTRIBUTES.NBT, CraftMagicNumbers.NBT.TAG_COMPOUND);
for (int i = 0; i < nbttaglist.size(); ++i) {
if (!(nbttaglist.get(i) instanceof NBTTagCompound)) {
continue;
}
NBTTagCompound nbttagcompound = (NBTTagCompound) nbttaglist.get(i);
if (!nbttagcompound.hasKeyOfType(ATTRIBUTES_UUID_HIGH.NBT, CraftMagicNumbers.NBT.TAG_ANY_NUMBER)) {
continue;
}
if (!nbttagcompound.hasKeyOfType(ATTRIBUTES_UUID_LOW.NBT, CraftMagicNumbers.NBT.TAG_ANY_NUMBER)) {
continue;
}
if (!(nbttagcompound.get(ATTRIBUTES_IDENTIFIER.NBT) instanceof NBTTagString) || !CraftItemFactory.KNOWN_NBT_ATTRIBUTE_NAMES.contains(nbttagcompound.getString(ATTRIBUTES_IDENTIFIER.NBT))) {
continue;
}
if (!(nbttagcompound.get(ATTRIBUTES_NAME.NBT) instanceof NBTTagString) || nbttagcompound.getString(ATTRIBUTES_NAME.NBT).isEmpty()) {
continue;
}
if (!nbttagcompound.hasKeyOfType(ATTRIBUTES_VALUE.NBT, CraftMagicNumbers.NBT.TAG_ANY_NUMBER)) {
continue;
}
if (!nbttagcompound.hasKeyOfType(ATTRIBUTES_TYPE.NBT, CraftMagicNumbers.NBT.TAG_ANY_NUMBER) || nbttagcompound.getInt(ATTRIBUTES_TYPE.NBT) < 0 || nbttagcompound.getInt(ATTRIBUTES_TYPE.NBT) > 2) {
continue;
}
if (save == null) {
save = new NBTTagList();
}
NBTTagCompound entry = new NBTTagCompound();
entry.set(ATTRIBUTES_UUID_HIGH.NBT, nbttagcompound.get(ATTRIBUTES_UUID_HIGH.NBT));
entry.set(ATTRIBUTES_UUID_LOW.NBT, nbttagcompound.get(ATTRIBUTES_UUID_LOW.NBT));
entry.set(ATTRIBUTES_IDENTIFIER.NBT, nbttagcompound.get(ATTRIBUTES_IDENTIFIER.NBT));
entry.set(ATTRIBUTES_NAME.NBT, nbttagcompound.get(ATTRIBUTES_NAME.NBT));
entry.set(ATTRIBUTES_VALUE.NBT, nbttagcompound.get(ATTRIBUTES_VALUE.NBT));
entry.set(ATTRIBUTES_TYPE.NBT, nbttagcompound.get(ATTRIBUTES_TYPE.NBT));
save.add(entry);
}
unhandledTags.put(ATTRIBUTES.NBT, save);
}
Set<String> keys = tag.getKeys(); Set<String> keys = tag.getKeys();
for (String key : keys) { for (String key : keys) {
if (!getHandledTags().contains(key)) { if (!getHandledTags().contains(key)) {
@ -391,6 +369,45 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable {
return enchantments; return enchantments;
} }
static Multimap<Attribute, AttributeModifier> buildModifiers(NBTTagCompound tag, ItemMetaKey key) {
Multimap<Attribute, AttributeModifier> modifiers = HashMultimap.create();
if (!tag.hasKey(key.NBT)) {
return modifiers;
}
NBTTagList mods = tag.getList(key.NBT, CraftMagicNumbers.NBT.TAG_COMPOUND);
int size = mods.size();
for (int i = 0; i < size; i++) {
NBTTagCompound entry = mods.getCompound(i);
if (entry.isEmpty()) {
// entry is not an actual NBTTagCompound. getCompound returns empty NBTTagCompound in that case
continue;
}
net.minecraft.server.AttributeModifier nmsModifier = GenericAttributes.a(entry);
Preconditions.checkNotNull(nmsModifier, "Could not create AttributeModifier. %s", entry.toString());
AttributeModifier attribMod = CraftAttributeInstance.convert(nmsModifier);
String attributeName = entry.getString(ATTRIBUTES_IDENTIFIER.NBT);
Preconditions.checkArgument(!Strings.isNullOrEmpty(attributeName), "Missing Attribute for AttributeModifier. %s", entry.toString());
Attribute attribute = CraftAttributeMap.fromMinecraft(attributeName);
Preconditions.checkNotNull(attribute, "Could not convert to Bukkit Attribute. %s", attributeName);
if (entry.hasKeyOfType(ATTRIBUTES_SLOT.NBT, CraftMagicNumbers.NBT.TAG_STRING)) {
String slotName = entry.getString(ATTRIBUTES_SLOT.NBT);
Preconditions.checkArgument(!Strings.isNullOrEmpty(slotName), "Missing Slot when Slot is specified. %s", entry.toString());
EquipmentSlot slot = CraftEquipmentSlot.getSlot(EnumItemSlot.a(slotName.toLowerCase(Locale.ROOT))); // PAIL rename fromName
Preconditions.checkNotNull(slot, "No Slot found when Slot was specified. %s", entry.toString());
attribMod = new AttributeModifier(attribMod.getUniqueId(), attribMod.getName(), attribMod.getAmount(), attribMod.getOperation(), slot);
}
modifiers.put(attribute, attribMod);
}
return modifiers;
}
CraftMetaItem(Map<String, Object> map) { CraftMetaItem(Map<String, Object> map) {
setDisplayName(SerializableMeta.getString(map, NAME.BUKKIT, true)); setDisplayName(SerializableMeta.getString(map, NAME.BUKKIT, true));
setLocalizedName(SerializableMeta.getString(map, LOCNAME.BUKKIT, true)); setLocalizedName(SerializableMeta.getString(map, LOCNAME.BUKKIT, true));
@ -401,6 +418,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable {
} }
enchantments = buildEnchantments(map, ENCHANTMENTS); enchantments = buildEnchantments(map, ENCHANTMENTS);
attributeModifiers = buildModifiers(map, ATTRIBUTES);
Integer repairCost = SerializableMeta.getObject(Integer.class, map, REPAIR.BUKKIT, true); Integer repairCost = SerializableMeta.getObject(Integer.class, map, REPAIR.BUKKIT, true);
if (repairCost != null) { if (repairCost != null) {
@ -474,6 +492,42 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable {
return enchantments; return enchantments;
} }
static Multimap<Attribute, AttributeModifier> buildModifiers(Map<String, Object> map, ItemMetaKey key) {
Map<?, ?> mods = SerializableMeta.getObject(Map.class, map, key.BUKKIT, true);
Multimap<Attribute, AttributeModifier> result = HashMultimap.create();
if (mods == null) {
return result;
}
for (Object obj : mods.keySet()) {
if (!(obj instanceof String)) {
continue;
}
String attributeName = (String) obj;
if (Strings.isNullOrEmpty(attributeName)) {
continue;
}
List<?> list = SerializableMeta.getObject(List.class, mods, attributeName, true);
if (list == null || list.isEmpty()) {
return result;
}
for (Object o : list) {
if (!(o instanceof AttributeModifier)) { // this catches null
continue;
}
AttributeModifier modifier = (AttributeModifier) o;
Attribute attribute = EnumUtils.getEnum(Attribute.class, attributeName.toUpperCase(Locale.ROOT));
if (attribute == null) {
continue;
}
result.put(attribute, modifier);
}
}
return result;
}
@Overridden @Overridden
void applyToItem(NBTTagCompound itemTag) { void applyToItem(NBTTagCompound itemTag) {
if (hasDisplayName()) { if (hasDisplayName()) {
@ -492,6 +546,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable {
} }
applyEnchantments(enchantments, itemTag, ENCHANTMENTS); applyEnchantments(enchantments, itemTag, ENCHANTMENTS);
applyModifiers(attributeModifiers, itemTag, ATTRIBUTES);
if (hasRepairCost()) { if (hasRepairCost()) {
itemTag.setInt(REPAIR.NBT, repairCost); itemTag.setInt(REPAIR.NBT, repairCost);
@ -542,6 +597,35 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable {
tag.set(key.NBT, list); tag.set(key.NBT, list);
} }
static void applyModifiers(Multimap<Attribute, AttributeModifier> modifiers, NBTTagCompound tag, ItemMetaKey key) {
if (modifiers == null || modifiers.isEmpty()) {
return;
}
NBTTagList list = new NBTTagList();
for (Map.Entry<Attribute, AttributeModifier> entry : modifiers.entries()) {
if (entry.getKey() == null || entry.getValue() == null) {
continue;
}
net.minecraft.server.AttributeModifier nmsModifier = CraftAttributeInstance.convert(entry.getValue());
NBTTagCompound sub = GenericAttributes.a(nmsModifier);
Preconditions.checkState(!sub.isEmpty(), "Could not convert AttributeModifier. It was supplied in an invalid format. The following was supplied: %s", sub.toString());
String name = CraftAttributeMap.toMinecraft(entry.getKey());
Preconditions.checkArgument(!Strings.isNullOrEmpty(name), "Could not convert to Bukkit Attribute. %s", entry.getKey().name());
sub.setString(ATTRIBUTES_IDENTIFIER.NBT, name); // Attribute Name
if (entry.getValue().getSlot() != null) {
EnumItemSlot slot = CraftEquipmentSlot.getNMS(entry.getValue().getSlot());
if (slot != null) {
sub.setString(ATTRIBUTES_SLOT.NBT, slot.d()); // PAIL rename getSlotName, getName
}
}
list.add(sub);
}
tag.set(key.NBT, list);
}
void setDisplayTag(NBTTagCompound tag, String key, NBTBase value) { void setDisplayTag(NBTTagCompound tag, String key, NBTBase value) {
final NBTTagCompound display = tag.getCompound(DISPLAY.NBT); final NBTTagCompound display = tag.getCompound(DISPLAY.NBT);
@ -559,7 +643,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable {
@Overridden @Overridden
boolean isEmpty() { boolean isEmpty() {
return !(hasDisplayName() || hasLocalizedName() || hasEnchants() || hasLore() || hasRepairCost() || !unhandledTags.isEmpty() || hideFlag != 0 || isUnbreakable() || hasDamage()); return !(hasDisplayName() || hasLocalizedName() || hasEnchants() || hasLore() || hasRepairCost() || !unhandledTags.isEmpty() || hideFlag != 0 || isUnbreakable() || hasDamage() || hasAttributeModifiers());
} }
public String getDisplayName() { public String getDisplayName() {
@ -713,6 +797,136 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable {
this.unbreakable = unbreakable; this.unbreakable = unbreakable;
} }
@Override
public boolean hasAttributeModifiers() {
return attributeModifiers != null && !attributeModifiers.isEmpty();
}
@Override
public Multimap<Attribute, AttributeModifier> getAttributeModifiers() {
return hasAttributeModifiers() ? ImmutableMultimap.copyOf(attributeModifiers) : null;
}
private void checkAttributeList() {
if (attributeModifiers == null) {
attributeModifiers = HashMultimap.create();
}
}
@Override
public Multimap<Attribute, AttributeModifier> getAttributeModifiers(@Nullable EquipmentSlot slot) {
checkAttributeList();
SetMultimap<Attribute, AttributeModifier> result = HashMultimap.create();
for (Map.Entry<Attribute, AttributeModifier> entry : attributeModifiers.entries()) {
if (entry.getValue().getSlot() == null || entry.getValue().getSlot() == slot) {
result.put(entry.getKey(), entry.getValue());
}
}
return result;
}
@Override
public Collection<AttributeModifier> getAttributeModifiers(@Nonnull Attribute attribute) {
Preconditions.checkNotNull(attribute, "Attribute cannot be null");
return attributeModifiers.containsKey(attribute) ? ImmutableList.copyOf(attributeModifiers.get(attribute)) : null;
}
@Override
public boolean addAttributeModifier(@Nonnull Attribute attribute, @Nonnull AttributeModifier modifier) {
Preconditions.checkNotNull(attribute, "Attribute cannot be null");
Preconditions.checkNotNull(modifier, "AttributeModifier cannot be null");
checkAttributeList();
for (Map.Entry<Attribute, AttributeModifier> entry : attributeModifiers.entries()) {
Preconditions.checkArgument(!entry.getValue().getUniqueId().equals(modifier.getUniqueId()), "Cannot register AttributeModifier. Modifier is already applied! %s", modifier);
}
return attributeModifiers.put(attribute, modifier);
}
@Override
public void setAttributeModifiers(@Nullable Multimap<Attribute, AttributeModifier> attributeModifiers) {
if (attributeModifiers == null || attributeModifiers.isEmpty()) {
this.attributeModifiers = HashMultimap.create();
return;
}
Iterator<Map.Entry<Attribute, AttributeModifier>> iterator = attributeModifiers.entries().iterator();
this.attributeModifiers.clear();
while (iterator.hasNext()) {
Map.Entry<Attribute, AttributeModifier> next = iterator.next();
if (next.getKey() == null || next.getValue() == null) {
iterator.remove();
continue;
}
this.attributeModifiers.put(next.getKey(), next.getValue());
}
}
@Override
public boolean removeAttributeModifier(@Nonnull Attribute attribute) {
Preconditions.checkNotNull(attribute, "Attribute cannot be null");
checkAttributeList();
return !attributeModifiers.removeAll(attribute).isEmpty();
}
@Override
public boolean removeAttributeModifier(@Nullable EquipmentSlot slot) {
checkAttributeList();
int removed = 0;
Iterator<Map.Entry<Attribute, AttributeModifier>> iter = attributeModifiers.entries().iterator();
while (iter.hasNext()) {
Map.Entry<Attribute, AttributeModifier> entry = iter.next();
// Explicitly match against null because (as of MC 1.13) AttributeModifiers without a -
// set slot are active in any slot.
if (entry.getValue().getSlot() == null || entry.getValue().getSlot() == slot) {
iter.remove();
++removed;
}
}
return removed > 0;
}
@Override
public boolean removeAttributeModifier(@Nonnull Attribute attribute, @Nonnull AttributeModifier modifier) {
Preconditions.checkNotNull(attribute, "Attribute cannot be null");
Preconditions.checkNotNull(modifier, "AttributeModifier cannot be null");
checkAttributeList();
int removed = 0;
Iterator<Map.Entry<Attribute, AttributeModifier>> iter = attributeModifiers.entries().iterator();
while (iter.hasNext()) {
Map.Entry<Attribute, AttributeModifier> entry = iter.next();
if (entry.getKey() == null || entry.getValue() == null) {
iter.remove();
++removed;
continue; // remove all null values while we are here
}
if (entry.getKey() == attribute && entry.getValue().getUniqueId().equals(modifier.getUniqueId())) {
iter.remove();
++removed;
}
}
return removed > 0;
}
private static boolean compareModifiers(Multimap<Attribute, AttributeModifier> first, Multimap<Attribute, AttributeModifier> second) {
if (first == null || second == null) {
return false;
}
for (Map.Entry<Attribute, AttributeModifier> entry : first.entries()) {
if (!second.containsEntry(entry.getKey(), entry.getValue())) {
return false;
}
}
for (Map.Entry<Attribute, AttributeModifier> entry : second.entries()) {
if (!first.containsEntry(entry.getKey(), entry.getValue())) {
return false;
}
}
return true;
}
@Override @Override
public boolean hasDamage() { public boolean hasDamage() {
return damage > 0; return damage > 0;
@ -754,6 +968,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable {
&& (this.hasEnchants() ? that.hasEnchants() && this.enchantments.equals(that.enchantments) : !that.hasEnchants()) && (this.hasEnchants() ? that.hasEnchants() && this.enchantments.equals(that.enchantments) : !that.hasEnchants())
&& (this.hasLore() ? that.hasLore() && this.lore.equals(that.lore) : !that.hasLore()) && (this.hasLore() ? that.hasLore() && this.lore.equals(that.lore) : !that.hasLore())
&& (this.hasRepairCost() ? that.hasRepairCost() && this.repairCost == that.repairCost : !that.hasRepairCost()) && (this.hasRepairCost() ? that.hasRepairCost() && this.repairCost == that.repairCost : !that.hasRepairCost())
&& (this.hasAttributeModifiers() ? that.hasAttributeModifiers() && compareModifiers(this.attributeModifiers, that.attributeModifiers) : !that.hasAttributeModifiers())
&& (this.unhandledTags.equals(that.unhandledTags)) && (this.unhandledTags.equals(that.unhandledTags))
&& (this.hideFlag == that.hideFlag) && (this.hideFlag == that.hideFlag)
&& (this.isUnbreakable() == that.isUnbreakable()) && (this.isUnbreakable() == that.isUnbreakable())
@ -787,6 +1002,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable {
hash = 61 * hash + hideFlag; hash = 61 * hash + hideFlag;
hash = 61 * hash + (isUnbreakable() ? 1231 : 1237); hash = 61 * hash + (isUnbreakable() ? 1231 : 1237);
hash = 61 * hash + (hasDamage() ? this.damage : 0); hash = 61 * hash + (hasDamage() ? this.damage : 0);
hash = 61 * hash + (hasAttributeModifiers() ? this.attributeModifiers.hashCode() : 0);
return hash; return hash;
} }
@ -801,6 +1017,9 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable {
if (this.enchantments != null) { if (this.enchantments != null) {
clone.enchantments = new HashMap<Enchantment, Integer>(this.enchantments); clone.enchantments = new HashMap<Enchantment, Integer>(this.enchantments);
} }
if (this.hasAttributeModifiers()) {
clone.attributeModifiers = HashMultimap.create(this.attributeModifiers);
}
clone.hideFlag = this.hideFlag; clone.hideFlag = this.hideFlag;
clone.unbreakable = this.unbreakable; clone.unbreakable = this.unbreakable;
clone.damage = this.damage; clone.damage = this.damage;
@ -831,6 +1050,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable {
} }
serializeEnchantments(enchantments, builder, ENCHANTMENTS); serializeEnchantments(enchantments, builder, ENCHANTMENTS);
serializeModifiers(attributeModifiers, builder, ATTRIBUTES);
if (hasRepairCost()) { if (hasRepairCost()) {
builder.put(REPAIR.BUKKIT, repairCost); builder.put(REPAIR.BUKKIT, repairCost);
@ -891,6 +1111,25 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable {
builder.put(key.BUKKIT, enchants.build()); builder.put(key.BUKKIT, enchants.build());
} }
static void serializeModifiers(Multimap<Attribute, AttributeModifier> modifiers, ImmutableMap.Builder<String, Object> builder, ItemMetaKey key) {
if (modifiers == null || modifiers.isEmpty()) {
return;
}
Map<String, List<Object>> mods = new HashMap<>();
for (Map.Entry<Attribute, AttributeModifier> entry : modifiers.entries()) {
if (entry.getKey() == null) {
continue;
}
Collection<AttributeModifier> modCollection = modifiers.get(entry.getKey());
if (modCollection == null || modCollection.isEmpty()) {
continue;
}
mods.put(entry.getKey().name(), new ArrayList<>(modCollection));
}
builder.put(key.BUKKIT, mods);
}
static void safelyAdd(Iterable<?> addFrom, Collection<String> addTo, int maxItemLength) { static void safelyAdd(Iterable<?> addFrom, Collection<String> addTo, int maxItemLength) {
if (addFrom == null) { if (addFrom == null) {
return; return;
@ -944,6 +1183,13 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable {
HIDEFLAGS.NBT, HIDEFLAGS.NBT,
UNBREAKABLE.NBT, UNBREAKABLE.NBT,
DAMAGE.NBT, DAMAGE.NBT,
ATTRIBUTES.NBT,
ATTRIBUTES_IDENTIFIER.NBT,
ATTRIBUTES_NAME.NBT,
ATTRIBUTES_VALUE.NBT,
ATTRIBUTES_UUID_HIGH.NBT,
ATTRIBUTES_UUID_LOW.NBT,
ATTRIBUTES_SLOT.NBT,
CraftMetaMap.MAP_SCALING.NBT, CraftMetaMap.MAP_SCALING.NBT,
CraftMetaMap.MAP_ID.NBT, CraftMetaMap.MAP_ID.NBT,
CraftMetaPotion.POTION_EFFECTS.NBT, CraftMetaPotion.POTION_EFFECTS.NBT,