DamageTypeRestriction modifier for ATTACK ability trigger, very fun to use yes. Includes an advanced translation system to express this modifier.

This commit:
(1) Allows the GUI to correctly display the DamageTypeRestriction modifier
(2) Includes code to parse input for the DamageTypeRestriction
(3) Translation system to display the trigger in a more user-friendly manner
(4) Fixes offhand abilities firing even when encumbered by two-handedness
This commit is contained in:
Gunging 2023-03-01 00:31:12 -06:00
parent 7c243e4ac8
commit 0994e6b0c3
7 changed files with 636 additions and 13 deletions

View File

@ -1,6 +1,7 @@
package net.Indyuce.mmoitems.api.player; package net.Indyuce.mmoitems.api.player;
import io.lumine.mythic.lib.MythicLib; 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.item.NBTItem;
import io.lumine.mythic.lib.api.player.EquipmentSlot; import io.lumine.mythic.lib.api.player.EquipmentSlot;
import io.lumine.mythic.lib.api.player.MMOPlayerData; import io.lumine.mythic.lib.api.player.MMOPlayerData;
@ -216,11 +217,18 @@ public class PlayerData {
final VolatileMMOItem item = equipped.getCached(); final VolatileMMOItem item = equipped.getCached();
// Abilities // 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()) { for (AbilityData abilityData : ((AbilityListData) item.getData(ItemStats.ABILITIES)).getAbilities()) {
ModifierSource modSource = equipped.getCached().getType().getModifierSource(); 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 // Modifier application rules
final ModifierSource source = item.getType().getModifierSource(); final ModifierSource source = item.getType().getModifierSource();

View File

@ -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:
* <br><br>
* + Translate the trigger name depending on the DTR <br>
* + Change color of damage types / scalings in modifiers <br>
* +
*/
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<DamageType, String> 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<AttackType, String> 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<DamageType, String> 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.
* <p>
* 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.
* </p>
* This method will rename it correctly; for example: WEAPON MAGIC = Magic Attack
* <p></p>
* 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<DamageType> white = DamageType.getWhitelist(attackType);
ArrayList<DamageType> 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<DamageType> white = DamageType.getWhitelist(damageTypeRestriction);
ArrayList<DamageType> 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<DamageType> white, @NotNull ArrayList<DamageType> 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 <HEX0038C2>;
* what on earth even is <HEX0038C2> 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.
* <br><br>
* Compare simply casting a fireball, vs simply hitting
* with a baton, vs coating the baton with magic fire and
* then attacking with it. <br>
* The last case would be an 'Ability-Assisted Attack'
*/
BOTH
}

View File

