All Downloads are FREE. Search and download functionalities are using the official Maven repository.

think.rpgitems.item.RPGItem Maven / Gradle / Ivy

There is a newer version: 3.12.2
Show newest version
package think.rpgitems.item;

import cat.nyaa.nyaacore.Message;
import cat.nyaa.nyaacore.Pair;
import cat.nyaa.nyaacore.utils.ItemStackUtils;
import cat.nyaa.nyaacore.utils.ItemTagUtils;
import com.google.common.base.Strings;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Multimap;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.*;
import org.bukkit.attribute.Attribute;
import org.bukkit.block.Block;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.MemoryConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.Damageable;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.LeatherArmorMeta;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import think.rpgitems.AdminCommands;
import think.rpgitems.I18n;
import think.rpgitems.RPGItems;
import think.rpgitems.data.Context;
import think.rpgitems.event.LoreUpdateEvent;
import think.rpgitems.power.*;
import think.rpgitems.power.cond.SlotCondition;
import think.rpgitems.power.marker.*;
import think.rpgitems.power.propertymodifier.Modifier;
import think.rpgitems.power.propertymodifier.RgiParameter;
import think.rpgitems.power.proxy.Interceptor;
import think.rpgitems.power.trigger.BaseTriggers;
import think.rpgitems.power.trigger.Trigger;
import think.rpgitems.utils.ColorHelper;
import think.rpgitems.utils.MaterialUtils;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.bukkit.ChatColor.COLOR_CHAR;
import static think.rpgitems.utils.ItemTagUtils.*;

public class RPGItem {
    @Deprecated
    public static final int MC_ENCODED_ID_LENGTH = 16;
    public static final NamespacedKey TAG_META = new NamespacedKey(RPGItems.plugin, "meta");
    public static final NamespacedKey TAG_ITEM_UID = new NamespacedKey(RPGItems.plugin, "item_uid");
    public static final NamespacedKey TAG_IS_MODEL = new NamespacedKey(RPGItems.plugin, "is_model");
    public static final NamespacedKey TAG_DURABILITY = new NamespacedKey(RPGItems.plugin, "durability");
    public static final NamespacedKey TAG_OWNER = new NamespacedKey(RPGItems.plugin, "owner");
    public static final NamespacedKey TAG_STACK_ID = new NamespacedKey(RPGItems.plugin, "stack_id");
    public static final NamespacedKey TAG_MODIFIER = new NamespacedKey(RPGItems.plugin, "property_modifier");
    public static final NamespacedKey TAG_VERSION = new NamespacedKey(RPGItems.plugin, "version");
    public static final String DAMAGE_TYPE = "RGI_DAMAGE_TYPE";
    public static final String NBT_UID = "rpgitem_uid";
    public static final String NBT_ITEM_UUID = "rpgitem_item_uuid";
    public static final String NBT_IS_MODEL = "rpgitem_is_model";

    private static final Cache> modifierCache = CacheBuilder.newBuilder().concurrencyLevel(1).expireAfterAccess(1, TimeUnit.MINUTES).build();

    static RPGItems plugin;
    private boolean ignoreWorldGuard = false;
    private List description = new ArrayList<>();
    private boolean showPowerText = true;
    private boolean showArmourLore = true;
    private Map enchantMap = null;
    private List itemFlags = new ArrayList<>();
    private boolean customItemModel = false;
    private EnchantMode enchantMode = EnchantMode.DISALLOW;

    // Powers
    private List powers = new ArrayList<>();
    private List> conditions = new ArrayList<>();
    private List markers = new ArrayList<>();
    private Map placeholders= new HashMap<>();
    @SuppressWarnings("rawtypes")
    private Map triggers = new HashMap<>();
    private HashMap keys = new HashMap<>();
    private File file;

    private NamespacedKey namespacedKey;
    private Material item;
    private int dataValue;
    private int id;
    private int uid;
    private String name;
    private boolean hasPermission;
    private String permission;
    private String displayName;
    private String displayNameColored;
    private int damageMin = 0;
    private int damageMax = 3;
    private DamageMode damageMode = DamageMode.FIXED;
    private AttributeMode attributeMode = AttributeMode.PARTIAL_UPDATE;
    private int armour = 0;
    private String armourExpression = "";
    private String damageType = "";
    private boolean canBeOwned = false;
    private boolean hasStackId = false;
    private boolean alwaysAllowMelee = false;

    private String author = plugin.cfg.defaultAuthor;
    private String note = plugin.cfg.defaultNote;
    private String license = plugin.cfg.defaultLicense;

    private int tooltipWidth = 150;
    // Durability
    private int maxDurability = -1;
    private boolean hasDurabilityBar = plugin.cfg.forceBar;
    private int defaultDurability;
    private int durabilityLowerBound;
    private int durabilityUpperBound;
    private BarFormat barFormat = BarFormat.DEFAULT;

    private int blockBreakingCost = 0;
    private int hittingCost = 0;
    private int hitCost = 0;
    private boolean hitCostByDamage = false;
    private String mcVersion;
    private int pluginVersion;
    private int pluginSerial;
    private List lore;
    private int customModelData;
    private boolean isTemplate;
    private Set templates = new HashSet<>();
    private Set templatePlaceholders = new HashSet<>();
    private String quality;
    private String type = "item";

    public RPGItem(String name, int uid, CommandSender author) {
        this.name = name;
        this.uid = uid;
        this.setAuthor(author instanceof Player ? ((Player) author).getUniqueId().toString() : plugin.cfg.defaultAuthor);
        setEnchantMode(plugin.cfg.defaultEnchantMode);
        setItem(Material.WOODEN_SWORD);
        setDisplayName(getItem().toString());
        getItemFlags().add(ItemFlag.HIDE_ATTRIBUTES);
        setMcVersion(RPGItems.getServerMCVersion());
        setPluginSerial(RPGItems.getSerial());
        setPluginVersion(RPGItems.getVersion());
        rebuild();
    }

    public RPGItem(ConfigurationSection s, File f) throws UnknownPowerException, UnknownExtensionException {
        setFile(f);
        name = s.getString("name");
        id = s.getInt("id");
        uid = s.getInt("uid");

        if (uid == 0) {
            uid = ItemManager.nextUid();
        }
        restore(s);
    }

    public RPGItem(ConfigurationSection s, String name, int uid) throws UnknownPowerException, UnknownExtensionException {
        if (uid >= 0) throw new IllegalArgumentException();
        this.name = name;
        this.uid = uid;
        restore(s);
    }

    public static void updateItemStack(ItemStack item) {
        Optional rItem = ItemManager.toRPGItem(item);
        rItem.ifPresent(r -> r.updateItem(item, false));
    }

    private void restore(ConfigurationSection s) throws UnknownPowerException, UnknownExtensionException {
        setAuthor(s.getString("author", ""));
        setNote(s.getString("note", ""));
        setLicense(s.getString("license", ""));
        setPluginVersion(s.getInt("pluginVersion", 0));
        setPluginSerial(s.getInt("pluginSerial", 0));
        setMcVersion(s.getString("mcVersion", ""));

        String display = s.getString("display");

        setDisplayName(display);
        List desc = s.getStringList("description");
        desc.replaceAll(ColorHelper::parseColor);
        setDescription(desc);
        setDamageMin(s.getInt("damageMin"));
        setDamageMax(s.getInt("damageMax"));
        setArmour(s.getInt("armour", 0), false);
        setArmourExpression(s.getString("armourExpression", ""));
        setDamageType(s.getString("DamageType", ""));
        setAttributeMode(AttributeMode.valueOf(s.getString("attributemode", "PARTIAL_UPDATE")));
        String materialName = s.getString("item");
        setItem(MaterialUtils.getMaterial(materialName, Bukkit.getConsoleSender()));
        ItemMeta itemMeta = Bukkit.getItemFactory().getItemMeta(getItem());
        if (itemMeta instanceof LeatherArmorMeta) {
            setDataValue(s.getInt("item_colour"));
        } else if (itemMeta instanceof Damageable) {
            setDataValue(s.getInt("item_data"));
        }
        setIgnoreWorldGuard(s.getBoolean("ignoreWorldGuard", false));
        setCanBeOwned(s.getBoolean("canBeOwned", false));
        setHasStackId(s.getBoolean("hasStackId", false));
        placeholders.clear();
        // Powers
        ConfigurationSection powerList = s.getConfigurationSection("powers");
        if (powerList != null) {
            for (String sectionKey : powerList.getKeys(false)) {
                ConfigurationSection section = powerList.getConfigurationSection(sectionKey);
                String powerName = Objects.requireNonNull(section).getString("powerName");
                // 3.7 -> 3.8 Migration
                if (Objects.requireNonNull(powerName).endsWith("condition")) {
                    loadCondition(section, powerName);
                } else if (Stream.of("attributemodifier", "lorefilter", "ranged", "rangedonly", "selector", "unbreakable").anyMatch(powerName::endsWith)) {
                    loadMarker(section, powerName);
                } else {
                    loadPower(section, powerName);
                }
            }
        }
        // Conditions
        ConfigurationSection conditionList = s.getConfigurationSection("conditions");
        if (conditionList != null) {
            for (String sectionKey : conditionList.getKeys(false)) {
                ConfigurationSection section = Objects.requireNonNull(conditionList).getConfigurationSection(sectionKey);
                String conditionName = Objects.requireNonNull(Objects.requireNonNull(section).getString("conditionName"));
                loadCondition(section, conditionName);
            }
        }
        // Markers
        ConfigurationSection markerList = s.getConfigurationSection("markers");
        if (markerList != null) {
            for (String sectionKey : markerList.getKeys(false)) {
                ConfigurationSection section = Objects.requireNonNull(markerList).getConfigurationSection(sectionKey);
                String markerName = Objects.requireNonNull(Objects.requireNonNull(section).getString("markerName"));
                loadMarker(section, markerName);
            }
        }
        // Triggers
        ConfigurationSection triggerList = s.getConfigurationSection("triggers");
        if (triggerList != null) {
            for (String sectionKey : triggerList.getKeys(false)) {
                ConfigurationSection section = Objects.requireNonNull(triggerList).getConfigurationSection(sectionKey);
                loadTrigger(section, sectionKey);
            }
        }

        Map> duplicatePlaceholderIds = checkDuplicatePlaceholderIds();
        if (!duplicatePlaceholderIds.isEmpty()){
            Logger logger = RPGItems.plugin.getLogger();
            String duplicateMsg = getDuplicatePlaceholderMsg(duplicatePlaceholderIds);
            logger.log(Level.WARNING, duplicateMsg);
        }
        setHasPermission(s.getBoolean("haspermission", false));
        setPermission(s.getString("permission", "rpgitem.item." + name));
        setCustomItemModel(s.getBoolean("customItemModel", false));

        setHitCost(s.getInt("hitCost", 1));
        setHittingCost(s.getInt("hittingCost", 1));
        setBlockBreakingCost(s.getInt("blockBreakingCost", 1));
        setHitCostByDamage(s.getBoolean("hitCostByDamage", false));
        setMaxDurability(s.getInt("maxDurability", getItem().getMaxDurability()));
        setDefaultDurability(s.getInt("defaultDurability", getMaxDurability()));
        if (getDefaultDurability() <= 0) {
            setDefaultDurability(getMaxDurability());
        }
        setDurabilityLowerBound(s.getInt("durabilityLowerBound", 0));
        setDurabilityUpperBound(s.getInt("durabilityUpperBound", getItem().getMaxDurability()));
        if (s.isBoolean("forceBar")) {
            setHasDurabilityBar(getItem().getMaxDurability() == 0 || s.getBoolean("forceBar") || isCustomItemModel());
        }
        setHasDurabilityBar(s.getBoolean("hasDurabilityBar", isHasDurabilityBar()));

        setShowPowerText(s.getBoolean("showPowerText", true));
        setShowArmourLore(s.getBoolean("showArmourLore", true));
        setCustomModelData(s.getInt("customModelData",  -1));
        setQuality(s.getString("quality", null));
        setType(s.getString("item", "item"));

        if (s.isConfigurationSection("enchantments")) {
            ConfigurationSection enchConf = s.getConfigurationSection("enchantments");
            setEnchantMap(new HashMap<>());
            for (String enchName : Objects.requireNonNull(enchConf).getKeys(false)) {
                Enchantment ench;
                try {
                    ench = Enchantment.getByKey(NamespacedKey.minecraft(enchName));
                } catch (IllegalArgumentException e) {
                    @SuppressWarnings("deprecation")
                    Enchantment old = Enchantment.getByName(enchName);
                    if (old == null) {
                        throw new IllegalArgumentException("Unknown enchantment " + enchName);
                    }
                    ench = old;
                }
                if (ench != null) {
                    getEnchantMap().put(ench, enchConf.getInt(enchName));
                }
            }
        }
        String enchantModeStr = s.getString("enchantMode", plugin.cfg.defaultEnchantMode.name());
        try {
            setEnchantMode(EnchantMode.valueOf(enchantModeStr));
        } catch (IllegalArgumentException e) {
            setEnchantMode(EnchantMode.DISALLOW);
        }
        setItemFlags(new ArrayList<>());
        if (s.isList("itemFlags")) {
            List flags = s.getStringList("itemFlags");
            for (String flagName : flags) {
                try {
                    getItemFlags().add(ItemFlag.valueOf(flagName));
                } catch (IllegalArgumentException e) {
                    plugin.getLogger().log(Level.WARNING, "Ignoring unknown item flags", e);
                }
            }
        }
        if (s.isBoolean("numericBar")) {
            setBarFormat(s.getBoolean("numericBar") ? BarFormat.NUMERIC : BarFormat.DEFAULT);
        }
        if (s.isString("barFormat")) {
            setBarFormat(BarFormat.valueOf(s.getString("barFormat")));
        }
        String damageModeStr = s.getString("damageMode", "FIXED");
        try {
            setDamageMode(DamageMode.valueOf(damageModeStr));
        } catch (IllegalArgumentException e) {
            setDamageMode(DamageMode.FIXED);
        }
        setAlwaysAllowMelee(s.getBoolean("alwaysAllowMelee", false));
        this.setIsTemplate(s.getBoolean("isTemplate", false));
        templates.clear();
        ConfigurationSection templatesList = s.getConfigurationSection("templates");
        if (templatesList != null) {
            for (String sectionKey : templatesList.getKeys(false)) {
                String tmp = (String) templatesList.get(sectionKey);
                templates.add(tmp);
            }
        }
        ConfigurationSection templatePlaceholdersList = s.getConfigurationSection("templatePlaceholders");
        if (templatePlaceholdersList != null) {
            for (String sectionKey : templatePlaceholdersList.getKeys(false)) {
                String tmp = (String) templatePlaceholdersList.get(sectionKey);
                templatePlaceholders.add(tmp);
            }
        }
        rebuild();
    }

    public String getDuplicatePlaceholderMsg(Map> duplicatePlaceholderIds) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("duplicate placeholder key found in item: ")
                .append(getName()).append("\n");
        duplicatePlaceholderIds.forEach((k,v)->{
            stringBuilder.append("key: ")
                    .append(k)
                    .append(", values: [");
            v.forEach((ph) ->{
                stringBuilder.append(ph.toString());
                stringBuilder.append(",");
            });
            stringBuilder.append("]");
        });
        String msg = stringBuilder.toString();
        return msg;
    }

    public int getCustomModelData() {
        return customModelData;
    }

    public void setCustomModelData(int customModelData) {
        this.customModelData = customModelData;
    }

    public void setArmourExpression(String armour) {
        this.armourExpression = armour;
    }

    private void loadPower(ConfigurationSection section, String powerName) throws UnknownPowerException {
        NamespacedKey key = PowerManager.parseKey(powerName);
        Class power = PowerManager.getPower(key);
        if (power == null) {
            plugin.getLogger().warning("Unknown power:" + key + " on item " + this.name);
            throw new UnknownPowerException(key);
        }
        Power pow = PowerManager.instantiate(power);
        pow.setItem(this);
        pow.init(section);
        addPower(key, pow, false);
    }

    private void loadCondition(ConfigurationSection section, String powerName) throws UnknownPowerException {
        NamespacedKey key = PowerManager.parseKey(powerName);
        Class> condition = PowerManager.getCondition(key);
        if (condition == null) {
            plugin.getLogger().warning("Unknown condition:" + key + " on item " + this.name);
            throw new UnknownPowerException(key);
        }
        Condition pow = PowerManager.instantiate(condition);
        pow.setItem(this);
        pow.init(section);
        addCondition(key, pow, false);
    }

    private void loadMarker(ConfigurationSection section, String powerName) throws UnknownPowerException {
        NamespacedKey key = PowerManager.parseKey(powerName);
        Class marker = PowerManager.getMarker(key);
        if (marker == null) {
            plugin.getLogger().warning("Unknown marker:" + key + " on item " + this.name);
            throw new UnknownPowerException(key);
        }
        Marker pow = PowerManager.instantiate(marker);
        pow.setItem(this);
        pow.init(section);
        addMarker(key, pow, false);
    }

    @SuppressWarnings("rawtypes")
    private void loadTrigger(ConfigurationSection section, String triggerName) throws UnknownPowerException {
        String baseTrigger = section.getString("base");
        if (baseTrigger == null) {
            throw new IllegalArgumentException();
        }
        Trigger base = Trigger.get(baseTrigger);
        if (base == null) {
            plugin.getLogger().warning("Unknown base trigger:" + baseTrigger + " on item " + this.name);
            throw new UnknownPowerException(new NamespacedKey(RPGItems.plugin, baseTrigger));
        }

        Trigger newTrigger = base.copy(triggerName);

        newTrigger.setItem(this);
        newTrigger.init(section);
        triggers.put(triggerName, newTrigger);
    }


    @SuppressWarnings("rawtypes")
    public void save(ConfigurationSection s) {
        s.set("name", name);
        if (id != 0) {
            s.set("id", id);
        }
        s.set("uid", uid);

        s.set("author", getAuthor());
        s.set("note", getNote());
        s.set("license", getLicense());

        s.set("mcVersion", getMcVersion());
        s.set("pluginSerial", getPluginSerial());

        s.set("haspermission", isHasPermission());
        s.set("permission", getPermission());
        s.set("display", getDisplayNameRaw().replaceAll("" + COLOR_CHAR, "&"));
        s.set("damageMin", getDamageMin());
        s.set("damageMax", getDamageMax());
        s.set("armour", getArmour());
        s.set("armourExpression", getArmourExpression());
        s.set("DamageType", getDamageType());
        s.set("attributemode", attributeMode.name());
        ArrayList descriptionConv = new ArrayList<>(getDescription());
        descriptionConv.replaceAll(s1 -> s1.replaceAll("" + COLOR_CHAR, "&"));
        s.set("description", descriptionConv);
        s.set("item", getItem().toString());
        s.set("ignoreWorldGuard", isIgnoreWorldGuard());
        s.set("canBeOwned", isCanBeOwned());
        s.set("hasStackId", isHasStackId());

        ItemMeta itemMeta = Bukkit.getItemFactory().getItemMeta(getItem());

        if (itemMeta instanceof LeatherArmorMeta) {
            s.set("item_colour", getDataValue());
        } else if (itemMeta instanceof Damageable) {
            s.set("item_data", getDataValue());
        }
        ConfigurationSection powerConfigs = s.createSection("powers");
        int i = 0;
        for (Power p : powers) {
            MemoryConfiguration pConfig = new MemoryConfiguration();
            pConfig.set("powerName", getPropertyHolderKey(p).toString());
            p.save(pConfig);
            powerConfigs.set(Integer.toString(i), pConfig);
            i++;
        }
        ConfigurationSection conditionConfigs = s.createSection("conditions");
        i = 0;
        for (Condition p : conditions) {
            MemoryConfiguration pConfig = new MemoryConfiguration();
            pConfig.set("conditionName", p.getNamespacedKey().toString());
            p.save(pConfig);
            conditionConfigs.set(Integer.toString(i), pConfig);
            i++;
        }
        ConfigurationSection markerConfigs = s.createSection("markers");
        i = 0;
        for (Marker p : markers) {
            MemoryConfiguration pConfig = new MemoryConfiguration();
            pConfig.set("markerName", p.getNamespacedKey().toString());
            p.save(pConfig);
            markerConfigs.set(Integer.toString(i), pConfig);
            i++;
        }
        ConfigurationSection triggerConfigs = s.createSection("triggers");
        for (Entry p : triggers.entrySet()) {
            MemoryConfiguration pConfig = new MemoryConfiguration();
            p.getValue().save(pConfig);
            pConfig.set("base", p.getValue().getBase());
            triggerConfigs.set(p.getKey(), pConfig);
            i++;
        }

        s.set("hitCost", getHitCost());
        s.set("hittingCost", getHittingCost());
        s.set("blockBreakingCost", getBlockBreakingCost());
        s.set("hitCostByDamage", isHitCostByDamage());
        s.set("maxDurability", getMaxDurability());
        s.set("defaultDurability", getDefaultDurability());
        s.set("durabilityLowerBound", getDurabilityLowerBound());
        s.set("durabilityUpperBound", getDurabilityUpperBound());
        s.set("hasDurabilityBar", isHasDurabilityBar());
        s.set("showPowerText", isShowPowerText());
        s.set("showArmourLore", isShowArmourLore());
        s.set("damageMode", getDamageMode().name());
        s.set("customModelData", getCustomModelData());

        Map enchantMap = getEnchantMap();
        if (enchantMap != null) {
            ConfigurationSection ench = s.createSection("enchantments");
            for (Enchantment e : enchantMap.keySet()) {
                ench.set(e.getKey().getKey(), enchantMap.get(e));
            }
        } else {
            s.set("enchantments", null);
        }
        s.set("enchantMode", enchantMode.name());
        List itemFlags = getItemFlags();
        if (!itemFlags.isEmpty()) {
            List tmp = new ArrayList<>();
            for (ItemFlag flag : itemFlags) {
                tmp.add(flag.name());
            }
            s.set("itemFlags", tmp);
        } else {
            s.set("itemFlags", null);
        }
        s.set("customItemModel", isCustomItemModel());
        s.set("barFormat", getBarFormat().name());
        s.set("alwaysAllowMelee", isAlwaysAllowMelee());

        s.set("isTemplate", isTemplate());
        s.set("quality", getQuality());
        s.set("type", getType());
        ConfigurationSection templatesConfigs = s.createSection("templates");
        Set templates = getTemplates();
        Iterator it = templates.iterator();
        for (i = 0; i < templates.size(); i++) {
            String next = it.next();
            templatesConfigs.set(String.valueOf(i), next);
        }
        ConfigurationSection templatePlaceHolderConfigs = s.createSection("templatePlaceholders");
        Set templatePlaceHolders = getTemplatePlaceHolders();
        Iterator it1 = templatePlaceHolders.iterator();
        for (i = 0; i < templatePlaceHolders.size(); i++) {
            String next = it1.next();
            templatePlaceHolderConfigs.set(String.valueOf(i), next);
        }
    }


    public String getDamageType() {
        return this.damageType;
    }

    public void setDamageType(String damageType) {
        this.damageType = damageType;
    }

    public String getArmourExpression() {
        return armourExpression;
    }

    public void updateItem(ItemStack item) {
        updateItem(item, false);
    }

    public void updateItem(ItemStack item, boolean loreOnly) {
        List oldLore = item == null || item.getItemMeta() == null || item.getItemMeta().getLore() == null ? new ArrayList<>() : new ArrayList<>(item.getItemMeta().getLore());
        List reservedLores = this.filterLores(item);
        item.setType(getItem());
        ItemMeta meta = item.getItemMeta();
        List lore = new ArrayList<>(getLore());

        PersistentDataContainer itemTagContainer = Objects.requireNonNull(meta).getPersistentDataContainer();
        SubItemTagContainer rpgitemsTagContainer = makeTag(itemTagContainer, TAG_META);
        set(rpgitemsTagContainer, TAG_ITEM_UID, getUid());
        addDurabilityBar(rpgitemsTagContainer, lore);
        if (meta instanceof LeatherArmorMeta) {
            ((LeatherArmorMeta) meta).setColor(Color.fromRGB(getDataValue()));
        }
        Damageable damageable = (Damageable) meta;
        if (getMaxDurability() > 0) {
            int durability = computeIfAbsent(rpgitemsTagContainer, TAG_DURABILITY, PersistentDataType.INTEGER, this::getDefaultDurability);
            if (isCustomItemModel()) {
                damageable.setDamage(getDataValue());
            } else {
                damageable.setDamage((getItem().getMaxDurability() - ((short) ((double) getItem().getMaxDurability() * ((double) durability / (double) getMaxDurability())))));
            }
        } else {
            if (isCustomItemModel()) {
                damageable.setDamage(getDataValue());
            } else {
                damageable.setDamage(getItem().getMaxDurability() != 0 ? 0 : getDataValue());
            }
        }
        // Patch for mcMMO buff. See SkillUtils.java#removeAbilityBuff in mcMMO
        if (oldLore.contains("mcMMO Ability Tool"))
            lore.add("mcMMO Ability Tool");

        lore.addAll(reservedLores);
        LoreUpdateEvent event = new LoreUpdateEvent(this, item, oldLore, lore);
        Bukkit.getPluginManager().callEvent(event);
        item = event.item;
        meta.setLore(event.newLore);

        //quality prefix
        String qualityPrefix = plugin.cfg.qualityPrefixes.get(getQuality());
        if (qualityPrefix != null){
            if (meta.hasDisplayName() && !meta.getDisplayName().startsWith(qualityPrefix)){
                String displayName = meta.getDisplayName();
                meta.setDisplayName(qualityPrefix + displayName);
            }
        }

        if (loreOnly) {
            rpgitemsTagContainer.commit();
            item.setItemMeta(meta);
            return;
        }

        meta.setUnbreakable(isCustomItemModel() || hasMarker(Unbreakable.class));
        meta.removeItemFlags(meta.getItemFlags().toArray(new ItemFlag[0]));

        if (getCustomModelData() != -1) {
            meta.setCustomModelData(customModelData);
        }else{
            meta.setCustomModelData(null);
        }
        for (ItemFlag flag : getItemFlags()) {
            meta.addItemFlags(flag);
        }
        if (getEnchantMode() == EnchantMode.DISALLOW) {
            Set enchs = meta.getEnchants().keySet();
            for (Enchantment e : enchs) {
                meta.removeEnchant(e);
            }
        }
        Map enchantMap = getEnchantMap();
        if (enchantMap != null) {
            for (Entry e : enchantMap.entrySet()) {
                meta.addEnchant(e.getKey(), Math.max(meta.getEnchantLevel(e.getKey()), e.getValue()), true);
            }
        }
        checkAndMakeUnique(rpgitemsTagContainer);
        rpgitemsTagContainer.commit();
        item.setItemMeta(refreshAttributeModifiers(meta));
        try {
            ItemTagUtils.setInt(item, NBT_UID, uid);
            if (RPGItems.plugin.cfg.itemStackUuid) {
                if (ItemTagUtils.getString(item, NBT_ITEM_UUID).isEmpty()) {
                    UUID uuid = UUID.randomUUID();
                    ItemTagUtils.setString(item, NBT_ITEM_UUID, uuid.toString());
                }
            }
            LoreUpdateEvent.Post post = new LoreUpdateEvent.Post(event, this, item);
            Bukkit.getPluginManager().callEvent(post);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    private final static NamespacedKey RGI_UNIQUE_MARK = new NamespacedKey(RPGItems.plugin, "RGI_UNIQUE_MARK");

    private final static NamespacedKey RGI_UNIQUE_ID = new NamespacedKey(RPGItems.plugin, "RGI_UNIQUE_ID");
    private void checkAndMakeUnique(SubItemTagContainer meta) {
        List markers = getMarker(Unique.class);
        List conditions = getConditions(SlotCondition.class);

        if (!markers.isEmpty() ) {
            Unique unique = markers.get(0);
            if(unique.enabled){
                if (!meta.has(RGI_UNIQUE_MARK, PersistentDataType.BYTE)) {
                    meta.set(RGI_UNIQUE_MARK, PersistentDataType.BYTE, (byte) 0);
                }
                meta.set(RGI_UNIQUE_ID, PersistentDataType.STRING, UUID.randomUUID().toString());
            }else {
                meta.remove(RGI_UNIQUE_MARK);
                meta.remove(RGI_UNIQUE_ID);
            }
        }
        if(!conditions.isEmpty()){
            if (!meta.has(RGI_UNIQUE_MARK, PersistentDataType.BYTE)) {
                meta.set(RGI_UNIQUE_MARK, PersistentDataType.BYTE, (byte) 0);
            }
            meta.set(RGI_UNIQUE_ID, PersistentDataType.STRING, UUID.randomUUID().toString());
        }
    }

    private void addDurabilityBar(PersistentDataContainer meta, List lore) {
        int maxDurability = getMaxDurability();
        if (maxDurability > 0) {
            int durability = computeIfAbsent(meta, TAG_DURABILITY, PersistentDataType.INTEGER, this::getDefaultDurability);
            if (isHasDurabilityBar()) {
                StringBuilder out = new StringBuilder();
                char boxChar = '\u25A0';
                double ratio = (double) durability / (double) maxDurability;
                BarFormat barFormat = getBarFormat();
                switch (barFormat) {
                    case NUMERIC_BIN, NUMERIC_BIN_MINUS_ONE, NUMERIC_HEX, NUMERIC_HEX_MINUS_ONE, NUMERIC, NUMERIC_MINUS_ONE -> {
                        out.append(ChatColor.GREEN).append(boxChar).append(" ");
                        out.append(ratio < 0.1 ? ChatColor.RED : ratio < 0.3 ? ChatColor.YELLOW : ChatColor.GREEN);
                        out.append(formatBar(durability, maxDurability, barFormat));
                        out.append(ChatColor.RESET).append(" / ").append(ChatColor.AQUA);
                        out.append(formatBar(maxDurability, maxDurability, barFormat));
                        out.append(ChatColor.GREEN).append(boxChar);
                    }
                    case DEFAULT -> {
                        int boxCount = tooltipWidth / 7;
                        int mid = (int) ((double) boxCount * (ratio));
                        for (int i = 0; i < boxCount; i++) {
                            out.append(i < mid ? ChatColor.GREEN : i == mid ? ChatColor.YELLOW : ChatColor.RED);
                            out.append(boxChar);
                        }
                    }
                }
                if (lore.isEmpty() || !lore.get(lore.size() - 1).contains(boxChar + ""))
                    lore.add(out.toString());
                else
                    lore.set(lore.size() - 1, out.toString());
            }
        }
    }

    private String formatBar(int durability, int maxDurability, BarFormat barFormat) {
        switch (barFormat) {
            case NUMERIC -> {
                return String.valueOf(durability);
            }
            case NUMERIC_MINUS_ONE -> {
                return String.valueOf(durability - 1);
            }
            case NUMERIC_HEX -> {
                int hexLen = String.format("%X", maxDurability).length();
                return String.format(String.format("0x%%0%dX", hexLen), durability);
            }
            case NUMERIC_HEX_MINUS_ONE -> {
                int hexLenM1 = String.format("%X", maxDurability - 1).length();
                return String.format(String.format("0x%%0%dX", hexLenM1), durability - 1);
            }
            case NUMERIC_BIN -> {
                int binLen = Integer.toBinaryString(maxDurability).length();
                return String.format(String.format("0b%%%ds", binLen), Integer.toBinaryString(durability)).replace(' ', '0');
            }
            case NUMERIC_BIN_MINUS_ONE -> {
                int binLenM1 = Integer.toBinaryString(maxDurability - 1).length();
                return String.format(String.format("0b%%%ds", binLenM1), Integer.toBinaryString(durability - 1)).replace(' ', '0');
            }
        }
        throw new UnsupportedOperationException();
    }

    private List filterLores(ItemStack i) {
        List ret = new ArrayList<>();
        List patterns = getMarker(LoreFilter.class).stream()
                                                               .filter(p -> !Strings.isNullOrEmpty(p.regex))
                                                               .map(LoreFilter::compile)
                                                               .toList();
        if (patterns.isEmpty()) return Collections.emptyList();
        if (!i.hasItemMeta() || !Objects.requireNonNull(i.getItemMeta()).hasLore()) return Collections.emptyList();
        for (String str : Objects.requireNonNull(i.getItemMeta().getLore())) {
            for (LoreFilter p : patterns) {
                Matcher matcher = p.pattern().matcher(ChatColor.stripColor(str));
                if (p.find ? matcher.find() : matcher.matches()) {
                    ret.add(str);
                    break;
                }
            }
        }

        return ret;
    }

    private ItemMeta refreshAttributeModifiers(ItemMeta itemMeta) {
        List attributeModifiers = getMarker(AttributeModifier.class);
        Multimap old = itemMeta.getAttributeModifiers();
        if (attributeMode.equals(AttributeMode.FULL_UPDATE)) {
            if (old != null && !old.isEmpty()) {
                old.forEach(itemMeta::removeAttributeModifier);
            }
        }
        if (!attributeModifiers.isEmpty()) {
            for (AttributeModifier attributeModifier : attributeModifiers) {
                Attribute attribute = attributeModifier.attribute;
                UUID uuid = new UUID(attributeModifier.uuidMost, attributeModifier.uuidLeast);
                org.bukkit.attribute.AttributeModifier modifier = new org.bukkit.attribute.AttributeModifier(
                        uuid,
                        attributeModifier.name,
                        attributeModifier.amount,
                        attributeModifier.operation,
                        attributeModifier.slot
                );
                if (old != null) {
                    old.entries().stream().filter(m -> m.getValue().getUniqueId().equals(uuid)).findAny().ifPresent(
                            e -> itemMeta.removeAttributeModifier(e.getKey(), e.getValue())
                    );
                }
                itemMeta.addAttributeModifier(attribute, modifier);
            }
        }
        return itemMeta;
    }

    public boolean canDoMeleeTo(ItemStack item, Entity entity) {
        if (hasMarker(RangedOnly.class)) {
            return false;
        }
        if (item.getType() == Material.BOW || item.getType() == Material.SNOWBALL || item.getType() == Material.EGG || item.getType() == Material.POTION) {
            return isAlwaysAllowMelee();
        }
        return true;
    }

    public boolean canDoProjectileTo(ItemStack item, double distance, Entity entity) {
        List ranged = getMarker(Ranged.class, true);
        if (!ranged.isEmpty()) {
            return !(ranged.get(0).rm > distance) && !(distance > ranged.get(0).r);
        }
        return true;
    }

    /**
     * Event-type independent melee damage event
     *
     * @param p            Player who launched the damager
     * @param originDamage Origin damage value
     * @param stack        ItemStack of this item
     * @param entity       Victim of this damage event
     * @return Final damage or -1 if should cancel this event
     */
    public double meleeDamage(Player p, double originDamage, ItemStack stack, Entity entity) {
        double damage = originDamage;
        if (!canDoMeleeTo(stack, entity) || ItemManager.canUse(p, this) == Event.Result.DENY) {
            return -1;
        }
        boolean can = consumeDurability(stack, getHittingCost());
        if (!can) {
            return -1;
        }
        switch (getDamageMode()) {
            case MULTIPLY:
            case FIXED:
            case ADDITIONAL:
                damage = getDamageMin() != getDamageMax() ? (getDamageMin() + ThreadLocalRandom.current().nextInt(getDamageMax() - getDamageMin() + 1)) : getDamageMin();

                if (getDamageMode() == DamageMode.MULTIPLY) {
                    damage *= originDamage;
                    break;
                }

                Collection potionEffects = p.getActivePotionEffects();
                double strength = 0, weak = 0;
                for (PotionEffect pe : potionEffects) {
                    if (pe.getType().equals(PotionEffectType.INCREASE_DAMAGE)) {
                        strength = 3 * (pe.getAmplifier() + 1);//MC 1.9+
                    }
                    if (pe.getType().equals(PotionEffectType.WEAKNESS)) {
                        weak = 4 * (pe.getAmplifier() + 1);//MC 1.9+
                    }
                }
                damage = damage + strength - weak;

                if (getDamageMode() == DamageMode.ADDITIONAL) {
                    damage += originDamage;
                }
                if (damage < 0) damage = 0;
                break;
            case VANILLA:
                //no-op
                break;
        }
        return damage;
    }

    /**
     * Event-type independent projectile damage event
     *
     * @param p            Player who launched the damager
     * @param originDamage Origin damage value
     * @param stack        ItemStack of this item
     * @param damager      Projectile of this damage event
     * @param entity       Victim of this damage event
     * @return Final damage or -1 if should cancel this event
     */
    public double projectileDamage(Player p, double originDamage, ItemStack stack, Entity damager, Entity entity) {
        double damage = originDamage;
        if (ItemManager.canUse(p, this) == Event.Result.DENY) {
            return -1;
        }

        double distance = p.getLocation().distance(entity.getLocation());
        if (!canDoProjectileTo(stack, distance, entity)) {
            return -1;
        }

        switch (getDamageMode()) {
            case FIXED:
            case ADDITIONAL:
            case MULTIPLY:
                damage = getDamageMin() != getDamageMax() ? (getDamageMin() + ThreadLocalRandom.current().nextInt(getDamageMax() - getDamageMin() + 1)) : getDamageMin();

                if (getDamageMode() == DamageMode.MULTIPLY) {
                    damage *= originDamage;
                    break;
                }

                //Apply force adjustments
                if (damager.hasMetadata("RPGItems.Force")) {
                    damage *= damager.getMetadata("RPGItems.Force").get(0).asFloat();
                }
                if (getDamageMode() == DamageMode.ADDITIONAL) {
                    damage += originDamage;
                }
                break;
            case VANILLA:
                //no-op
                break;
        }
        return damage;
    }

    /**
     * Event-type independent take damage event
     *
     * @param p            Player taking damage
     * @param originDamage Origin damage value
     * @param stack        ItemStack of this item
     * @param damager      Cause of this damage. May be null
     * @return Final damage or -1 if should cancel this event
     */
    public double takeDamage(Player p, double originDamage, ItemStack stack, Entity damager) {
        if (ItemManager.canUse(p, this) == Event.Result.DENY) {
            return originDamage;
        }
        boolean can;
        if (!isHitCostByDamage()) {
            can = consumeDurability(stack, getHitCost());
        } else {
            can = consumeDurability(stack, (int) (getHitCost() * originDamage / 100d));
        }
        if (can && getArmour() > 0) {
            originDamage -= Math.round(originDamage * (((double) getArmour()) / 100d));
        }
        return originDamage;
    }

    /**
     * Event-type independent take damage event
     *
     * @param p     Player taking damage
     * @param stack ItemStack of this item
     * @param block Block
     * @return If should process this event
     */
    public boolean breakBlock(Player p, ItemStack stack, Block block) {
        return consumeDurability(stack, getBlockBreakingCost());
    }

    public static List getModifiers(ItemStack stack) {
        Optional opt = ItemTagUtils.getString(stack, NBT_ITEM_UUID);
        if (opt.isEmpty()){
            Optional rpgItemOpt = ItemManager.toRPGItemByMeta(stack);
            if (rpgItemOpt.isEmpty()){
                return Collections.emptyList();
            }
            RPGItem rpgItem = rpgItemOpt.get();
            rpgItem.updateItem(stack);
            Optional opt1 = ItemTagUtils.getString(stack, NBT_ITEM_UUID);
            if (opt1.isEmpty()) {
                return Collections.emptyList();
            }
            opt = opt1;
        }

        UUID key = UUID.fromString(opt.get());
        List modifiers = modifierCache.getIfPresent(key);
        if (modifiers == null){
            ItemMeta itemMeta = stack.getItemMeta();
            if (itemMeta == null)return new ArrayList<>();
            SubItemTagContainer tag = makeTag(Objects.requireNonNull(itemMeta).getPersistentDataContainer(), TAG_MODIFIER);
            modifiers = getModifiers(tag, key);
        }
        return modifiers;
    }

    public static List getModifiers(Player player) {
        UUID key = player.getUniqueId();
        List modifiers = modifierCache.getIfPresent(key);
        if (modifiers == null){
            SubItemTagContainer tag = makeTag(player.getPersistentDataContainer(), TAG_MODIFIER);
            modifiers = getModifiers(tag, key);
        }
        return modifiers;
    }


    public static List getModifiers(SubItemTagContainer tag) {
        return getModifiers(tag, null);
    }

    public static void invalidateModifierCache() {
        modifierCache.invalidateAll();
    }

    public static List getModifiers(SubItemTagContainer tag, UUID key) {
        Optional uuid = Optional.ofNullable(key);
        if (uuid.isEmpty()){
            uuid = Optional.of(UUID.randomUUID());
        }

        try {
            return modifierCache.get(uuid.get(), () -> getModifiersUncached(tag));
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        } finally {
            tag.tryDispose();
        }
    }

    private static List getModifiersUncached(SubItemTagContainer tag) {
        List ret = new ArrayList<>();
        int i = 0;
        try {
            for (NamespacedKey key = PowerManager.parseKey(String.valueOf(i)); tag.has(key, PersistentDataType.TAG_CONTAINER); key = PowerManager.parseKey(String.valueOf(++i))) {
                PersistentDataContainer container = getTag(tag, key);
                String modifierName = getString(container, "modifier_name");
                Class modifierClass = PowerManager.getModifier(PowerManager.parseKey(modifierName));
                Modifier modifier = PowerManager.instantiate(modifierClass);
                modifier.init(container);
                ret.add(modifier);
            }
            return ret;
        } finally {
            tag.commit();
        }
    }

    private  boolean triggerPreCheck(Player player, ItemStack i, TEvent event, Trigger trigger, List powers) {
        if (i.getType().equals(Material.AIR)) return false;
        if (powers.isEmpty()) return false;
        if (checkPermission(player, true) == Event.Result.DENY) return false;

        RPGItemsPowersPreFireEvent preFire = new RPGItemsPowersPreFireEvent<>(player, i, event, this, trigger, powers);
        Bukkit.getServer().getPluginManager().callEvent(preFire);
        return !preFire.isCancelled();
    }

    private  PowerResult checkConditions(Player player, ItemStack i, Pimpl pimpl, List> conds, Map> context) {
        Set ids = pimpl.getPower().getConditions();
        List> conditions = conds.stream().filter(p -> ids.contains(p.id())).toList();
        List> failed = conditions.stream().filter(p -> p.isStatic() ? !context.get(p).isOK() : !p.check(player, i, context).isOK()).toList();
        if (failed.isEmpty()) return null;
        return failed.stream().anyMatch(Condition::isCritical) ? PowerResult.abort() : PowerResult.condition();
    }

    private Map, PowerResult> checkStaticCondition(Player player, ItemStack i, List> conds) {
        Set ids = powers.stream().flatMap(p -> p.getConditions().stream()).collect(Collectors.toSet());
        List> statics = conds.stream().filter(Condition::isStatic).filter(p -> ids.contains(p.id())).toList();
        Map, PowerResult> result = new LinkedHashMap<>();
        for (Condition c : statics) {
            result.put(c, c.check(player, i, Collections.unmodifiableMap(result)));
        }
        return result;
    }

    public  TReturn power(Player player, ItemStack i, TEvent event, Trigger trigger, Object context) {
        powerCustomTrigger(player, i, event, trigger, context);

        List powers = this.getPower(trigger, player, i);
        TReturn ret = trigger.def(player, i, event);
        if (!triggerPreCheck(player, i, event, trigger, powers)) return ret;
        try {
            List> conds = getConditions();
            Map, PowerResult> staticCond = checkStaticCondition(player, i, conds);
            Map> resultMap = new LinkedHashMap<>(staticCond);
            for (TPower power : powers) {
                PowerResult result = checkConditions(player, i, power, conds, resultMap);
                if (result != null) {
                    resultMap.put(power.getPower(), result);
                } else {
                    if (power.getPower().requiredContext() != null) {
                        result = handleContext(player, i, event, trigger, power);
                    } else {
                        result = trigger.run(power, player, i, event, context);
                    }
                    resultMap.put(power.getPower(), result);
                }
                ret = trigger.next(ret, result);
                if (result.isAbort()) break;
            }
            triggerPostFire(player, i, event, trigger, resultMap, ret);
            return ret;
        } finally {
            Context.instance().cleanTemp(player.getUniqueId());
        }
    }

    @SuppressWarnings("unchecked")
    public  void powerCustomTrigger(Player player, ItemStack i, TEvent event, Trigger trigger, Object context) {
        this.triggers.entrySet()
                     .parallelStream()
                     .filter(e -> trigger.getClass().isInstance(e.getValue()))
                     .sorted(Comparator.comparing(en -> en.getValue().getPriority()))
                     .filter(e -> e.getValue().check(player, i, event)).forEachOrdered(e -> this.power(player, i, event, e.getValue(), context));
    }

    public  TReturn power(Player player, ItemStack i, TEvent event, Trigger trigger) {
        return power(player, i, event, trigger, null);
    }

    public  PowerResult handleContext(Player player, ItemStack i, TEvent event, Trigger trigger, TPower power) {
        PowerResult result;
        String contextKey = power.getPower().requiredContext();
        Object context = Context.instance().get(player.getUniqueId(), contextKey);
        if (context == null) {
            return PowerResult.context();
        }
        if (context instanceof Location) {
            if (power instanceof PowerLocation) {
                PowerResult overrideResult = BaseTriggers.LOCATION.run((PowerLocation) power, player, i, event, context);
                result = trigger.warpResult(overrideResult, power, player, i, event);
            } else {
                throw new IllegalStateException();
            }
        } else if (context instanceof Pair) {
            Object key = ((Pair) context).getKey();
            if (key instanceof LivingEntity) {
                PowerResult overrideResult = BaseTriggers.LIVINGENTITY.run((PowerLivingEntity) power, player, i, event, context);
                result = trigger.warpResult(overrideResult, power, player, i, event);
            } else {
                throw new IllegalStateException();
            }
        } else {
            throw new IllegalStateException();
        }
        return result;
    }

    private  void triggerPostFire(Player player, ItemStack itemStack, TEvent event, Trigger trigger, Map> resultMap, TReturn ret) {
        RPGItemsPowersPostFireEvent postFire = new RPGItemsPowersPostFireEvent<>(player, itemStack, event, this, trigger, resultMap, ret);
        Bukkit.getServer().getPluginManager().callEvent(postFire);

        if (getItemStackDurability(itemStack).map(d -> d <= 0).orElse(false)) {
            itemStack.setAmount(0);
            itemStack.setType(Material.AIR);
        }
    }

    public void rebuild() {
        List lines = getTooltipLines();
        lines.remove(0);
        setLore(lines);
    }

    @SuppressWarnings("deprecation")
    public List getTooltipLines() {
        ArrayList output = new ArrayList<>();
        output.add(getDisplayName());

        // add powerLores
        if (isShowPowerText()) {
            for (Power p : getPowers()) {
                String txt = p.displayText();
                if (txt != null && txt.length() > 0) {
                    output.add(txt);
                }
            }
        }

        // add descriptions
        output.addAll(getDescription());

        // compute width
        int width = 0;
        for (String str : output) {
            width = Math.max(width, Utils.getStringWidth(ChatColor.stripColor(str)));
        }

        // compute armorMinLen
        int armorMinLen = 0;
        String damageStr = null;
        if (isShowArmourLore()) {
            if (getArmour() != 0) {
                damageStr = getArmour() + "% " + I18n.formatDefault("item.armour");
            }
            if ((getDamageMin() != 0 || getDamageMax() != 0) && getDamageMode() != DamageMode.VANILLA) {
                damageStr = damageStr == null ? "" : damageStr + " & ";
                if (getDamageMode() == DamageMode.ADDITIONAL) {
                    damageStr += I18n.formatDefault("item.additionaldamage", getDamageMin() == getDamageMax() ? String.valueOf(getDamageMin()) : getDamageMin() + "-" + getDamageMax());
                } else if (getDamageMode() == DamageMode.MULTIPLY) {
                    damageStr += I18n.formatDefault("item.multiplydamage", getDamageMin() == getDamageMax() ? String.valueOf(getDamageMin()) : getDamageMin() + "-" + getDamageMax());
                } else {
                    damageStr += I18n.formatDefault("item.damage", getDamageMin() == getDamageMax() ? String.valueOf(getDamageMin()) : getDamageMin() + "-" + getDamageMax());
                }
            }
            if (damageStr != null) {
                armorMinLen = Math.max(armorMinLen, Utils.getStringWidth(ChatColor.stripColor(damageStr)));
            }
        }
        tooltipWidth = width = Math.max(width, armorMinLen);

        if (isShowArmourLore()) {
            if (damageStr != null) {
                output.add(1, ChatColor.WHITE + damageStr);
            }
        }

        return output;
    }

    public ItemStack toItemStack() {
        ItemStack rStack = new ItemStack(getItem());
        ItemMeta meta = rStack.getItemMeta();
        PersistentDataContainer itemTagContainer = Objects.requireNonNull(meta).getPersistentDataContainer();
        SubItemTagContainer rpgitemsTagContainer = makeTag(itemTagContainer, TAG_META);
        set(rpgitemsTagContainer, TAG_ITEM_UID, getUid());
        if (isHasStackId()) {
            set(rpgitemsTagContainer, TAG_STACK_ID, UUID.randomUUID());
        }
        rpgitemsTagContainer.commit();
        meta.setDisplayName(getDisplayName());
        rStack.setItemMeta(meta);

        updateItem(rStack, false);
        return rStack;
    }

    public void toModel(ItemStack itemStack) {
        updateItem(itemStack);
        ItemMeta itemMeta = itemStack.getItemMeta();
        SubItemTagContainer meta = makeTag(Objects.requireNonNull(itemMeta).getPersistentDataContainer(), TAG_META);
        meta.remove(TAG_OWNER);
        meta.remove(TAG_STACK_ID);
        set(meta, TAG_IS_MODEL, true);
        try {
            ItemTagUtils.setBoolean(itemStack, NBT_IS_MODEL, true);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
        meta.commit();
        itemMeta.setDisplayName(getDisplayName());
        itemStack.setItemMeta(itemMeta);
    }

    public void unModel(ItemStack itemStack, Player owner) {
        updateItem(itemStack);
        ItemMeta itemMeta = itemStack.getItemMeta();
        SubItemTagContainer meta = makeTag(Objects.requireNonNull(itemMeta).getPersistentDataContainer(), TAG_META);
        if (isCanBeOwned()) {
            set(meta, TAG_OWNER, owner);
        }
        if (isHasStackId()) {
            set(meta, TAG_STACK_ID, UUID.randomUUID());
        }
        meta.remove(TAG_IS_MODEL);
        meta.commit();
        itemMeta.setDisplayName(getDisplayName());
        itemStack.setItemMeta(itemMeta);
    }

    public Event.Result checkPermission(Player p, boolean showWarn) {
        if (isHasPermission() && !p.hasPermission(getPermission())) {
            if (showWarn)
                p.sendMessage(I18n.getInstance(p.getLocale()).format("message.error.permission", getDisplayName()));
            return Event.Result.DENY;
        }
        return Event.Result.ALLOW;
    }

    public void print(CommandSender sender) {
        print(sender, true);
    }

    public void print(CommandSender sender, boolean advance) {
        String author = this.getAuthor();
        BaseComponent authorComponent = new TextComponent(author);
        try {
            UUID uuid = UUID.fromString(this.getAuthor());
            OfflinePlayer authorPlayer = Bukkit.getOfflinePlayer(uuid);
            author = authorPlayer.getName();
            authorComponent = AdminCommands.getAuthorComponent(authorPlayer, author);
        } catch (IllegalArgumentException ignored) {
        }

        String locale = RPGItems.plugin.cfg.language;
        if (sender instanceof Player) {
            locale = ((Player) sender).getLocale();
            new Message("")
                    .append(I18n.getInstance(((Player) sender).getLocale()).format("message.item.print"), toItemStack())
                    .send(sender);
        } else {
            List lines = getTooltipLines();
            for (String line : lines) {
                sender.sendMessage(line);
            }
        }
        I18n i18n = I18n.getInstance(locale);

        new Message("").append(I18n.formatDefault("message.print.author"), Collections.singletonMap("{author}", authorComponent)).send(sender);
        if (!advance) {
            return;
        }

        new Message(I18n.formatDefault("message.print.license", getLicense())).send(sender);
        new Message(I18n.formatDefault("message.print.note", getNote())).send(sender);

        sender.sendMessage(I18n.formatDefault("message.durability.info", getMaxDurability(), getDefaultDurability(), getDurabilityLowerBound(), getDurabilityUpperBound()));
        if (isCustomItemModel()) {
            sender.sendMessage(I18n.formatDefault("message.print.customitemmodel", getItem().name() + ":" + getDataValue()));
        }
        if (!getItemFlags().isEmpty()) {
            StringBuilder str = new StringBuilder();
            for (ItemFlag flag : getItemFlags()) {
                if (str.length() > 0) {
                    str.append(", ");
                }
                str.append(flag.name());
            }
            sender.sendMessage(I18n.formatDefault("message.print.itemflags") + str);
        }
    }

    public void setItemStackDurability(ItemStack item, int val) {
        ItemMeta itemMeta = item.getItemMeta();
        SubItemTagContainer tagContainer = makeTag(Objects.requireNonNull(itemMeta), TAG_META);
        if (getMaxDurability() != -1) {
            set(tagContainer, TAG_DURABILITY, val);
        }
        tagContainer.commit();
        item.setItemMeta(itemMeta);
        this.updateItem(item, true);
    }

    public Optional getItemStackDurability(ItemStack item) {
        if (getMaxDurability() == -1) {
            return Optional.empty();
        }
        ItemMeta itemMeta = item.getItemMeta();
        //Power Consume will make this null in triggerPostFire().
        if(itemMeta == null){
            return Optional.empty();
        }
        SubItemTagContainer tagContainer = makeTag(itemMeta, TAG_META);
        int durability = computeIfAbsent(tagContainer, TAG_DURABILITY, PersistentDataType.INTEGER, this::getDefaultDurability);
        tagContainer.commit();
        item.setItemMeta(itemMeta);
        return Optional.of(durability);
    }

    public boolean consumeDurability(ItemStack item, int val) {
        return consumeDurability(item, val, true);
    }

    public boolean consumeDurability(ItemStack item, int val, boolean checkbound) {
        if (val == 0) return true;
        int durability;
        ItemMeta itemMeta = item.getItemMeta();
        if (getMaxDurability() != -1) {
            SubItemTagContainer tagContainer = makeTag(Objects.requireNonNull(itemMeta), TAG_META);
            durability = computeIfAbsent(tagContainer, TAG_DURABILITY, PersistentDataType.INTEGER, this::getDefaultDurability);
            if (checkbound && (
                    (val > 0 && durability < getDurabilityLowerBound()) ||
                            (val < 0 && durability > getDurabilityUpperBound())
            )) {
                tagContainer.commit();
                item.setItemMeta(itemMeta);
                return false;
            }
            if (durability <= val
                        && hasMarker(Unbreakable.class)
                        && !isCustomItemModel()) {
                tagContainer.commit();
                item.setItemMeta(itemMeta);
                return false;
            }
            durability -= val;
            if (durability > getMaxDurability()) {
                durability = getMaxDurability();
            }
            set(tagContainer, TAG_DURABILITY, durability);
            tagContainer.commit();
            item.setItemMeta(itemMeta);
            this.updateItem(item, true);
        }
        return true;
    }

    public void give(Player player, int count, boolean wear) {
        ItemStack itemStack = toItemStack();
        itemStack.setAmount(count);
        if (wear) {
            if (
                    item.equals(Material.CHAINMAIL_HELMET) ||
                            item.equals(Material.DIAMOND_HELMET) ||
                            item.equals(Material.GOLDEN_HELMET) ||
                            item.equals(Material.IRON_HELMET) ||
                            item.equals(Material.LEATHER_HELMET) ||
                            item.equals(Material.TURTLE_HELMET)
            ) {
                if (player.getInventory().getHelmet() == null) {
                    player.getInventory().setHelmet(itemStack);
                    return;
                }
            } else if (
                           item.equals(Material.CHAINMAIL_CHESTPLATE) ||
                                   item.equals(Material.DIAMOND_CHESTPLATE) ||
                                   item.equals(Material.GOLDEN_CHESTPLATE) ||
                                   item.equals(Material.IRON_CHESTPLATE) ||
                                   item.equals(Material.LEATHER_CHESTPLATE)
            ) {
                if (player.getInventory().getChestplate() == null) {
                    player.getInventory().setChestplate(itemStack);
                    return;
                }
            } else if (
                           item.equals(Material.CHAINMAIL_LEGGINGS) ||
                                   item.equals(Material.DIAMOND_LEGGINGS) ||
                                   item.equals(Material.GOLDEN_LEGGINGS) ||
                                   item.equals(Material.IRON_LEGGINGS) ||
                                   item.equals(Material.LEATHER_LEGGINGS)
            ) {
                if (player.getInventory().getLeggings() == null) {
                    player.getInventory().setLeggings(itemStack);
                    return;
                }
            } else if (
                           item.equals(Material.CHAINMAIL_BOOTS) ||
                                   item.equals(Material.DIAMOND_BOOTS) ||
                                   item.equals(Material.GOLDEN_BOOTS) ||
                                   item.equals(Material.IRON_BOOTS) ||
                                   item.equals(Material.LEATHER_BOOTS)
            ) {
                if (player.getInventory().getBoots() == null) {
                    player.getInventory().setBoots(itemStack);
                    return;
                }
            }
        }
        HashMap overflow = player.getInventory().addItem(itemStack);
        for (ItemStack o : overflow.values()) {
            player.getWorld().dropItemNaturally(player.getLocation(), o);
        }
    }

    public boolean hasMarker(Class marker) {
        return markers.stream().anyMatch(p -> p.getClass().equals(marker));
    }

    public  List getMarker(Class marker) {
        return markers.stream().filter(p -> p.getClass().equals(marker)).map(marker::cast).collect(Collectors.toList());
    }

    public > List getConditions(Class condition) {
        return conditions.stream().filter(p -> p.getClass().equals(condition)).map(condition::cast).collect(Collectors.toList());
    }

    public  List getMarker(Class marker, boolean subclass) {
        return subclass ? markers.stream().filter(marker::isInstance).map(marker::cast).collect(Collectors.toList()) : getMarker(marker);
    }

    public  List getMarker(NamespacedKey key, Class marker) {
        return markers.stream().filter(p -> p.getClass().equals(marker) && getPropertyHolderKey(p).equals(key)).map(marker::cast).collect(Collectors.toList());
    }

    public  List getPower(NamespacedKey key, Class power) {
        return powers.stream().filter(p -> p.getClass().equals(power) && getPropertyHolderKey(p).equals(key)).map(power::cast).collect(Collectors.toList());
    }

    public > List getCondition(NamespacedKey key, Class condition) {
        return powers.stream().filter(p -> p.getClass().equals(condition) && getPropertyHolderKey(p).equals(key)).map(condition::cast).collect(Collectors.toList());
    }

    public Condition getCondition(String id) {
        return conditions.stream().filter(c -> c.id().equals(id)).findAny().orElse(null);
    }

    public void addPower(NamespacedKey key, Power power) {
        addPower(key, power, true);
    }

    private void addPower(NamespacedKey key, Power power, boolean update) {
        if ("".equals(power.getPlaceholderId())){
            String placeholderId = power.getName() + "-" + getPowers().stream().filter(power1 -> power1.getName().equals(power.getName())).count();
            power.setPlaceholderId(placeholderId);
        }
        powers.add(power);
        keys.put(power, key);
        String placeholderId = power.getPlaceholderId();
        placeholders.put(placeholderId, power);
        if (update) {
            rebuild();
        }
    }

    public void removePower(Power power) {
        powers.remove(power);
        keys.remove(power);
        String placeholderId = power.getPlaceholderId();
        if (!"".equals(placeholderId)){
            placeholders.remove(placeholderId, power);
        }
        power.deinit();
        rebuild();
    }

    public void addCondition(NamespacedKey key, Condition condition) {
        addCondition(key, condition, true);
    }

    private void addCondition(NamespacedKey key, Condition condition, boolean update) {
        if ("".equals(condition.getPlaceholderId())){
            String placeholderId = condition.getName() + "-" + getConditions().stream().filter(power1 -> power1.getName().equals(condition.getName())).count();
            condition.setPlaceholderId(placeholderId);
        }
        conditions.add(condition);
        keys.put(condition, key);
        String placeholderId = condition.getPlaceholderId();
        placeholders.put(placeholderId, condition);
        if (update) {
            rebuild();
        }
    }

    public void removeCondition(Condition condition) {
        conditions.remove(condition);
        keys.remove(condition);
        String placeholderId = condition.getPlaceholderId();
        if (!"".equals(placeholderId)){
            placeholders.remove(placeholderId, condition);
        }
        rebuild();
    }

    public void addMarker(NamespacedKey key, Marker marker) {
        addMarker(key, marker, true);
    }

    private void addMarker(NamespacedKey key, Marker marker, boolean update) {
        if ("".equals(marker.getPlaceholderId())){
            String placeholderId = marker.getName() + "-" + getMarkers().stream().filter(power1 -> power1.getName().equals(marker.getName())).count();
            marker.setPlaceholderId(placeholderId);
        }
        markers.add(marker);
        keys.put(marker, key);
        String placeholderId = marker.getPlaceholderId();
        placeholders.put(placeholderId, marker);
        if (update) {
            rebuild();
        }
    }

    public void removeMarker(Marker marker) {
        markers.remove(marker);
        keys.remove(marker);
        String placeholderId = marker.getPlaceholderId();
        if (!"".equals(placeholderId)){
            placeholders.remove(placeholderId, marker);
        }
        rebuild();
    }

    public Map> checkDuplicatePlaceholderIds(){
        Map> ids = new HashMap<>();
        getPlaceholdersStream()
                .map(ph -> ph)
                .forEach(placeholder -> {
                    String placeholderId = placeholder.getPlaceholderId();
                    if("".equals(placeholderId)){
                        return;
                    }
                    List placeholderHolders = ids.computeIfAbsent(placeholderId, (s) -> new ArrayList<>());
                    placeholderHolders.add(placeholder);
                });
        Map> result = new HashMap<>();
        ids.forEach((key, value) ->{
            if (value.size()>1){
                result.put(key, value);
            }
        });
        return result;
    }

    public Stream getPlaceholdersStream() {
        List powers = getPowers();
        List> conditions = getConditions();
        List markers = getMarkers();
        return Stream.concat(
                Stream.concat(
                        powers.stream().map(ph -> ((PlaceholderHolder) ph)),
                        conditions.stream().map(ph -> ((PlaceholderHolder) ph))),
                markers.stream());
    }

    public void addDescription(String str) {
        getDescription().add(ColorHelper.parseColor(str));
        rebuild();
    }

    public void toggleBar() {
        setHasDurabilityBar(!isHasDurabilityBar());
        rebuild();
    }

    public BaseComponent getComponent(CommandSender sender) {
        String locale = RPGItems.plugin.cfg.language;
        if (sender instanceof Player) {
            locale = ((Player) sender).getLocale();
        }
        return getComponent(locale);
    }

    public BaseComponent getComponent(String locale) {
        BaseComponent msg = new TextComponent(getDisplayName());
        msg.setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/rpgitem " + getName()));
        HoverEvent hover = new HoverEvent(HoverEvent.Action.SHOW_ITEM,
                new BaseComponent[]{new TextComponent(ItemStackUtils.itemToJson(toItemStack()))});
        msg.setHoverEvent(hover);
        return msg;
    }

    private  List getPower(Trigger trigger, Player player, ItemStack stack) {
        return powers.stream()
                .filter(p -> p.getTriggers().contains(trigger))
                .map(p -> {
                    Class cls = p.getClass();
                    Power proxy = Interceptor.create(p, player, stack, trigger);
                    return PowerManager.createImpl(cls, proxy);
                })
                .map(p -> p.cast(trigger.getPowerClass()))
                .collect(Collectors.toList());
    }

    public PlaceholderHolder getPlaceholderHolder(String placeholderId) {
        return placeholders.get(placeholderId);
    }

    public PlaceholderHolder replacePlaceholder(String powerId, PlaceholderHolder placeHolder) {
        YamlConfiguration yamlConfiguration = new YamlConfiguration();
        PlaceholderHolder oldPh = placeholders.get(powerId);
        PlaceholderHolder newPh = null;
        if (oldPh == null){
            return null;
        }
        placeHolder.save(yamlConfiguration);
        if (oldPh instanceof Power){
            int i = powers.indexOf(oldPh);
            if (i == -1){
                throw new IllegalStateException();
            }
            Class pwClz = ((Power) oldPh).getClass();
            Power pow = PowerManager.instantiate(pwClz);
            pow.setItem(this);
            pow.init(yamlConfiguration);
            powers.set(i, pow);
            newPh = pow;
        }else if (oldPh instanceof Condition){
            int i = conditions.indexOf(oldPh);
            if (i == -1){
                throw new IllegalStateException();
            }
            Class pwClz = ((Condition) oldPh).getClass();
            Condition pow = PowerManager.instantiate(pwClz);
            pow.setItem(this);
            pow.init(yamlConfiguration);
            conditions.set(i, pow);
            newPh = pow;
        }else if (oldPh instanceof Marker){
            int i = markers.indexOf(oldPh);
            if (i == -1){
                throw new IllegalStateException();
            }
            Class pwClz = ((Marker) oldPh).getClass();
            Marker pow = PowerManager.instantiate(pwClz);
            pow.setItem(this);
            pow.init(yamlConfiguration);
            markers.set(i, pow);
            newPh = pow;
        }
        if(newPh == null){
            return null;
        }

        NamespacedKey remove = keys.remove(oldPh);
        keys.put(newPh, remove);
        placeholders.put(powerId, newPh);
        return newPh;
    }



    public void updateFromTemplate(RPGItem target) throws UnknownPowerException {
        Set templatePlaceHolders = target.getTemplatePlaceHolders();
        Map> powerMap = new LinkedHashMap<>();
        Map valMap = new LinkedHashMap<>();
        //extract original val from self
        templatePlaceHolders.forEach(s -> {
            String[] split = s.split(":");
            PlaceholderHolder power = getPlaceholderHolder(split[0]);
            String propName = split[1];
            Object origVal = null;
            try {
                origVal = getPropVal(power.getClass(), propName, power);
                List strings = powerMap.computeIfAbsent(split[0], (str) -> new ArrayList<>());
                strings.add(s);
                valMap.put(s, origVal);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
                throw new RuntimeException();
            }
        });

        copyFromTemplate(target);

        //replace powers & fill placeholders
        target.getPlaceholdersStream().forEach(power -> {
                String powerId = power.getPlaceholderId();
                PlaceholderHolder replaced = replacePlaceholder(powerId, power);
                List strings = powerMap.get(powerId);
                if (strings != null){
                    strings.forEach(s ->{
                        String[] split = s.split(":");
                        String propName = split[1];
                        Object origVal = valMap.get(s);
                        try {
                            setPropVal(replaced.getClass(), propName, replaced, origVal);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                            throw new RuntimeException();
                        }
                    });
                }
        });

        ItemManager.save(this);
    }

    private Object getPropVal(Class aClass, String propName, PlaceholderHolder placeholder) throws IllegalAccessException {
        Field getMethod = getField(aClass, propName);
        getMethod.setAccessible(true);
        return getMethod.get(placeholder);
    }

    private void setPropVal(Class aClass, String propName, PlaceholderHolder placeholder, Object value) throws IllegalAccessException {
        Field getMethod = getField(aClass, propName);
        getMethod.setAccessible(true);
        getMethod.set(placeholder, value);
    }


    private Field getField(Class aClass, String methodName) {
        Field getMethod = null;
        while (true){
            try{
                getMethod = aClass.getDeclaredField(methodName);
                break;
            }catch (NoSuchFieldException e){
                aClass = aClass.getSuperclass();
            }
            if (aClass == null){
                throw new RuntimeException("invalid placeholder");
            }
        }
        return getMethod;
    }

    private void copyFromTemplate(RPGItem target) throws UnknownPowerException {
        List powers = new ArrayList<>(getPowers());
        List markers =  new ArrayList<>(getMarkers());
        List> conditions =  new ArrayList<>(getConditions());
        boolean isTemplate = isTemplate();
        Set templates =  new HashSet<>(getTemplates());
        placeholders.clear();
        this.powers.clear();
        this.markers.clear();
        this.conditions.clear();

        //copy other settings,
        YamlConfiguration config = new YamlConfiguration();
        target.save(config);
        this.restore(config);

        this.powers = powers;
        this.markers = markers;
        this.conditions = conditions;
        this.isTemplate = isTemplate;
        this.templates = templates;
        this.rebuildPlaceholder();
    }

    private void rebuildPlaceholder() {
        placeholders.clear();
        getPlaceholdersStream().forEach(placeholderHolder -> placeholders.put(placeholderHolder.getPlaceholderId(), placeholderHolder));
    }

    public List getTemplatePlaceholders() {
        return new ArrayList<>(templatePlaceholders);
    }

    public void addTrigger(String name, Trigger trigger) {
        triggers.put(name, trigger);
    }

    public List> getConditions() {
        return conditions;
    }

    public void deinit() {
        powers.forEach(Power::deinit);
    }

    public int getArmour() {
        return armour;
    }

    public boolean isAlwaysAllowMelee() {
        return alwaysAllowMelee;
    }

    public boolean isCanBeOwned() {
        return canBeOwned;
    }

    public boolean isHasStackId() {
        return hasStackId;
    }

    public void setAlwaysAllowMelee(boolean alwaysAllowMelee) {
        this.alwaysAllowMelee = alwaysAllowMelee;
    }

    public void setArmour(int a) {
        setArmour(a, true);
    }

    public void setArmour(int a, boolean update) {
        armour = a;
        if (update) {
            rebuild();
        }
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public int getBlockBreakingCost() {
        return blockBreakingCost;
    }

    public void setBlockBreakingCost(int blockBreakingCost) {
        this.blockBreakingCost = blockBreakingCost;
    }

    public int getDamageMax() {
        return damageMax;
    }

    public void setCanBeOwned(boolean canBeOwned) {
        this.canBeOwned = canBeOwned;
    }

    private void setDamageMax(int damageMax) {
        this.damageMax = damageMax;
    }

    public int getDamageMin() {
        return damageMin;
    }

    private void setDamageMin(int damageMin) {
        this.damageMin = damageMin;
    }

    public void setDamage(int min, int max) {
        setDamageMin(min);
        setDamageMax(max);
        rebuild();
    }

    public DamageMode getDamageMode() {
        return damageMode;
    }

    public void setDamageMode(DamageMode damageMode) {
        this.damageMode = damageMode;
    }

    public int getDataValue() {
        return dataValue;
    }

    public void setDataValue(int dataValue) {
        this.dataValue = dataValue;
    }

    public int getDefaultDurability() {
        return defaultDurability;
    }

    public void setDefaultDurability(int newVal) {
        defaultDurability = newVal;
    }

    public List getDescription() {
        return description;
    }

    public void setDescription(List description) {
        this.description = description;
    }

    public String getDisplayNameRaw() {
        return displayName;
    }
    public String getDisplayName() {
        return displayNameColored;
    }

    public void setDisplayName(String displayName) {
        this.displayName = displayName;
        this.displayNameColored = ColorHelper.parseColor(displayName);
    }

    public int getDurabilityLowerBound() {
        return durabilityLowerBound;
    }

    public void setDurabilityLowerBound(int durabilityLowerBound) {
        this.durabilityLowerBound = durabilityLowerBound;
    }

    public int getDurabilityUpperBound() {
        return durabilityUpperBound;
    }

    public void setDurabilityUpperBound(int durabilityUpperBound) {
        this.durabilityUpperBound = durabilityUpperBound;
    }

    public void setDurabilityBound(int min, int max) {
        setDurabilityLowerBound(min);
        setDurabilityUpperBound(max);
    }

    public Map getEnchantMap() {
        return enchantMap;
    }

    public void setEnchantMap(Map enchantMap) {
        this.enchantMap = enchantMap;
    }

    public File getFile() {
        return file;
    }

    public EnchantMode getEnchantMode() {
        return enchantMode;
    }

    public void setEnchantMode(EnchantMode enchantMode) {
        this.enchantMode = enchantMode;
    }

    void setFile(File itemFile) {
        file = itemFile;
    }

    public int getHitCost() {
        return hitCost;
    }

    public void setHasStackId(boolean hasStackId) {
        this.hasStackId = hasStackId;
    }

    public void setHitCost(int hitCost) {
        this.hitCost = hitCost;
    }

    public int getHittingCost() {
        return hittingCost;
    }

    public void setHittingCost(int hittingCost) {
        this.hittingCost = hittingCost;
    }

    public Material getItem() {
        return item;
    }

    public void setItem(Material material) {
        item = material;
    }

    public List getItemFlags() {
        return itemFlags;
    }

    public void setItemFlags(List itemFlags) {
        this.itemFlags = itemFlags;
    }

    public String getLicense() {
        return license;
    }

    public void setLicense(String license) {
        this.license = license;
    }

    public List getLore() {
        return Collections.unmodifiableList(lore);
    }

    private void setLore(List lore) {
        this.lore = lore;
    }

    public int getMaxDurability() {
        return maxDurability;
    }

    public void setMaxDurability(int newVal) {
        maxDurability = newVal <= 0 ? -1 : newVal;
        setDefaultDurability(maxDurability);
    }

    public String getMcVersion() {
        return mcVersion;
    }

    public void setMcVersion(String mcVersion) {
        this.mcVersion = mcVersion;
    }

    public String getName() {
        return name;
    }

    public NamespacedKey getNamespacedKey() {
        return namespacedKey;
    }

    public void setNamespacedKey(NamespacedKey namespacedKey) {
        this.namespacedKey = namespacedKey;
    }

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note;
    }

    public String getPermission() {
        return Strings.isNullOrEmpty(permission) ? "rpgitems.item.use." + getName() : permission;
    }

    public void setPermission(String p) {
        permission = p;
    }

    public int getPluginVersion() {
        return pluginVersion;
    }

    public void setPluginVersion(int pluginVersion) {
        this.pluginVersion = pluginVersion;
    }

    public int getPluginSerial() {
        return pluginSerial;
    }

    public void setPluginSerial(int pluginSerial) {
        this.pluginSerial = pluginSerial;
    }

    public List getPowers() {
        return powers;
    }

    public List getMarkers() {
        return markers;
    }

    public Map getTriggers() {
        return triggers;
    }

    public NamespacedKey getPropertyHolderKey(PropertyHolder power) {
        return Objects.requireNonNull(keys.get(power));
    }

    public NamespacedKey removePropertyHolderKey(PropertyHolder power) {
        return Objects.requireNonNull(keys.remove(power));
    }

    public int getUid() {
        return uid;
    }

    public boolean isCustomItemModel() {
        return customItemModel;
    }

    public void setCustomItemModel(boolean customItemModel) {
        this.customItemModel = customItemModel;
    }

    public boolean isHasDurabilityBar() {
        return hasDurabilityBar;
    }

    public void setHasDurabilityBar(boolean hasDurabilityBar) {
        this.hasDurabilityBar = hasDurabilityBar;
    }

    public boolean isHasPermission() {
        return hasPermission;
    }

    public void setHasPermission(boolean b) {
        hasPermission = b;
    }

    public boolean isHitCostByDamage() {
        return hitCostByDamage;
    }

    public void setHitCostByDamage(boolean hitCostByDamage) {
        this.hitCostByDamage = hitCostByDamage;
    }

    public boolean isIgnoreWorldGuard() {
        return ignoreWorldGuard;
    }

    public void setIgnoreWorldGuard(boolean ignoreWorldGuard) {
        this.ignoreWorldGuard = ignoreWorldGuard;
    }

    public BarFormat getBarFormat() {
        return barFormat;
    }

    public void setBarFormat(BarFormat barFormat) {
        this.barFormat = barFormat;
    }

    public boolean isShowArmourLore() {
        return showArmourLore;
    }

    public void setShowArmourLore(boolean showArmourLore) {
        this.showArmourLore = showArmourLore;
    }

    public boolean isShowPowerText() {
        return showPowerText;
    }

    public void setShowPowerText(boolean showPowerText) {
        this.showPowerText = showPowerText;
    }

    public void setAttributeMode(AttributeMode attributeMode) {
        this.attributeMode = attributeMode;
    }

    public boolean isTemplate() {
        return isTemplate;
    }

    public void setIsTemplate(boolean template) {
        isTemplate = template;
    }

    public boolean isTemplateOf(String templateName){
        return templates.contains(templateName);
    }

    public Set getTemplates(){
        return templates;
    }

    public void setTemplateOf(String templateName){
        this.templates.add(templateName);
    }

    public void setTemplatePlaceHolders(List placeHolder) {
        this.templatePlaceholders.clear();
        this.templatePlaceholders.addAll(placeHolder);
    }

    public void addTemplatePlaceHolder(String placeHolder){
        this.templatePlaceholders.add(placeHolder);
    }

    public String getQuality() {
        return quality;
    }

    public void setQuality(String quality) {
        this.quality = quality;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public void removeTemplatePlaceHolder(String placeHolder){
        this.templatePlaceholders.remove(placeHolder);
    }

    private Set getTemplatePlaceHolders() {
        return templatePlaceholders;
    }

    public AttributeMode getAttributeMode() {
        return attributeMode;
    }

    public enum DamageMode {
        FIXED,
        VANILLA,
        ADDITIONAL,
        MULTIPLY,
    }

    public enum EnchantMode {
        DISALLOW,
        PERMISSION,
        ALLOW
    }

    public enum AttributeMode {
        FULL_UPDATE, PARTIAL_UPDATE
    }

    public enum BarFormat {
        DEFAULT,
        NUMERIC,
        NUMERIC_MINUS_ONE,
        NUMERIC_HEX,
        NUMERIC_HEX_MINUS_ONE,
        NUMERIC_BIN,
        NUMERIC_BIN_MINUS_ONE,
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy