+ */
+public class StatCategoryGUI extends AbstractEditorGUI {
+
+ public StatCategoryGUI(Player player, ItemGeneratorReference itemGenerator) {
+ super(player, 1, "Editor/Item Stats - Category", itemGenerator);
+ }
+
+ @Override
+ public void setContents() {
+ // Slot 1 — General
+ setSlot(1, new Slot(createItem(Material.PAPER,
+ "&eGeneral Stats",
+ "&7Classic typed stats (critical rate, dodge, etc.)",
+ "",
+ "&6Left-Click: &eOpen")) {
+ @Override
+ public void onLeftClick() {
+ openSubMenu(new StatListGUI(player, itemGenerator, EditorGUI.ItemType.ITEM_STATS, "list"));
+ }
+ });
+
+ // Slot 3 — Damage %
+ setSlot(3, new Slot(createItem(Material.IRON_SWORD,
+ "&eDamage Buffs &6(%)",
+ "&7Per-damage-type % buff stats",
+ "",
+ "&6Left-Click: &eOpen")) {
+ @Override
+ public void onLeftClick() {
+ openSubMenu(new StatListGUI(player, itemGenerator, EditorGUI.ItemType.ITEM_STATS, "list-damage-buffs"));
+ }
+ });
+
+ // Slot 5 — Defense %
+ setSlot(5, new Slot(createItem(Material.IRON_CHESTPLATE,
+ "&eDefense Buffs &6(%)",
+ "&7Per-damage-type % defense buff stats",
+ "",
+ "&6Left-Click: &eOpen")) {
+ @Override
+ public void onLeftClick() {
+ openSubMenu(new StatListGUI(player, itemGenerator, EditorGUI.ItemType.ITEM_STATS, "list-defense-buffs"));
+ }
+ });
+
+ // Slot 7 — Penetration
+ setSlot(7, new Slot(createItem(Material.ARROW,
+ "&ePenetration",
+ "&7Per-damage-type penetration stats",
+ "",
+ "&6Left-Click: &eOpen")) {
+ @Override
+ public void onLeftClick() {
+ openSubMenu(new StatListGUI(player, itemGenerator, EditorGUI.ItemType.ITEM_STATS, "list-penetration"));
+ }
+ });
+ }
+}
diff --git a/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/editor/stats/StatGUI.java b/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/editor/stats/StatGUI.java
index e963b508..53cb7e5c 100644
--- a/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/editor/stats/StatGUI.java
+++ b/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/editor/stats/StatGUI.java
@@ -140,6 +140,37 @@ public void onRightClick() {
}
});
}
+
+ // Slot 6 — icon material (cosmetic; shown in StatListGUI)
+ String iconRaw = itemGenerator.getConfig().getString(ItemType.ICON.getPath(this.path), "PAPER");
+ Material iconMat = Material.PAPER;
+ try { iconMat = Material.valueOf(iconRaw.toUpperCase()); } catch (IllegalArgumentException ignored) {}
+ setSlot(6, new Slot(createItem(iconMat,
+ "&eIcon Material",
+ "&bCurrent: &a" + iconRaw,
+ "&6Left-Click: &eSet (type material name)",
+ "&6Right-Click: &eReset to PAPER")) {
+ @Override
+ public void onLeftClick() {
+ sendSetMessage(ItemType.ICON.getTitle(),
+ itemGenerator.getConfig().getString(ItemType.ICON.getPath(path), "PAPER"),
+ s -> {
+ try {
+ Material.valueOf(s.toUpperCase()); // validate
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("Unknown material: " + s);
+ }
+ itemGenerator.getConfig().set(ItemType.ICON.getPath(path), s.toUpperCase());
+ saveAndReopen();
+ });
+ }
+
+ @Override
+ public void onRightClick() {
+ itemGenerator.getConfig().set(ItemType.ICON.getPath(path), "PAPER");
+ saveAndReopen();
+ }
+ });
}
public enum ItemType {
@@ -149,6 +180,7 @@ public enum ItemType {
MAX("max"),
FLAT_RANGE("flat-range"),
ROUND("round"),
+ ICON("icon"),
;
private final String path;
diff --git a/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/editor/stats/StatListGUI.java b/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/editor/stats/StatListGUI.java
index 8e7b6b08..5ab2a4f6 100644
--- a/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/editor/stats/StatListGUI.java
+++ b/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/editor/stats/StatListGUI.java
@@ -17,17 +17,31 @@
public class StatListGUI extends AbstractEditorGUI {
private final EditorGUI.ItemType itemType;
+ /** Full config path to the list section, e.g. "generator.item-stats.list" */
+ private final String listSectionPath;
+ /**
+ * Opens the stat list for a specific sub-section (e.g. "list", "list-damage-buffs").
+ */
+ public StatListGUI(Player player, ItemGeneratorReference itemGenerator,
+ EditorGUI.ItemType itemType, String listSection) {
+ super(player, 6, "Editor/" + itemType.getTitle() + " (" + listSection + ")", itemGenerator);
+ this.itemType = itemType;
+ this.listSectionPath = itemType.getPath() + '.' + listSection;
+ }
+
+ /**
+ * Backward-compatible constructor — defaults to the standard "list" section.
+ */
public StatListGUI(Player player, ItemGeneratorReference itemGenerator, EditorGUI.ItemType itemType) {
- super(player, 6, "Editor/" + itemType.getTitle(), itemGenerator);
- this.itemType = itemType;
+ this(player, itemGenerator, itemType, "list");
}
@Override
public void setContents() {
JYML cfg = itemGenerator.getConfig();
List list = new ArrayList<>();
- ConfigurationSection section = cfg.getConfigurationSection(MainStatsGUI.ItemType.LIST.getPath(this.itemType));
+ ConfigurationSection section = cfg.getConfigurationSection(this.listSectionPath);
if (section != null) {
list.addAll(section.getKeys(false));
}
@@ -70,11 +84,22 @@ public void setContents() {
if (fabledHook != null) itemStack = fabledHook.getAttributeIndicator(entry);
break;
}
+ default: {
+ // For ITEM_STATS categories, read per-entry icon from config
+ String iconKey = this.listSectionPath + '.' + entry + ".icon";
+ String iconName = cfg.getString(iconKey, "PAPER");
+ try {
+ itemStack = new ItemStack(Material.valueOf(iconName.toUpperCase()));
+ } catch (IllegalArgumentException ignored) {
+ itemStack = new ItemStack(Material.PAPER);
+ }
+ break;
+ }
}
if (itemStack == null) {
itemStack = new ItemStack(Material.PAPER);
}
- String path = MainStatsGUI.ItemType.LIST.getPath(this.itemType) + '.' + entry + '.';
+ String path = this.listSectionPath + '.' + entry + '.';
String roundDisplay = this.itemType == EditorGUI.ItemType.FABLED_ATTRIBUTES
? ""
: "&bRound: &a" + cfg.getBoolean(path + "round", false);
@@ -90,13 +115,14 @@ public void setContents() {
roundDisplay,
"",
"&eModify");
+ final String entryPath = this.listSectionPath + '.' + entry;
setSlot(i, new Slot(itemStack) {
@Override
public void onLeftClick() {
openSubMenu(new StatGUI(player,
itemGenerator,
itemType,
- MainStatsGUI.ItemType.LIST.getPath(itemType) + '.' + entry));
+ entryPath));
}
});
diff --git a/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/generators/DuplicableStatGenerator.java b/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/generators/DuplicableStatGenerator.java
new file mode 100644
index 00000000..7f6df08c
--- /dev/null
+++ b/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/generators/DuplicableStatGenerator.java
@@ -0,0 +1,142 @@
+package studio.magemonkey.divinity.modules.list.itemgenerator.generators;
+
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+import org.jetbrains.annotations.NotNull;
+import studio.magemonkey.codex.config.api.JYML;
+import studio.magemonkey.codex.util.NumberUT;
+import studio.magemonkey.codex.util.StringUT;
+import studio.magemonkey.codex.util.random.Rnd;
+import studio.magemonkey.divinity.Divinity;
+import studio.magemonkey.divinity.modules.list.itemgenerator.ItemGeneratorManager;
+import studio.magemonkey.divinity.modules.list.itemgenerator.api.AbstractAttributeGenerator;
+import studio.magemonkey.divinity.modules.list.itemgenerator.api.DamageInformation;
+import studio.magemonkey.divinity.stats.bonus.BonusCalculator;
+import studio.magemonkey.divinity.stats.bonus.StatBonus;
+import studio.magemonkey.divinity.stats.items.api.DuplicableItemLoreStat;
+import studio.magemonkey.divinity.stats.items.api.ItemLoreStat;
+import studio.magemonkey.divinity.utils.LoreUT;
+
+import java.util.*;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+/**
+ * Generator for DuplicableItemLoreStat subtypes (DynamicBuffStat, PenetrationStat).
+ * Each stat independently rolls against its own chance — no global min/max pool.
+ */
+public class DuplicableStatGenerator> extends AbstractAttributeGenerator {
+
+ private final Map attributes;
+
+ public DuplicableStatGenerator(
+ @NotNull Divinity plugin,
+ @NotNull ItemGeneratorManager.GeneratorItem generatorItem,
+ @NotNull String basePath,
+ @NotNull String listSection,
+ @NotNull Collection attributesAll,
+ @NotNull Function idExtractor,
+ @NotNull String placeholder
+ ) {
+ super(plugin, generatorItem, placeholder);
+
+ JYML cfg = generatorItem.getConfig();
+
+ String loreFormatKey = basePath + listSection + ".lore-format";
+ this.loreFormat = StringUT.color(cfg.getStringList(loreFormatKey));
+ this.attributes = new LinkedHashMap<>();
+
+ for (T att : attributesAll) {
+ String path2 = basePath + listSection + "." + idExtractor.apply(att) + ".";
+
+ cfg.addMissing(path2 + "chance", 0D);
+ cfg.addMissing(path2 + "scale-by-level", 1D);
+ cfg.addMissing(path2 + "min", 0D);
+ cfg.addMissing(path2 + "max", 0D);
+ cfg.addMissing(path2 + "flat-range", false);
+ cfg.addMissing(path2 + "round", false);
+
+ if (!this.loreFormat.contains(att.getPlaceholder())) {
+ this.loreFormat.add(att.getPlaceholder());
+ cfg.set(loreFormatKey, this.loreFormat);
+ }
+
+ double chance = cfg.getDouble(path2 + "chance", 0D);
+ double m1 = cfg.getDouble(path2 + "min", 0D);
+ double m2 = cfg.getDouble(path2 + "max", 0D);
+ if (m1 > m2) { double t = m1; m1 = m2; m2 = t; }
+ double scale = cfg.getDouble(path2 + "scale-by-level", 1D);
+ boolean flatRange = cfg.getBoolean(path2 + "flat-range", false);
+ boolean roundValues = cfg.getBoolean(path2 + "round", false);
+
+ this.attributes.put(att, new DamageInformation(chance, m1, m2, scale, flatRange, roundValues));
+ }
+ }
+
+ @Override
+ public void generate(@NotNull ItemStack item, int itemLevel) {
+ ItemMeta meta = item.getItemMeta();
+ if (meta == null) return;
+ List lore = meta.getLore();
+ if (lore == null) return;
+
+ int generatorPos = lore.indexOf(this.placeholder);
+ if (generatorPos < 0) return;
+
+ // Roll each stat independently against its own chance
+ List toApply = new ArrayList<>();
+ for (Map.Entry entry : this.attributes.entrySet()) {
+ DamageInformation info = entry.getValue();
+ if (info.getChance() <= 0) continue;
+ if (Rnd.get(true) < info.getChance()) {
+ toApply.add(entry.getKey());
+ }
+ }
+
+ if (toApply.isEmpty()) {
+ LoreUT.replacePlaceholder(item, this.placeholder, null);
+ return;
+ }
+
+ // Insert lore-format (stat placeholders) and remove the generator marker
+ for (String format : this.getLoreFormat()) {
+ generatorPos = LoreUT.addToLore(lore, generatorPos, format);
+ }
+ lore.remove(this.placeholder);
+ meta.setLore(lore);
+ item.setItemMeta(meta);
+
+ // Generate and write values for each rolled stat
+ for (T stat : toApply) {
+ if (!stat.hasPlaceholder(item)) continue;
+
+ DamageInformation info = this.attributes.get(stat);
+ if (info == null) continue;
+
+ BiFunction vMod =
+ generatorItem.getMaterialModifiers(item, (ItemLoreStat>) stat);
+
+ double vScale = generatorItem.getScaleOfLevel(info.getScaleByLevel(), itemLevel);
+ double vMin = BonusCalculator.SIMPLE_FULL.apply(info.getMin(), Arrays.asList(vMod)) * vScale;
+ double vMax = BonusCalculator.SIMPLE_FULL.apply(info.getMax(), Arrays.asList(vMod)) * vScale;
+ double vFin = NumberUT.round(Rnd.getDouble(vMin, vMax));
+ if (info.isRound()) {
+ vFin = Math.round(vFin);
+ }
+
+ if (vFin != 0) {
+ stat.add(item, new StatBonus(new double[]{vFin}, false, null), -1);
+ }
+
+ for (StatBonus bonus : generatorItem.getClassBonuses((ItemLoreStat>) stat)) {
+ stat.add(item, bonus, -1);
+ }
+ for (StatBonus bonus : generatorItem.getRarityBonuses((ItemLoreStat>) stat)) {
+ stat.add(item, bonus, -1);
+ }
+ for (StatBonus bonus : generatorItem.getMaterialBonuses(item, (ItemLoreStat>) stat)) {
+ stat.add(item, bonus, -1);
+ }
+ }
+ }
+}
diff --git a/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/generators/TypedStatGenerator.java b/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/generators/TypedStatGenerator.java
index aa8b3bcb..faaf1700 100644
--- a/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/generators/TypedStatGenerator.java
+++ b/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/generators/TypedStatGenerator.java
@@ -232,6 +232,7 @@ public void generate(@NotNull ItemStack item, int itemLevel) {
} else if (stat instanceof DurabilityStat) {
DurabilityStat rStat = (DurabilityStat) stat;
rStat.add(item, new double[]{vFin, vFin}, -1);
+ rStat.syncVanillaBar(item);
}
}
diff --git a/src/main/java/studio/magemonkey/divinity/modules/list/magicdust/MagicDustManager.java b/src/main/java/studio/magemonkey/divinity/modules/list/magicdust/MagicDustManager.java
index aaa54da4..a3ded9f4 100644
--- a/src/main/java/studio/magemonkey/divinity/modules/list/magicdust/MagicDustManager.java
+++ b/src/main/java/studio/magemonkey/divinity/modules/list/magicdust/MagicDustManager.java
@@ -171,7 +171,7 @@ public boolean isRateableItem(@NotNull ItemStack target) {
}
public void openGUIPaid(@NotNull Player player, @Nullable ItemStack target, boolean force) {
- if (!force && !Perms.has(player, Perms.MAGIC_DUST_GUI)) {
+ if (!force && !player.hasPermission(Perms.MAGIC_DUST_GUI)) {
plugin.lang().Error_NoPerm.send(player);
return;
}
diff --git a/src/main/java/studio/magemonkey/divinity/modules/list/repair/RepairManager.java b/src/main/java/studio/magemonkey/divinity/modules/list/repair/RepairManager.java
index 5287400f..10c81c35 100644
--- a/src/main/java/studio/magemonkey/divinity/modules/list/repair/RepairManager.java
+++ b/src/main/java/studio/magemonkey/divinity/modules/list/repair/RepairManager.java
@@ -177,7 +177,7 @@ public boolean openAnvilGUI(
@Nullable RepairType type,
boolean isForce) {
- if (!isForce && !Perms.has(player, Perms.REPAIR_GUI)) {
+ if (!isForce && !player.hasPermission(Perms.REPAIR_GUI)) {
plugin.lang().Error_NoPerm.send(player);
return false;
}
@@ -211,6 +211,7 @@ ItemStack getResult(@NotNull ItemStack target, @NotNull Player player) {
double max = arr[1];
ItemStack result = new ItemStack(target);
this.duraStat.add(result, new double[]{max, max}, -1);
+ this.duraStat.syncVanillaBar(result);
return result;
}
@@ -382,6 +383,7 @@ protected boolean onDragDrop(
durNow = (int) Math.min(durMax, durNow + durMax * 1D * (rPerc * 1D / 100D));
this.duraStat.add(target, new double[]{durNow, durMax}, -1);
+ this.duraStat.syncVanillaBar(target);
e.setCurrentItem(target);
if (lost != null) {
diff --git a/src/main/java/studio/magemonkey/divinity/modules/list/sell/SellManager.java b/src/main/java/studio/magemonkey/divinity/modules/list/sell/SellManager.java
index afe101b5..e9fbc5d5 100644
--- a/src/main/java/studio/magemonkey/divinity/modules/list/sell/SellManager.java
+++ b/src/main/java/studio/magemonkey/divinity/modules/list/sell/SellManager.java
@@ -74,7 +74,7 @@ public void shutdown() {
}
public void openSellGUI(@NotNull Player player, boolean isForce) {
- if (!isForce && !Perms.has(player, Perms.SELL_GUI)) {
+ if (!isForce && !player.hasPermission(Perms.SELL_GUI)) {
plugin.lang().Error_NoPerm.send(player);
return;
}
diff --git a/src/main/java/studio/magemonkey/divinity/modules/list/soulbound/SoulboundManager.java b/src/main/java/studio/magemonkey/divinity/modules/list/soulbound/SoulboundManager.java
index 79613e30..17ba685b 100644
--- a/src/main/java/studio/magemonkey/divinity/modules/list/soulbound/SoulboundManager.java
+++ b/src/main/java/studio/magemonkey/divinity/modules/list/soulbound/SoulboundManager.java
@@ -210,7 +210,7 @@ public void onSoulStart(InventoryClickEvent e) {
}
} else {
if (this.hasOwner(item)) {
- if (!this.isOwner(item, p) && !Perms.has(p, Perms.BYPASS_REQ_USER_UNTRADEABLE)) {
+ if (!this.isOwner(item, p) && !p.hasPermission(Perms.BYPASS_REQ_USER_UNTRADEABLE)) {
e.setCancelled(true);
return;
}
diff --git a/src/main/java/studio/magemonkey/divinity/stats/EntityStats.java b/src/main/java/studio/magemonkey/divinity/stats/EntityStats.java
index 0ca1fa00..dd84c6a0 100644
--- a/src/main/java/studio/magemonkey/divinity/stats/EntityStats.java
+++ b/src/main/java/studio/magemonkey/divinity/stats/EntityStats.java
@@ -2,6 +2,7 @@
import lombok.Getter;
import org.bukkit.Material;
+import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance;
import org.bukkit.attribute.AttributeModifier;
import org.bukkit.block.Biome;
@@ -51,6 +52,9 @@
import studio.magemonkey.divinity.stats.items.attributes.DefenseAttribute;
import studio.magemonkey.divinity.stats.items.attributes.api.SimpleStat;
import studio.magemonkey.divinity.stats.items.attributes.api.TypedStat;
+import studio.magemonkey.divinity.hooks.EHook;
+import studio.magemonkey.divinity.hooks.external.FabledHook;
+import studio.magemonkey.divinity.stats.items.attributes.stats.DynamicBuffStat;
import studio.magemonkey.divinity.utils.ItemUtils;
import java.util.*;
@@ -411,7 +415,7 @@ public synchronized List getEquipment() {
return new ArrayList<>(this.inventory);
}
- private void updateInventory() {
+ public void updateInventory() {
this.inventory.clear();
ItemStack[] armor = new ItemStack[0];
@@ -438,6 +442,7 @@ private void updateInventory() {
}
public void updateAll() {
+ if(EngineCfg.LEGACY_COMBAT) return;
if (!EngineCfg.ATTRIBUTES_EFFECTIVE_FOR_MOBS && !this.isPlayer()) {
return;
}
@@ -575,6 +580,171 @@ private void updateBonusAttributes() {
this.applyBonusAttribute(nbt, value);
}
+
+ // MC 1.21+ vanilla attributes — handled separately, not in NBTAttribute enum
+ try {
+ Attribute attr = (Attribute) VersionManager.getNms().getAttribute("SCALE");
+ if (attr != null) this.applyScaleAttribute(attr, calcVanillaStatValue(TypedStat.Type.SCALE));
+ } catch (Exception ignored) {}
+
+ try {
+ Attribute attr = (Attribute) VersionManager.getNms().getAttribute("WATER_MOVEMENT_EFFICIENCY");
+ if (attr != null) applyVanillaAttributeModifier(attr, WATER_MOV_EFF_MODIFIER_UUID,
+ "divinity.water_movement_efficiency", calcVanillaStatValue(TypedStat.Type.WATER_MOVEMENT_EFFICIENCY));
+ } catch (Exception ignored) {}
+
+ try {
+ Attribute attr = (Attribute) VersionManager.getNms().getAttribute("MOVEMENT_EFFICIENCY");
+ if (attr != null) applyVanillaAttributeModifier(attr, MOV_EFF_MODIFIER_UUID,
+ "divinity.movement_efficiency", calcVanillaStatValue(TypedStat.Type.MOVEMENT_EFFICIENCY));
+ } catch (Exception ignored) {}
+
+ try {
+ Attribute attr = (Attribute) VersionManager.getNms().getAttribute("SNEAKING_SPEED");
+ if (attr != null) applyVanillaAttributeModifier(attr, SNEAK_SPEED_MODIFIER_UUID,
+ "divinity.sneaking_speed", calcVanillaStatValue(TypedStat.Type.SNEAKING_SPEED));
+ } catch (Exception ignored) {}
+
+ try {
+ Attribute attr = (Attribute) VersionManager.getNms().getAttribute("BLOCK_BREAK_SPEED");
+ if (attr != null) applyVanillaAttributeModifier(attr, BLOCK_BREAK_SPEED_UUID,
+ "divinity.block_break_speed", calcVanillaStatValue(TypedStat.Type.BLOCK_BREAK_SPEED));
+ } catch (Exception ignored) {}
+
+ try {
+ Attribute attr = (Attribute) VersionManager.getNms().getAttribute("BLOCK_INTERACTION_RANGE");
+ if (attr != null) applyVanillaAttributeModifier(attr, BLOCK_INTERACT_RANGE_UUID,
+ "divinity.block_interaction_range", calcVanillaStatValue(TypedStat.Type.BLOCK_INTERACTION_RANGE));
+ } catch (Exception ignored) {}
+
+ try {
+ Attribute attr = (Attribute) VersionManager.getNms().getAttribute("ENTITY_INTERACTION_RANGE");
+ if (attr != null) applyVanillaAttributeModifier(attr, ENTITY_INTERACT_RANGE_UUID,
+ "divinity.entity_interaction_range", calcVanillaStatValue(TypedStat.Type.ENTITY_INTERACTION_RANGE));
+ } catch (Exception ignored) {}
+
+ try {
+ Attribute attr = (Attribute) VersionManager.getNms().getAttribute("EXPLOSION_KNOCKBACK_RESISTANCE");
+ if (attr != null) applyVanillaAttributeModifier(attr, EXPLOSION_KB_RES_UUID,
+ "divinity.explosion_knockback_resistance", calcVanillaStatValue(TypedStat.Type.EXPLOSION_KNOCKBACK_RESISTANCE));
+ } catch (Exception ignored) {}
+
+ try {
+ Attribute attr = (Attribute) VersionManager.getNms().getAttribute("FALL_DAMAGE_MULTIPLIER");
+ if (attr != null) applyVanillaAttributeModifier(attr, FALL_DAMAGE_MULT_UUID,
+ "divinity.fall_damage_multiplier", calcVanillaStatValue(TypedStat.Type.FALL_DAMAGE_MULTIPLIER));
+ } catch (Exception ignored) {}
+
+ try {
+ Attribute attr = (Attribute) VersionManager.getNms().getAttribute("FLYING_SPEED");
+ if (attr != null) applyVanillaAttributeModifier(attr, FLYING_SPEED_UUID,
+ "divinity.flying_speed", calcVanillaStatValue(TypedStat.Type.FLYING_SPEED));
+ } catch (Exception ignored) {}
+
+ try {
+ Attribute attr = (Attribute) VersionManager.getNms().getAttribute("GRAVITY");
+ if (attr != null) applyVanillaAttributeModifier(attr, GRAVITY_UUID,
+ "divinity.gravity", calcVanillaStatValue(TypedStat.Type.GRAVITY));
+ } catch (Exception ignored) {}
+
+ try {
+ Attribute attr = (Attribute) VersionManager.getNms().getAttribute("JUMP_STRENGTH");
+ if (attr != null) applyVanillaAttributeModifier(attr, JUMP_STRENGTH_UUID,
+ "divinity.jump_strength", calcVanillaStatValue(TypedStat.Type.JUMP_STRENGTH));
+ } catch (Exception ignored) {}
+
+ try {
+ Attribute attr = (Attribute) VersionManager.getNms().getAttribute("MAX_ABSORPTION");
+ if (attr != null) applyVanillaAttributeModifier(attr, MAX_ABSORPTION_UUID,
+ "divinity.max_absorption", calcVanillaStatValue(TypedStat.Type.MAX_ABSORPTION));
+ } catch (Exception ignored) {}
+
+ try {
+ Attribute attr = (Attribute) VersionManager.getNms().getAttribute("MINING_EFFICIENCY");
+ if (attr != null) applyVanillaAttributeModifier(attr, MINING_EFFICIENCY_UUID,
+ "divinity.mining_efficiency", calcVanillaStatValue(TypedStat.Type.MINING_EFFICIENCY));
+ } catch (Exception ignored) {}
+
+ try {
+ Attribute attr = (Attribute) VersionManager.getNms().getAttribute("OXYGEN_BONUS");
+ if (attr != null) applyVanillaAttributeModifier(attr, OXYGEN_BONUS_UUID,
+ "divinity.oxygen_bonus", calcVanillaStatValue(TypedStat.Type.OXYGEN_BONUS));
+ } catch (Exception ignored) {}
+
+ try {
+ Attribute attr = (Attribute) VersionManager.getNms().getAttribute("SAFE_FALL_DISTANCE");
+ if (attr != null) applyVanillaAttributeModifier(attr, SAFE_FALL_DISTANCE_UUID,
+ "divinity.safe_fall_distance", calcVanillaStatValue(TypedStat.Type.SAFE_FALL_DISTANCE));
+ } catch (Exception ignored) {}
+
+ try {
+ Attribute attr = (Attribute) VersionManager.getNms().getAttribute("STEP_HEIGHT");
+ if (attr != null) applyVanillaAttributeModifier(attr, STEP_HEIGHT_UUID,
+ "divinity.step_height", calcVanillaStatValue(TypedStat.Type.STEP_HEIGHT));
+ } catch (Exception ignored) {}
+
+ try {
+ Attribute attr = (Attribute) VersionManager.getNms().getAttribute("SUBMERGED_MINING_SPEED");
+ if (attr != null) applyVanillaAttributeModifier(attr, SUBMERGED_MINING_SPEED_UUID,
+ "divinity.submerged_mining_speed", calcVanillaStatValue(TypedStat.Type.SUBMERGED_MINING_SPEED));
+ } catch (Exception ignored) {}
+ }
+
+ private static final UUID SCALE_MODIFIER_UUID = UUID.fromString("d141e000-5ca1-4000-0000-000000000001");
+ private static final UUID WATER_MOV_EFF_MODIFIER_UUID = UUID.fromString("d141e000-5ca1-4000-0000-000000000002");
+ private static final UUID MOV_EFF_MODIFIER_UUID = UUID.fromString("d141e000-5ca1-4000-0000-000000000003");
+ private static final UUID SNEAK_SPEED_MODIFIER_UUID = UUID.fromString("d141e000-5ca1-4000-0000-000000000004");
+ private static final UUID BLOCK_BREAK_SPEED_UUID = UUID.fromString("d141e000-5ca1-4000-0000-000000000005");
+ private static final UUID BLOCK_INTERACT_RANGE_UUID = UUID.fromString("d141e000-5ca1-4000-0000-000000000006");
+ private static final UUID ENTITY_INTERACT_RANGE_UUID = UUID.fromString("d141e000-5ca1-4000-0000-000000000007");
+ private static final UUID EXPLOSION_KB_RES_UUID = UUID.fromString("d141e000-5ca1-4000-0000-000000000008");
+ private static final UUID FALL_DAMAGE_MULT_UUID = UUID.fromString("d141e000-5ca1-4000-0000-000000000009");
+ private static final UUID FLYING_SPEED_UUID = UUID.fromString("d141e000-5ca1-4000-0000-00000000000a");
+ private static final UUID GRAVITY_UUID = UUID.fromString("d141e000-5ca1-4000-0000-00000000000b");
+ private static final UUID JUMP_STRENGTH_UUID = UUID.fromString("d141e000-5ca1-4000-0000-00000000000c");
+ private static final UUID MAX_ABSORPTION_UUID = UUID.fromString("d141e000-5ca1-4000-0000-00000000000d");
+ private static final UUID MINING_EFFICIENCY_UUID = UUID.fromString("d141e000-5ca1-4000-0000-00000000000e");
+ private static final UUID OXYGEN_BONUS_UUID = UUID.fromString("d141e000-5ca1-4000-0000-00000000000f");
+ private static final UUID SAFE_FALL_DISTANCE_UUID = UUID.fromString("d141e000-5ca1-4000-0000-000000000010");
+ private static final UUID STEP_HEIGHT_UUID = UUID.fromString("d141e000-5ca1-4000-0000-000000000011");
+ private static final UUID SUBMERGED_MINING_SPEED_UUID = UUID.fromString("d141e000-5ca1-4000-0000-000000000012");
+
+ @SuppressWarnings("deprecation")
+ private void applyVanillaAttributeModifier(@NotNull Attribute attr, @NotNull UUID modUuid, @NotNull String modName, double value) {
+ AttributeInstance attInst = this.entity.getAttribute(attr);
+ if (attInst == null) return;
+ for (AttributeModifier mod : new HashSet<>(attInst.getModifiers())) {
+ try {
+ if (modUuid.equals(mod.getUniqueId())) {
+ if (mod.getAmount() == value) return;
+ attInst.removeModifier(mod);
+ break;
+ }
+ } catch (Exception ignored) {}
+ }
+ if (value == 0D) return;
+ attInst.addModifier(new AttributeModifier(modUuid, modName, value, Operation.ADD_NUMBER));
+ }
+
+ @SuppressWarnings("deprecation")
+ private void applyScaleAttribute(@NotNull Attribute scaleAttr, double value) {
+ applyVanillaAttributeModifier(scaleAttr, SCALE_MODIFIER_UUID, "divinity.scale", value);
+ }
+
+ private double calcVanillaStatValue(@NotNull TypedStat.Type type) {
+ TypedStat typedStat = ItemStats.getStat(type);
+ if (!(typedStat instanceof SimpleStat)) return 0D;
+ SimpleStat ss = (SimpleStat) typedStat;
+ List> bonuses = new ArrayList<>();
+ for (ItemStack item : this.getEquipment()) {
+ if (item == null || item.getType().isAir()) continue;
+ bonuses.addAll(ss.get(item, player));
+ }
+ bonuses.addAll(this.getBonuses(ss));
+ double value = BonusCalculator.SIMPLE_FULL.apply(0D, bonuses);
+ value = this.getEffectBonus(ss, false).applyAsDouble(value);
+ if (ss.getCapability() >= 0 && value > ss.getCapability()) value = ss.getCapability();
+ return value / 100D;
}
private void applyBonusAttribute(@NotNull NBTAttribute att, double value) {
@@ -683,6 +853,10 @@ public Map getDamageTypes(boolean safe) {
double value = Rnd.getDouble(range[0], range[1]);
value *= dmgAtt.getDamageModifierByBiome(bio); // Multiply by Biome
value = this.getEffectBonus(dmgAtt, safe).applyAsDouble(value);
+ if (this.isPlayer()) {
+ FabledHook fHook = (FabledHook) Divinity.getInstance().getHook(EHook.SKILL_API);
+ if (fHook != null) value = fHook.applyStatScale(this.player, "damage_" + dmgAtt.getId(), value);
+ }
if (value > 0D) {
map.put(dmgAtt, value);
@@ -716,6 +890,10 @@ public Map getDefenseTypes(boolean safe) {
double value = BonusCalculator.SIMPLE_FULL.apply(0D, bonuses);
value = this.getEffectBonus(dt, safe).applyAsDouble(value);
+ if (this.isPlayer()) {
+ FabledHook fHook = (FabledHook) Divinity.getInstance().getHook(EHook.SKILL_API);
+ if (fHook != null) value = fHook.applyStatScale(this.player, "defense_" + dt.getId(), value);
+ }
if (value > 0D) {
map.put(dt, value);
}
@@ -778,6 +956,56 @@ public double getItemStat(@NotNull SimpleStat.Type type, boolean safe) {
}
}
+ // Apply Fabled attribute/stat scaling if player and Fabled is loaded
+ if (this.isPlayer()) {
+ FabledHook fHook = (FabledHook) Divinity.getInstance().getHook(EHook.SKILL_API);
+ if (fHook != null) {
+ value = fHook.applyStatScale(this.player, type.name().toLowerCase(), value);
+ }
+ }
+
+ return value;
+ }
+
+ public double getPenetration(@NotNull studio.magemonkey.divinity.stats.items.attributes.stats.PenetrationStat pen) {
+ List equip = this.getEquipment();
+ List> bonuses = new ArrayList<>();
+ for (ItemStack item : equip) {
+ if (item == null || item.getType().isAir()) continue;
+ bonuses.addAll(pen.get(item, player));
+ }
+ double value = studio.magemonkey.divinity.stats.bonus.BonusCalculator.SIMPLE_FULL.apply(0D, bonuses);
+ if (pen.getCapacity() >= 0 && value > pen.getCapacity()) {
+ value = pen.getCapacity();
+ }
+ if (this.isPlayer()) {
+ FabledHook fHook = (FabledHook) Divinity.getInstance().getHook(EHook.SKILL_API);
+ if (fHook != null) {
+ value = fHook.applyStatScale(this.player, "penetration_" + pen.getPenId(), value);
+ }
+ }
+ return value;
+ }
+
+ public double getDynamicBuff(@NotNull DynamicBuffStat buff) {
+ List equip = this.getEquipment();
+ List> bonuses = new ArrayList<>();
+ for (ItemStack item : equip) {
+ if (item == null || item.getType().isAir()) continue;
+ bonuses.addAll(buff.get(item, player));
+ }
+ double value = BonusCalculator.SIMPLE_FULL.apply(0D, bonuses);
+ if (buff.getCapacity() >= 0 && value > buff.getCapacity()) {
+ value = buff.getCapacity();
+ }
+ if (this.isPlayer()) {
+ FabledHook fHook = (FabledHook) Divinity.getInstance().getHook(EHook.SKILL_API);
+ if (fHook != null) {
+ String buffKey = (buff.getBuffTarget() == DynamicBuffStat.BuffTarget.DAMAGE ? "damagebuff_" : "defensebuff_")
+ + buff.getBuffId();
+ value = fHook.applyStatScale(this.player, buffKey, value);
+ }
+ }
return value;
}
diff --git a/src/main/java/studio/magemonkey/divinity/stats/ProjectileStats.java b/src/main/java/studio/magemonkey/divinity/stats/ProjectileStats.java
index 58e4fc6c..e81038e1 100644
--- a/src/main/java/studio/magemonkey/divinity/stats/ProjectileStats.java
+++ b/src/main/java/studio/magemonkey/divinity/stats/ProjectileStats.java
@@ -27,8 +27,9 @@ public static ItemStack getSrcWeapon(@NotNull Projectile e) {
return ((Trident) e).getItem();
}
if (!e.hasMetadata(PROJECTILE_SOURCE_WEAPON)) return null;
-
- Object val = e.getMetadata(PROJECTILE_SOURCE_WEAPON).get(0).value();
+ var meta = e.getMetadata(PROJECTILE_SOURCE_WEAPON);
+ if (meta.isEmpty()) return null;
+ Object val = meta.get(0).value();
return (ItemStack) val;
}
@@ -38,8 +39,9 @@ public static void setPower(@NotNull Projectile e, double power) {
public static double getPower(@NotNull Projectile e) {
if (!e.hasMetadata(PROJECTILE_LAUNCH_POWER)) return 1D;
-
- return e.getMetadata(PROJECTILE_LAUNCH_POWER).get(0).asDouble();
+ var meta = e.getMetadata(PROJECTILE_LAUNCH_POWER);
+ if (meta.isEmpty()) return 1D;
+ return meta.get(0).asDouble();
}
public static void setPickable(@NotNull Entity pp, boolean b) {
@@ -48,7 +50,8 @@ public static void setPickable(@NotNull Entity pp, boolean b) {
public static boolean isPickable(@NotNull Entity pp) {
if (!pp.hasMetadata(PROJECTILE_PICKABLE)) return true;
-
- return pp.getMetadata(PROJECTILE_PICKABLE).get(0).asBoolean();
+ var meta = pp.getMetadata(PROJECTILE_PICKABLE);
+ if (meta.isEmpty()) return true;
+ return meta.get(0).asBoolean();
}
}
diff --git a/src/main/java/studio/magemonkey/divinity/stats/bonus/BonusMap.java b/src/main/java/studio/magemonkey/divinity/stats/bonus/BonusMap.java
index 9e4d26ac..65ad5383 100644
--- a/src/main/java/studio/magemonkey/divinity/stats/bonus/BonusMap.java
+++ b/src/main/java/studio/magemonkey/divinity/stats/bonus/BonusMap.java
@@ -14,6 +14,8 @@
import studio.magemonkey.divinity.stats.items.attributes.*;
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.DynamicBuffStat;
+import studio.magemonkey.divinity.stats.items.attributes.stats.PenetrationStat;
import java.util.Collection;
import java.util.HashMap;
@@ -198,6 +200,57 @@ public void loadAmmo(@NotNull JYML cfg, @NotNull String path) {
}
}
+ public void loadDamageBuffs(@NotNull JYML cfg, @NotNull String path) {
+ for (String id : cfg.getSection(path)) {
+ DynamicBuffStat stat = ItemStats.getDamageBuff(id);
+ if (stat == null) continue;
+
+ String sVal = cfg.getString(path + "." + id);
+ if (sVal == null) continue;
+
+ String[] split = sVal.split("%", 2);
+ boolean perc = split.length == 2 && split[1].isEmpty();
+ double val = StringUT.getDouble(split[0], 0, true);
+
+ BiFunction func = (isBonus, apply) -> perc == isBonus ? apply + val : apply;
+ this.bonus.put(stat, func);
+ }
+ }
+
+ public void loadDefenseBuffs(@NotNull JYML cfg, @NotNull String path) {
+ for (String id : cfg.getSection(path)) {
+ DynamicBuffStat stat = ItemStats.getDefenseBuff(id);
+ if (stat == null) continue;
+
+ String sVal = cfg.getString(path + "." + id);
+ if (sVal == null) continue;
+
+ String[] split = sVal.split("%", 2);
+ boolean perc = split.length == 2 && split[1].isEmpty();
+ double val = StringUT.getDouble(split[0], 0, true);
+
+ BiFunction func = (isBonus, apply) -> perc == isBonus ? apply + val : apply;
+ this.bonus.put(stat, func);
+ }
+ }
+
+ public void loadPenetrations(@NotNull JYML cfg, @NotNull String path) {
+ for (String id : cfg.getSection(path)) {
+ PenetrationStat stat = ItemStats.getPenetration(id);
+ if (stat == null) continue;
+
+ String sVal = cfg.getString(path + "." + id);
+ if (sVal == null) continue;
+
+ String[] split = sVal.split("%", 2);
+ boolean perc = split.length == 2 && split[1].isEmpty();
+ double val = StringUT.getDouble(split[0], 0, true);
+
+ BiFunction func = (isBonus, apply) -> perc == isBonus ? apply + val : apply;
+ this.bonus.put(stat, func);
+ }
+ }
+
public void loadHands(@NotNull JYML cfg, @NotNull String path) {
for (String id : cfg.getSection(path)) {
HandAttribute stat;
diff --git a/src/main/java/studio/magemonkey/divinity/stats/items/ItemStats.java b/src/main/java/studio/magemonkey/divinity/stats/items/ItemStats.java
index 0faa6fb2..59aa4954 100644
--- a/src/main/java/studio/magemonkey/divinity/stats/items/ItemStats.java
+++ b/src/main/java/studio/magemonkey/divinity/stats/items/ItemStats.java
@@ -1,6 +1,5 @@
package studio.magemonkey.divinity.stats.items;
-import org.bukkit.Bukkit;
import org.bukkit.Keyed;
import org.bukkit.NamespacedKey;
import org.bukkit.attribute.Attribute;
@@ -20,6 +19,7 @@
import studio.magemonkey.codex.modules.IModule;
import studio.magemonkey.codex.util.DataUT;
import studio.magemonkey.divinity.Divinity;
+import studio.magemonkey.divinity.config.EngineCfg;
import studio.magemonkey.divinity.modules.api.QModuleDrop;
import studio.magemonkey.divinity.stats.items.api.DuplicableItemLoreStat;
import studio.magemonkey.divinity.stats.items.api.DynamicStat;
@@ -29,6 +29,8 @@
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.stats.items.attributes.stats.DynamicBuffStat;
+import studio.magemonkey.divinity.stats.items.attributes.stats.PenetrationStat;
import studio.magemonkey.divinity.utils.ItemUtils;
import java.util.*;
@@ -44,6 +46,9 @@ public class ItemStats {
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 Map DAMAGE_BUFFS = new LinkedHashMap<>();
+ private static final Map DEFENSE_BUFFS = new LinkedHashMap<>();
+ private static final Map PENETRATIONS = new LinkedHashMap<>();
private static final Divinity plugin = Divinity.getInstance();
private static final List KEY_ID =
List.of(new NamespacedKey(plugin, ItemTags.TAG_ITEM_ID),
@@ -95,6 +100,9 @@ public static void clear() {
MULTI_ATTRIBUTES.clear();
DAMAGE_DEFAULT = null;
DEFENSE_DEFAULT = null;
+ DAMAGE_BUFFS.clear();
+ DEFENSE_BUFFS.clear();
+ PENETRATIONS.clear();
}
public static void registerDamage(@NotNull DamageAttribute dmg) {
@@ -138,6 +146,48 @@ public static Collection getDynamicStats() {
return Collections.unmodifiableSet(DYNAMIC_STATS);
}
+ public static void registerDamageBuff(@NotNull DynamicBuffStat buff) {
+ DAMAGE_BUFFS.put(buff.getBuffId(), buff);
+ }
+
+ public static void registerDefenseBuff(@NotNull DynamicBuffStat buff) {
+ DEFENSE_BUFFS.put(buff.getBuffId(), buff);
+ }
+
+ @NotNull
+ public static Collection getDamageBuffs() {
+ return DAMAGE_BUFFS.values();
+ }
+
+ @NotNull
+ public static Collection getDefenseBuffs() {
+ return DEFENSE_BUFFS.values();
+ }
+
+ @Nullable
+ public static DynamicBuffStat getDamageBuff(@NotNull String id) {
+ return DAMAGE_BUFFS.get(id.toLowerCase());
+ }
+
+ @Nullable
+ public static DynamicBuffStat getDefenseBuff(@NotNull String id) {
+ return DEFENSE_BUFFS.get(id.toLowerCase());
+ }
+
+ public static void registerPenetration(@NotNull PenetrationStat pen) {
+ PENETRATIONS.put(pen.getPenId(), pen);
+ }
+
+ @NotNull
+ public static Collection getPenetrations() {
+ return PENETRATIONS.values();
+ }
+
+ @Nullable
+ public static PenetrationStat getPenetration(@NotNull String id) {
+ return PENETRATIONS.get(id.toLowerCase());
+ }
+
private static void updateDefenseByDefault() {
if (DAMAGES.isEmpty()) return;
@@ -308,13 +358,12 @@ public static boolean hasStat(@NotNull ItemStack item, @Nullable Player player,
// ----------------------------------------------------------------- //
public static void updateVanillaAttributes(@NotNull ItemStack item, @Nullable Player player) {
+ if(EngineCfg.FULL_LEGACY || EngineCfg.LEGACY_COMBAT) return;
+
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));
- addAttribute(item,
- player,
- NBTAttribute.KNOCKBACK_RESISTANCE,
- getStat(item, player, TypedStat.Type.KNOCKBACK_RESISTANCE));
+ addAttribute(item, player, NBTAttribute.KNOCKBACK_RESISTANCE, getStat(item, player, TypedStat.Type.KNOCKBACK_RESISTANCE));
double vanilla = DamageAttribute.getVanillaDamage(item);
if (vanilla > 1) addAttribute(item, player, NBTAttribute.ATTACK_DAMAGE, vanilla);
@@ -328,16 +377,12 @@ public static void updateVanillaAttributes(@NotNull ItemStack item, @Nullable Pl
toughness == 0 ? DefenseAttribute.getVanillaToughness(item) : toughness);
}
ItemMeta im = item.getItemMeta();
- if (im == null) {
- im = Bukkit.getItemFactory().getItemMeta(item.getType());
- }
// For 1.20.4+, the HIDE_ATTRIBUTES flag doesn't work unless an attribute has been added that's not the default.
// Note: This only applies to Paper and its forks.
if (Version.CURRENT.isAtLeast(Version.V1_20_R4)) {
Attribute moveSpeed = VersionManager.getNms().getAttribute("MOVEMENT_SPEED");
- if (!im.hasAttributeModifiers()
- || im.getAttributeModifiers(VersionManager.getNms().getAttribute("MOVEMENT_SPEED")) == null) {
+ if (im.getAttributeModifiers(VersionManager.getNms().getAttribute("MOVEMENT_SPEED")) == null) {
//noinspection RedundantCast
im.addAttributeModifier(moveSpeed,
new AttributeModifier(((Keyed) moveSpeed).getKey().getKey(), 0, Operation.ADD_NUMBER));
@@ -352,6 +397,7 @@ private static void addAttribute(@NotNull ItemStack item,
@Nullable Player player,
@NotNull NBTAttribute att,
double value) {
+ //if(EngineCfg.LEGACY_COMBAT) return;
ItemMeta meta = item.getItemMeta();
if (meta == null) return;
diff --git a/src/main/java/studio/magemonkey/divinity/stats/items/ItemTags.java b/src/main/java/studio/magemonkey/divinity/stats/items/ItemTags.java
index 0dd2fc39..16c4a2fb 100644
--- a/src/main/java/studio/magemonkey/divinity/stats/items/ItemTags.java
+++ b/src/main/java/studio/magemonkey/divinity/stats/items/ItemTags.java
@@ -13,7 +13,10 @@ public class ItemTags {
public static final String TAG_ITEM_STAT = "ITEM_STAT_";
public static final String TAG_ITEM_DAMAGE = "ITEM_DAMAGE_";
public static final String TAG_ITEM_DEFENSE = "ITEM_DEFENSE_";
- public static final String TAG_ITEM_FABLED_ATTR = "ITEM_FABLED_ATTR_";
+ public static final String TAG_ITEM_FABLED_ATTR = "ITEM_FABLED_ATTR_";
+ public static final String TAG_ITEM_DAMAGE_BUFF = "ITEM_DAMAGE_BUFF_";
+ public static final String TAG_ITEM_DEFENSE_BUFF = "ITEM_DEFENSE_BUFF_";
+ public static final String TAG_ITEM_PENETRATION = "ITEM_PENETRATION_";
public static final String TAG_REQ_USER_LEVEL = "ITEM_USER_LEVEL";
diff --git a/src/main/java/studio/magemonkey/divinity/stats/items/attributes/api/TypedStat.java b/src/main/java/studio/magemonkey/divinity/stats/items/attributes/api/TypedStat.java
index a611f3d3..9599690b 100644
--- a/src/main/java/studio/magemonkey/divinity/stats/items/attributes/api/TypedStat.java
+++ b/src/main/java/studio/magemonkey/divinity/stats/items/attributes/api/TypedStat.java
@@ -48,37 +48,81 @@ enum Type {
// DIRECT_DAMAGE(ItemType.WEAPON, true, false, true),
// Generic vanilla types
- ARMOR(SimpleStat.ItemType.ARMOR, false, true, true),
- ARMOR_TOUGHNESS(SimpleStat.ItemType.ARMOR, false, true, true),
+ ARMOR(SimpleStat.ItemType.BOTH, false, true, true),
+ ARMOR_TOUGHNESS(SimpleStat.ItemType.BOTH, false, true, true),
ATTACK_SPEED(SimpleStat.ItemType.BOTH, true, true, true),
BASE_ATTACK_SPEED(SimpleStat.ItemType.BOTH, false, true, true),
KNOCKBACK_RESISTANCE(SimpleStat.ItemType.BOTH, false, true, true),
MAX_HEALTH(SimpleStat.ItemType.BOTH, false, true, true),
- MOVEMENT_SPEED(SimpleStat.ItemType.ARMOR, true, true, true),
+ MOVEMENT_SPEED(SimpleStat.ItemType.BOTH, true, true, true),
// All the other types
- AOE_DAMAGE(SimpleStat.ItemType.WEAPON, true, false, true),
- PVP_DAMAGE(SimpleStat.ItemType.WEAPON, true, true, true),
- PVE_DAMAGE(SimpleStat.ItemType.WEAPON, true, true, true),
- DODGE_RATE(SimpleStat.ItemType.ARMOR, true, true, true),
- ACCURACY_RATE(SimpleStat.ItemType.WEAPON, true, true, true),
+ AOE_DAMAGE(SimpleStat.ItemType.BOTH, true, false, true),
+ PVP_DAMAGE(SimpleStat.ItemType.BOTH, true, true, true),
+ PVE_DAMAGE(SimpleStat.ItemType.BOTH, true, true, true),
+ DODGE_RATE(SimpleStat.ItemType.BOTH, true, true, true),
+ ACCURACY_RATE(SimpleStat.ItemType.BOTH, true, true, true),
BLOCK_RATE(SimpleStat.ItemType.BOTH, true, true, true),
- BLOCK_DAMAGE(SimpleStat.ItemType.ARMOR, true, true, true),
+ BLOCK_DAMAGE(SimpleStat.ItemType.BOTH, true, true, true),
LOOT_RATE(SimpleStat.ItemType.BOTH, true, true, true),
- BURN_RATE(SimpleStat.ItemType.WEAPON, true, true, true),
- PVP_DEFENSE(SimpleStat.ItemType.ARMOR, true, false, true),
- PVE_DEFENSE(SimpleStat.ItemType.ARMOR, true, true, true),
- CRITICAL_RATE(SimpleStat.ItemType.WEAPON, true, true, true),
- CRITICAL_DAMAGE(SimpleStat.ItemType.WEAPON, false, false, true),
+ BURN_RATE(SimpleStat.ItemType.BOTH, true, true, true),
+ PVP_DEFENSE(SimpleStat.ItemType.BOTH, true, false, true),
+ PVE_DEFENSE(SimpleStat.ItemType.BOTH, true, true, true),
+ CRITICAL_RATE(SimpleStat.ItemType.BOTH, true, true, true),
+ CRITICAL_DAMAGE(SimpleStat.ItemType.BOTH, false, false, true),
+ SKILL_CRITICAL_RATE(SimpleStat.ItemType.BOTH, true, true, true),
+ SKILL_CRITICAL_DAMAGE(SimpleStat.ItemType.BOTH, false, false, true),
DURABILITY(SimpleStat.ItemType.BOTH, false, true, false),
- PENETRATION(SimpleStat.ItemType.WEAPON, true, true, true),
- VAMPIRISM(SimpleStat.ItemType.WEAPON, true, true, true),
- BLEED_RATE(SimpleStat.ItemType.WEAPON, true, true, true),
- DISARM_RATE(SimpleStat.ItemType.WEAPON, true, true, true),
+ PENETRATION(SimpleStat.ItemType.BOTH, true, true, true),
+ VAMPIRISM(SimpleStat.ItemType.BOTH, true, true, true),
+ BLEED_RATE(SimpleStat.ItemType.BOTH, true, true, true),
+ DISARM_RATE(SimpleStat.ItemType.BOTH, true, true, true),
SALE_PRICE(SimpleStat.ItemType.BOTH, true, true, false),
- THORNMAIL(SimpleStat.ItemType.ARMOR, true, false, true),
+ THORNMAIL(SimpleStat.ItemType.BOTH, true, false, true),
HEALTH_REGEN(SimpleStat.ItemType.BOTH, true, true, true),
MANA_REGEN(SimpleStat.ItemType.BOTH, true, true, true),
+ MAX_MANA(SimpleStat.ItemType.BOTH, false, false, true),
+ CC_RESISTANCE(SimpleStat.ItemType.BOTH, true, false, true),
+ CC_DURATION(SimpleStat.ItemType.BOTH, true, false, true),
+ HEALING_CAST(SimpleStat.ItemType.BOTH, true, false, true),
+ HEALING_RECEIVED(SimpleStat.ItemType.BOTH, true, false, true),
+ //PLACEHOLDERS
+ //MAGIC AND SUMMONS
+ SKILL_EFFECTIVNESS(SimpleStat.ItemType.BOTH, true, true, true),
+ SUMMON_POWER(SimpleStat.ItemType.BOTH, true, true, true),
+ SUMMON_HP(SimpleStat.ItemType.BOTH, true, true, true),
+ SUMMON_DURATION(SimpleStat.ItemType.BOTH, true, true, true),
+ //projectiles
+ PROJECTILE_COUNT(SimpleStat.ItemType.BOTH, false, true, true),
+ PROJECTILE_SPEED(SimpleStat.ItemType.BOTH, true, true, true),
+ //BLEED AND STUN
+ BLEED_STACKS(SimpleStat.ItemType.BOTH, false, true, true),
+ BLEED_DURATION(SimpleStat.ItemType.BOTH, false, true, true),
+ BLEED_DAMAGEBUFF(SimpleStat.ItemType.BOTH, true, true, true),
+ STUN_STACKS(SimpleStat.ItemType.BOTH, false, true, true),
+ STUN_DURATION(SimpleStat.ItemType.BOTH, true, true, true),
+ //
+ // Entity size (maps to generic.scale Bukkit attribute)
+ SCALE(SimpleStat.ItemType.BOTH, false, true, true),
+ // MC 1.21+ vanilla attributes
+ WATER_MOVEMENT_EFFICIENCY(SimpleStat.ItemType.BOTH, true, false, true),
+ MOVEMENT_EFFICIENCY(SimpleStat.ItemType.BOTH, true, false, true),
+ SNEAKING_SPEED(SimpleStat.ItemType.BOTH, true, false, true),
+ // MC 1.20.5+ vanilla attributes
+ BLOCK_BREAK_SPEED(SimpleStat.ItemType.BOTH, true, false, true),
+ BLOCK_INTERACTION_RANGE(SimpleStat.ItemType.BOTH, false, true, true),
+ ENTITY_INTERACTION_RANGE(SimpleStat.ItemType.BOTH, false, true, true),
+ EXPLOSION_KNOCKBACK_RESISTANCE(SimpleStat.ItemType.BOTH, true, false, true),
+ FALL_DAMAGE_MULTIPLIER(SimpleStat.ItemType.BOTH, true, true, true),
+ FLYING_SPEED(SimpleStat.ItemType.BOTH, false, false, true),
+ GRAVITY(SimpleStat.ItemType.BOTH, false, true, true),
+ JUMP_STRENGTH(SimpleStat.ItemType.BOTH, false, false, true),
+ MAX_ABSORPTION(SimpleStat.ItemType.BOTH, false, false, true),
+ MINING_EFFICIENCY(SimpleStat.ItemType.BOTH, false, false, true),
+ OXYGEN_BONUS(SimpleStat.ItemType.BOTH, false, false, true),
+ SAFE_FALL_DISTANCE(SimpleStat.ItemType.BOTH, false, false, true),
+ STEP_HEIGHT(SimpleStat.ItemType.BOTH, false, false, true),
+ SUBMERGED_MINING_SPEED(SimpleStat.ItemType.BOTH, true, false, true),
;
private final SimpleStat.ItemType type;
diff --git a/src/main/java/studio/magemonkey/divinity/stats/items/attributes/stats/DurabilityStat.java b/src/main/java/studio/magemonkey/divinity/stats/items/attributes/stats/DurabilityStat.java
index 3093139b..9e080f47 100644
--- a/src/main/java/studio/magemonkey/divinity/stats/items/attributes/stats/DurabilityStat.java
+++ b/src/main/java/studio/magemonkey/divinity/stats/items/attributes/stats/DurabilityStat.java
@@ -24,8 +24,12 @@
public class DurabilityStat extends ItemLoreStat implements TypedStat {
private double cap;
- public DurabilityStat(@NotNull String name, @NotNull String format, double cap) {
- super(TypedStat.Type.DURABILITY.name(),
+ public DurabilityStat(
+ @NotNull String name,
+ @NotNull String format,
+ double cap) {
+ super(
+ TypedStat.Type.DURABILITY.name(),
name,
format,
"%ITEM_STAT_" + TypedStat.Type.DURABILITY.name() + "%",
@@ -111,7 +115,9 @@ public boolean isBroken(@NotNull ItemStack item) {
return durability != null && durability[0] == 0 && !EngineCfg.ATTRIBUTES_DURABILITY_BREAK_ITEMS;
}
- public boolean reduceDurability(@NotNull LivingEntity li, @NotNull ItemStack item, int amount) {
+ public boolean reduceDurability(
+ @NotNull LivingEntity li, @NotNull ItemStack item, int amount) {
+
if (!(li instanceof Player) && !EngineCfg.ATTRIBUTES_DURABILITY_REDUCE_FOR_MOBS) return false;
if (this.isUnbreakable(item)) return false;
@@ -151,44 +157,38 @@ public boolean reduceDurability(@NotNull LivingEntity li, @NotNull ItemStack ite
}
boolean result = this.add(item, new double[]{lose, max}, -1);
-
- if (result) {
- syncVanillaBar(item, lose, max);
- }
-
+ if (result) syncVanillaBar(item);
return result;
-
- }
-
- @Override
- @NotNull
- public String formatValue(@NotNull ItemStack item, double[] values) {
- return EngineCfg.getDurabilityFormat((int) values[0], (int) values[1]);
}
/**
- * Syncs the durability stat with the vanilla durability bar. Should be called after any change to the durability stat.
- * Note: This method assumes that the durability stat is already updated with the new values before calling it.
- *
- * @param item the item that needs updating
- * @param current the current durability value on the item
- * @param maxCustom the max value possible to be set for the item
+ * Synchronizes the vanilla durability bar to reflect Divinity custom durability as a percentage.
+ * Safeguard: if vanilla bar would show 100% but Divinity dura is not max, vanilla bar shows at least 1 damage.
*/
- public void syncVanillaBar(@NotNull ItemStack item, double current, double maxCustom) {
- ItemMeta meta = item.getItemMeta();
- if (!(meta instanceof Damageable)) return;
-
- Damageable damageable = (Damageable) meta;
+ public void syncVanillaBar(@NotNull ItemStack item) {
+ double[] dur = this.getRaw(item);
+ if (dur == null || dur[1] <= 0) return;
+ if (!(item.getItemMeta() instanceof Damageable)) return;
+ double percent = dur[0] / dur[1];
int maxVanilla = item.getType().getMaxDurability();
if (maxVanilla <= 0) return;
- double percent = current / maxCustom;
- // Scale the vanilla value to the custom percentage
- int vanillaDamage = (int) ((1.0 - percent) * maxVanilla);
+ int vanillaDamage = (int) Math.round(maxVanilla * (1.0 - percent));
+
+ // Safeguard: don't show full vanilla bar when divinity dura is not max
+ if (vanillaDamage == 0 && dur[0] < dur[1]) {
+ vanillaDamage = 1;
+ }
- damageable.setDamage(vanillaDamage);
- item.setItemMeta(damageable);
+ Damageable meta = (Damageable) item.getItemMeta();
+ meta.setDamage(vanillaDamage);
+ item.setItemMeta((ItemMeta) meta);
}
-}
\ No newline at end of file
+ @Override
+ @NotNull
+ public String formatValue(@NotNull ItemStack item, double[] values) {
+ return EngineCfg.getDurabilityFormat((int) values[0], (int) values[1]);
+ }
+}
diff --git a/src/main/java/studio/magemonkey/divinity/stats/items/attributes/stats/DynamicBuffStat.java b/src/main/java/studio/magemonkey/divinity/stats/items/attributes/stats/DynamicBuffStat.java
new file mode 100644
index 00000000..ef72eb97
--- /dev/null
+++ b/src/main/java/studio/magemonkey/divinity/stats/items/attributes/stats/DynamicBuffStat.java
@@ -0,0 +1,156 @@
+package studio.magemonkey.divinity.stats.items.attributes.stats;
+
+import lombok.Getter;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+import org.bukkit.persistence.PersistentDataContainer;
+import org.bukkit.persistence.PersistentDataType;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import studio.magemonkey.codex.util.ItemUT;
+import studio.magemonkey.codex.util.NumberUT;
+import studio.magemonkey.codex.util.StringUT;
+import studio.magemonkey.divinity.config.EngineCfg;
+import studio.magemonkey.divinity.stats.bonus.BonusCalculator;
+import studio.magemonkey.divinity.stats.bonus.StatBonus;
+import studio.magemonkey.divinity.stats.items.ItemStats;
+import studio.magemonkey.divinity.stats.items.ItemTags;
+import studio.magemonkey.divinity.stats.items.api.DuplicableItemLoreStat;
+import studio.magemonkey.divinity.stats.items.api.DynamicStat;
+
+import java.util.*;
+import java.util.function.BiFunction;
+
+public class DynamicBuffStat extends DuplicableItemLoreStat implements DynamicStat {
+
+ public enum BuffTarget { DAMAGE, DEFENSE }
+
+ @Getter
+ private final BuffTarget buffTarget;
+ @Getter
+ private final String buffId;
+ @Getter
+ private final Set hooks;
+ @Getter
+ private final double capacity;
+
+ public DynamicBuffStat(
+ @NotNull BuffTarget buffTarget,
+ @NotNull String buffId,
+ @NotNull String name,
+ @NotNull String format,
+ @NotNull Set hooks,
+ double capacity
+ ) {
+ super(
+ buffTarget.name().toLowerCase() + "_buff_" + buffId.toLowerCase(),
+ name,
+ format,
+ "%" + buffTarget.name() + "_BUFF_" + buffId + "%",
+ buffTarget == BuffTarget.DAMAGE
+ ? ItemTags.TAG_ITEM_DAMAGE_BUFF
+ : ItemTags.TAG_ITEM_DEFENSE_BUFF,
+ StatBonus.DATA_TYPE
+ );
+ this.buffTarget = buffTarget;
+ this.buffId = buffId.toLowerCase();
+ this.hooks = hooks;
+ this.capacity = capacity;
+
+ ItemStats.registerDynamicStat(this);
+ }
+
+ @Override
+ @NotNull
+ public Class getParameterClass() {
+ return StatBonus.class;
+ }
+
+ public boolean isApplicableTo(@NotNull String typeId) {
+ return this.hooks.contains(typeId.toLowerCase());
+ }
+
+ public double getTotal(@NotNull ItemStack item, @Nullable Player player) {
+ return BonusCalculator.SIMPLE_FULL.apply(0D, get(item, player));
+ }
+
+ @NotNull
+ public List> get(@NotNull ItemStack item, @Nullable Player player) {
+ List> bonuses = new ArrayList<>();
+ double base = 0;
+ double percent = 0;
+
+ for (StatBonus bonus : this.getAllRaw(item)) {
+ if (!bonus.meetsRequirement(player)) continue;
+ double[] value = bonus.getValue();
+ if (value.length == 1 && bonus.isPercent()) {
+ percent += value[0];
+ } else {
+ base += value[0];
+ }
+ }
+
+ {
+ double finalBase = base;
+ bonuses.add((isPercent, input) -> isPercent ? input : input + finalBase);
+ double finalPercent = percent;
+ bonuses.add((isPercent, input) -> isPercent ? input + finalPercent : input);
+ }
+
+ return bonuses;
+ }
+
+ @Override
+ @NotNull
+ public String formatValue(@NotNull ItemStack item, @NotNull StatBonus statBonus) {
+ String sVal = NumberUT.format(statBonus.getValue()[0]);
+ if (statBonus.isPercent()) {
+ sVal += EngineCfg.LORE_CHAR_PERCENT;
+ }
+ return sVal;
+ }
+
+ @Override
+ @NotNull
+ public String getFormat(@Nullable Player p, @NotNull ItemStack item, @NotNull StatBonus value) {
+ StatBonus.Condition> condition = value.getCondition();
+ return StringUT.colorFix(super.getFormat(item, value)
+ .replace("%condition%", condition == null || !EngineCfg.LORE_STYLE_REQ_USER_DYN_UPDATE
+ ? ""
+ : condition.getFormat(p, item)));
+ }
+
+ @Override
+ @NotNull
+ public ItemStack updateItem(@Nullable Player p, @NotNull ItemStack item) {
+ ItemMeta meta = item.getItemMeta();
+ if (meta == null) return item;
+
+ int amount = this.getAmount(item);
+ if (amount == 0) return item;
+ List lore = meta.getLore();
+ if (lore == null) return item;
+
+ for (int i = 0; i < amount; i++) {
+ int loreIndex = -1;
+ String metaId = "";
+ for (org.bukkit.NamespacedKey key : this.keys) {
+ metaId = key.getKey() + i;
+ loreIndex = ItemUT.getLoreIndex(item, metaId);
+ if (loreIndex >= 0) break;
+ }
+ if (loreIndex < 0) continue;
+
+ @Nullable StatBonus arr = this.getRaw(item, i);
+ if (arr == null) continue;
+ String formatNew = this.getFormat(p, item, arr);
+ lore.set(loreIndex, formatNew);
+ meta.setLore(lore);
+ item.setItemMeta(meta);
+ ItemUT.addLoreTag(item, metaId, formatNew);
+ }
+
+ return item;
+ }
+}
diff --git a/src/main/java/studio/magemonkey/divinity/stats/items/attributes/stats/PenetrationStat.java b/src/main/java/studio/magemonkey/divinity/stats/items/attributes/stats/PenetrationStat.java
new file mode 100644
index 00000000..3d89fa64
--- /dev/null
+++ b/src/main/java/studio/magemonkey/divinity/stats/items/attributes/stats/PenetrationStat.java
@@ -0,0 +1,161 @@
+package studio.magemonkey.divinity.stats.items.attributes.stats;
+
+import lombok.Getter;
+import org.bukkit.entity.Player;
+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.util.ItemUT;
+import studio.magemonkey.codex.util.NumberUT;
+import studio.magemonkey.codex.util.StringUT;
+import studio.magemonkey.divinity.config.EngineCfg;
+import studio.magemonkey.divinity.stats.bonus.BonusCalculator;
+import studio.magemonkey.divinity.stats.bonus.StatBonus;
+import studio.magemonkey.divinity.stats.items.ItemStats;
+import studio.magemonkey.divinity.stats.items.ItemTags;
+import studio.magemonkey.divinity.stats.items.api.DuplicableItemLoreStat;
+import studio.magemonkey.divinity.stats.items.api.DynamicStat;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.function.BiFunction;
+
+/**
+ * A configurable penetration stat read from penetration.yml.
+ *
+ *
+ *
{@code percent-pen: true} — reduces the victim's effective defense by a percentage.
+ * Works with LEGACY, CUSTOM and FACTOR defense formulas.
+ *
{@code percent-pen: false} — reduces the victim's effective defense by a flat value.
+ * Only applied under the CUSTOM defense formula; ignored for LEGACY/FACTOR.
+ *
+ *
+ * Each stat declares {@code hooks} — a list of damage-type IDs (matching damage.yml keys)
+ * that this penetration applies to.
+ */
+public class PenetrationStat extends DuplicableItemLoreStat implements DynamicStat {
+
+ @Getter private final String penId;
+ @Getter private final Set hooks; // damage-type IDs this pen applies to
+ @Getter private final boolean percentPen; // true = %, false = flat
+ @Getter private final double capacity;
+
+ public PenetrationStat(
+ @NotNull String penId,
+ @NotNull String name,
+ @NotNull String format,
+ @NotNull Set hooks,
+ boolean percentPen,
+ double capacity
+ ) {
+ super(
+ "penetration_" + penId.toLowerCase(),
+ name,
+ format,
+ "%PENETRATION_" + penId.toUpperCase() + "%",
+ ItemTags.TAG_ITEM_PENETRATION,
+ StatBonus.DATA_TYPE
+ );
+ this.penId = penId.toLowerCase();
+ this.hooks = hooks;
+ this.percentPen = percentPen;
+ this.capacity = capacity;
+
+ ItemStats.registerPenetration(this);
+ ItemStats.registerDynamicStat(this);
+ }
+
+ @Override
+ @NotNull
+ public Class getParameterClass() {
+ return StatBonus.class;
+ }
+
+ /** Returns true if this penetration applies to the given damage type. */
+ public boolean isApplicableTo(@NotNull String damageTypeId) {
+ return this.hooks.contains(damageTypeId.toLowerCase());
+ }
+
+ public double getTotal(@NotNull ItemStack item, @Nullable Player player) {
+ return BonusCalculator.SIMPLE_FULL.apply(0D, get(item, player));
+ }
+
+ @NotNull
+ public List> get(@NotNull ItemStack item, @Nullable Player player) {
+ List> bonuses = new ArrayList<>();
+ double base = 0;
+ double percent = 0;
+
+ for (StatBonus bonus : this.getAllRaw(item)) {
+ if (!bonus.meetsRequirement(player)) continue;
+ double[] value = bonus.getValue();
+ if (value.length == 1 && bonus.isPercent()) {
+ percent += value[0];
+ } else {
+ base += value[0];
+ }
+ }
+
+ final double fb = base;
+ final double fp = percent;
+ bonuses.add((isPercent, input) -> isPercent ? input : input + fb);
+ bonuses.add((isPercent, input) -> isPercent ? input + fp : input);
+
+ return bonuses;
+ }
+
+ @Override
+ @NotNull
+ public String getFormat(@Nullable Player p, @NotNull ItemStack item, @NotNull StatBonus value) {
+ StatBonus.Condition> condition = value.getCondition();
+ return StringUT.colorFix(super.getFormat(item, value)
+ .replace("%condition%", condition == null || !EngineCfg.LORE_STYLE_REQ_USER_DYN_UPDATE
+ ? ""
+ : condition.getFormat(p, item)));
+ }
+
+ @Override
+ @NotNull
+ public ItemStack updateItem(@Nullable Player p, @NotNull ItemStack item) {
+ ItemMeta meta = item.getItemMeta();
+ if (meta == null) return item;
+
+ int amount = this.getAmount(item);
+ if (amount == 0) return item;
+ List lore = meta.getLore();
+ if (lore == null) return item;
+
+ for (int i = 0; i < amount; i++) {
+ int loreIndex = -1;
+ String metaId = "";
+ for (org.bukkit.NamespacedKey key : this.keys) {
+ metaId = key.getKey() + i;
+ loreIndex = ItemUT.getLoreIndex(item, metaId);
+ if (loreIndex >= 0) break;
+ }
+ if (loreIndex < 0) continue;
+
+ @Nullable StatBonus arr = this.getRaw(item, i);
+ if (arr == null) continue;
+ String formatNew = this.getFormat(p, item, arr);
+ lore.set(loreIndex, formatNew);
+ meta.setLore(lore);
+ item.setItemMeta(meta);
+ ItemUT.addLoreTag(item, metaId, formatNew);
+ }
+
+ return item;
+ }
+
+ @Override
+ @NotNull
+ public String formatValue(@NotNull ItemStack item, @NotNull StatBonus statBonus) {
+ String sVal = NumberUT.format(statBonus.getValue()[0]);
+ if (statBonus.isPercent()) {
+ sVal += EngineCfg.LORE_CHAR_PERCENT;
+ }
+ return sVal;
+ }
+}
diff --git a/src/main/java/studio/magemonkey/divinity/utils/DivinityProvider.java b/src/main/java/studio/magemonkey/divinity/utils/DivinityProvider.java
index 9a58d397..6eee5500 100644
--- a/src/main/java/studio/magemonkey/divinity/utils/DivinityProvider.java
+++ b/src/main/java/studio/magemonkey/divinity/utils/DivinityProvider.java
@@ -101,10 +101,6 @@ public DivinityItemType getItem(String id) {
public DivinityProvider.DivinityItemType getItem(ItemStack itemStack) {
String id = ItemStats.getId(itemStack);
if (id == null) return null;
- QModuleDrop> module = ItemStats.getModule(itemStack);
- if (module != null) {
- id = module.getId() + ":" + id;
- }
return getItem(id);
}
@@ -118,15 +114,7 @@ public boolean isCustomItemOfId(ItemStack item, String id) {
id = PrefixHelper.stripPrefix(NAMESPACE, id);
String itemId = ItemStats.getId(item);
- if (itemId == null) return false;
-
- String[] split = id.split(":", 2);
- if (split.length < 2) {
- return itemId.equals(id);
- }
-
- QModuleDrop> module = ItemStats.getModule(item);
- return module != null && module.getId().equalsIgnoreCase(split[0]) && itemId.equals(split[1]);
+ return itemId != null && itemId.equals(id);
}
public static class DivinityItemType extends ItemType {
@@ -158,7 +146,7 @@ public String getNamespace() {
@Override
public String getID() {
- return this.moduleItem.getModule().getId() + ":" + this.moduleItem.getId();
+ return this.moduleItem.getId();
}
@Override
diff --git a/src/main/java/studio/magemonkey/divinity/utils/ItemUtils.java b/src/main/java/studio/magemonkey/divinity/utils/ItemUtils.java
index 91d18de2..6be05dbf 100644
--- a/src/main/java/studio/magemonkey/divinity/utils/ItemUtils.java
+++ b/src/main/java/studio/magemonkey/divinity/utils/ItemUtils.java
@@ -33,7 +33,6 @@
import studio.magemonkey.divinity.stats.items.ItemStats;
import studio.magemonkey.divinity.stats.items.attributes.stats.DurabilityStat;
import studio.magemonkey.divinity.stats.items.requirements.ItemRequirements;
-import studio.magemonkey.divinity.Perms;
import studio.magemonkey.divinity.stats.items.requirements.api.UserRequirement;
import studio.magemonkey.divinity.types.ItemGroup;
import studio.magemonkey.divinity.types.ItemSubType;
@@ -63,7 +62,7 @@ public static boolean canUse(@NotNull ItemStack item, @NotNull Player player, bo
if (!Hooks.isNPC(player)) {
for (UserRequirement> req : ItemRequirements.getUserRequirements()) {
- if (!Perms.has(player, req.getBypassPermission()) && !req.canUse(player, item)) {
+ if (!player.hasPermission(req.getBypassPermission()) && !req.canUse(player, item)) {
if (msg) req.getDenyMessage(player, item)
.replace("%item%", ItemUT.getItemName(item))
.replace("%player%", player.getName())
diff --git a/src/main/java/studio/magemonkey/divinity/utils/LoreUT.java b/src/main/java/studio/magemonkey/divinity/utils/LoreUT.java
index b79ec62a..2d10276f 100644
--- a/src/main/java/studio/magemonkey/divinity/utils/LoreUT.java
+++ b/src/main/java/studio/magemonkey/divinity/utils/LoreUT.java
@@ -9,7 +9,9 @@
import studio.magemonkey.divinity.Divinity;
import studio.magemonkey.divinity.config.EngineCfg;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Set;
public class LoreUT {
@@ -127,8 +129,10 @@ public static void replaceEnchants(@NotNull ItemStack item) {
String value = EngineCfg.LORE_STYLE_ENCHANTMENTS_FORMAT_MAIN
.replace("%name%", plugin.lang().getEnchantment(e))
.replace("%value%",
- level > EngineCfg.LORE_STYLE_ENCHANTMENTS_FORMAT_MAX_ROMAN ? String.valueOf(level)
- : NumberUT.toRoman(level));
+ !EngineCfg.LORE_STYLE_ENCHANTMENTS_ROMAN_SYSTEM ? String.valueOf(level)
+ : (level > EngineCfg.LORE_STYLE_ENCHANTMENTS_FORMAT_MAX_ROMAN
+ ? String.valueOf(level)
+ : NumberUT.toRoman(level)));
lore.add(pos, value);
}
meta.setLore(lore);
@@ -136,4 +140,44 @@ public static void replaceEnchants(@NotNull ItemStack item) {
replacePlaceholder(item, "%ENCHANTS%", null);
}
+
+ /**
+ * Removes all enchantments from both the item's NBT/meta and its lore.
+ * Mirrors the same format used by {@link #replaceEnchants} so the generated
+ * lore lines are identified and stripped correctly.
+ * Call this instead of the raw {@code meta.removeEnchant} loop when you
+ * want the lore to stay clean (e.g. the {@code -noenchants} command flag).
+ */
+ public static void removeEnchants(@NotNull ItemStack item) {
+ ItemMeta meta = item.getItemMeta();
+ if (meta == null) return;
+
+ // Snapshot before clearing so we know which lore lines to remove
+ Map enchants = new HashMap<>(meta.getEnchants());
+
+ // Strip lore lines that replaceEnchants() would have added
+ List lore = meta.getLore();
+ if (lore != null) {
+ for (Map.Entry entry : enchants.entrySet()) {
+ Enchantment e = entry.getKey();
+ int level = entry.getValue();
+ String line = EngineCfg.LORE_STYLE_ENCHANTMENTS_FORMAT_MAIN
+ .replace("%name%", plugin.lang().getEnchantment(e))
+ .replace("%value%",
+ !EngineCfg.LORE_STYLE_ENCHANTMENTS_ROMAN_SYSTEM
+ ? String.valueOf(level)
+ : (level > EngineCfg.LORE_STYLE_ENCHANTMENTS_FORMAT_MAX_ROMAN
+ ? String.valueOf(level)
+ : NumberUT.toRoman(level)));
+ lore.remove(studio.magemonkey.codex.util.StringUT.color(line));
+ lore.remove(line); // fallback – uncolored form
+ }
+ lore.remove("%ENCHANTS%"); // placeholder if still present (shouldn't happen after generation)
+ meta.setLore(lore);
+ }
+
+ // Remove from meta enchant map
+ enchants.keySet().forEach(meta::removeEnchant);
+ item.setItemMeta(meta);
+ }
}
diff --git a/src/main/resources/engine.yml b/src/main/resources/engine.yml
index 81ca70bf..52cb5138 100644
--- a/src/main/resources/engine.yml
+++ b/src/main/resources/engine.yml
@@ -63,10 +63,34 @@ attributes:
# When enabled, allows to hold in hand items with requirements that player don't meet.
# Even when this is 'true', item attributes won't be applied to a player until he meet the requirements.
allow-hold-items-you-cant-use: false
+ # When enabled, hides attributes and enchantments by default on all custom items.
+ hide-flags: true
combat:
# Whether to use the old combat formula for calculating defenses
legacy-combat: false
+ # Defense formula. Only used when legacy-combat is false.
+ # Options: FACTOR (minecraft formula), CUSTOM
+ defense-formula: FACTOR
+ # Custom defense formula. Only used when defense-formula is CUSTOM.
+ # Placeholders: damage, defense (sum of all matching defenses after all penetration),
+ # defense_ (individual defense, e.g. defense_weapon, defense_physical),
+ # toughness
+ # NOTE: 'defense' is ALWAYS >= 0 here. Penetration cannot make defense go negative —
+ # all defense values are clamped to 0 before being passed to this formula.
+ # In CUSTOM mode, ALL defenses with matching block-damage-types are summed.
+ # In FACTOR mode, only the highest priority defense is used (1:1).
+ custom-defense-formula: 'damage*(25/(25+defense))'
+ # If true, flat penetration that exceeds the target's total defense amplifies damage.
+ # Example: flatPen=100, defense=30 → overflow=70 → bonus damage via overflow-pen-formula.
+ # Only applies in CUSTOM defense formula mode.
+ overflow-pen-amplifies: false
+ # Formula for bonus damage when flat pen overflow occurs (overflow-pen-amplifies must be true).
+ # Placeholders: damage (incoming damage before defense), overflow (flatPen - defense),
+ # defense (total defense before flat pen was applied)
+ # Result is added on top of the post-defense damage.
+ # Tune this for your server's typical defense and flat pen values.
+ overflow-pen-formula: 'damage*(overflow/100)'
# Shield settings.
shield:
block:
@@ -226,6 +250,7 @@ lore:
format:
main: '&c▸ %name%: %value%'
enchantments:
+ roman-system: true # true = roman numerals (I, II, III...), false = arabic numbers (1, 2, 3...)
format:
main: '&c▸ %name% %value%'
max-roman: 10
diff --git a/src/main/resources/item_stats/stats/damage_buffs_percent.yml b/src/main/resources/item_stats/stats/damage_buffs_percent.yml
new file mode 100644
index 00000000..2b0d6365
--- /dev/null
+++ b/src/main/resources/item_stats/stats/damage_buffs_percent.yml
@@ -0,0 +1,15 @@
+# Damage Buff % stats — auto-generated from damage.yml on first server start.
+# Each entry buffs the listed damage type(s) by a percentage.
+# You can manually add cross-type buffs by listing multiple IDs under 'hook'.
+#
+# Example:
+# slashing_and_piercing:
+# enabled: true
+# name: '&3Slashing & Piercing Buff %'
+# format: '&3▸ %name%: &f%value%%condition%'
+# capacity: -1
+# hook:
+# - slashing
+# - piercing
+# PLACEHOLDER: %DAMAGE_BUFF_{ID}%
+
diff --git a/src/main/resources/item_stats/stats/defense_buffs_percent.yml b/src/main/resources/item_stats/stats/defense_buffs_percent.yml
new file mode 100644
index 00000000..3db96034
--- /dev/null
+++ b/src/main/resources/item_stats/stats/defense_buffs_percent.yml
@@ -0,0 +1,14 @@
+# Defense Buff % stats — auto-generated from defense.yml on first server start.
+# Each entry buffs the listed defense type(s) by a percentage.
+# You can manually add cross-type buffs by listing multiple IDs under 'hook'.
+#
+# Example:
+# physical_and_pierce:
+# enabled: true
+# name: '&9Physical & Pierce Defense Buff %'
+# format: '&9▸ %name%: &f%value%%condition%'
+# capacity: -1
+# hook:
+# - physical
+# - pierce
+# PLACEHOLDER: %DEFENSE_BUFF_{ID}%
diff --git a/src/main/resources/item_stats/stats/general_stats.yml b/src/main/resources/item_stats/stats/general_stats.yml
new file mode 100644
index 00000000..79c60532
--- /dev/null
+++ b/src/main/resources/item_stats/stats/general_stats.yml
@@ -0,0 +1,402 @@
+#DIRECT_DAMAGE:
+# enabled: true
+# name: Direct Damage
+# format: '&f▸ %name%: &f%value%'
+# capacity: 100.0
+
+# Vanilla Stats
+ARMOR:
+ enabled: true
+ name: Armor
+ format: '&3▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+ARMOR_TOUGHNESS:
+ enabled: true
+ name: Armor Toughness
+ format: '&9▸ %name%: &f%value% %condition%'
+ capacity: 100.0
+
+BASE_ATTACK_SPEED:
+ enabled: true
+ name: Base Attack Speed
+ format: '&e▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+ATTACK_SPEED:
+ enabled: true
+ name: Attack Speed
+ format: '&e▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+KNOCKBACK_RESISTANCE:
+ enabled: true
+ name: Knockback Resistance
+ format: '&e▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+MOVEMENT_SPEED:
+ enabled: true
+ name: Movement Speed
+ format: '&e▸ %name%: &f%value% %condition%'
+ capacity: 70.0
+
+MAX_HEALTH:
+ enabled: true
+ name: Max Health
+ format: '&c▸ %name%: &f%value% %condition%'
+ capacity: -1
+BLOCK_BREAK_SPEED:
+ enabled: true
+ name: Block Break Speed
+ format: '&3▸ %name%: &f%value% %condition%'
+ capacity: 200.0
+
+BLOCK_INTERACTION_RANGE:
+ enabled: true
+ name: Block Reach
+ format: '&3▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+SCALE:
+ enabled: true
+ name: Scale
+ format: '&b▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+WATER_MOVEMENT_EFFICIENCY:
+ enabled: true
+ name: Water Movement Efficiency
+ format: '&3▸ %name%: &f%value% %condition%'
+ capacity: 100.0
+
+MOVEMENT_EFFICIENCY:
+ enabled: true
+ name: Movement Efficiency
+ format: '&3▸ %name%: &f%value% %condition%'
+ capacity: 100.0
+
+SNEAKING_SPEED:
+ enabled: true
+ name: Sneaking Speed
+ format: '&3▸ %name%: &f%value% %condition%'
+ capacity: 70.0
+
+ENTITY_INTERACTION_RANGE:
+ enabled: true
+ name: Entity Reach
+ format: '&3▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+EXPLOSION_KNOCKBACK_RESISTANCE:
+ enabled: true
+ name: Explosion KB Resistance
+ format: '&3▸ %name%: &f%value% %condition%'
+ capacity: 100.0
+
+FALL_DAMAGE_MULTIPLIER:
+ enabled: true
+ name: Fall Damage Modifier
+ format: '&3▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+FLYING_SPEED:
+ enabled: true
+ name: Flying Speed
+ format: '&3▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+GRAVITY:
+ enabled: true
+ name: Gravity
+ format: '&3▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+JUMP_STRENGTH:
+ enabled: true
+ name: Jump Strength
+ format: '&3▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+MAX_ABSORPTION:
+ enabled: true
+ name: Max Absorption
+ format: '&3▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+MINING_EFFICIENCY:
+ enabled: true
+ name: Mining Efficiency
+ format: '&3▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+OXYGEN_BONUS:
+ enabled: true
+ name: Oxygen Bonus
+ format: '&3▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+SAFE_FALL_DISTANCE:
+ enabled: true
+ name: Safe Fall Distance
+ format: '&3▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+STEP_HEIGHT:
+ enabled: true
+ name: Step Height
+ format: '&3▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+SUBMERGED_MINING_SPEED:
+ enabled: true
+ name: Submerged Mining Speed
+ format: '&3▸ %name%: &f%value% %condition%'
+ capacity: 100.0
+# Custom Stats
+
+MAX_MANA:
+ enabled: true
+ name: Max Mana
+ format: '&9▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+AOE_DAMAGE:
+ enabled: true
+ name: AoE Damage
+ format: '&3▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+PVP_DAMAGE:
+ enabled: true
+ name: PvP Damage
+ format: '&b▸ %name%: &f%value% %condition%'
+ capacity: 200.0
+
+PVE_DAMAGE:
+ enabled: true
+ name: PvE Damage
+ format: '&b▸ %name%: &f%value% %condition%'
+ capacity: 200.0
+
+DODGE_RATE:
+ enabled: true
+ name: Dodge Rate
+ format: '&6▸ %name%: &f%value% %condition%'
+ capacity: 45.0
+
+ACCURACY_RATE:
+ enabled: true
+ name: Accuracy Rate
+ format: '&6▸ %name%: &f%value% %condition%'
+ capacity: 30.0
+
+BLOCK_RATE:
+ enabled: true
+ name: Block Rate
+ format: '&6▸ %name%: &f%value% %condition%'
+ capacity: 100.0
+
+BLOCK_DAMAGE:
+ enabled: true
+ name: Block Damage
+ format: '&6▸ %name%: &f%value% %condition%'
+ capacity: 100.0
+
+CRITICAL_RATE:
+ enabled: true
+ name: Crit. Strike Rate
+ format: '&a▸ %name%: &f%value% %condition%'
+ capacity: 100.0
+
+CRITICAL_DAMAGE:
+ enabled: true
+ name: Crit. Strike Dmg
+ format: '&a▸ %name%: &f%value% %condition%'
+ capacity: 3.5
+
+SKILL_CRITICAL_RATE:
+ enabled: true
+ name: Skill Crit. Rate
+ format: '&a▸ %name%: &f%value% %condition%'
+ capacity: 100.0
+
+SKILL_CRITICAL_DAMAGE:
+ enabled: true
+ name: Skill Crit. Dmg
+ format: '&a▸ %name%: &f%value% %condition%'
+ capacity: 3.5
+
+PVP_DEFENSE:
+ enabled: true
+ name: PvP Defense
+ format: '&b▸ %name%: &f%value% %condition%'
+ capacity: 100.0
+
+PVE_DEFENSE:
+ enabled: true
+ name: PvE Defense
+ format: '&b▸ %name%: &f%value% %condition%'
+ capacity: 100.0
+
+LOOT_RATE:
+ enabled: true
+ name: Loot Rate
+ format: '&e▸ %name%: &f%value% %condition%'
+ capacity: 250.0
+
+DURABILITY:
+ enabled: true
+ name: Durability
+ format: '&7▸ %name%: &f%value%'
+ capacity: -1
+
+PENETRATION:
+ enabled: true
+ name: Armor Penetration
+ format: '&c▸ %name%: &f%value% %condition%'
+ capacity: 60.0
+
+VAMPIRISM:
+ enabled: true
+ name: Vampirism
+ format: '&c▸ %name%: &f%value% %condition%'
+ capacity: 35.0
+
+BURN_RATE:
+ enabled: true
+ name: Chance to Burn
+ format: '&c▸ %name%: &f%value% %condition%'
+ capacity: 100.0
+
+SALE_PRICE:
+ enabled: true
+ name: Sale Price
+ format: '&d▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+BLEED_RATE:
+ enabled: true
+ name: Chance to Open Wounds
+ format: '&c▸ %name%: &f%value% %condition%'
+ capacity: 75.0
+ settings:
+ damage: '%damage% * 0.5'
+ of-max-health: false
+ duration: 10.0
+
+DISARM_RATE:
+ enabled: true
+ name: Chance to Disarm
+ format: '&c▸ %name%: &f%value% %condition%'
+ capacity: 25.0
+
+THORNMAIL:
+ enabled: true
+ name: Thornmail
+ format: '&c▸ %name%: &f%value% %condition%'
+ capacity: 35.0
+
+HEALTH_REGEN:
+ enabled: true
+ name: Health Regen
+ format: '&c▸ %name%: &f%value% %condition%'
+ capacity: -1.0
+
+MANA_REGEN:
+ enabled: true
+ name: Mana Regen
+ format: '&9▸ %name%: &f%value% %condition%'
+ capacity: -1.0
+
+# CC & Healing Stats
+CC_RESISTANCE:
+ enabled: true
+ name: CC Resistance
+ format: '&e▸ %name%: &f%value% %condition%'
+ capacity: 60.0
+
+CC_DURATION:
+ enabled: true
+ name: CC Duration
+ format: '&e▸ %name%: &f%value% %condition%'
+ capacity: -1.0
+
+HEALING_CAST:
+ enabled: true
+ name: Healing Cast
+ format: '&a▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+HEALING_RECEIVED:
+ enabled: true
+ name: Healing Received
+ format: '&a▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+## PLACEHOLDERS (THEY DON'T WORK ON THEIR OWN)
+SKILL_EFFECTIVNESS:
+ enabled: true
+ name: Skill Effectivness
+ format: '&b▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+SUMMON_POWER:
+ enabled: true
+ name: Summon power
+ format: '&b▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+SUMMON_HP:
+ enabled: true
+ name: Summon HP
+ format: '&b▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+SUMMON_DURATION:
+ enabled: true
+ name: Summon duration
+ format: '&b▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+BLEED_STACKS:
+ enabled: true
+ name: Bleed Stacks
+ format: '&b▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+BLEED_DURATION:
+ enabled: true
+ name: Bleed Duration
+ format: '&b▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+STUN_STACKS:
+ enabled: true
+ name: Stun Stacks
+ format: '&b▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+STUN_DURATION:
+ enabled: true
+ name: Stun Duration
+ format: '&b▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+PROJECTILE_COUNT:
+ enabled: true
+ name: Projectile Count
+ format: '&b▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+PROJECTILE_SPEED:
+ enabled: true
+ name: Projectile Speed
+ format: '&b▸ %name%: &f%value% %condition%'
+ capacity: -1
+
+BLEED_DAMAGEBUFF:
+ enabled: true
+ name: Bleed base
+ format: '&b▸ %name%: &f%value% %condition%'
+ capacity: -1
diff --git a/src/main/resources/item_stats/stats/penetration.yml b/src/main/resources/item_stats/stats/penetration.yml
new file mode 100644
index 00000000..66b8aee1
--- /dev/null
+++ b/src/main/resources/item_stats/stats/penetration.yml
@@ -0,0 +1,76 @@
+# ============================================================
+# Penetration Stats — penetration.yml
+# ============================================================
+#
+# Each entry defines one penetration stat that players can roll
+# on their items. On server start, an entry is auto-generated
+# for every registered damage type (damage.yml) if missing.
+#
+# Fields:
+# enabled — whether this penetration stat is active
+# name — display name (supports color codes)
+# format — lore line format. Placeholders: %name%, %value%, %condition%
+# capacity — maximum value (-1 = unlimited)
+# percent-pen — true = % penetration (reduces defense by a percentage)
+# false = flat penetration (subtracts flat defense value)
+# NOTE: flat penetration only works with CUSTOM defense formula.
+# hooks — list of damage-type IDs (from damage.yml) this stat applies to
+#
+# Example: a sword enchanted with 30 physical_pen (flat) will subtract
+# 30 from the target's physical defense before damage is calculated.
+# PLACEHOLDER: %PENETRATION_{ID}%
+# ============================================================
+
+physical_pen:
+ enabled: true
+ name: '&cPhysical Penetration'
+ format: '&c▸ %name%: &f%value%%condition%'
+ capacity: -1
+ percent-pen: false
+ hooks:
+ - physical
+
+magical_pen:
+ enabled: true
+ name: '&dMagical Penetration'
+ format: '&d▸ %name%: &f%value%%condition%'
+ capacity: 60
+ percent-pen: true
+ hooks:
+ - magical
+
+fire_pen:
+ enabled: true
+ name: '&6Fire Penetration'
+ format: '&6▸ %name%: &f%value%%condition%'
+ capacity: -1
+ percent-pen: false
+ hooks:
+ - fire
+
+poison_pen:
+ enabled: true
+ name: '&2Poison Penetration'
+ format: '&2▸ %name%: &f%value%%condition%'
+ capacity: -1
+ percent-pen: false
+ hooks:
+ - poison
+
+water_pen:
+ enabled: true
+ name: '&bWater Penetration'
+ format: '&b▸ %name%: &f%value%%condition%'
+ capacity: -1
+ percent-pen: false
+ hooks:
+ - water
+
+wind_pen:
+ enabled: true
+ name: '&fWind Penetration'
+ format: '&f▸ %name%: &f%value%%condition%'
+ capacity: -1
+ percent-pen: false
+ hooks:
+ - wind
diff --git a/src/main/resources/modules/item_generator/items/common.yml b/src/main/resources/modules/item_generator/items/common.yml
index 22a57731..37ded3f2 100644
--- a/src/main/resources/modules/item_generator/items/common.yml
+++ b/src/main/resources/modules/item_generator/items/common.yml
@@ -16,7 +16,13 @@ lore:
- '%GENERATOR_SKILLS%'
- '%GENERATOR_DEFENSE%'
- '%GENERATOR_DAMAGE%'
+ - '%GENERATOR_DAMAGE_BUFFS%'
+ - '%GENERATOR_DEFENSE_BUFFS%'
+ - '%GENERATOR_PENETRATION%'
- '%GENERATOR_STATS%'
+ - '%GENERATOR_DAMAGE_BUFFS%'
+ - '%GENERATOR_DEFENSE_BUFFS%'
+ - '%GENERATOR_PENETRATION%'
- '%GENERATOR_FABLED_ATTR%'
- '%GENERATOR_SOCKETS_GEM%'
- '%GENERATOR_SOCKETS_ESSENCE%'
@@ -265,6 +271,36 @@ generator:
min: 0
max: 0
flat-range: false
+ list-damage-buffs:
+ # Per-damage-type % damage buff stats (DynamicBuffStat DAMAGE)
+ # Add entries here matching IDs from damage_buffs_percent.yml
+ # Example:
+ # physical:
+ # chance: 10.0
+ # scale-by-level: 1.025
+ # min: 5.0
+ # max: 15.0
+ # flat-range: false
+ list-defense-buffs:
+ # Per-damage-type % defense buff stats (DynamicBuffStat DEFENSE)
+ # Add entries here matching IDs from defense_buffs_percent.yml
+ # Example:
+ # physical:
+ # chance: 10.0
+ # scale-by-level: 1.025
+ # min: 5.0
+ # max: 15.0
+ # flat-range: false
+ list-penetration:
+ # Per-damage-type penetration stats (PenetrationStat)
+ # Add entries here matching IDs from penetration.yml
+ # Example:
+ # physical_pen:
+ # chance: 6.0
+ # scale-by-level: 1.025
+ # min: 4.0
+ # max: 10.0
+ # flat-range: false
fabled-attributes:
minimum: 1
maximum: 4
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index edf93db9..54147ce9 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -58,38 +58,28 @@ permissions:
description: Bypass all item player requirements.
default: op
children:
- quantumrpg.bypass.requirement: true
- divinity.bypass.requirement.class: true
- divinity.bypass.requirement.level: true
- divinity.bypass.requirement.soulbound: true
- divinity.bypass.requirement.untradeable: true
+ divinity.bypass.class: true
+ divinity.bypass.level: true
+ divinity.bypass.soulbound: true
+ divinity.bypass.untradeable: true
divinity.bypass.requirement.level:
description: Bypass item player level requirement.
default: op
- children:
- quantumrpg.bypass.requirement.level: true
divinity.bypass.requirement.class:
description: Bypass item player class requirement.
default: op
- children:
- quantumrpg.bypass.requirement.class: true
divinity.bypass.requirement.soulbound:
description: Bypass item player soulbound requirement.
default: op
- children:
- quantumrpg.bypass.requirement.soulbound: true
divinity.bypass.requirement.untradeable:
description: Bypass item untradeable requirement.
default: op
- children:
- quantumrpg.bypass.requirement.untradeable: true
# Classes ----------------------------------------------------
divinity.classes:
description: Full access to Classes module.
default: op
children:
- quantumrpg.classes: true
divinity.classes.cmd: true
divinity.classes.class.*: true
divinity.classes.cmd:
@@ -103,93 +93,13 @@ permissions:
divinity.classes.cmd.aspects: true
divinity.classes.cmd.addexp: true
divinity.classes.cmd.addlevel: true
- divinity.classes.cmd.addskill: true
- divinity.classes.cmd.addaspectpoints: true
- divinity.classes.cmd.addskillpoints: true
- divinity.classes.cmd.setclass: true
- divinity.classes.cmd.reset: true
- divinity.classes.cmd.resetaspectpoints: true
- divinity.classes.cmd.resetskillpoints: true
- quantumrpg.classes.cmd.addskill: true
- quantumrpg.classes.cmd.addaspectpoints: true
- quantumrpg.classes.cmd.addskillpoints: true
- quantumrpg.classes.cmd.setclass: true
- quantumrpg.classes.cmd.reset: true
- quantumrpg.classes.cmd.resetaspectpoints: true
- quantumrpg.classes.cmd.resetskillpoints: true
- divinity.classes.cmd.cast:
- description: Access to /class cast command.
- default: op
- children:
- quantumrpg.classes.cmd.cast: true
- divinity.classes.cmd.select:
- description: Access to /class select command.
- default: true
- children:
- quantumrpg.classes.cmd.select: true
- divinity.classes.cmd.skills:
- description: Access to /class skills command.
- default: true
- children:
- quantumrpg.classes.cmd.skills: true
- divinity.classes.cmd.stats:
- description: Access to /class stats command.
- default: true
- children:
- quantumrpg.classes.cmd.stats: true
- divinity.classes.cmd.aspects:
- description: Access to /class aspects command.
- default: true
- children:
- quantumrpg.classes.cmd.aspects: true
- divinity.classes.cmd.addexp:
- description: Access to /class addexp command.
- default: op
- children:
- quantumrpg.classes.cmd.addexp: true
- divinity.classes.cmd.addlevel:
- description: Access to /class addlevel command.
- default: op
- children:
- quantumrpg.classes.cmd.addlevel: true
- divinity.classes.cmd.addskill:
- description: Access to /class addskill command.
- default: op
- children:
quantumrpg.classes.cmd.addskill: true
- divinity.classes.cmd.addaspectpoints:
- description: Access to /class addaspectpoints command.
- default: op
- children:
quantumrpg.classes.cmd.addaspectpoints: true
- divinity.classes.cmd.addskillpoints:
- description: Access to /class addskillpoints command.
- default: op
- children:
quantumrpg.classes.cmd.addskillpoints: true
- divinity.classes.cmd.setclass:
- description: Access to /class setclass command.
- default: op
- children:
quantumrpg.classes.cmd.setclass: true
- divinity.classes.cmd.reset:
- description: Access to /class reset command.
- default: op
- children:
quantumrpg.classes.cmd.reset: true
- divinity.classes.cmd.resetaspectpoints:
- description: Access to /class resetaspectpoints command.
- default: op
- children:
quantumrpg.classes.cmd.resetaspectpoints: true
- divinity.classes.cmd.resetskillpoints:
- description: Access to /class resetskillpoints command.
- default: op
- children:
quantumrpg.classes.cmd.resetskillpoints: true
- divinity.classes.class.*:
- description: Access to all classes.
- default: op
quantumrpg.classes.cmd.cast:
description: Access to /class cast command.
default: op
@@ -234,23 +144,6 @@ permissions:
default: op
# Combat Log ----------------------------------------------------
- divinity.combatlog:
- description: Full access to Combat Log module.
- default: op
- children:
- divinity.combatlog.cmd: true
- quantumrpg.combatlog.cmd: true
- divinity.combatlog.cmd:
- description: Access to all Combat Log commands.
- default: op
- children:
- divinity.combatlog.cmd.log: true
- quantumrpg.combatlog.cmd.log: true
- divinity.combatlog.cmd.log:
- description: Access to /combatlog log command.
- default: true
- children:
- quantumrpg.combatlog.cmd.log: true
quantumrpg.combatlog:
description: Full access to Combat Log module.
default: op
@@ -266,30 +159,6 @@ permissions:
default: true
# Dismantle ----------------------------------------------------
- divinity.dismantle:
- description: Full access to the Dismantle module.
- default: op
- children:
- divinity.dismantle.cmd: true
- divinity.dismantle.gui: true
- quantumrpg.dismantle.cmd: true
- quantumrpg.dismantle.gui: true
- divinity.dismantle.cmd:
- description: Access to Dismantle commands.
- default: op
- children:
- divinity.dismantle.cmd.open: true
- quantumrpg.dismantle.cmd.open: true
- divinity.dismantle.cmd.open:
- description: Access to /dismantle open command.
- default: op
- children:
- quantumrpg.dismantle.cmd.open: true
- divinity.dismantle.gui:
- description: Access to Dismantle GUI.
- default: op
- children:
- quantumrpg.dismantle.gui: true
quantumrpg.dismantle:
description: Full access to the Dismantle module.
default: op
@@ -306,50 +175,6 @@ permissions:
default: op
# Essences ----------------------------------------------------
- divinity.essences:
- description: Full access to Essences module.
- default: op
- children:
- divinity.essences.cmd: true
- divinity.essences.gui: true
- quantumrpg.essences.cmd: true
- quantumrpg.essences.gui: true
- divinity.essences.cmd:
- description: Access to all Essences commands.
- default: op
- children:
- divinity.essences.cmd.merchant: true
- divinity.essences.cmd.merchant.others: true
- quantumrpg.essences.cmd.merchant: true
- quantumrpg.essences.cmd.merchant.others: true
- divinity.essences.cmd.merchant:
- description: Access to /essences merchant command.
- default: op
- children:
- quantumrpg.essences.cmd.merchant: true
- divinity.essences.cmd.merchant.others:
- description: Access to /essences merchant [player] command.
- default: op
- children:
- quantumrpg.essences.cmd.merchant.others: true
- divinity.essences.gui:
- description: Access to all Essences GUIs.
- default: op
- children:
- divinity.essences.gui.user: true
- divinity.essences.gui.merchant: true
- quantumrpg.essences.gui.user: true
- quantumrpg.essences.gui.merchant: true
- divinity.essences.gui.user:
- description: Access to Default Socketing GUI.
- default: op
- children:
- quantumrpg.essences.gui.user: true
- divinity.essences.gui.merchant:
- description: Access to Merchant Socketing GUI.
- default: op
- children:
- quantumrpg.essences.gui.merchant: true
quantumrpg.essences:
description: Full access to Essences module.
default: op
@@ -384,30 +209,6 @@ permissions:
default: op
# Extractor ----------------------------------------------------
- divinity.extractor:
- description: Full access to Extractor module.
- default: op
- children:
- divinity.extractor.cmd: true
- divinity.extractor.gui: true
- quantumrpg.extractor.cmd: true
- quantumrpg.extractor.gui: true
- divinity.extractor.cmd:
- description: Access to all Extractor commands.
- default: op
- children:
- divinity.extractor.cmd.open: true
- quantumrpg.extractor.cmd.open: true
- divinity.extractor.cmd.open:
- description: Access to /extractor open command.
- default: op
- children:
- quantumrpg.extractor.cmd.open: true
- divinity.extractor.gui:
- description: Access Extractor GUI.
- default: op
- children:
- quantumrpg.extractor.gui: true
quantumrpg.extractor:
description: Full access to Extractor module.
default: op
@@ -427,30 +228,6 @@ permissions:
default: op
# Fortify ----------------------------------------------------
- divinity.fortify:
- description: Full access to Fortify module.
- default: op
- children:
- divinity.fortify.cmd: true
- quantumrpg.fortify.cmd: true
- divinity.fortify.cmd:
- description: Access to all Fortify commands.
- default: op
- children:
- divinity.fortify.cmd.fortify: true
- divinity.fortify.cmd.unfortify: true
- quantumrpg.fortify.cmd.fortify: true
- quantumrpg.fortify.cmd.unfortify: true
- divinity.fortify.cmd.fortify:
- description: Access to /fortify fortify command.
- default: op
- children:
- quantumrpg.fortify.cmd.fortify: true
- divinity.fortify.cmd.unfortify:
- description: Access to /fortify unfortify command.
- default: op
- children:
- quantumrpg.fortify.cmd.unfortify: true
quantumrpg.fortify:
description: Full access to Fortify module.
default: op
@@ -470,50 +247,6 @@ permissions:
default: op
# Gems ----------------------------------------------------
- divinity.gems:
- description: Full access to Gems module.
- default: op
- children:
- divinity.gems.cmd: true
- divinity.gems.gui: true
- quantumrpg.gems.cmd: true
- quantumrpg.gems.gui: true
- divinity.gems.cmd:
- description: Access to all Gems commands.
- default: op
- children:
- divinity.gems.cmd.merchant: true
- divinity.gems.cmd.merchant.others: true
- quantumrpg.gems.cmd.merchant: true
- quantumrpg.gems.cmd.merchant.others: true
- divinity.gems.cmd.merchant:
- description: Access to /gems merchant command.
- default: op
- children:
- quantumrpg.gems.cmd.merchant: true
- divinity.gems.cmd.merchant.others:
- description: Access to /gems merchant [player] command.
- default: op
- children:
- quantumrpg.gems.cmd.merchant.others: true
- divinity.gems.gui:
- description: Access to all Gems GUIs.
- default: op
- children:
- divinity.gems.gui.user: true
- divinity.gems.gui.merchant: true
- quantumrpg.gems.gui.user: true
- quantumrpg.gems.gui.merchant: true
- divinity.gems.gui.user:
- description: Access to user socketing GUI.
- default: op
- children:
- quantumrpg.gems.gui.user: true
- divinity.gems.gui.merchant:
- description: Access to Merchant socketing GUI.
- default: op
- children:
- quantumrpg.gems.gui.merchant: true
quantumrpg.gems:
description: Full access to Gems module.
default: op
@@ -546,23 +279,6 @@ permissions:
default: op
# Identify ----------------------------------------------------
- divinity.identify:
- description: Full access to Identify module.
- default: op
- children:
- divinity.identify.cmd: true
- quantumrpg.identify.cmd: true
- divinity.identify.cmd:
- description: Access to all Identify commands.
- default: op
- children:
- divinity.identify.cmd.identify: true
- quantumrpg.identify.cmd.identify: true
- divinity.identify.cmd.identify:
- description: Access to /identify identify command.
- default: op
- children:
- quantumrpg.identify.cmd.identify: true
quantumrpg.identify:
description: Full access to Identify module.
default: op
@@ -578,30 +294,6 @@ permissions:
default: op
# Magic Dust ----------------------------------------------------
- divinity.magicdust:
- description: Full access to Magic Dust module.
- default: op
- children:
- divinity.magicdust.cmd: true
- divinity.magicdust.gui: true
- quantumrpg.magicdust.cmd: true
- quantumrpg.magicdust.gui: true
- divinity.magicdust.cmd:
- description: Full access to Magic Dust commands.
- default: op
- children:
- divinity.magicdust.cmd.open: true
- quantumrpg.magicdust.cmd.open: true
- divinity.magicdust.cmd.open:
- description: Allows to use /magicdust open command.
- default: op
- children:
- quantumrpg.magicdust.cmd.open: true
- divinity.magicdust.gui:
- description: Allows to use Magic Dust GUI.
- default: op
- children:
- quantumrpg.magicdust.gui: true
quantumrpg.magicdust:
description: Full access to Magic Dust module.
default: op
@@ -621,100 +313,6 @@ permissions:
default: op
# Party ----------------------------------------------------
- divinity.party:
- description: Access to Party module.
- default: true
- children:
- divinity.party.cmd: true
- quantumrpg.party.cmd: true
- divinity.party.cmd:
- description: Access to Party commands.
- default: op
- children:
- divinity.party.cmd.chat: true
- divinity.party.cmd.create: true
- divinity.party.cmd.disband: true
- divinity.party.cmd.drop: true
- divinity.party.cmd.exp: true
- divinity.party.cmd.invite: true
- divinity.party.cmd.join: true
- divinity.party.cmd.kick: true
- divinity.party.cmd.leave: true
- divinity.party.cmd.menu: true
- divinity.party.cmd.roll: true
- divinity.party.cmd.tp: true
- quantumrpg.party.cmd.chat: true
- quantumrpg.party.cmd.create: true
- quantumrpg.party.cmd.disband: true
- quantumrpg.party.cmd.drop: true
- quantumrpg.party.cmd.exp: true
- quantumrpg.party.cmd.invite: true
- quantumrpg.party.cmd.join: true
- quantumrpg.party.cmd.kick: true
- quantumrpg.party.cmd.leave: true
- quantumrpg.party.cmd.menu: true
- quantumrpg.party.cmd.roll: true
- quantumrpg.party.cmd.tp: true
- divinity.party.cmd.chat:
- description: Access to /party chat command.
- default: true
- children:
- quantumrpg.party.cmd.chat: true
- divinity.party.cmd.create:
- description: Access to /party create command.
- default: true
- children:
- quantumrpg.party.cmd.create: true
- divinity.party.cmd.disband:
- description: Access to /party disband command.
- default: true
- children:
- quantumrpg.party.cmd.disband: true
- divinity.party.cmd.drop:
- description: Access to /party drop command.
- default: true
- children:
- quantumrpg.party.cmd.drop: true
- divinity.party.cmd.exp:
- description: Access to /party exp command.
- default: true
- children:
- quantumrpg.party.cmd.exp: true
- divinity.party.cmd.invite:
- description: Access to /party invite command.
- default: true
- children:
- quantumrpg.party.cmd.invite: true
- divinity.party.cmd.join:
- description: Access to /party join command.
- default: true
- children:
- quantumrpg.party.cmd.join: true
- divinity.party.cmd.kick:
- description: Access to /party kick command.
- default: true
- children:
- quantumrpg.party.cmd.kick: true
- divinity.party.cmd.leave:
- description: Access to /party leave command.
- default: true
- children:
- quantumrpg.party.cmd.leave: true
- divinity.party.cmd.menu:
- description: Access to /party menu command.
- default: true
- children:
- quantumrpg.party.cmd.menu: true
- divinity.party.cmd.roll:
- description: Access to /party roll command.
- default: true
- children:
- quantumrpg.party.cmd.roll: true
- divinity.party.cmd.tp:
- description: Access to /party tp command.
- default: true
- children:
- quantumrpg.party.cmd.tp: true
quantumrpg.party:
description: Access to Party module.
default: true
@@ -774,30 +372,6 @@ permissions:
default: true
# Refine ----------------------------------------------------
- divinity.refine:
- description: Access to Refine module.
- default: op
- children:
- divinity.refine.cmd: true
- quantumrpg.refine.cmd: true
- divinity.refine.cmd:
- description: Access to Refine commands.
- default: op
- children:
- divinity.refine.cmd.refine: true
- divinity.refine.cmd.downgrade: true
- quantumrpg.refine.cmd.refine: true
- quantumrpg.refine.cmd.downgrade: true
- divinity.refine.cmd.refine:
- description: Access to /refine refine command.
- default: op
- children:
- quantumrpg.refine.cmd.refine: true
- divinity.refine.cmd.downgrade:
- description: Access to /refine downgrade command.
- default: op
- children:
- quantumrpg.refine.cmd.downgrade: true
quantumrpg.refine:
description: Access to Refine module.
default: op
@@ -811,30 +385,6 @@ permissions:
quantumrpg.refine.cmd.downgrade: true
# Repair ----------------------------------------------------
- divinity.repair:
- description: Access to Repair module.
- default: op
- children:
- divinity.repair.cmd: true
- divinity.repair.gui: true
- quantumrpg.repair.cmd: true
- quantumrpg.repair.gui: true
- divinity.repair.cmd:
- description: Access to Repair commands.
- default: op
- children:
- divinity.repair.cmd.open: true
- quantumrpg.repair.cmd.open: true
- divinity.repair.cmd.open:
- description: Access to /repair open command.
- default: op
- children:
- quantumrpg.repair.cmd.open: true
- divinity.repair.gui:
- description: Access to Repair GUI.
- default: op
- children:
- quantumrpg.repair.gui: true
quantumrpg.repair:
description: Access to Repair module.
default: op
@@ -851,50 +401,6 @@ permissions:
default: op
# Runes ----------------------------------------------------
- divinity.runes:
- description: Full access to Runes module.
- default: op
- children:
- divinity.runes.cmd: true
- divinity.runes.gui: true
- quantumrpg.runes.cmd: true
- quantumrpg.runes.gui: true
- divinity.runes.cmd:
- description: Access to all Runes commands.
- default: op
- children:
- divinity.runes.cmd.merchant: true
- divinity.runes.cmd.merchant.others: true
- quantumrpg.runes.cmd.merchant: true
- quantumrpg.runes.cmd.merchant.others: true
- divinity.runes.cmd.merchant:
- description: Access to /runes merchant command.
- default: op
- children:
- quantumrpg.runes.cmd.merchant: true
- divinity.runes.cmd.merchant.others:
- description: Access to /runes merchant [player] command.
- default: op
- children:
- quantumrpg.runes.cmd.merchant.others: true
- divinity.runes.gui:
- description: Access to all Runes GUIs.
- default: op
- children:
- divinity.runes.gui.user: true
- divinity.runes.gui.merchant: true
- quantumrpg.runes.gui.user: true
- quantumrpg.runes.gui.merchant: true
- divinity.runes.gui.user:
- description: Access to Default Socketing GUI.
- default: op
- children:
- quantumrpg.runes.gui.user: true
- divinity.runes.gui.merchant:
- description: Access to Merchant Socketing GUI.
- default: op
- children:
- quantumrpg.runes.gui.merchant: true
quantumrpg.runes:
description: Full access to Runes module.
default: op
@@ -929,30 +435,6 @@ permissions:
default: op
# Sell ----------------------------------------------------
- divinity.sell:
- description: Full access to the Sell module.
- default: op
- children:
- divinity.sell.cmd: true
- divinity.sell.gui: true
- quantumrpg.sell.cmd: true
- quantumrpg.sell.gui: true
- divinity.sell.cmd:
- description: Access to Sell commands.
- default: op
- children:
- divinity.sell.cmd.open: true
- quantumrpg.sell.cmd.open: true
- divinity.sell.cmd.open:
- description: Access to /sell open command.
- default: op
- children:
- quantumrpg.sell.cmd.open: true
- divinity.sell.gui:
- description: Access to Sell GUI.
- default: op
- children:
- quantumrpg.sell.gui: true
quantumrpg.sell:
description: Full access to the Sell module.
default: op
@@ -969,30 +451,6 @@ permissions:
default: op
# Soulbound ----------------------------------------------------
- divinity.soulbound:
- description: Access to Soulbound module.
- default: op
- children:
- divinity.soulbound.cmd: true
- quantumrpg.soulbound.cmd: true
- divinity.soulbound.cmd:
- description: Access to Soulbound commands.
- default: op
- children:
- divinity.soulbound.cmd.soul: true
- divinity.soulbound.cmd.untradeable: true
- quantumrpg.soulbound.cmd.soul: true
- quantumrpg.soulbound.cmd.untradeable: true
- divinity.soulbound.cmd.soul:
- description: Access to /soulbound soul command.
- default: op
- children:
- quantumrpg.soulbound.cmd.soul: true
- divinity.soulbound.cmd.untradeable:
- description: Access to /soulbound untradeable command.
- default: op
- children:
- quantumrpg.soulbound.cmd.untradeable: true
quantumrpg.soulbound:
description: Access to Soulbound module.
default: op
diff --git a/src/test/java/studio/magemonkey/divinity/testutil/DependencyResolver.java b/src/test/java/studio/magemonkey/divinity/testutil/DependencyResolver.java
index 77b471b2..bd8c87f0 100644
--- a/src/test/java/studio/magemonkey/divinity/testutil/DependencyResolver.java
+++ b/src/test/java/studio/magemonkey/divinity/testutil/DependencyResolver.java
@@ -12,7 +12,7 @@
@Slf4j
public class DependencyResolver {
private static List repositories =
- List.of("https://repo.travja.dev/snapshots/", "https://repo1.maven.org/maven2");
+ List.of("https://central.sonatype.com/repository/maven-snapshots/", "https://repo1.maven.org/maven2");
public static File resolve(String dependency) throws FileNotFoundException {
log.info("Attempting to resolve dependency " + dependency);
diff --git a/src/test/java/studio/magemonkey/divinity/utils/DivinityProviderTest.java b/src/test/java/studio/magemonkey/divinity/utils/DivinityProviderTest.java
index 4e8712c3..f60dc867 100644
--- a/src/test/java/studio/magemonkey/divinity/utils/DivinityProviderTest.java
+++ b/src/test/java/studio/magemonkey/divinity/utils/DivinityProviderTest.java
@@ -5,17 +5,14 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
-import org.bukkit.inventory.ItemStack;
import studio.magemonkey.codex.Codex;
import studio.magemonkey.codex.CodexEngine;
import studio.magemonkey.codex.items.CodexItemManager;
import studio.magemonkey.codex.modules.ModuleManager;
import studio.magemonkey.divinity.Divinity;
-import studio.magemonkey.divinity.modules.api.QModuleDrop;
import studio.magemonkey.divinity.modules.list.arrows.ArrowManager;
import studio.magemonkey.divinity.modules.list.customitems.CustomItemsManager;
import studio.magemonkey.divinity.modules.list.itemgenerator.ItemGeneratorManager;
-import studio.magemonkey.divinity.stats.items.ItemStats;
import java.util.List;
import java.util.logging.Logger;
@@ -63,11 +60,9 @@ void setUp() {
itemGenModule = spy(new ItemGeneratorManager(divinity));
when(moduleManager.getModule("item_generator")).thenReturn(itemGenModule);
when(moduleManager.getModules()).thenReturn(List.of(arrowModule, itemGenModule));
- doReturn("item_generator").when(itemGenModule).getId();
customItemsModule = spy(new CustomItemsManager(divinity));
when(moduleManager.getModule("custom_items")).thenReturn(customItemsModule);
- doReturn("custom_items").when(customItemsModule).getId();
//noinspection unchecked
when(divinity.getModuleManager()).thenReturn(moduleManager);
@@ -83,8 +78,6 @@ void afterEach() {
void getItem_usesLevel() {
ItemGeneratorManager.GeneratorItem generatorItem = mock(ItemGeneratorManager.GeneratorItem.class);
doReturn(generatorItem).when(itemGenModule).getItemById("foobar");
- when(generatorItem.getId()).thenReturn("foobar");
- doReturn((QModuleDrop>) itemGenModule).when(generatorItem).getModule();
DivinityProvider.DivinityItemType item = provider.getItem("DIVINITY_item_generator:foobar~level:5");
@@ -100,8 +93,6 @@ void getItem_usesLevel() {
void getItem_usesMaterial() {
ItemGeneratorManager.GeneratorItem generatorItem = mock(ItemGeneratorManager.GeneratorItem.class);
doReturn(generatorItem).when(itemGenModule).getItemById("foobar");
- when(generatorItem.getId()).thenReturn("foobar");
- doReturn((QModuleDrop>) itemGenModule).when(generatorItem).getModule();
DivinityProvider.DivinityItemType item =
provider.getItem("DIVINITY_item_generator:foobar~material:VANILLA_DIAMOND");
@@ -119,8 +110,6 @@ void getItem_usesMaterial() {
void getItem_noModule_returnsItem() {
ItemGeneratorManager.GeneratorItem generatorItem = mock(ItemGeneratorManager.GeneratorItem.class);
doReturn(generatorItem).when(itemGenModule).getItemById("foobar");
- when(generatorItem.getId()).thenReturn("foobar");
- doReturn((QModuleDrop>) itemGenModule).when(generatorItem).getModule();
DivinityProvider.DivinityItemType item = provider.getItem("DIVINITY_foobar");
@@ -136,8 +125,6 @@ void getItem_noModule_returnsItem() {
void getItem_customItems_returnsItem() {
CustomItemsManager.CustomItem codexItem = mock(CustomItemsManager.CustomItem.class);
doReturn(codexItem).when(customItemsModule).getItemById("foobar");
- when(codexItem.getId()).thenReturn("foobar");
- doReturn((QModuleDrop>) customItemsModule).when(codexItem).getModule();
DivinityProvider.DivinityItemType item = provider.getItem("DIVINITY_custom_items:foobar");
@@ -148,40 +135,4 @@ void getItem_customItems_returnsItem() {
assertEquals(codexItem, item.getModuleItem());
assertInstanceOf(DivinityProvider.DivinityItemType.class, item);
}
-
- @Test
- void getItem_namespacedIdIncludesModule() {
- CustomItemsManager.CustomItem codexItem = mock(CustomItemsManager.CustomItem.class);
- doReturn(codexItem).when(customItemsModule).getItemById("foobar");
- when(codexItem.getId()).thenReturn("foobar");
- doReturn((QModuleDrop>) customItemsModule).when(codexItem).getModule();
-
- DivinityProvider.DivinityItemType item = provider.getItem("DIVINITY_custom_items:foobar");
-
- assertNotNull(item);
- assertEquals("DIVINITY_custom_items:foobar", item.getNamespacedID());
- }
-
- @Test
- void getItem_itemStackUsesStoredModule() {
- ItemStack itemStack = mock(ItemStack.class);
-
- ItemGeneratorManager.GeneratorItem generatorItem = mock(ItemGeneratorManager.GeneratorItem.class);
- doReturn(generatorItem).when(itemGenModule).getItemById("foobar");
-
- CustomItemsManager.CustomItem codexItem = mock(CustomItemsManager.CustomItem.class);
- doReturn(codexItem).when(customItemsModule).getItemById("foobar");
-
- try (MockedStatic itemStats = mockStatic(ItemStats.class)) {
- itemStats.when(() -> ItemStats.getId(itemStack)).thenReturn("foobar");
- itemStats.when(() -> ItemStats.getModule(itemStack)).thenReturn(customItemsModule);
-
- DivinityProvider.DivinityItemType item = provider.getItem(itemStack);
-
- assertNotNull(item);
- assertEquals(codexItem, item.getModuleItem());
- verify(customItemsModule).getItemById("foobar");
- verify(itemGenModule, never()).getItemById("foobar");
- }
- }
-}
+}
\ No newline at end of file
diff --git a/src/test/resources/common.yml b/src/test/resources/common.yml
index 2c17c913..89cf3fd6 100644
--- a/src/test/resources/common.yml
+++ b/src/test/resources/common.yml
@@ -16,6 +16,9 @@ lore:
- '%GENERATOR_SKILLS%'
- '%GENERATOR_DEFENSE%'
- '%GENERATOR_DAMAGE%'
+ - '%GENERATOR_DAMAGE_BUFFS%'
+ - '%GENERATOR_DEFENSE_BUFFS%'
+ - '%GENERATOR_PENETRATION%'
- '%GENERATOR_STATS%'
- '%GENERATOR_FABLED_ATTR%'
- '%GENERATOR_SOCKETS_GEM%'