diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/player/PlayerData.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/player/PlayerData.java index 3b41cac7..16d51a5d 100644 --- a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/player/PlayerData.java +++ b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/player/PlayerData.java @@ -1,6 +1,7 @@ package net.Indyuce.mmoitems.api.player; import io.lumine.mythic.lib.MythicLib; +import io.lumine.mythic.lib.api.crafting.recipes.MythicCraftingManager; import io.lumine.mythic.lib.api.item.NBTItem; import io.lumine.mythic.lib.api.player.EquipmentSlot; import io.lumine.mythic.lib.api.player.MMOPlayerData; @@ -216,11 +217,18 @@ public class PlayerData { final VolatileMMOItem item = equipped.getCached(); // Abilities - if (item.hasData(ItemStats.ABILITIES)) + if (item.hasData(ItemStats.ABILITIES) && + + // Do not add this ability if it is offhanded and offhand abilities are disabled + !(equipped.getSlot() == EquipmentSlot.OFF_HAND && MMOItems.plugin.getLanguage().disableOffhandAbilities) && + + // Do not add this ability if it is either of the hand slots, and the player is encumbered, and abilities don't bypass encumbering + !((equipped.getSlot() == EquipmentSlot.MAIN_HAND || equipped.getSlot() == EquipmentSlot.OFF_HAND) && + isEncumbered() && !MMOItems.plugin.getLanguage().abilitiesBypassEncumbering)) + for (AbilityData abilityData : ((AbilityListData) item.getData(ItemStats.ABILITIES)).getAbilities()) { ModifierSource modSource = equipped.getCached().getType().getModifierSource(); - mmoData.getPassiveSkillMap().addModifier(new PassiveSkill("MMOItemsItem", abilityData, equipped.getSlot(), modSource)); - } + mmoData.getPassiveSkillMap().addModifier(new PassiveSkill("MMOItemsItem", abilityData, equipped.getSlot(), modSource));} // Modifier application rules final ModifierSource source = item.getType().getModifierSource(); diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/util/DamageTypeRestrictionSettings.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/util/DamageTypeRestrictionSettings.java new file mode 100644 index 00000000..5e9914a6 --- /dev/null +++ b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/util/DamageTypeRestrictionSettings.java @@ -0,0 +1,445 @@ +package net.Indyuce.mmoitems.api.util; + +import io.lumine.mythic.lib.api.util.ui.SilentNumbers; +import io.lumine.mythic.lib.damage.DamageType; +import io.lumine.mythic.lib.skill.trigger.TriggerType; +import net.Indyuce.mmoitems.MMOItems; +import org.bukkit.configuration.ConfigurationSection; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * The DamageTypeRestriction [DTR] modifier has several built-in features, so it's easier + * to keep them all in this one place, since they also include functions to use them: + *

+ * + Translate the trigger name depending on the DTR
+ * + Change color of damage types / scalings in modifiers
+ * + + */ +public class DamageTypeRestrictionSettings { + + /** + * Read the values directly off a configuration section + * + * @param config The configuration section, supposedly in the plugin config.yml + */ + public DamageTypeRestrictionSettings(@Nullable ConfigurationSection config) { + if (config == null) { + //DTR//MythicCraftingManager.log("\u00a78DTR\u00a7a SET\u00a7c Null config provided"); + return; } + + /* + * damage-type-restrictions: + * + * damage-type-translations: + * MAGIC: "Magic" + * PHYSICAL: "Melee" + * PROJECTILE: "Ranged" + * WEAPON: "Weapon" + * SKILL: "Skill" + * UNARMED: "Unarmed" + * ON_HIT: "Reaction" + * MINION: "Minion" + * DOT: "Lingering" + * + * attack-type-translations: + * WEAPON: "Attack" + * SKILL: "Ability Hit" + * NEITHER: "Damage" + * BOTH: "Ability-Assisted Attack" + * + * damage-type-colors: + * MAGIC: "&9" + * PHYSICAL: "&8" + * WEAPON: "&7" + * SKILL: "&f" + * PROJECTILE: "&a" + * UNARMED: "&e" + * ON_HIT: "&0" + * MINION: "&d" + * DOT: "&3" + */ + ConfigurationSection damageTypeTranslations = config.isConfigurationSection("damage-type-translations") ? config.getConfigurationSection("damage-type-translations") : null; + ConfigurationSection attackTypeTranslations = config.isConfigurationSection("attack-type-translations") ? config.getConfigurationSection("attack-type-translations") : null; + if (damageTypeTranslations == null || attackTypeTranslations == null) { advancedTriggerDisplay = false; } else { + //DTR//MythicCraftingManager.log("\u00a78DTR\u00a7a SET\u00a7a Accepted advanced trigger display"); + + // Both are defined, use advanced trigger display + advancedTriggerDisplay = true; + + // Translate damage types + damageNames.put(DamageType.MAGIC, damageTypeTranslations.getString("MAGIC", "Magic")); + damageNames.put(DamageType.PHYSICAL, damageTypeTranslations.getString("PHYSICAL", "Melee")); + damageNames.put(DamageType.PROJECTILE, damageTypeTranslations.getString("PROJECTILE", "Projectile")); + damageNames.put(DamageType.WEAPON, damageTypeTranslations.getString("WEAPON", "Weapon")); + damageNames.put(DamageType.SKILL, damageTypeTranslations.getString("SKILL", "Skill")); + damageNames.put(DamageType.UNARMED, damageTypeTranslations.getString("UNARMED", "Unarmed")); + damageNames.put(DamageType.ON_HIT, damageTypeTranslations.getString("ON_HIT", "Reaction")); + damageNames.put(DamageType.MINION, damageTypeTranslations.getString("MINION", "Minion")); + damageNames.put(DamageType.DOT, damageTypeTranslations.getString("DOT", "Lingering")); + + // Translate attack types + attackNames.put(AttackType.WEAPON, attackTypeTranslations.getString("WEAPON", "Attack")); + attackNames.put(AttackType.SKILL, attackTypeTranslations.getString("SKILL", "Ability Hit")); + attackNames.put(AttackType.BOTH, attackTypeTranslations.getString("BOTH", "Ability-Assisted Attack")); + attackNames.put(AttackType.NEITHER, attackTypeTranslations.getString("NEITHER", "Damage")); + } + + ConfigurationSection damageTypeColors = config.isConfigurationSection("damage-type-colors") ? config.getConfigurationSection("damage-type-colors") : null; + if (damageTypeColors == null) { advancedRecoloring = false; } else { + //DTR//MythicCraftingManager.log("\u00a78DTR\u00a7a SET\u00a7a Accepted advanced recoloring"); + + // Colors are defined + advancedRecoloring = true; + + // This information actually already exists + for (DamageType dt : DamageType.values()) { + + // Well is there an override? + String colour = damageTypeColors.getString(dt.toString(), null); + if (colour == null) { continue; } + + // Just match the name of the damage type, default to its default color + damageColor.put(dt, colour); + } + } + } + + /** + * @return If trigger names will be translated depending on the DTR, this has the + * advantage of being user-friendly at the disadvantage of having to configure + * names of each modifier combination ~ WEAPON MAGIC !SKILL for example would be + * 'Staff Attack' + */ + public boolean isAdvancedTriggerDisplay() { return advancedTriggerDisplay; } + boolean advancedTriggerDisplay = true; + + /** + * @return If the color of damage type modifiers in modifier names + * and trigger names will be recolored when actually displaying + * into the item. + */ + public boolean isAdvancedRecoloring() { return advancedRecoloring; } + boolean advancedRecoloring = true; + + /** + * Example: + * PROJECTILE -> Ranged + * + * @param type The damage type you intend to translate + * + * @return The player-friendly name off this damage type + */ + @NotNull public String getDamageName(@NotNull DamageType type) { return damageNames.getOrDefault(type, SilentNumbers.titleCaseConversion(type.toString().replace("-", " ").replace("_", " "))); } + @NotNull HashMap damageNames = new HashMap<>(); + + /** + * Example: + * SKILL -> Ability Hit + * + * @param type The attack type you intend to translate + * + * @return The player-friendly name off this attack type + */ + @NotNull public String getAttackName(@NotNull AttackType type) { return attackNames.getOrDefault(type, "Damage"); } + @NotNull HashMap attackNames = new HashMap<>(); + + /** + * Example: + * SKILL &f (default) -> <#FEEFEF> (specified in config) + * + * @param type The damage you intend to get its color + * + * @return The 'translated' color of this damage type, to + * override the default damage colors mostly. + */ + @NotNull public String getDamageColor(@NotNull DamageType type) { return damageColor.getOrDefault(type, SilentNumbers.titleCaseConversion(type.toString().replace("-", " ").replace("_", " "))); } + @NotNull HashMap damageColor = new HashMap<>(); + + @NotNull public static final String SKMOD_DAMAGE_TYPE_DAMAGE = "\u00a7o■"; + @NotNull public static final String SKMOD_DAMAGE_TYPE_BLACK = "\u00a7c!"; + @NotNull public static final String SKMOD_DAMAGE_TYPE_AND = "\u00a77 "; + @NotNull public static final String SKMOD_DAMAGE_TYPE_COMMA = "\u00a77,"; + @NotNull public static final String SKMOD_DAMAGE_TYPE_OR = "\u00a77/"; + + /** + * Usually the displayed name of the trigger is just... the name of the trigger. + *

+ * However, when using the Damage Type skill modifier, this can be misleading; + * for example, the {@link TriggerType#ATTACK} will no longer trigger with any attack. + *

+ * This method will rename it correctly; for example: WEAPON MAGIC = Magic Attack + *

+ * This only supports the trigger {@link TriggerType#ATTACK} + * + * @param trigger The trigger by which this skill fires + * @param attackType The encoded skill modifier value + * + * @return The way the trigger should display in lore. + */ + @NotNull public String getTriggerDisplayName(@NotNull TriggerType trigger, double attackType) { + + // Use the default? + String triggerDisplayName = MMOItems.plugin.getLanguage().getCastingModeName(trigger); + + // If no skill modifiers are used, or the config option is disabled + if (attackType == 0 || !isAdvancedTriggerDisplay()) { + //APP//MythicCraftingManager.log("\u00a78ABT\u00a73 APP\u00a7c No advanced trigger display\u00a7e " + attackType); + return triggerDisplayName; } + + // Currently, only ATTACK trigger is supported + if (!TriggerType.ATTACK.equals(trigger)) { + //APP//MythicCraftingManager.log("\u00a78ABT\u00a73 APP\u00a7c Not a supported trigger"); + return triggerDisplayName; } + + boolean named = false; + boolean orMode = false; + if (attackType < 0) { orMode = true; attackType *= -1; } + String separatorSymbol = (orMode ? SKMOD_DAMAGE_TYPE_OR : SKMOD_DAMAGE_TYPE_AND); + + // Decode + ArrayList white = DamageType.getWhitelist(attackType); + ArrayList black = DamageType.getBlacklist(attackType); + + // Currently, only ATTACK trigger is supported + if (white.isEmpty()) { + //APP//MythicCraftingManager.log("\u00a78ABT\u00a73 APP\u00a7c Empty whitelist, blacklist is not supported"); + return triggerDisplayName; } + + // Special names sector + if (TriggerType.ATTACK.equals(trigger)) { + //APP//MythicCraftingManager.log("\u00a78ABT\u00a73 APP\u00a77 Identified as the ATTACK trigger"); + + // Very specific overrides + if (white.size() == 1 && white.contains(DamageType.MINION)) { + + // Minion Attack + triggerDisplayName = getDamageName(DamageType.MINION) + SKMOD_DAMAGE_TYPE_AND + getAttackName(AttackType.WEAPON); + named = true; + + } else if (white.size() == 1 && white.contains(DamageType.DOT)) { + + // Lingering Attack + triggerDisplayName = getDamageName(DamageType.DOT) + SKMOD_DAMAGE_TYPE_AND + getAttackName(AttackType.WEAPON); + named = true; + + } else if (white.size() == 2 && white.contains(DamageType.MINION) && white.contains(DamageType.PROJECTILE)) { + + // Minion Ranged Attack + triggerDisplayName = getDamageName(DamageType.MINION) + SKMOD_DAMAGE_TYPE_AND + getDamageName(DamageType.PROJECTILE) + SKMOD_DAMAGE_TYPE_AND + getAttackName(AttackType.WEAPON); + named = true; + + } else if (white.size() == 2 && white.contains(DamageType.MINION) && white.contains(DamageType.MAGIC)) { + + // Minion Magic Attack + triggerDisplayName = getDamageName(DamageType.MINION) + SKMOD_DAMAGE_TYPE_AND + getDamageName(DamageType.MAGIC) + SKMOD_DAMAGE_TYPE_AND + getAttackName(AttackType.WEAPON); + named = true; + + } else { + + // Skill, Attack, or generic damage + boolean isWeapon = white.contains(DamageType.WEAPON); + boolean isSkill = white.contains(DamageType.SKILL); + boolean both = isWeapon && isSkill; + boolean neither = !isWeapon && !isSkill; + + // Elemental type + StringBuilder builder = new StringBuilder(); + for (DamageType whitelisted : white) { + + // Ignore weapon and skill + if (whitelisted == DamageType.WEAPON || + whitelisted == DamageType.SKILL) { + continue; } + + // Append separator + if (builder.length() > 1) { builder.append(separatorSymbol); } + + // Append the type + builder + //.append(damageColors(whitelisted.getColor())) // Sawala doesn't think colours are gud + .append(getDamageName(whitelisted)); + } + + String latter; + String built = builder.toString(); + String former = ""; + + // Requires any other damage type? + if (built.length() > 0) { + if (neither) { latter = SKMOD_DAMAGE_TYPE_AND + getAttackName(AttackType.NEITHER); } + else if (both) { latter = SKMOD_DAMAGE_TYPE_AND + getAttackName(AttackType.BOTH); } + else if (isWeapon) { latter = SKMOD_DAMAGE_TYPE_AND + getAttackName(AttackType.WEAPON); } + else { latter = SKMOD_DAMAGE_TYPE_AND + getAttackName(AttackType.SKILL); } + + // Only characterized by weapon + } else { + + if (neither) { latter = getAttackName(AttackType.NEITHER); } + else if (both) { latter = getAttackName(AttackType.BOTH); } + else if (isWeapon) { latter = getAttackName(AttackType.WEAPON); } + else { latter = getAttackName(AttackType.SKILL); } + } + + //APP//MythicCraftingManager.log("\u00a78ABT\u00a73 APP\u00a77 Former:\u00a73 " + former); + //APP//MythicCraftingManager.log("\u00a78ABT\u00a73 APP\u00a77 Built:\u00a73 " + built); + //APP//MythicCraftingManager.log("\u00a78ABT\u00a73 APP\u00a77 Latter:\u00a73 " + latter); + + named = true; + triggerDisplayName = former + built + latter; + } + } + + // Just display the damage type restriction as squares + else if (TriggerType.KILL_ENTITY.equals(trigger)) { + + // Elemental type + StringBuilder builder = new StringBuilder(); + for (DamageType whitelisted : white) { + + // Append separator + if (builder.length() > 1) { builder.append(separatorSymbol); } + + // Append the type + builder + //.append(damageColors(whitelisted.getColor())) // Sawala doesn't think colours are gud + .append(getDamageName(whitelisted)); + } + + named = true; + triggerDisplayName = builder + SKMOD_DAMAGE_TYPE_AND + triggerDisplayName; + } + + // No special name just default it + if (!named) { + + // Append that + triggerDisplayName += " " + damageTypeRestrictionDisplay(separatorSymbol, white, black); + } + + return triggerDisplayName; + } + + /** + * @param damageTypeRestriction Number that encodes for the damage type restriction + * + * @return A nice chain of colored boxes (real) that represents this damage type restriction. + */ + @NotNull String damageTypeRestrictionDisplay(double damageTypeRestriction) { + + boolean orMode = false; + if (damageTypeRestriction < 0) { orMode = true; damageTypeRestriction *= -1; } + String separatorSymbol = (orMode ? SKMOD_DAMAGE_TYPE_OR : SKMOD_DAMAGE_TYPE_AND); + + // Decode + ArrayList white = DamageType.getWhitelist(damageTypeRestriction); + ArrayList black = DamageType.getBlacklist(damageTypeRestriction); + + // Just build the string man + return damageTypeRestrictionDisplay(separatorSymbol, white, black); + } + + /** + * + * @param separatorSymbol Separator symbol to use between whitelisted damage types + * @param white Damage types whitelisted + * @param black Damage types blacklisted + * + * @return A nice chain of colored boxes (real) that represents this damage type restriction. + */ + @NotNull String damageTypeRestrictionDisplay(@NotNull String separatorSymbol, @NotNull ArrayList white, @NotNull ArrayList black) { + + StringBuilder append = new StringBuilder(); + for (DamageType w : white) { + + // Append separator + if (append.length() > 1) { append.append(separatorSymbol); } + + // Append damage + append.append(damageColors(w.getColor())).append(SKMOD_DAMAGE_TYPE_DAMAGE); + } + + // Separator for blacklist + if (append.length() > 1 && black.size() > 0) { append.append(SKMOD_DAMAGE_TYPE_COMMA); } + + for (DamageType b : black) { + + // Append separator + if (append.length() > 1) { append.append(SKMOD_DAMAGE_TYPE_AND); } + + // Append damage + append.append(SKMOD_DAMAGE_TYPE_BLACK).append(damageColors(b.getColor())).append(SKMOD_DAMAGE_TYPE_DAMAGE); + } + + return append.toString(); + } + + /** + * @param in The colored string in the default format + * + * @return Color overridden by user-specified counterpart. + */ + @NotNull public String damageColors(@Nullable String in) { + //SDC//MythicCraftingManager.log("\u00a78ABT\u00a7c SDC\u00a77 Recoloring\u00a7b " + in); + if (in == null) { return ""; } + if (!isAdvancedRecoloring()) { return in; } + + /* + * Sawala's agony color replacements + * + * Because I literally had everything consistent in &8 &a &9 and he was like &4 &2 ; + * what on earth even is ffs for magic damage some ugly ass deep blue (I sleep) + */ + for (DamageType ty : damageColor.keySet()) { + + //SDC//MythicCraftingManager.log("\u00a78ABT\u00a7c SDC\u00a7e +\u00a77 Damage Type\u00a7b " + ty.toString()); + //SDC//MythicCraftingManager.log("\u00a78ABT\u00a7c SDC\u00a7e +\u00a77 Default Col\u00a7b " + ty.getColor() + "O"); + //SDC//MythicCraftingManager.log("\u00a78ABT\u00a7c SDC\u00a7e +\u00a77 Override Cl\u00a7b " + getDamageColor(ty) + "O"); + + // Both & and § + in = in.replace(ty.getColor().replace('\u00a7', '&'), getDamageColor(ty)); + in = in.replace(ty.getColor(), getDamageColor(ty)); + } + //SDC//MythicCraftingManager.log("\u00a78ABT\u00a7c SDC\u00a77 Result\u00a7b " + in); + + // he he ha ha + return in; + } +} + +/** + * A way to classify and translate the result of an {@link io.lumine.mythic.lib.damage.AttackMetadata}, + */ +enum AttackType { + + /** + * Dealing damage with weapons (real), Attacks. + */ + WEAPON, + + /** + * Dealing damage with abilities, Ability Hits. + */ + SKILL, + + /** + * No information on skill or weapon damage, + * treated as just generic Damage. + */ + NEITHER, + + /** + * Both skill and weapon damage types are present, + * this would make sense if the lore of the ability + * implies that your weapon is being used in the attack. + *

+ * Compare simply casting a fireball, vs simply hitting + * with a baton, vs coating the baton with magic fire and + * then attacking with it.
+ * The last case would be an 'Ability-Assisted Attack' + */ + BOTH +} \ No newline at end of file diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/gui/edition/AbilityEdition.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/gui/edition/AbilityEdition.java index bd5f1e1e..a0deeffb 100644 --- a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/gui/edition/AbilityEdition.java +++ b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/gui/edition/AbilityEdition.java @@ -1,5 +1,7 @@ package net.Indyuce.mmoitems.gui.edition; +import io.lumine.mythic.lib.damage.DamageType; +import io.lumine.mythic.lib.skill.handler.SkillHandler; import io.lumine.mythic.lib.skill.trigger.TriggerType; import net.Indyuce.mmoitems.ItemStats; import net.Indyuce.mmoitems.MMOItems; @@ -22,6 +24,8 @@ import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.text.DecimalFormat; import java.util.ArrayList; @@ -64,16 +68,14 @@ public class AbilityEdition extends EditionInventory { abilityItemMeta.setLore(abilityItemLore); abilityItem.setItemMeta(abilityItemMeta); + TriggerType castMode = null; if (ability != null) { String castModeConfigString = getEditedSection().getString("ability." + configKey + ".mode"); String castModeFormat = castModeConfigString == null ? "" : castModeConfigString.toUpperCase().replace(" ", "_").replace("-", "_").replaceAll("[^A-Z0-9_]", ""); - TriggerType castMode; try { castMode = TriggerType.valueOf(castModeFormat); - } catch (RuntimeException exception) { - castMode = null; - } + } catch (RuntimeException ignored) { } ItemStack castModeItem = new ItemStack(Material.ARMOR_STAND); ItemMeta castModeItemMeta = castModeItem.getItemMeta(); @@ -96,6 +98,8 @@ public class AbilityEdition extends EditionInventory { if (ability != null) { ConfigurationSection section = getEditedSection().getConfigurationSection("ability." + configKey); for (String modifier : ability.getHandler().getModifiers()) { + if (!sensibleModifier(modifier, castMode)) { continue; } + ItemStack modifierItem = VersionMaterial.GRAY_DYE.toItem(); ItemMeta modifierItemMeta = modifierItem.getItemMeta(); modifierItemMeta.setDisplayName(ChatColor.GREEN + MMOUtils.caseOnWords(modifier.toLowerCase().replace("-", " "))); @@ -105,9 +109,32 @@ public class AbilityEdition extends EditionInventory { modifierItemLore.add(""); try { + + // Current Value Yeah + NumericStatFormula heuh = new NumericStatFormula(section.get(modifier)); + String currentValue = heuh.toString(); + if (SkillHandler.SKMOD_DAMAGE_TYPE.equals(modifier)) { + double dam = heuh.getBase(); + boolean orMode = dam < 0; + if (orMode) { dam *= -1; } + + // Parse display + ArrayList whitelist = DamageType.getWhitelist(dam); + ArrayList blacklist = DamageType.getBlacklist(dam); + + // I guess append + StringBuilder builder = new StringBuilder(orMode ? "OR" : ""); + for (DamageType white : whitelist) { if (builder.length() > 0) { builder.append(" "); } builder.append(white); } + for (DamageType black : blacklist) { if (builder.length() > 0) { builder.append(" "); } builder.append("!").append(black); } + + // Build Input + currentValue = builder.toString() + " \u00a78(\u00a79" + heuh.toString() + "\u00a78)"; + } + modifierItemLore.add(ChatColor.GRAY + "Current Value: " + ChatColor.GOLD - + (section.contains(modifier) ? new NumericStatFormula(section.get(modifier)).toString() + + (section.contains(modifier) ? currentValue : MODIFIER_FORMAT.format(ability.getDefaultModifier(modifier)))); + } catch (IllegalArgumentException exception) { modifierItemLore.add(ChatColor.GRAY + "Could not read value. Using default"); } @@ -146,6 +173,29 @@ public class AbilityEdition extends EditionInventory { return inv; } + /** + * Some modifiers, like Timer or Damage Type Restriction, + * only make sense if used in the triggers where they are + * supported. + * + * @param modifier Modifier in question + * @param trigger Trigger in question + * + * @return If this modifier makes sense for this trigger + */ + boolean sensibleModifier(@NotNull String modifier, @Nullable TriggerType trigger) { + + // Missing cast mode might as well show all modifiers + if (trigger == null) { return true; } + + // These modifiers only work with the specific trigger + if (modifier.equals(SkillHandler.SKMOD_DAMAGE_TYPE)) { return trigger.equals(TriggerType.ATTACK); } + if (modifier.equals(SkillHandler.SKMOD_TIMER)) { return trigger.equals(TriggerType.TIMER); } + + // Modifier is compatible with any trigger by default + return true; + } + @Override public void whenClicked(InventoryClickEvent event) { ItemStack item = event.getCurrentItem(); diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/manager/ConfigManager.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/manager/ConfigManager.java index 9cf6b78c..83e78b40 100644 --- a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/manager/ConfigManager.java +++ b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/manager/ConfigManager.java @@ -7,6 +7,7 @@ import net.Indyuce.mmoitems.api.ConfigFile; import net.Indyuce.mmoitems.api.ReforgeOptions; import net.Indyuce.mmoitems.api.item.util.ConfigItem; import net.Indyuce.mmoitems.api.item.util.ConfigItems; +import net.Indyuce.mmoitems.api.util.DamageTypeRestrictionSettings; import net.Indyuce.mmoitems.api.util.NumericStatFormula; import net.Indyuce.mmoitems.api.util.message.Message; import net.Indyuce.mmoitems.stat.GemUpgradeScaling; @@ -36,6 +37,7 @@ public class ConfigManager implements Reloadable { // cached config files private ConfigFile loreFormat, stats, dynLore; + private FileConfiguration abilities; // Language private final Map triggerTypeNames = new HashMap<>(); @@ -48,6 +50,8 @@ public class ConfigManager implements Reloadable { public NumericStatFormula defaultItemCapacity; public ReforgeOptions revisionOptions, gemRevisionOptions, phatLootsOptions; public final List opStats = new ArrayList<>(); + public boolean abilitiesBypassEncumbering, disableOffhandAbilities; + public DamageTypeRestrictionSettings damageTypeRestrictionSettings; public ConfigManager() { mkdir("layouts"); @@ -144,7 +148,7 @@ public class ConfigManager implements Reloadable { // Trigger types triggerTypeNames.clear(); - final FileConfiguration abilities = new ConfigFile("/language", "abilities").getConfig(); + abilities = new ConfigFile("/language", "abilities").getConfig(); for (TriggerType type : TriggerType.values()) triggerTypeNames.put(type, abilities.getString("cast-mode." + type.getLowerCaseId(), type.getName())); } @@ -155,6 +159,7 @@ public class ConfigManager implements Reloadable { loreFormat = new ConfigFile("/language", "lore-format"); stats = new ConfigFile("/language", "stats"); dynLore = new ConfigFile("/language", "dynamic-lore"); + abilities = new ConfigFile("/language", "abilities").getConfig(); loadTranslations(); @@ -173,6 +178,8 @@ public class ConfigManager implements Reloadable { keepSoulboundOnDeath = MMOItems.plugin.getConfig().getBoolean("soulbound.keep-on-death"); rerollOnItemUpdate = MMOItems.plugin.getConfig().getBoolean("item-revision.reroll-when-updated"); levelSpread = MMOItems.plugin.getConfig().getDouble("item-level-spread"); + abilitiesBypassEncumbering = MMOItems.plugin.getConfig().getBoolean("abilities-bypass-encumbering", !MMOItems.plugin.getConfig().getBoolean("two-handed-item-restriction", true)); + disableOffhandAbilities = MMOItems.plugin.getConfig().getBoolean("disable-abilities-in-offhand", false); opStatsEnabled = MMOItems.plugin.getConfig().getBoolean("op-item-stats.enabled"); opStats.clear(); @@ -187,6 +194,8 @@ public class ConfigManager implements Reloadable { gemRevisionOptions = gemKeepData != null ? new ReforgeOptions(gemKeepData) : new ReforgeOptions(false, false, false, false, false, false, false, true); phatLootsOptions = phatLoots != null ? new ReforgeOptions(phatLoots) : new ReforgeOptions(false, false, false, false, false, false, false, true); + damageTypeRestrictionSettings = new DamageTypeRestrictionSettings(abilities.getConfigurationSection("damage-type-restriction")); + List exemptedPhatLoots = MMOItems.plugin.getConfig().getStringList("item-revision.disable-phat-loot"); for (String epl : exemptedPhatLoots) phatLootsOptions.addToBlacklist(epl); diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/stat/Abilities.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/stat/Abilities.java index 63a9a223..64e78981 100644 --- a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/stat/Abilities.java +++ b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/stat/Abilities.java @@ -5,8 +5,15 @@ import io.lumine.mythic.lib.MythicLib; import io.lumine.mythic.lib.api.item.ItemTag; import io.lumine.mythic.lib.api.item.SupportedNBTTagValues; import io.lumine.mythic.lib.api.util.AltChar; +import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackCategory; +import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackProvider; +import io.lumine.mythic.lib.api.util.ui.SilentNumbers; +import io.lumine.mythic.lib.damage.DamageType; +import io.lumine.mythic.lib.skill.handler.SkillHandler; import io.lumine.mythic.lib.skill.trigger.TriggerType; import net.Indyuce.mmoitems.MMOItems; +import net.Indyuce.mmoitems.api.util.DamageTypeRestrictionSettings; +import net.Indyuce.mmoitems.api.util.message.FFPMMOItems; import net.Indyuce.mmoitems.util.MMOUtils; import net.Indyuce.mmoitems.api.item.build.ItemStackBuilder; import net.Indyuce.mmoitems.api.item.mmoitem.ReadMMOItem; @@ -56,16 +63,24 @@ public class Abilities extends ItemStat //Modify Lore List abilityLore = new ArrayList<>(); boolean splitter = !MMOItems.plugin.getLanguage().abilitySplitter.equals(""); + DamageTypeRestrictionSettings settings = MMOItems.plugin.getLanguage().damageTypeRestrictionSettings; String modifierFormat = ItemStat.translate("ability-modifier"), abilityFormat = ItemStat.translate("ability-format"); data.getAbilities().forEach(ability -> { - abilityLore.add(abilityFormat.replace("{trigger}", MMOItems.plugin.getLanguage().getCastingModeName(ability.getTrigger())).replace("{ability}", ability.getAbility().getName())); + + // Replace name of trigger, as well as name of ability + String triggerDisplayName = settings.getTriggerDisplayName(ability.getTrigger(), ability.getModifier(SkillHandler.SKMOD_DAMAGE_TYPE)); + abilityLore.add(abilityFormat.replace("{trigger}", triggerDisplayName).replace("{ability}", ability.getAbility().getName())); for (String modifier : ability.getModifiers()) { + + // Damage Type Modifier does not display in lore + if (modifier.equals(SkillHandler.SKMOD_DAMAGE_TYPE)) { continue; } + item.getLore().registerPlaceholder("ability_" + ability.getAbility().getHandler().getId().toLowerCase() + "_" + modifier, MythicLib.plugin.getMMOConfig().decimal.format(ability.getModifier(modifier))); - abilityLore.add(modifierFormat.replace("{modifier}", ability.getAbility().getModifierName(modifier)).replace("{value}", + abilityLore.add(modifierFormat.replace("{modifier}", settings.damageColors(ability.getAbility().getModifierName(modifier))).replace("{value}", MythicLib.plugin.getMMOConfig().decimal.format(ability.getModifier(modifier)))); } @@ -133,8 +148,65 @@ public class Abilities extends ItemStat + ChatColor.GRAY + "."); return; } + String number = message; - new NumericStatFormula(message).fillConfigurationSection(inv.getEditedSection(), "ability." + configKey + "." + edited, + // If we are editing the damage types and the provided value is not a number already + if (SkillHandler.SKMOD_DAMAGE_TYPE.equals(edited) && !SilentNumbers.IntTryParse(number)) { + + // Might come in handy.... + FriendlyFeedbackProvider ffp = new FriendlyFeedbackProvider(FFPMMOItems.get()); + ffp.activatePrefix(true, "Edition"); + boolean failure = false; + + // Or Mode + boolean orMode = message.startsWith("OR "); + if (orMode) { message = message.substring("OR ".length()); } + + // Build arrays + ArrayList whitelisted = new ArrayList<>(); + ArrayList blacklisted = new ArrayList<>(); + + // Split by spaces + String[] typesSplit = message.split(" "); + for (String ty : typesSplit) { + + // Crop blacklist + boolean blacklist = false; + String observed = ty.toUpperCase().replace("-", "_").replace(" ", "_"); + if (observed.startsWith("!")) { observed = observed.substring(1); blacklist = true; } + + // Identify Damage Type + try { + + // Un-parse + DamageType damageType = DamageType.valueOf(observed); + + // Add to the lists + if (blacklist) { blacklisted.add(damageType); } else { whitelisted.add(damageType); } + + // Mention + } catch (IllegalArgumentException ignored) { + + // no + failure = true; + + ffp.log(FriendlyFeedbackCategory.ERROR, "Unknown damage type '$r{0}$b' in '$u{1}$b'. ", observed, ty); + } + } + + // Cancel + if (failure) { + + // Errors + ffp.sendAllTo(inv.getPlayer()); + throw new IllegalArgumentException("$bInvalid input! Please specify damage types to require or blacklist, for example: '$eMAGIC WEAPON !SKILL$b' or '$eOR PHYSICAL PROJECTILE MINION !MAGIC$b'. "); + } + + // Bake number + number = String.valueOf(DamageType.encodeDamageTypeMatch(whitelisted, blacklisted, orMode)); + } + + new NumericStatFormula(number).fillConfigurationSection(inv.getEditedSection(), "ability." + configKey + "." + edited, FormulaSaveOption.NONE); inv.registerTemplateEdition(); inv.getPlayer().sendMessage(MMOItems.plugin.getPrefix() + ChatColor.GOLD + MMOUtils.caseOnWords(edited.replace("-", " ")) + ChatColor.GRAY diff --git a/MMOItems-Dist/src/main/resources/default/language/abilities.yml b/MMOItems-Dist/src/main/resources/default/language/abilities.yml index ef8efc9b..14a98aa1 100644 --- a/MMOItems-Dist/src/main/resources/default/language/abilities.yml +++ b/MMOItems-Dist/src/main/resources/default/language/abilities.yml @@ -21,3 +21,42 @@ cast-mode: trident-hit: Trident Hit damaged-by-entity: Damaged By Entity shift-right-click: Shift Right Click + +# For the On Attack trigger there exists the +# Modifier 'Damage Type Restriction' that supports +# advanced translation logic +damage-type-restrictions: + + ## ----- + ## Translate the damage types + damage-type-translations: + MAGIC: "Magic" + PHYSICAL: "Melee" + PROJECTILE: "Ranged" + WEAPON: "Weapon" + SKILL: "Skill" + UNARMED: "Unarmed" + ON_HIT: "Reaction" + MINION: "Minion" + DOT: "Lingering" + + ## ----- + # Translate the types of attack + attack-type-translations: + WEAPON: "Attack" + SKILL: "Ability Hit" + NEITHER: "Damage" + BOTH: "Ability-Assisted Attack" + + ## ----- + ## Change the color of scalings + # damage-type-colors: + # MAGIC: "&9" + # PHYSICAL: "&8" + # WEAPON: "&7" + # SKILL: "&f" + # PROJECTILE: "&a" + # UNARMED: "&e" + # ON_HIT: "&0" + # MINION: "&d" + # DOT: "&3" \ No newline at end of file diff --git a/pom.xml b/pom.xml index 326ada34..93242623 100644 --- a/pom.xml +++ b/pom.xml @@ -57,7 +57,7 @@ io.lumine MythicLib-dist - 1.5.1-SNAPSHOT + 1.5.2-SNAPSHOT provided