@ -1,5 +1,7 @@
package net.Indyuce.mmoitems.gui.edition; 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 io.lumine.mythic.lib.skill.trigger.TriggerType;
import net.Indyuce.mmoitems.ItemStats; import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.MMOItems; import net.Indyuce.mmoitems.MMOItems;
@ -22,6 +24,8 @@ import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList; import java.util.ArrayList;
@ -64,16 +68,14 @@ public class AbilityEdition extends EditionInventory {
abilityItemMeta.setLore(abilityItemLore); abilityItemMeta.setLore(abilityItemLore);
abilityItem.setItemMeta(abilityItemMeta); abilityItem.setItemMeta(abilityItemMeta);
TriggerType castMode = null;
if (ability != null) { if (ability != null) {
String castModeConfigString = getEditedSection().getString("ability." + configKey + ".mode"); String castModeConfigString = getEditedSection().getString("ability." + configKey + ".mode");
String castModeFormat = castModeConfigString == null ? "" String castModeFormat = castModeConfigString == null ? ""
: castModeConfigString.toUpperCase().replace(" ", "_").replace("-", "_").replaceAll("[^A-Z0-9_]", ""); : castModeConfigString.toUpperCase().replace(" ", "_").replace("-", "_").replaceAll("[^A-Z0-9_]", "");
TriggerType castMode;
try { try {
castMode = TriggerType.valueOf(castModeFormat); castMode = TriggerType.valueOf(castModeFormat);
} catch (RuntimeException exception) { } catch (RuntimeException ignored) { }
castMode = null;
}
ItemStack castModeItem = new ItemStack(Material.ARMOR_STAND); ItemStack castModeItem = new ItemStack(Material.ARMOR_STAND);
ItemMeta castModeItemMeta = castModeItem.getItemMeta(); ItemMeta castModeItemMeta = castModeItem.getItemMeta();
@ -96,6 +98,8 @@ public class AbilityEdition extends EditionInventory {
if (ability != null) { if (ability != null) {
ConfigurationSection section = getEditedSection().getConfigurationSection("ability." + configKey); ConfigurationSection section = getEditedSection().getConfigurationSection("ability." + configKey);
for (String modifier : ability.getHandler().getModifiers()) { for (String modifier : ability.getHandler().getModifiers()) {
if (!sensibleModifier(modifier, castMode)) { continue; }
ItemStack modifierItem = VersionMaterial.GRAY_DYE.toItem(); ItemStack modifierItem = VersionMaterial.GRAY_DYE.toItem();
ItemMeta modifierItemMeta = modifierItem.getItemMeta(); ItemMeta modifierItemMeta = modifierItem.getItemMeta();
modifierItemMeta.setDisplayName(ChatColor.GREEN + MMOUtils.caseOnWords(modifier.toLowerCase().replace("-", " "))); modifierItemMeta.setDisplayName(ChatColor.GREEN + MMOUtils.caseOnWords(modifier.toLowerCase().replace("-", " ")));
@ -105,9 +109,32 @@ public class AbilityEdition extends EditionInventory {
modifierItemLore.add(""); modifierItemLore.add("");
try { 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<DamageType> whitelist = DamageType.getWhitelist(dam);
ArrayList<DamageType> 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 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)))); : MODIFIER_FORMAT.format(ability.getDefaultModifier(modifier))));
} catch (IllegalArgumentException exception) { } catch (IllegalArgumentException exception) {
modifierItemLore.add(ChatColor.GRAY + "Could not read value. Using default"); modifierItemLore.add(ChatColor.GRAY + "Could not read value. Using default");
} }
@ -146,6 +173,29 @@ public class AbilityEdition extends EditionInventory {
return inv; 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 @Override
public void whenClicked(InventoryClickEvent event) { public void whenClicked(InventoryClickEvent event) {
ItemStack item = event.getCurrentItem(); ItemStack item = event.getCurrentItem();

View File

@ -7,6 +7,7 @@ import net.Indyuce.mmoitems.api.ConfigFile;
import net.Indyuce.mmoitems.api.ReforgeOptions; import net.Indyuce.mmoitems.api.ReforgeOptions;
import net.Indyuce.mmoitems.api.item.util.ConfigItem; import net.Indyuce.mmoitems.api.item.util.ConfigItem;
import net.Indyuce.mmoitems.api.item.util.ConfigItems; 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.NumericStatFormula;
import net.Indyuce.mmoitems.api.util.message.Message; import net.Indyuce.mmoitems.api.util.message.Message;
import net.Indyuce.mmoitems.stat.GemUpgradeScaling; import net.Indyuce.mmoitems.stat.GemUpgradeScaling;
@ -36,6 +37,7 @@ public class ConfigManager implements Reloadable {
// cached config files // cached config files
private ConfigFile loreFormat, stats, dynLore; private ConfigFile loreFormat, stats, dynLore;
private FileConfiguration abilities;
// Language // Language
private final Map<TriggerType, String> triggerTypeNames = new HashMap<>(); private final Map<TriggerType, String> triggerTypeNames = new HashMap<>();
@ -48,6 +50,8 @@ public class ConfigManager implements Reloadable {
public NumericStatFormula defaultItemCapacity; public NumericStatFormula defaultItemCapacity;
public ReforgeOptions revisionOptions, gemRevisionOptions, phatLootsOptions; public ReforgeOptions revisionOptions, gemRevisionOptions, phatLootsOptions;
public final List<String> opStats = new ArrayList<>(); public final List<String> opStats = new ArrayList<>();
public boolean abilitiesBypassEncumbering, disableOffhandAbilities;
public DamageTypeRestrictionSettings damageTypeRestrictionSettings;
public ConfigManager() { public ConfigManager() {
mkdir("layouts"); mkdir("layouts");
@ -144,7 +148,7 @@ public class ConfigManager implements Reloadable {
// Trigger types // Trigger types
triggerTypeNames.clear(); triggerTypeNames.clear();
final FileConfiguration abilities = new ConfigFile("/language", "abilities").getConfig(); abilities = new ConfigFile("/language", "abilities").getConfig();
for (TriggerType type : TriggerType.values()) for (TriggerType type : TriggerType.values())
triggerTypeNames.put(type, abilities.getString("cast-mode." + type.getLowerCaseId(), type.getName())); 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"); loreFormat = new ConfigFile("/language", "lore-format");
stats = new ConfigFile("/language", "stats"); stats = new ConfigFile("/language", "stats");
dynLore = new ConfigFile("/language", "dynamic-lore"); dynLore = new ConfigFile("/language", "dynamic-lore");
abilities = new ConfigFile("/language", "abilities").getConfig();
loadTranslations(); loadTranslations();
@ -173,6 +178,8 @@ public class ConfigManager implements Reloadable {
keepSoulboundOnDeath = MMOItems.plugin.getConfig().getBoolean("soulbound.keep-on-death"); keepSoulboundOnDeath = MMOItems.plugin.getConfig().getBoolean("soulbound.keep-on-death");
rerollOnItemUpdate = MMOItems.plugin.getConfig().getBoolean("item-revision.reroll-when-updated"); rerollOnItemUpdate = MMOItems.plugin.getConfig().getBoolean("item-revision.reroll-when-updated");
levelSpread = MMOItems.plugin.getConfig().getDouble("item-level-spread"); 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"); opStatsEnabled = MMOItems.plugin.getConfig().getBoolean("op-item-stats.enabled");
opStats.clear(); 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); 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); 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<String> exemptedPhatLoots = MMOItems.plugin.getConfig().getStringList("item-revision.disable-phat-loot"); List<String> exemptedPhatLoots = MMOItems.plugin.getConfig().getStringList("item-revision.disable-phat-loot");
for (String epl : exemptedPhatLoots) for (String epl : exemptedPhatLoots)
phatLootsOptions.addToBlacklist(epl); phatLootsOptions.addToBlacklist(epl);

View File

@ -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.ItemTag;
import io.lumine.mythic.lib.api.item.SupportedNBTTagValues; import io.lumine.mythic.lib.api.item.SupportedNBTTagValues;
import io.lumine.mythic.lib.api.util.AltChar; 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 io.lumine.mythic.lib.skill.trigger.TriggerType;
import net.Indyuce.mmoitems.MMOItems; 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.util.MMOUtils;
import net.Indyuce.mmoitems.api.item.build.ItemStackBuilder; import net.Indyuce.mmoitems.api.item.build.ItemStackBuilder;
import net.Indyuce.mmoitems.api.item.mmoitem.ReadMMOItem; import net.Indyuce.mmoitems.api.item.mmoitem.ReadMMOItem;
@ -56,16 +63,24 @@ public class Abilities extends ItemStat<RandomAbilityListData, AbilityListData>
//Modify Lore //Modify Lore
List<String> abilityLore = new ArrayList<>(); List<String> abilityLore = new ArrayList<>();
boolean splitter = !MMOItems.plugin.getLanguage().abilitySplitter.equals(""); boolean splitter = !MMOItems.plugin.getLanguage().abilitySplitter.equals("");
DamageTypeRestrictionSettings settings = MMOItems.plugin.getLanguage().damageTypeRestrictionSettings;
String modifierFormat = ItemStat.translate("ability-modifier"), abilityFormat = ItemStat.translate("ability-format"); String modifierFormat = ItemStat.translate("ability-modifier"), abilityFormat = ItemStat.translate("ability-format");
data.getAbilities().forEach(ability -> { 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()) { 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, item.getLore().registerPlaceholder("ability_" + ability.getAbility().getHandler().getId().toLowerCase() + "_" + modifier,
MythicLib.plugin.getMMOConfig().decimal.format(ability.getModifier(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)))); MythicLib.plugin.getMMOConfig().decimal.format(ability.getModifier(modifier))));
} }
@ -133,8 +148,65 @@ public class Abilities extends ItemStat<RandomAbilityListData, AbilityListData>
+ ChatColor.GRAY + "."); + ChatColor.GRAY + ".");
return; 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<DamageType> whitelisted = new ArrayList<>();
ArrayList<DamageType> 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); FormulaSaveOption.NONE);
inv.registerTemplateEdition(); inv.registerTemplateEdition();
inv.getPlayer().sendMessage(MMOItems.plugin.getPrefix() + ChatColor.GOLD + MMOUtils.caseOnWords(edited.replace("-", " ")) + ChatColor.GRAY inv.getPlayer().sendMessage(MMOItems.plugin.getPrefix() + ChatColor.GOLD + MMOUtils.caseOnWords(edited.replace("-", " ")) + ChatColor.GRAY

View File

@ -21,3 +21,42 @@ cast-mode:
trident-hit: Trident Hit trident-hit: Trident Hit
damaged-by-entity: Damaged By Entity damaged-by-entity: Damaged By Entity
shift-right-click: Shift Right Click 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"

View File

@ -57,7 +57,7 @@
<dependency> <dependency>
<groupId>io.lumine</groupId> <groupId>io.lumine</groupId>
<artifactId>MythicLib-dist</artifactId> <artifactId>MythicLib-dist</artifactId>
<version>1.5.1-SNAPSHOT</version> <version>1.5.2-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<!-- Jetbrains Annotations --> <!-- Jetbrains Annotations -->