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

studio.magemonkey.divinity.stats.items.ItemStats Maven / Gradle / Ivy

The newest version!
package studio.magemonkey.divinity.stats.items;

import org.bukkit.NamespacedKey;
import org.bukkit.attribute.AttributeModifier;
import org.bukkit.attribute.AttributeModifier.Operation;
import org.bukkit.entity.Player;
import org.bukkit.event.entity.EntityDamageEvent.DamageCause;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import studio.magemonkey.codex.api.meta.NBTAttribute;
import studio.magemonkey.codex.modules.IModule;
import studio.magemonkey.codex.util.DataUT;
import studio.magemonkey.divinity.Divinity;
import studio.magemonkey.divinity.modules.api.QModuleDrop;
import studio.magemonkey.divinity.stats.items.api.DuplicableItemLoreStat;
import studio.magemonkey.divinity.stats.items.api.DynamicStat;
import studio.magemonkey.divinity.stats.items.api.ItemLoreStat;
import studio.magemonkey.divinity.stats.items.attributes.*;
import studio.magemonkey.divinity.stats.items.attributes.SocketAttribute.Type;
import studio.magemonkey.divinity.stats.items.attributes.api.SimpleStat;
import studio.magemonkey.divinity.stats.items.attributes.api.TypedStat;
import studio.magemonkey.divinity.stats.items.attributes.stats.DurabilityStat;
import studio.magemonkey.divinity.utils.ItemUtils;

import java.util.*;

public class ItemStats {

    private static final Map            DAMAGES          = new LinkedHashMap<>();
    private static final Map           DEFENSES         = new LinkedHashMap<>();
    private static final Map         STATS            = new HashMap<>();
    private static final Map  AMMO             = new HashMap<>();
    private static final Map  HANDS            = new HashMap<>();
    private static final Map> SOCKETS          = new HashMap<>();
    private static final Map>            ATTRIBUTES       = new HashMap<>();
    private static final Map>  MULTI_ATTRIBUTES = new HashMap<>();
    private static final Set                        DYNAMIC_STATS    = new HashSet<>();
    private static final Divinity                                plugin           = Divinity.getInstance();
    private static final List                     KEY_ID           = List.of(
            new NamespacedKey(plugin, ItemTags.TAG_ITEM_ID),
            Objects.requireNonNull(NamespacedKey.fromString("prorpgitems:" + ItemTags.TAG_ITEM_ID.toLowerCase())),
            Objects.requireNonNull(NamespacedKey.fromString("prorpgitems:qrpg_" + ItemTags.TAG_ITEM_ID.toLowerCase())),
            Objects.requireNonNull(NamespacedKey.fromString("quantumrpg:qrpg_" + ItemTags.TAG_ITEM_ID.toLowerCase())));
    private static final List                     KEY_MODULE       = List.of(
            new NamespacedKey(plugin, ItemTags.TAG_ITEM_MODULE),
            Objects.requireNonNull(NamespacedKey.fromString("prorpgitems:" + ItemTags.TAG_ITEM_MODULE.toLowerCase())),
            Objects.requireNonNull(NamespacedKey.fromString(
                    "prorpgitems:qrpg_" + ItemTags.TAG_ITEM_MODULE.toLowerCase())),
            Objects.requireNonNull(NamespacedKey.fromString(
                    "quantumrpg:qrpg_" + ItemTags.TAG_ITEM_MODULE.toLowerCase())));
    private static final List                     KEY_LEVEL        = List.of(
            new NamespacedKey(plugin, ItemTags.TAG_ITEM_LEVEL),
            Objects.requireNonNull(NamespacedKey.fromString("prorpgitems:" + ItemTags.TAG_ITEM_LEVEL.toLowerCase())),
            Objects.requireNonNull(NamespacedKey.fromString(
                    "prorpgitems:qrpg_" + ItemTags.TAG_ITEM_LEVEL.toLowerCase())),
            Objects.requireNonNull(NamespacedKey.fromString(
                    "quantumrpg:qrpg_" + ItemTags.TAG_ITEM_LEVEL.toLowerCase())));
    private static final List                     KEY_SOCKET       = List.of(
            new NamespacedKey(plugin, ItemTags.TAG_ITEM_SOCKET_RATE),
            Objects.requireNonNull(NamespacedKey.fromString(
                    "prorpgitems:" + ItemTags.TAG_ITEM_SOCKET_RATE.toLowerCase())),
            Objects.requireNonNull(NamespacedKey.fromString(
                    "prorpgitems:qrpg_" + ItemTags.TAG_ITEM_SOCKET_RATE.toLowerCase())),
            Objects.requireNonNull(NamespacedKey.fromString(
                    "quantumrpg:qrpg_" + ItemTags.TAG_ITEM_SOCKET_RATE.toLowerCase())));
    private static       DamageAttribute                         DAMAGE_DEFAULT;
    private static       DefenseAttribute                        DEFENSE_DEFAULT;

