package org.bukkit.craftbukkit.inventory; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import net.minecraft.server.NBTTagCompound; import net.minecraft.server.NBTTagInt; import net.minecraft.server.NBTTagList; import org.apache.commons.lang3.Validate; import org.bukkit.Color; import org.bukkit.Material; import org.bukkit.configuration.serialization.DelegateDeserialization; import org.bukkit.inventory.meta.PotionMeta; import org.bukkit.potion.PotionData; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionType; import org.bukkit.craftbukkit.inventory.CraftMetaItem.SerializableMeta; import org.bukkit.craftbukkit.potion.CraftPotionUtil; import org.bukkit.craftbukkit.util.CraftMagicNumbers; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap.Builder; @DelegateDeserialization(SerializableMeta.class) class CraftMetaPotion extends CraftMetaItem implements PotionMeta { static final ItemMetaKey AMPLIFIER = new ItemMetaKey("Amplifier", "amplifier"); static final ItemMetaKey AMBIENT = new ItemMetaKey("Ambient", "ambient"); static final ItemMetaKey DURATION = new ItemMetaKey("Duration", "duration"); static final ItemMetaKey SHOW_PARTICLES = new ItemMetaKey("ShowParticles", "has-particles"); static final ItemMetaKey POTION_EFFECTS = new ItemMetaKey("CustomPotionEffects", "custom-effects"); static final ItemMetaKey POTION_COLOR = new ItemMetaKey("CustomPotionColor", "custom-color"); static final ItemMetaKey ID = new ItemMetaKey("Id", "potion-id"); static final ItemMetaKey DEFAULT_POTION = new ItemMetaKey("Potion", "potion-type"); // Having an initial "state" in ItemMeta seems bit dirty but the UNCRAFTABLE potion type // is treated as the empty form of the meta because it represents an empty potion with no effect private PotionData type = new PotionData(PotionType.UNCRAFTABLE, false, false); private List<PotionEffect> customEffects; private Color color; CraftMetaPotion(CraftMetaItem meta) { super(meta); if (!(meta instanceof CraftMetaPotion)) { return; } CraftMetaPotion potionMeta = (CraftMetaPotion) meta; this.type = potionMeta.type; this.color = potionMeta.color; if (potionMeta.hasCustomEffects()) { this.customEffects = new ArrayList<PotionEffect>(potionMeta.customEffects); } } CraftMetaPotion(NBTTagCompound tag) { super(tag); if (tag.hasKey(DEFAULT_POTION.NBT)) { type = CraftPotionUtil.toBukkit(tag.getString(DEFAULT_POTION.NBT)); } if (tag.hasKey(POTION_COLOR.NBT)) { color = Color.fromRGB(tag.getInt(POTION_COLOR.NBT)); } if (tag.hasKey(POTION_EFFECTS.NBT)) { NBTTagList list = tag.getList(POTION_EFFECTS.NBT, CraftMagicNumbers.NBT.TAG_COMPOUND); int length = list.size(); customEffects = new ArrayList<PotionEffect>(length); for (int i = 0; i < length; i++) { NBTTagCompound effect = list.get(i); PotionEffectType type = PotionEffectType.getById(effect.getByte(ID.NBT)); int amp = effect.getByte(AMPLIFIER.NBT); int duration = effect.getInt(DURATION.NBT); boolean ambient = effect.getBoolean(AMBIENT.NBT); boolean particles = effect.getBoolean(SHOW_PARTICLES.NBT); customEffects.add(new PotionEffect(type, duration, amp, ambient, particles)); } } } CraftMetaPotion(Map<String, Object> map) { super(map); type = CraftPotionUtil.toBukkit(SerializableMeta.getString(map, DEFAULT_POTION.BUKKIT, true)); Color color = SerializableMeta.getObject(Color.class, map, POTION_COLOR.BUKKIT, true); if (color != null) { setColor(color); } Iterable<?> rawEffectList = SerializableMeta.getObject(Iterable.class, map, POTION_EFFECTS.BUKKIT, true); if (rawEffectList == null) { return; } for (Object obj : rawEffectList) { if (!(obj instanceof PotionEffect)) { throw new IllegalArgumentException("Object in effect list is not valid. " + obj.getClass()); } addCustomEffect((PotionEffect) obj, true); } } @Override void applyToItem(NBTTagCompound tag) { super.applyToItem(tag); tag.setString(DEFAULT_POTION.NBT, CraftPotionUtil.fromBukkit(type)); if (hasColor()) { tag.setInt(POTION_COLOR.NBT, color.asRGB()); } if (customEffects != null) { NBTTagList effectList = new NBTTagList(); tag.set(POTION_EFFECTS.NBT, effectList); for (PotionEffect effect : customEffects) { NBTTagCompound effectData = new NBTTagCompound(); effectData.setByte(ID.NBT, (byte) effect.getType().getId()); effectData.setByte(AMPLIFIER.NBT, (byte) effect.getAmplifier()); effectData.setInt(DURATION.NBT, effect.getDuration()); effectData.setBoolean(AMBIENT.NBT, effect.isAmbient()); effectData.setBoolean(SHOW_PARTICLES.NBT, effect.hasParticles()); effectList.add(effectData); } } } @Override boolean isEmpty() { return super.isEmpty() && isPotionEmpty(); } boolean isPotionEmpty() { return (type.getType() == PotionType.UNCRAFTABLE) && !(hasCustomEffects() || hasColor()); } @Override boolean applicableTo(Material type) { switch(type) { case POTION: case SPLASH_POTION: case LINGERING_POTION: case TIPPED_ARROW: return true; default: return false; } } @Override public CraftMetaPotion clone() { CraftMetaPotion clone = (CraftMetaPotion) super.clone(); clone.type = type; if (this.customEffects != null) { clone.customEffects = new ArrayList<PotionEffect>(this.customEffects); } return clone; } @Override public void setBasePotionData(PotionData data) { Validate.notNull(data, "PotionData cannot be null"); this.type = data; } @Override public PotionData getBasePotionData() { return type; } @Override public boolean hasCustomEffects() { return customEffects != null; } @Override public List<PotionEffect> getCustomEffects() { if (hasCustomEffects()) { return ImmutableList.copyOf(customEffects); } return ImmutableList.of(); } @Override public boolean addCustomEffect(PotionEffect effect, boolean overwrite) { Validate.notNull(effect, "Potion effect must not be null"); int index = indexOfEffect(effect.getType()); if (index != -1) { if (overwrite) { PotionEffect old = customEffects.get(index); if (old.getAmplifier() == effect.getAmplifier() && old.getDuration() == effect.getDuration() && old.isAmbient() == effect.isAmbient()) { return false; } customEffects.set(index, effect); return true; } else { return false; } } else { if (customEffects == null) { customEffects = new ArrayList<PotionEffect>(); } customEffects.add(effect); return true; } } @Override public boolean removeCustomEffect(PotionEffectType type) { Validate.notNull(type, "Potion effect type must not be null"); if (!hasCustomEffects()) { return false; } boolean changed = false; Iterator<PotionEffect> iterator = customEffects.iterator(); while (iterator.hasNext()) { PotionEffect effect = iterator.next(); if (type.equals(effect.getType())) { iterator.remove(); changed = true; } } if (customEffects.isEmpty()) { customEffects = null; } return changed; } @Override public boolean hasCustomEffect(PotionEffectType type) { Validate.notNull(type, "Potion effect type must not be null"); return indexOfEffect(type) != -1; } @Override public boolean setMainEffect(PotionEffectType type) { Validate.notNull(type, "Potion effect type must not be null"); int index = indexOfEffect(type); if (index == -1 || index == 0) { return false; } PotionEffect old = customEffects.get(0); customEffects.set(0, customEffects.get(index)); customEffects.set(index, old); return true; } private int indexOfEffect(PotionEffectType type) { if (!hasCustomEffects()) { return -1; } for (int i = 0; i < customEffects.size(); i++) { if (customEffects.get(i).getType().equals(type)) { return i; } } return -1; } @Override public boolean clearCustomEffects() { boolean changed = hasCustomEffects(); customEffects = null; return changed; } @Override public boolean hasColor() { return color != null; } @Override public Color getColor() { return color; } @Override public void setColor(Color color) { this.color = color; } @Override int applyHash() { final int original; int hash = original = super.applyHash(); if (type.getType() != PotionType.UNCRAFTABLE) { hash = 73 * hash + type.hashCode(); } if (hasColor()) { hash = 73 * hash + color.hashCode(); } if (hasCustomEffects()) { hash = 73 * hash + customEffects.hashCode(); } return original != hash ? CraftMetaPotion.class.hashCode() ^ hash : hash; } @Override public boolean equalsCommon(CraftMetaItem meta) { if (!super.equalsCommon(meta)) { return false; } if (meta instanceof CraftMetaPotion) { CraftMetaPotion that = (CraftMetaPotion) meta; return type.equals(that.type) && (this.hasCustomEffects() ? that.hasCustomEffects() && this.customEffects.equals(that.customEffects) : !that.hasCustomEffects()) && (this.hasColor() ? that.hasColor() && this.color.equals(that.color) : !that.hasColor()); } return true; } @Override boolean notUncommon(CraftMetaItem meta) { return super.notUncommon(meta) && (meta instanceof CraftMetaPotion || isPotionEmpty()); } @Override Builder<String, Object> serialize(Builder<String, Object> builder) { super.serialize(builder); if (type.getType() != PotionType.UNCRAFTABLE) { builder.put(DEFAULT_POTION.BUKKIT, CraftPotionUtil.fromBukkit(type)); } if (hasColor()) { builder.put(POTION_COLOR.BUKKIT, getColor()); } if (hasCustomEffects()) { builder.put(POTION_EFFECTS.BUKKIT, ImmutableList.copyOf(this.customEffects)); } return builder; } }