/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.royaldev.royalcommands.spawninfo; import com.comphenix.attribute.AttributeStorage; import com.comphenix.attribute.Attributes; import com.comphenix.attribute.NbtFactory; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import java.io.Serializable; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; /** * A class used in item-spawning to determine and store information about the spawn status of an item. * <br/> * This class is {@link Serializable}, but it can also be stored as a String, as seen here: * {@link org.royaldev.royalcommands.spawninfo.SpawnInfo#toString()}. * * @author jkcclemens * @since 3.2.1 */ public class SpawnInfo implements Serializable { private static final long serialVersionUID = 3232013L; /** * List of <em>spawned</em> components used in the creation of this item. */ private final List<String> components = new ArrayList<>(); /** * Name of player that spawned this item. If item is not spawned, this should be null. If it has no spawner, this * should be null. */ private String spawner; /** * Whether the item is spawned or not. */ private boolean spawned; /** * Whether the item was made with spawned components. */ private boolean hasComponents; /** * Constructs a SpawnInfo object assuming that the item was not spawned. */ public SpawnInfo() { this.spawner = null; this.spawned = false; this.hasComponents = false; } /** * Constructs a SpawnInfo object from a stored String. * * @param stored String to restore SpawnInfo from * @throws IllegalArgumentException If <code>stored</code> is null or invalid * @see org.royaldev.royalcommands.spawninfo.SpawnInfo#toString() */ public SpawnInfo(final String stored) { if (stored == null) throw new IllegalArgumentException("String cannot be null!"); final String[] splitStored = stored.split("/"); if (splitStored.length < 4) throw new IllegalArgumentException("Invalid stored string!"); this.spawner = splitStored[1]; this.spawned = "true".equalsIgnoreCase(splitStored[0]); this.hasComponents = "true".equalsIgnoreCase(splitStored[2]); if (splitStored[3].startsWith("[") && splitStored[3].endsWith("]")) { this.components.addAll(Arrays.asList(splitStored[3].substring(1, splitStored[3].length() - 1).split(", "))); } } /** * Constructs a SpawnInfo object, assigning values to "spawner" and "spawned." * * @param spawner Name of player that spawned the item * @param spawned If the item is spawned */ public SpawnInfo(final String spawner, final boolean spawned) { this.spawner = spawner; this.spawned = spawned; this.hasComponents = false; } /** * Constructs a SpawnInfo object, assigning default values. * * @param spawner Name of player that spawned item * @param spawned If the item is spawned * @param hasComponents If the item was made with spawned items * @param components The spawned items the item was made with */ public SpawnInfo(final String spawner, final boolean spawned, boolean hasComponents, Collection<String> components) { this.spawner = spawner; this.spawned = spawned; this.hasComponents = hasComponents; this.components.addAll(components); } /** * Gets the <em>spawned</em> components used to make this item. If there are none, an empty list will be returned. * Never returns null. * * @return List of spawned components; never null */ public List<String> getComponents() { return components; } /** * Gets the name of the player that spawned this item. <strong>May be null</strong> if the item was not spawned or * has no spawner. * * @return String or null */ public String getSpawner() { return spawner; } /** * Sets the name of the player that spawned this item. Use null if the item was not spawned or has no spawner. * * @param spawner Name of player or null */ public void setSpawner(String spawner) { this.spawner = spawner; } /** * Returns if the item was made with spawned components. * * @return boolean */ public boolean hasComponents() { return hasComponents; } /** * Returns if the item is spawned. * * @return boolean */ public boolean isSpawned() { return spawned; } /** * Sets if the item is spawned. * * @param spawned true if it was spawned, false if not */ public void setSpawned(boolean spawned) { this.spawned = spawned; } /** * Sets if the item was made with spawned components. * * @param hasComponents true if made with spawned components, false if not */ public void setHasComponents(boolean hasComponents) { this.hasComponents = hasComponents; } /** * Gets the SpawnInfo object as a String. This String can be used to reconstruct the same SpawnInfo object. * * @return String representing SpawnInfo object * @see SpawnInfo#SpawnInfo(String) */ @Override public String toString() { return String.valueOf(this.spawned) + "/" + this.spawner + "/" + this.hasComponents + "/" + this.components.toString(); } public static final class SpawnInfoManager { private static final UUID uuid = UUID.fromString("553ade7d-86cd-469e-a4ff-c6fbb564d961"); private static final UUID defaultUUID = UUID.fromString("4f0925a7-abf4-4a61-8c81-2028d453ff92"); private static final Map<Material, Attributes.Attribute> defaults = new HashMap<>(); static { try { defaults.put(Material.DIAMOND_AXE, createAttribute(6D, defaultUUID)); defaults.put(Material.DIAMOND_PICKAXE, createAttribute(5D, defaultUUID)); defaults.put(Material.DIAMOND_SPADE, createAttribute(4D, defaultUUID)); defaults.put(Material.DIAMOND_SWORD, createAttribute(7D, defaultUUID)); defaults.put(Material.GOLD_AXE, createAttribute(3D, defaultUUID)); defaults.put(Material.GOLD_PICKAXE, createAttribute(2D, defaultUUID)); defaults.put(Material.GOLD_SPADE, createAttribute(1D, defaultUUID)); defaults.put(Material.GOLD_SWORD, createAttribute(4D, defaultUUID)); defaults.put(Material.IRON_AXE, createAttribute(5D, defaultUUID)); defaults.put(Material.IRON_PICKAXE, createAttribute(4D, defaultUUID)); defaults.put(Material.IRON_SPADE, createAttribute(3D, defaultUUID)); defaults.put(Material.IRON_SWORD, createAttribute(6D, defaultUUID)); defaults.put(Material.STONE_AXE, createAttribute(4D, defaultUUID)); defaults.put(Material.STONE_PICKAXE, createAttribute(3D, defaultUUID)); defaults.put(Material.STONE_SPADE, createAttribute(2D, defaultUUID)); defaults.put(Material.STONE_SWORD, createAttribute(5D, defaultUUID)); defaults.put(Material.WOOD_AXE, createAttribute(3D, defaultUUID)); defaults.put(Material.WOOD_PICKAXE, createAttribute(2D, defaultUUID)); defaults.put(Material.WOOD_SPADE, createAttribute(1D, defaultUUID)); defaults.put(Material.WOOD_SWORD, createAttribute(4D, defaultUUID)); } catch (Exception ex) { ex.printStackTrace(); // wtf is going on } } /** * Applies the default attributes on items (e.g. Diamond Sword: +7 attack damage). * * @param is ItemStack to apply default attributes to * @return ItemStack with default attributes applied */ public static ItemStack applyDefaultAttributes(ItemStack is) { if (!defaults.containsKey(is.getType())) return is; final Attributes attr = new Attributes(is); attr.add(defaults.get(is.getType())); return attr.getStack(); } /** * Applies spawn information to an ItemStack. * <br/> * <strong>Note:</strong> ItemStacks containing AIR (ID: 0) will not have information applied, but will still * return the ItemStack. * * @param is ItemStack to apply information to * @param si SpawnInfo to apply * @return ItemStack with SpawnInfo applied; never null */ public static ItemStack applySpawnInfo(ItemStack is, SpawnInfo si) { return applySpawnInfo(is, si.toString()); } /** * Applies spawn information to an ItemStack. * <br/> * <strong>Note:</strong> ItemStacks containing AIR (ID: 0) will not have information applied, but will still * return the ItemStack. * * @param is ItemStack to apply information to * @param s SpawnInfo to apply (String form) * @return ItemStack with SpawnInfo applied; never null */ public static ItemStack applySpawnInfo(ItemStack is, String s) { if (is.getType() == Material.AIR) return is; // air; do not apply is = applyDefaultAttributes(is); final AttributeStorage as = AttributeStorage.newTarget(is, uuid); as.setData(s); return as.getTarget(); } private static Attributes.Attribute createAttribute(double amount, UUID uuid) { return Attributes.Attribute.newBuilder().name("default").type(Attributes.AttributeType.GENERIC_ATTACK_DAMAGE).operation(Attributes.Operation.ADD_NUMBER).amount(amount).uuid(uuid).build(); } /** * Retrieve the data stored in the item's attribute. * * @return The stored data, or defaultValue if not found. */ public static String getData(AttributeStorage as) { Attributes attr = new Attributes(as.getTarget()); try { final Method getAttribute = AttributeStorage.class.getDeclaredMethod("getAttribute", Attributes.class, UUID.class); final Field uniqueKey = AttributeStorage.class.getDeclaredField("uniqueKey"); getAttribute.setAccessible(true); uniqueKey.setAccessible(true); final Attributes.Attribute current = (Attributes.Attribute) getAttribute.invoke(as, attr, uniqueKey.get(as)); SpawnInfoManager.removeTagIfEmpty(attr); return current != null ? current.getName() : null; } catch (ReflectiveOperationException ex) { ex.printStackTrace(); } return null; } /** * Gets SpawnInfo from an ItemStack. If the ItemStack has no SpawnInfo attached, a default SpawnInfo will be * returned. * <br/> * <strong>Note:</strong> ItemStacks containing AIR (ID: 0) will always return a blank SpawnInfo, since they * cannot store NBT data. * * @param is ItemStack to obtain SpawnInfo from * @return SpawnInfo; never null */ public static SpawnInfo getSpawnInfo(ItemStack is) { if (is.getType() == Material.AIR) return new SpawnInfo(); // air cannot contain NBT data final AttributeStorage as = AttributeStorage.newTarget(is, uuid); String stored = SpawnInfoManager.getData(as); if (stored == null) stored = "false/null/false/null"; return new SpawnInfo(stored); } /** * Removes the data stored in the attributes. */ public static void removeData(AttributeStorage as) { final Attributes attributes = new Attributes(as.getTarget()); try { final Method getAttribute = AttributeStorage.class.getDeclaredMethod("getAttribute", Attributes.class, UUID.class); final Field uniqueKey = AttributeStorage.class.getDeclaredField("uniqueKey"); getAttribute.setAccessible(true); uniqueKey.setAccessible(true); final Attributes.Attribute current = (Attributes.Attribute) getAttribute.invoke(as, attributes, uniqueKey.get(as)); if (current == null) return; attributes.remove(current); SpawnInfoManager.removeTagIfEmpty(attributes); final Field target = AttributeStorage.class.getDeclaredField("target"); target.set(as, attributes.getStack()); } catch (ReflectiveOperationException ex) { ex.printStackTrace(); } } /** * Removes the attributes applied in * {@link SpawnInfo.SpawnInfoManager#applyDefaultAttributes(org.bukkit.inventory.ItemStack)}. * * @param is ItemStack to remove default attributes from * @return ItemStack with default attributes removed */ public static ItemStack removeDefaultAttributes(ItemStack is) { final Attributes attr = new Attributes(is); for (Attributes.Attribute a : attr.values()) { if (!a.getUUID().equals(defaultUUID)) continue; attr.remove(a); } return attr.getStack(); } /** * Removes all SpawnInfo from an ItemStack, leaving it as it was prior to SpawnInfo application. * * @param is ItemStack to remove SpawnInfo from * @return ItemStack without SpawnInfo */ public static ItemStack removeSpawnInfo(ItemStack is) { if (is.getType() == Material.AIR) return is; // silly air is = SpawnInfoManager.removeDefaultAttributes(is); final AttributeStorage as = AttributeStorage.newTarget(is, uuid); SpawnInfoManager.removeData(as); return as.getTarget(); } public static void removeTagIfEmpty(Attributes a) { if (a.size() <= 0) return; NbtFactory.NbtCompound nbt = NbtFactory.fromItemTag(a.stack); nbt.remove("AttributeModifiers"); if (nbt.isEmpty()) NbtFactory.setItemTag(a.stack, null); // FIXME: null } } }