    private static final double DEFAULT_ATTACK_SPEED = 4D;

    // TODO Register logs

    public static void clear() {
        DAMAGES.clear();
        DEFENSES.clear();
        STATS.clear();
        AMMO.clear();
        HANDS.clear();
        SOCKETS.clear();
        ATTRIBUTES.clear();
        MULTI_ATTRIBUTES.clear();
        DAMAGE_DEFAULT = null;
        DEFENSE_DEFAULT = null;
    }

    public static void registerDamage(@NotNull DamageAttribute dmg) {
        DAMAGES.put(dmg.getId(), dmg);
        if (DAMAGE_DEFAULT == null || dmg.getPriority() >= DAMAGE_DEFAULT.getPriority()) {
            DAMAGE_DEFAULT = dmg;
        }
        // Put default damage at the end of list every time when new damage is added.
        else {
            DAMAGES.remove(DAMAGE_DEFAULT.getId());
            DAMAGES.put(DAMAGE_DEFAULT.getId(), DAMAGE_DEFAULT);
        }
        ItemStats.updateDefenseByDefault();
    }

    public static void registerDefense(@NotNull DefenseAttribute def) {
        DEFENSES.put(def.getId(), def);
        ItemStats.updateDefenseByDefault();
    }

    public static void registerStat(@NotNull TypedStat stat) {
        if (stat.getCapability() == 0) return; // TODO Log

        STATS.put(stat.getType(), stat);
    }

    public static void registerAmmo(@NotNull AmmoAttribute ammo) {
        AMMO.put(ammo.getType(), ammo);
    }


    public static void registerHand(@NotNull HandAttribute hand) {
        HANDS.put(hand.getType(), hand);
    }

    public static void registerDynamicStat(@NotNull DynamicStat stat) {
        DYNAMIC_STATS.add(stat);
    }

    public static Collection getDynamicStats() {
        return Collections.unmodifiableSet(DYNAMIC_STATS);
    }

    private static void updateDefenseByDefault() {
        if (DAMAGES.isEmpty()) return;

        for (DamageAttribute dmg : ItemStats.getDamages()) {
            Optional opt = ItemStats.getDefenses().stream()
                    .filter(def -> def.isBlockable(dmg))
                    .sorted((def1, def2) -> def2.getPriority() - def1.getPriority())
                    .findFirst();

            if (opt.isPresent()) {
                dmg.setAttachedDefense(opt.get());
                //System.out.println("Attached def: " + opt.get().getId() + " to " + dmg.getId() + " dmg");
            }
        }

        DEFENSE_DEFAULT = ItemStats.getDamageByDefault().getAttachedDefense();
    }

    @NotNull
    public static Collection getDamages() {
        return DAMAGES.values();
    }

    @Nullable
    public static DamageAttribute getDamageById(@NotNull String id) {
        return DAMAGES.get(id.toLowerCase());
    }

    @Nullable
    public static DamageAttribute getDamageByCause(@NotNull DamageCause cause) {
        Optional opt = ItemStats.getDamages().stream()
                .filter(dmg -> dmg.isAttached(cause))
                .sorted((dmg1, dmg2) -> {
                    return dmg2.getPriority() - dmg1.getPriority();
                }).findFirst();

        return opt.isPresent() ? opt.get() : null;
    }

    @NotNull
    public static DamageAttribute getDamageByDefault() {
        return DAMAGE_DEFAULT;
    }

    public static boolean hasDamage(@NotNull ItemStack item, @Nullable Player player) {
        return ItemStats.getDamages().stream().anyMatch(dmg -> ItemStats.hasDamage(item, player, dmg));
    }

    public static boolean hasDamage(@NotNull ItemStack item, @Nullable Player player, @NotNull String id) {
        DamageAttribute dmgType = getDamageById(id);
        if (dmgType == null) return false;

        return ItemStats.hasDamage(item, player, dmgType);
    }

    public static boolean hasDamage(@NotNull ItemStack item,
                                    @Nullable Player player,
                                    @NotNull DamageAttribute dmgType) {
        return dmgType.getTotal(item, player)[1] > 0;
    }

    public static double getDamageMinOrMax(@NotNull ItemStack item,
                                           @Nullable Player player,
                                           @NotNull String id,
                                           int index) {
        DamageAttribute dmgType = getDamageById(id);
        if (dmgType == null) return 0D;

        return dmgType.getTotal(item, player)[index];
    }

    @NotNull
    public static Collection getDefenses() {
        return DEFENSES.values();
    }

    @Nullable
    public static DefenseAttribute getDefenseById(@NotNull String id) {
        return DEFENSES.get(id.toLowerCase());
    }

    public static boolean hasDefense(@NotNull ItemStack item, @Nullable Player player, @NotNull String id) {
        DefenseAttribute defType = getDefenseById(id);
        if (defType == null) return false;

        return ItemStats.hasDefense(item, player, defType);
    }

    public static boolean hasDefense(@NotNull ItemStack item,
                                     @Nullable Player player,
                                     @NotNull DefenseAttribute defType) {
        return defType.getTotal(item, player) != 0;
    }

    @Nullable
    public static DefenseAttribute getDefenseByDefault() {
        return DEFENSE_DEFAULT;
    }

    public static double getDefense(@NotNull ItemStack item, @Nullable Player player, @NotNull String id) {
        DefenseAttribute defType = getDefenseById(id);
        if (defType == null) return 0D;

        return defType.getTotal(item, player);
    }

    @NotNull
    public static Collection getStats() {
        return STATS.values();
    }

    @Nullable
    public static TypedStat getStat(@NotNull SimpleStat.Type type) {
        return STATS.get(type);
    }

    public static double getStat(@NotNull ItemStack item, @Nullable Player player, @NotNull SimpleStat.Type type) {
        return getStat(item, player, type, 0D);
    }

    public static double getStat(@NotNull ItemStack item,
                                 @Nullable Player player,
                                 @NotNull SimpleStat.Type type,
                                 double def) {
        TypedStat stat = getStat(type);
        if (stat instanceof SimpleStat) {
            return ((SimpleStat) stat).getTotal(item, player, def);
        }

        if (stat instanceof DurabilityStat) {
            double[] arr = ((DurabilityStat) stat).getRaw(item);
            return arr == null ? def : arr[0];
        }

        return def;
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    @Nullable
    public static  T getStat(@NotNull Class clazz) {
        for (TypedStat stat : ItemStats.getStats()) {
            Class clazz2 = stat.getClass();
            if (clazz.isAssignableFrom(clazz2)) {
                return (T) stat;
            }
        }
        return null;
    }

    public static boolean hasStat(@NotNull ItemStack item, @Nullable Player player, @NotNull SimpleStat.Type type) {
        TypedStat stat = ItemStats.getStat(type);
        if (stat == null) return false;

        if (stat instanceof SimpleStat) {
            SimpleStat rs = (SimpleStat) stat;
            double     d  = rs.getTotal(item, player);
            return d != 0D;
        }
        if (stat instanceof DurabilityStat) {
            DurabilityStat rs  = (DurabilityStat) stat;
            double[]       arr = rs.getRaw(item);
            return arr != null && arr.length == 2;// && arr[0] != 0D;
        }

        return false;
    }

    // ----------------------------------------------------------------- //

    public static void updateVanillaAttributes(@NotNull ItemStack item, @Nullable Player player) {
        addAttribute(item, player, NBTAttribute.MAX_HEALTH, getStat(item, player, TypedStat.Type.MAX_HEALTH));
        addAttribute(item, player, NBTAttribute.MOVEMENT_SPEED, getStat(item, player, TypedStat.Type.MOVEMENT_SPEED));
        addAttribute(item, player, NBTAttribute.ATTACK_SPEED, getStat(item, player, TypedStat.Type.ATTACK_SPEED));

        double vanilla = DamageAttribute.getVanillaDamage(item);
        if (vanilla > 1)
            addAttribute(item, player, NBTAttribute.ATTACK_DAMAGE, vanilla);
        if (ItemUtils.isArmor(item)) {
            addAttribute(item, player, NBTAttribute.ARMOR, DefenseAttribute.getVanillaArmor(item));
            double toughness = getStat(item, player, TypedStat.Type.ARMOR_TOUGHNESS);
            addAttribute(item,
                    player,
                    NBTAttribute.ARMOR_TOUGHNESS,
                    toughness == 0 ? DefenseAttribute.getVanillaToughness(item) : toughness);
        }
        ItemMeta im = item.getItemMeta();
        im.addItemFlags(ItemFlag.HIDE_ATTRIBUTES);
        item.setItemMeta(im);
    }

    private static void addAttribute(@NotNull ItemStack item,
                                     @Nullable Player player,
                                     @NotNull NBTAttribute att,
                                     double value) {
        ItemMeta meta = item.getItemMeta();
        if (meta == null) return;

        // Bows not needed damage tag at all.
        if (att == NBTAttribute.ATTACK_DAMAGE && ItemUtils.isBow(item)) {
            return;
        }

        // Do not add attribute with zero value
        // except the weapons, they will get vanilla speed.
        if (value == 0) {
            boolean isWeapon      = ItemUtils.isWeapon(item);
            boolean isAttackSpeed = att == NBTAttribute.ATTACK_SPEED;

            if (!isAttackSpeed && isWeapon) {
                return;
            }

            double defaultAttackSpeed = SimpleStat.getDefaultAttackSpeed(item, DEFAULT_ATTACK_SPEED);
            double baseAttackSpeed    = getStat(item, player, TypedStat.Type.BASE_ATTACK_SPEED, 4);
            if (baseAttackSpeed == defaultAttackSpeed) {
                return;
            }
        }

        // Fine values
        if (att == NBTAttribute.MOVEMENT_SPEED) {
            value = 0.1 * (1D + value / 100D) - 0.1;
        } else if (att == NBTAttribute.ATTACK_SPEED) {
            value /= 100D;

            // Attack speed the number of full-strength attacks per second.
            // A base value of 4.0 represents 4 attacks per second.

            boolean isArmor           = ItemUtils.isArmor(item);
            double  weaponModifier    =
                    SimpleStat.getDefaultAttackSpeed(item, DEFAULT_ATTACK_SPEED) + DEFAULT_ATTACK_SPEED;
            double  baseSpeedModifier = getStat(item, player, TypedStat.Type.BASE_ATTACK_SPEED, weaponModifier);
            double  baseSpeed         = baseSpeedModifier - DEFAULT_ATTACK_SPEED;
            if (isArmor) {
                baseSpeed = baseSpeedModifier;
            }

            value = baseSpeed * (1 + value);
        }

        for (EquipmentSlot slot : ItemUtils.getItemSlots(item)) {
            if (slot == EquipmentSlot.OFF_HAND
                    && (att == NBTAttribute.ATTACK_DAMAGE || att == NBTAttribute.ATTACK_SPEED)) continue;
            if (slot != EquipmentSlot.HAND && !ItemUtils.isArmor(item) && att == NBTAttribute.ATTACK_SPEED) continue;

            AttributeModifier am = new AttributeModifier(
                    att.getUUID(slot),
                    att.getNmsName(),
                    value,
                    Operation.ADD_NUMBER,
                    slot);

            meta.removeAttributeModifier(att.getAttribute(), am); // Avoid dupe and error
            meta.addAttributeModifier(att.getAttribute(), am);
        }
        item.setItemMeta(meta);
    }

    @NotNull
    public static Collection getAmmos() {
        return AMMO.values();
    }

    @Nullable
    public static AmmoAttribute getAmmo(@NotNull AmmoAttribute.Type type) {
        return AMMO.get(type);
    }

    @Nullable
    public static AmmoAttribute getAmmo(@NotNull ItemStack item) {
        for (AmmoAttribute ammo : getAmmos()) {
            String value = ammo.getRaw(item);
            if (value != null) {
                return ammo;
            }
        }
        return null;
    }

    // ================================================================================== //


    @NotNull
    public static Collection getHands() {
        return HANDS.values();
    }

    @Nullable
    public static HandAttribute getHand(@NotNull HandAttribute.Type type) {
        return HANDS.get(type);
    }

    @Nullable
    public static HandAttribute getHand(@NotNull ItemStack item) {
        for (HandAttribute hand : getHands()) {
            String value = hand.getRaw(item);
            if (value != null) {
                return hand;
            }
        }
        return null;
    }

    public static void registerAttribute(@NotNull ItemLoreStat att) {
        ATTRIBUTES.put(att.getId(), att);
    }

    @NotNull
    public static Collection> getAttributes() {
        return ATTRIBUTES.values();
    }

    @Nullable
    public static ItemLoreStat getAttribute(@NotNull String id) {
        return ATTRIBUTES.get(id.toLowerCase());
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    @Nullable
    public static > T getAttribute(@NotNull Class clazz) {
        for (ItemLoreStat stat : ItemStats.getAttributes()) {
            Class clazz2 = stat.getClass();
            if (clazz.isAssignableFrom(clazz2)) {
                return (T) stat;
            }
        }
        return null;
    }

    // ====================================================== //

    public static void registerSocket(@NotNull SocketAttribute socket) {
        SocketAttribute.Type         type = socket.getType();
        Map map  = SOCKETS.get(type);
        if (map == null) map = new HashMap<>();

        map.put(socket.getId(), socket);
        SOCKETS.put(type, map);
    }

    @Nullable
    public static SocketAttribute getSocket(@NotNull SocketAttribute.Type type, @NotNull String id) {
        Map map = SOCKETS.get(type);
        if (map == null) return null;

        return map.get(id.toLowerCase());
    }

    @NotNull
    public static Collection getSockets(@NotNull SocketAttribute.Type type) {
        Map map = SOCKETS.get(type);
        if (map == null) return Collections.emptySet();

        return map.values();
    }

    public static void setId(@NotNull ItemStack item, @NotNull String id) {
        DataUT.setData(item, KEY_ID.get(0), id);
    }

    @Nullable
    public static String getId(@NotNull ItemStack item) {
        for (NamespacedKey key : KEY_ID) {
            String data = DataUT.getStringData(item, key);
            if (data != null) return data;
        }
        return null;
    }

    public static void setLevel(@NotNull ItemStack item, int lvl) {
        if (lvl < 1) {
            for (NamespacedKey key : KEY_LEVEL) {
                DataUT.removeData(item, key);
            }
            return;
        }
        DataUT.setData(item, KEY_LEVEL.get(0), lvl);
    }

    public static int getLevel(@NotNull ItemStack item) {
        for (NamespacedKey key : KEY_LEVEL) {
            int data = DataUT.getIntData(item, key);
            if (data != 0) return data;
        }
        return 0;
    }

    // ======================================================== //

    public static void setModule(@NotNull ItemStack item, @NotNull String mod) {
        DataUT.setData(item, KEY_MODULE.get(0), mod);
    }

    @Nullable
    public static QModuleDrop getModule(@NotNull ItemStack item) {
        String data = null;
        for (NamespacedKey key : KEY_MODULE) {
            data = DataUT.getStringData(item, key);
            if (data != null) break;
        }
        if (data == null) return null;

        IModule mod = plugin.getModuleManager().getModule(data);
        if (mod instanceof QModuleDrop) {
            return (QModuleDrop) mod;
        }
        return null;
    }

    public static void setSocketRate(@NotNull ItemStack item, int rate) {
        DataUT.setData(item, KEY_SOCKET.get(0), rate);
    }

    public static int getSocketRate(@NotNull ItemStack item) {
        for (NamespacedKey key : KEY_SOCKET) {
            int data = DataUT.getIntData(item, key);
            if (data != 0) return data;
        }
        return 0;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy