diff --git a/pom.xml b/pom.xml index 04d3e404..e059ec1d 100644 --- a/pom.xml +++ b/pom.xml @@ -7,17 +7,17 @@ studio.magemonkey magemonkey-parent - 1.21.11-R2 + 1.21.11-R1 divinity - 1.0.2-R0.66-SNAPSHOT + 1.0.2-R0.57-SNAPSHOT Divinity Custom items, combat, and more! - 1.1.1-R1 - 1.0.4-R0.76-SNAPSHOT + 1.1.1-R0.17-SNAPSHOT + 1.0.4-R0.90-SNAPSHOT @@ -39,10 +39,6 @@ neetgames https://nexus.neetgames.com/repository/maven-releases/ - - jitpack.io - https://jitpack.io - @@ -73,7 +69,7 @@ compile - + studio.magemonkey codex @@ -145,7 +141,7 @@ com.gmail.nossr50.mcMMO mcMMO - 2.2.051 + 2.2.049 provided @@ -260,9 +256,9 @@ - VoidEdge - VoidEdge - https://void.travja.dev + MageMonkeyStudio + MageMonkeyStudio + https://magemonkey.studio diff --git a/src/main/java/studio/magemonkey/divinity/Divinity.java b/src/main/java/studio/magemonkey/divinity/Divinity.java index 9f0df133..dda5387f 100644 --- a/src/main/java/studio/magemonkey/divinity/Divinity.java +++ b/src/main/java/studio/magemonkey/divinity/Divinity.java @@ -54,7 +54,7 @@ /** * Divinity * - * @author © 2026 VoidEdge + * @author ©2025 MageMonkeyStudio */ public class Divinity extends CodexDataPlugin { diff --git a/src/main/java/studio/magemonkey/divinity/Perms.java b/src/main/java/studio/magemonkey/divinity/Perms.java index 7bf919d5..483f1c6e 100644 --- a/src/main/java/studio/magemonkey/divinity/Perms.java +++ b/src/main/java/studio/magemonkey/divinity/Perms.java @@ -1,13 +1,11 @@ package studio.magemonkey.divinity; -import org.bukkit.permissions.Permissible; import org.jetbrains.annotations.NotNull; import studio.magemonkey.divinity.modules.api.socketing.ModuleSocket; public class Perms { - private static final String PREFIX = "quantumrpg."; - private static final String DIVINITY = "divinity."; + private static final String PREFIX = "quantumrpg."; public static final String USER = PREFIX + "user"; public static final String ADMIN = PREFIX + "admin"; @@ -100,20 +98,4 @@ public static String getSocketGuiUser(@NotNull ModuleSocket module) { public static String getSocketGuiMerchant(@NotNull ModuleSocket module) { return SOCKET_GUI_MERCHANT.replace("%module%", module.getId()); } - - /** - * Checks whether the permissible has the given permission, accepting both - * the legacy {@code quantumrpg.*} namespace and the current {@code divinity.*} - * namespace as equivalent. - */ - public static boolean has(@NotNull Permissible permissible, @NotNull String permission) { - if (permissible.hasPermission(permission)) return true; - if (permission.startsWith(PREFIX)) { - return permissible.hasPermission(DIVINITY + permission.substring(PREFIX.length())); - } - if (permission.startsWith(DIVINITY)) { - return permissible.hasPermission(PREFIX + permission.substring(DIVINITY.length())); - } - return false; - } } diff --git a/src/main/java/studio/magemonkey/divinity/config/Config.java b/src/main/java/studio/magemonkey/divinity/config/Config.java index f2096441..509c3b02 100644 --- a/src/main/java/studio/magemonkey/divinity/config/Config.java +++ b/src/main/java/studio/magemonkey/divinity/config/Config.java @@ -18,6 +18,8 @@ import studio.magemonkey.divinity.stats.items.attributes.api.TypedStat; import studio.magemonkey.divinity.stats.items.attributes.stats.BleedStat; 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.stats.tiers.Tier; import studio.magemonkey.divinity.types.ItemGroup; import studio.magemonkey.divinity.types.ItemSubType; @@ -69,6 +71,9 @@ public void setupAttributes() { this.setupDamages(); this.setupDefense(); this.setupStats(); + this.setupDamageBuffs(); + this.setupDefenseBuffs(); + this.setupPenetrations(); this.setupHand(); this.setupAmmo(); this.setupSockets(); @@ -174,20 +179,14 @@ private void setupDefense() { private void setupStats() { JYML cfg; try { - cfg = JYML.loadOrExtract(plugin, "/item_stats/stats.yml"); + cfg = JYML.loadOrExtract(plugin, "/item_stats/stats/general_stats.yml"); } catch (InvalidConfigurationException e) { this.plugin.error("Failed to load stats config (" + this.plugin.getName() - + "/item_stats/stats.yml): Configuration error"); + + "/item_stats/stats/general_stats.yml): Configuration error"); e.printStackTrace(); return; } - cfg.addMissing("ARMOR_TOUGHNESS.enabled", true); - cfg.addMissing("ARMOR_TOUGHNESS.name", "Armor Toughness"); - cfg.addMissing("ARMOR_TOUGHNESS.format", "&9▸ %name%: &f%value% %condition%"); - cfg.addMissing("ARMOR_TOUGHNESS.capacity", 100.0); - cfg.save(); - for (SimpleStat.Type statType : TypedStat.Type.values()) { String path2 = statType.name() + "."; if (!cfg.getBoolean(path2 + "enabled")) { @@ -214,6 +213,109 @@ private void setupStats() { } } + private void setupDamageBuffs() { + JYML cfg; + try { + cfg = JYML.loadOrExtract(plugin, "/item_stats/stats/damage_buffs_percent.yml"); + } catch (InvalidConfigurationException e) { + this.plugin.error("Failed to load damage_buffs_percent config: Configuration error"); + e.printStackTrace(); + return; + } + + for (DamageAttribute dmg : ItemStats.getDamages()) { + String id = dmg.getId(); + String path = id + "."; + cfg.addMissing(path + "enabled", true); + cfg.addMissing(path + "name", dmg.getName() + " Buff %"); + cfg.addMissing(path + "format", "&3▸ %name%: &f%value%%condition%"); + cfg.addMissing(path + "capacity", -1.0); + cfg.addMissing(path + "hook", Collections.singletonList(id)); + } + cfg.saveChanges(); + + for (String buffId : cfg.getSection("")) { + if (!cfg.getBoolean(buffId + ".enabled")) continue; + String name = StringUT.color(cfg.getString(buffId + ".name", buffId)); + String format = StringUT.color(cfg.getString(buffId + ".format", "&3▸ %name%: &f%value%")); + double cap = cfg.getDouble(buffId + ".capacity", -1D); + Set hooks = new HashSet<>(cfg.getStringList(buffId + ".hook")); + + DynamicBuffStat buff = new DynamicBuffStat( + DynamicBuffStat.BuffTarget.DAMAGE, buffId, name, format, hooks, cap); + ItemStats.registerDamageBuff(buff); + } + } + + private void setupDefenseBuffs() { + JYML cfg; + try { + cfg = JYML.loadOrExtract(plugin, "/item_stats/stats/defense_buffs_percent.yml"); + } catch (InvalidConfigurationException e) { + this.plugin.error("Failed to load defense_buffs_percent config: Configuration error"); + e.printStackTrace(); + return; + } + + for (DefenseAttribute def : ItemStats.getDefenses()) { + String id = def.getId(); + String path = id + "."; + cfg.addMissing(path + "enabled", true); + cfg.addMissing(path + "name", def.getName() + " Buff %"); + cfg.addMissing(path + "format", "&9▸ %name%: &f%value%%condition%"); + cfg.addMissing(path + "capacity", -1.0); + cfg.addMissing(path + "hook", Collections.singletonList(id)); + } + cfg.saveChanges(); + + for (String buffId : cfg.getSection("")) { + if (!cfg.getBoolean(buffId + ".enabled")) continue; + String name = StringUT.color(cfg.getString(buffId + ".name", buffId)); + String format = StringUT.color(cfg.getString(buffId + ".format", "&9▸ %name%: &f%value%")); + double cap = cfg.getDouble(buffId + ".capacity", -1D); + Set hooks = new HashSet<>(cfg.getStringList(buffId + ".hook")); + + DynamicBuffStat buff = new DynamicBuffStat( + DynamicBuffStat.BuffTarget.DEFENSE, buffId, name, format, hooks, cap); + ItemStats.registerDefenseBuff(buff); + } + } + + private void setupPenetrations() { + JYML cfg; + try { + cfg = JYML.loadOrExtract(plugin, "/item_stats/stats/penetration.yml"); + } catch (InvalidConfigurationException e) { + this.plugin.error("Failed to load penetration config: Configuration error"); + e.printStackTrace(); + return; + } + + // Auto-generate a flat-pen entry for every registered damage type (if missing) + for (DamageAttribute dmg : ItemStats.getDamages()) { + String id = dmg.getId() + "_pen"; + String path = id + "."; + cfg.addMissing(path + "enabled", true); + cfg.addMissing(path + "name", dmg.getName() + " Penetration"); + cfg.addMissing(path + "format", "&c▸ %name%: &f%value%%condition%"); + cfg.addMissing(path + "capacity", -1.0); + cfg.addMissing(path + "percent-pen", false); + cfg.addMissing(path + "hooks", Collections.singletonList(dmg.getId())); + } + cfg.saveChanges(); + + for (String penId : cfg.getSection("")) { + if (!cfg.getBoolean(penId + ".enabled")) continue; + String name = StringUT.color(cfg.getString(penId + ".name", penId)); + String format = StringUT.color(cfg.getString(penId + ".format", "&c▸ %name%: &f%value%")); + double cap = cfg.getDouble(penId + ".capacity", -1D); + boolean percentPen = cfg.getBoolean(penId + ".percent-pen", false); + Set hooks = new HashSet<>(cfg.getStringList(penId + ".hooks")); + + new PenetrationStat(penId, name, format, hooks, percentPen, cap); + } + } + private void setupHand() { JYML cfg; try { diff --git a/src/main/java/studio/magemonkey/divinity/config/EngineCfg.java b/src/main/java/studio/magemonkey/divinity/config/EngineCfg.java index 5757107d..069fab34 100644 --- a/src/main/java/studio/magemonkey/divinity/config/EngineCfg.java +++ b/src/main/java/studio/magemonkey/divinity/config/EngineCfg.java @@ -48,6 +48,7 @@ public EngineCfg(@NotNull Divinity plugin) throws InvalidConfigurationException public static boolean ATTRIBUTES_ALLOW_HOLD_REQUIREMENTS; public static boolean ATTRIBUTES_DURABILITY_BREAK_ITEMS; + public static boolean ATTRIBUTES_HIDE_FLAGS; public static boolean ATTRIBUTES_DURABILITY_REDUCE_FOR_MOBS; public static Set ATTRIBUTES_DURABILITY_REDUCE_FOR_SKILL_API; @@ -56,6 +57,11 @@ public EngineCfg(@NotNull Divinity plugin) throws InvalidConfigurationException public static double COMBAT_SHIELD_BLOCK_BONUS_DAMAGE_MOD; public static int COMBAT_SHIELD_BLOCK_COOLDOWN; public static boolean LEGACY_COMBAT; + public static String DEFENSE_FORMULA_MODE; + public static String CUSTOM_DEFENSE_FORMULA; + public static boolean COMBAT_OVERFLOW_PEN_AMPLIFIES; + public static String COMBAT_OVERFLOW_PEN_FORMULA; + public static boolean FULL_LEGACY; public static boolean COMBAT_DISABLE_VANILLA_SWEEP; public static boolean COMBAT_REDUCE_PLAYER_HEALTH_BAR; public static boolean COMBAT_FISHING_HOOK_DO_DAMAGE; @@ -108,8 +114,9 @@ public EngineCfg(@NotNull Divinity plugin) throws InvalidConfigurationException public static String LORE_STYLE_REQ_ITEM_MODULE_FORMAT_SEPAR; public static String LORE_STYLE_REQ_ITEM_MODULE_FORMAT_COLOR; - public static String LORE_STYLE_ENCHANTMENTS_FORMAT_MAIN; - public static int LORE_STYLE_ENCHANTMENTS_FORMAT_MAX_ROMAN; + public static String LORE_STYLE_ENCHANTMENTS_FORMAT_MAIN; + public static int LORE_STYLE_ENCHANTMENTS_FORMAT_MAX_ROMAN; + public static boolean LORE_STYLE_ENCHANTMENTS_ROMAN_SYSTEM; public static String LORE_STYLE_FABLED_ATTRIBUTE_FORMAT; @@ -177,6 +184,7 @@ public void setup() { EngineCfg.ATTRIBUTES_EFFECTIVE_FOR_MOBS = cfg.getBoolean(path + "effective-for-mobs"); EngineCfg.ATTRIBUTES_EFFECTIVE_IN_OFFHAND = cfg.getBoolean(path + "effective-in-offhand"); EngineCfg.ATTRIBUTES_ALLOW_HOLD_REQUIREMENTS = cfg.getBoolean(path + "allow-hold-items-you-cant-use"); + EngineCfg.ATTRIBUTES_HIDE_FLAGS = cfg.getBoolean(path + "hide-flags"); path = "attributes.durability."; EngineCfg.ATTRIBUTES_DURABILITY_BREAK_ITEMS = cfg.getBoolean(path + "break-items-on-zero"); @@ -208,6 +216,10 @@ public void setup() { path = "combat."; EngineCfg.LEGACY_COMBAT = cfg.getBoolean(path + "legacy-combat", false); + EngineCfg.DEFENSE_FORMULA_MODE = cfg.getString(path + "defense-formula", "FACTOR").toUpperCase(); + EngineCfg.CUSTOM_DEFENSE_FORMULA = cfg.getString(path + "custom-defense-formula", "damage*(25/(25+defense))"); + EngineCfg.COMBAT_OVERFLOW_PEN_AMPLIFIES = cfg.getBoolean(path + "overflow-pen-amplifies", false); + EngineCfg.COMBAT_OVERFLOW_PEN_FORMULA = cfg.getString(path + "overflow-pen-formula", "damage*(overflow/100)"); EngineCfg.COMBAT_DISABLE_VANILLA_SWEEP = cfg.getBoolean(path + "disable-vanilla-sweep-attack"); EngineCfg.COMBAT_REDUCE_PLAYER_HEALTH_BAR = cfg.getBoolean(path + "compress-player-health-bar"); EngineCfg.COMBAT_FISHING_HOOK_DO_DAMAGE = cfg.getBoolean(path + "fishing-hook-do-damage"); @@ -415,9 +427,11 @@ public void setup() { path = "lore.stats.style.enchantments."; cfg.addMissing(path + "format.main", "&c▸ %name% %value%"); cfg.addMissing(path + "format.max-roman", 10); + cfg.addMissing(path + "roman-system", true); EngineCfg.LORE_STYLE_ENCHANTMENTS_FORMAT_MAIN = StringUT.color(cfg.getString(path + "format.main", "&c▸ %name% %value%")); EngineCfg.LORE_STYLE_ENCHANTMENTS_FORMAT_MAX_ROMAN = cfg.getInt(path + "format.max-roman", 10); + EngineCfg.LORE_STYLE_ENCHANTMENTS_ROMAN_SYSTEM = cfg.getBoolean(path + "roman-system", true); path = "lore.stats.style.fabled-attribute-format"; cfg.addMissing(path, "&7%attrPre%&3%name%&7%attrPost%"); diff --git a/src/main/java/studio/magemonkey/divinity/hooks/external/FabledHook.java b/src/main/java/studio/magemonkey/divinity/hooks/external/FabledHook.java index 901c7cad..705e77f6 100644 --- a/src/main/java/studio/magemonkey/divinity/hooks/external/FabledHook.java +++ b/src/main/java/studio/magemonkey/divinity/hooks/external/FabledHook.java @@ -4,6 +4,7 @@ import org.bukkit.Material; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.entity.EntityDamageByEntityEvent; @@ -16,6 +17,7 @@ import studio.magemonkey.codex.hooks.NHook; import studio.magemonkey.codex.util.StringUT; import studio.magemonkey.divinity.Divinity; +import studio.magemonkey.divinity.api.event.DivinityDamageEvent; import studio.magemonkey.divinity.config.EngineCfg; import studio.magemonkey.divinity.hooks.HookClass; import studio.magemonkey.divinity.hooks.HookLevel; @@ -28,9 +30,12 @@ import studio.magemonkey.fabled.Fabled; import studio.magemonkey.fabled.api.DefaultCombatProtection; import studio.magemonkey.fabled.api.enums.ExpSource; +import studio.magemonkey.fabled.api.enums.Operation; import studio.magemonkey.fabled.api.event.DynamicTriggerEvent; import studio.magemonkey.fabled.api.event.PlayerManaGainEvent; +import studio.magemonkey.fabled.api.event.PlayerMaxManaChangeEvent; import studio.magemonkey.fabled.api.event.SkillDamageEvent; +import studio.magemonkey.fabled.api.player.PlayerAttributeModifier; import studio.magemonkey.fabled.api.player.PlayerData; import studio.magemonkey.fabled.api.player.PlayerSkill; import studio.magemonkey.fabled.api.skills.Skill; @@ -118,6 +123,17 @@ public void onRegen(PlayerManaGainEvent e) { } } + @EventHandler + public void onMaxManaChange(PlayerMaxManaChangeEvent e) { + Player player = e.getPlayerData().getPlayer(); + if (player == null) return; + + double bonus = EntityStats.get(player).getItemStat(TypedStat.Type.MAX_MANA, false); + if (bonus != 0) { + e.setMaxMana(e.getMaxMana() + bonus); + } + } + @Override public void takeMana(@NotNull Player player, double amount, boolean ofMax) { PlayerData data = Fabled.getData(player); @@ -133,7 +149,7 @@ public void takeMana(@NotNull Player player, double amount, boolean ofMax) { data.setMana(Math.max(0, cur - amount)); } - private final List divinityIgnored = new ArrayList<>(); + private final Set divinityIgnored = new HashSet<>(); @EventHandler(priority = EventPriority.MONITOR) public void skillDamage(SkillDamageEvent event) { @@ -145,10 +161,38 @@ public void skillDamage(SkillDamageEvent event) { @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) public void damage(EntityDamageByEntityEvent event) { - if (!(event.getDamager() instanceof LivingEntity)) - return; + UUID damagerUuid = null; + if (event.getDamager() instanceof LivingEntity) { + damagerUuid = ((LivingEntity) event.getDamager()).getUniqueId(); + } else if (event.getDamager() instanceof Projectile) { + Projectile proj = (Projectile) event.getDamager(); + if (proj.getShooter() instanceof LivingEntity) + damagerUuid = ((LivingEntity) proj.getShooter()).getUniqueId(); + } + if (damagerUuid != null) + divinityIgnored.remove(damagerUuid); + } - divinityIgnored.remove(event.getDamager().getUniqueId()); + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onDivinityDamageStart(DivinityDamageEvent.Start e) { + LivingEntity damager = e.getDamager(); + if (damager == null || !damager.hasMetadata("fabled_dmg_flags")) return; + + int flags = damager.getMetadata("fabled_dmg_flags").get(0).asInt(); + if ((flags & 0x01) != 0) e.getDamageMeta().setIgnoreCrit(true); + if ((flags & 0x04) != 0) e.getDamageMeta().setBlockModifier(1.0); + if ((flags & 0x08) != 0) e.getDamagerItemStatsMap().put(TypedStat.Type.BLEED_RATE, 0.0); + if ((flags & 0x10) != 0) e.getDamagerItemStatsMap().put(TypedStat.Type.VAMPIRISM, 0.0); + if ((flags & 0x20) != 0) e.getDamageMeta().setIgnoreSkillCrit(true); + } + + @EventHandler + public void onDivinityDodge(DivinityDamageEvent.Dodge e) { + LivingEntity damager = e.getDamager(); + if (damager == null || !damager.hasMetadata("fabled_dmg_flags")) return; + + int flags = damager.getMetadata("fabled_dmg_flags").get(0).asInt(); + if ((flags & 0x02) != 0) e.setCancelled(true); } public void ignoreDivinity(LivingEntity player, boolean ignore) { @@ -241,6 +285,58 @@ public ItemStack getAttributeIndicator(String attributeId) { return itemStack; } + private final Map> playerAttrModifiers = new HashMap<>(); + + public void updateFabledAttributes(Player player) { + new BukkitRunnable() { + @Override + public void run() { + if (!Fabled.hasPlayerData(player)) return; + PlayerData data = Fabled.getData(player); + if (data == null) return; + + List oldUuids = playerAttrModifiers.remove(player.getUniqueId()); + if (oldUuids != null) { + for (UUID uuid : oldUuids) { + data.removeAttributeModifier(uuid, false); + } + } + + List newUuids = new ArrayList<>(); + PlayerInventory inventory = player.getInventory(); + int[] slots = {inventory.getHeldItemSlot(), 36, 37, 38, 39, 40}; + for (int slot : slots) { + ItemStack item = inventory.getItem(slot); + if (item == null) continue; + for (FabledAttribute attr : getAttributes()) { + Integer value = attr.getRaw(item); + if (value == null || value == 0) continue; + PlayerAttributeModifier modifier = new PlayerAttributeModifier( + "divinity.fabled_attr", value, Operation.ADD_NUMBER, false); + newUuids.add(modifier.getUUID()); + data.addAttributeModifier(attr.getId(), modifier, false); + } + } + + if (!newUuids.isEmpty()) { + playerAttrModifiers.put(player.getUniqueId(), newUuids); + } + + data.updatePlayerStat(player); + } + }.runTaskLater(plugin, 1L); + } + + public void clearFabledAttributes(Player player) { + List uuids = playerAttrModifiers.remove(player.getUniqueId()); + if (uuids == null || !Fabled.hasPlayerData(player)) return; + PlayerData data = Fabled.getData(player); + if (data == null) return; + for (UUID uuid : uuids) { + data.removeAttributeModifier(uuid, false); + } + } + public void updateSkills(Player player) { new BukkitRunnable() { @Override @@ -284,6 +380,22 @@ public void run() { } } }.runTaskLater(plugin, 1L); + updateFabledAttributes(player); + } + + /** + * Scales a Divinity stat value using Fabled's attribute and stat modifier system. + * Fabled attributes.yml can reference Divinity stat names (lowercase type names, e.g. "critical_rate"). + */ + public double applyStatScale(@NotNull Player player, @NotNull String statId, double value) { + try { + if (!Fabled.hasPlayerData(player)) return value; + PlayerData data = Fabled.getData(player); + if (data == null) return value; + return data.scaleStat(statId, value); + } catch (Exception ignored) { + return value; + } } public boolean isFakeDamage(EntityDamageByEntityEvent event) { diff --git a/src/main/java/studio/magemonkey/divinity/hooks/external/PlaceholderAPIHK.java b/src/main/java/studio/magemonkey/divinity/hooks/external/PlaceholderAPIHK.java index a4b6f26f..b0dc9587 100644 --- a/src/main/java/studio/magemonkey/divinity/hooks/external/PlaceholderAPIHK.java +++ b/src/main/java/studio/magemonkey/divinity/hooks/external/PlaceholderAPIHK.java @@ -17,6 +17,8 @@ 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.stats.items.attributes.stats.DynamicBuffStat; +import studio.magemonkey.divinity.stats.items.attributes.stats.PenetrationStat; public class PlaceholderAPIHK extends NHook { @@ -70,8 +72,8 @@ public String onPlaceholderRequest(Player player, String tmp) { try { SimpleStat.Type type = TypedStat.Type.valueOf(tt.toUpperCase()); return String.valueOf(NumberUT.round(EntityStats.get(player).getItemStat(type, true))); - } catch (IllegalArgumentException ex) { - return NULL; + } catch (Exception ex) { + return "0"; } } @@ -79,18 +81,59 @@ public String onPlaceholderRequest(Player player, String tmp) { String tt = tmp.replace("damage_", ""); DamageAttribute dt = ItemStats.getDamageById(tt); if (dt == null) { - return NULL; + return "0"; + } + try { + return String.valueOf(NumberUT.round(EntityStats.get(player).getDamageByType(dt))); + } catch (Exception ex) { + return "0"; } - return String.valueOf(NumberUT.round(EntityStats.get(player).getDamageByType(dt))); } if (tmp.startsWith("defense_")) { String tt = tmp.replace("defense_", ""); DefenseAttribute dt = ItemStats.getDefenseById(tt); if (dt == null) { - return NULL; + return "0"; + } + try { + return String.valueOf(NumberUT.round(EntityStats.get(player).getDefenseByType(dt))); + } catch (Exception ex) { + return "0"; + } + } + + if (tmp.startsWith("damagebuff_")) { + String tt = tmp.replace("damagebuff_", ""); + DynamicBuffStat buff = ItemStats.getDamageBuff(tt); + if (buff == null) return "0"; + try { + return String.valueOf(NumberUT.round(EntityStats.get(player).getDynamicBuff(buff))); + } catch (Exception ex) { + return "0"; + } + } + + if (tmp.startsWith("defensebuff_")) { + String tt = tmp.replace("defensebuff_", ""); + DynamicBuffStat buff = ItemStats.getDefenseBuff(tt); + if (buff == null) return "0"; + try { + return String.valueOf(NumberUT.round(EntityStats.get(player).getDynamicBuff(buff))); + } catch (Exception ex) { + return "0"; + } + } + + if (tmp.startsWith("penetration_")) { + String tt = tmp.replace("penetration_", ""); + PenetrationStat pen = ItemStats.getPenetration(tt); + if (pen == null) return "0"; + try { + return String.valueOf(NumberUT.round(EntityStats.get(player).getPenetration(pen))); + } catch (Exception ex) { + return "0"; } - return String.valueOf(NumberUT.round(EntityStats.get(player).getDefenseByType(dt))); } if (tmp.startsWith("class_")) { diff --git a/src/main/java/studio/magemonkey/divinity/manager/EntityManager.java b/src/main/java/studio/magemonkey/divinity/manager/EntityManager.java index ddc45f9f..46390700 100644 --- a/src/main/java/studio/magemonkey/divinity/manager/EntityManager.java +++ b/src/main/java/studio/magemonkey/divinity/manager/EntityManager.java @@ -19,6 +19,7 @@ import studio.magemonkey.divinity.api.event.DivinityDamageEvent; import studio.magemonkey.divinity.api.event.EntityDivinityItemPickupEvent; import studio.magemonkey.divinity.api.event.EntityEquipmentChangeEvent; +import studio.magemonkey.divinity.config.EngineCfg; import studio.magemonkey.divinity.modules.api.QModuleDrop; import studio.magemonkey.divinity.stats.EntityStats; import studio.magemonkey.divinity.stats.EntityStatsTask; @@ -90,6 +91,7 @@ public void shutdown() { @EventHandler(priority = EventPriority.MONITOR) public void onStatsDeath(EntityDeathEvent e) { + if(EngineCfg.FULL_LEGACY) return; LivingEntity entity = e.getEntity(); previousEquipment.remove(e.getEntity().getUniqueId()); EntityStats.get(entity).handleDeath(); @@ -98,17 +100,20 @@ public void onStatsDeath(EntityDeathEvent e) { // Clear stats on player exit @EventHandler(priority = EventPriority.HIGHEST) public void onStatsQuit(PlayerQuitEvent e) { + if(EngineCfg.FULL_LEGACY) return; EntityStats.purge(e.getPlayer()); } @EventHandler(priority = EventPriority.HIGHEST) public void onStatsJoin(PlayerJoinEvent e) { + if(EngineCfg.FULL_LEGACY) return; EntityStats.get(e.getPlayer()); this.pushToUpdate(e.getPlayer(), 1D); } @EventHandler public void quit(PlayerQuitEvent event) { + if(EngineCfg.FULL_LEGACY) return; previousEquipment.remove(event.getPlayer().getUniqueId()); } @@ -124,6 +129,7 @@ public void onStatsRegen(EntityRegainHealthEvent e) { @EventHandler(ignoreCancelled = true) public void onPickup(EntityPickupItemEvent e) { + if(EngineCfg.FULL_LEGACY) return; if (!ProjectileStats.isPickable(e.getItem())) { e.setCancelled(true); } @@ -138,6 +144,7 @@ public void onPickup(EntityPickupItemEvent e) { } private final void pushToUpdate(@NotNull LivingEntity entity, double time) { + if(EngineCfg.FULL_LEGACY) return; EntityEquipment equip = new EntityEquipmentSnapshot(entity); previousEquipment.put(entity.getUniqueId(), equip); if (time <= 0D) { @@ -153,6 +160,7 @@ public void run() { } private final void addDuplicatorFixer(@NotNull Entity entity) { + if(EngineCfg.FULL_LEGACY) return; entity.setMetadata(PACKET_DUPLICATOR_FIXER, new FixedMetadataValue(plugin, "fixed")); } diff --git a/src/main/java/studio/magemonkey/divinity/manager/damage/DamageManager.java b/src/main/java/studio/magemonkey/divinity/manager/damage/DamageManager.java index c1a3b290..3b641319 100644 --- a/src/main/java/studio/magemonkey/divinity/manager/damage/DamageManager.java +++ b/src/main/java/studio/magemonkey/divinity/manager/damage/DamageManager.java @@ -23,6 +23,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import studio.magemonkey.codex.api.items.PrefixHelper; +import studio.magemonkey.codex.util.eval.Evaluator; import studio.magemonkey.codex.hooks.Hooks; import studio.magemonkey.codex.manager.IListener; import studio.magemonkey.codex.registry.provider.DamageTypeProvider; @@ -46,6 +47,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.BleedStat; +import studio.magemonkey.divinity.stats.items.attributes.stats.DynamicBuffStat; +import studio.magemonkey.divinity.stats.items.attributes.stats.PenetrationStat; import java.util.*; import java.util.function.DoubleUnaryOperator; @@ -251,24 +254,119 @@ public void onDamageRPGStart(@NotNull DivinityDamageEvent.Start e) { if (!e.isExempt()) dmgType *= powerMod; dmgType *= blockMod; + // Apply damage buff % from attacker's equipment + if (statsDamager != null && dmgAtt != null) { + for (DynamicBuffStat buff : ItemStats.getDamageBuffs()) { + if (buff.isApplicableTo(dmgAtt.getId())) { + double buffPct = statsDamager.getDynamicBuff(buff); + if (buffPct != 0) dmgType *= (1.0 + buffPct / 100.0); + } + } + } + // Per-type penetration (PenetrationStat from penetration.yml) + // perTypePenMod: additional % pen multiplier (all formulas) + // perTypeFlatPen: flat defense reduction (CUSTOM formula only) + double perTypePenMod = 1.0; + double perTypeFlatPen = 0.0; + if (statsDamager != null && dmgAtt != null) { + for (PenetrationStat penStat : ItemStats.getPenetrations()) { + if (penStat.isApplicableTo(dmgAtt.getId())) { + double penValue = statsDamager.getPenetration(penStat); + if (penValue != 0) { + if (penStat.isPercentPen()) { + perTypePenMod *= Math.max(0D, 1.0 - penValue / 100.0); + } else { + perTypeFlatPen += penValue; + } + } + } + } + } double directType = dmgType * directMod; // Get direct value for this Damage Attribute dmgType = Math.max(0, dmgType - directType); // Deduct this value from damage if (dmgType > 0) { - DefenseAttribute defAtt = dmgAtt != null ? dmgAtt.getAttachedDefense() : null; - if (defAtt != null && defenses.containsKey(defAtt)) { - double def = Math.max(0, defenses.get(defAtt) * pveDefenseMod * penetrateMod); - - double defCalced; - if (EngineCfg.LEGACY_COMBAT) { - defCalced = Math.max(0, dmgType * (1 - (def * defAtt.getProtectionFactor() * 0.01))); - } else { - defCalced = Math.max(0, + if (EngineCfg.LEGACY_COMBAT) { + // Legacy: 1:1, highest priority defense only + DefenseAttribute defAtt = dmgAtt != null ? dmgAtt.getAttachedDefense() : null; + if (defAtt != null && defenses.containsKey(defAtt)) { + double def = Math.max(0, defenses.get(defAtt) * pveDefenseMod * penetrateMod * perTypePenMod); + // Apply defense buff % from victim's equipment + for (DynamicBuffStat dBuff : ItemStats.getDefenseBuffs()) { + if (dBuff.isApplicableTo(defAtt.getId())) { + double buffPct = statsVictim.getDynamicBuff(dBuff); + if (buffPct != 0) def *= (1.0 + buffPct / 100.0); + } + } + double defCalced = Math.max(0, dmgType * (1 - (def * defAtt.getProtectionFactor() * 0.01))); + meta.setDefendedDamage(defAtt, dmgType - defCalced); + dmgType = defCalced; + } + } else if ("CUSTOM".equals(EngineCfg.DEFENSE_FORMULA_MODE)) { + // Custom: collect ALL matching defenses (group sum + individual placeholders) + double totalDef = 0; + Map individualDefs = new HashMap<>(); + for (DefenseAttribute defAtt : ItemStats.getDefenses()) { + if (dmgAtt != null && defAtt.isBlockable(dmgAtt) && defenses.containsKey(defAtt)) { + double def = Math.max(0, defenses.get(defAtt) * pveDefenseMod * penetrateMod * perTypePenMod); + totalDef += def; + individualDefs.put(defAtt.getId(), def); + } + } + // Apply defense buff % from victim's equipment (on summed total) + if (totalDef > 0 && dmgAtt != null) { + for (DynamicBuffStat dBuff : ItemStats.getDefenseBuffs()) { + if (dBuff.isApplicableTo(dmgAtt.getId())) { + double buffPct = statsVictim.getDynamicBuff(dBuff); + if (buffPct != 0) totalDef *= (1.0 + buffPct / 100.0); + } + } + } + // Apply flat penetration to total defense (CUSTOM formula only) + double overflowFlatPen = 0; + double preFlatPenDefVal = totalDef; // saved for overflow formula's 'defense' placeholder + if (perTypeFlatPen > 0) { + totalDef = Math.max(0, totalDef - perTypeFlatPen); + if (EngineCfg.COMBAT_OVERFLOW_PEN_AMPLIFIES && perTypeFlatPen > preFlatPenDefVal) { + overflowFlatPen = perTypeFlatPen - preFlatPenDefVal; + } + } + if (totalDef > 0) { + double defCalced = Math.max(0, evaluateDefenseFormula( + EngineCfg.CUSTOM_DEFENSE_FORMULA, dmgType, totalDef, toughness, individualDefs)); + DefenseAttribute primaryDef = dmgAtt != null ? dmgAtt.getAttachedDefense() : null; + if (primaryDef != null) { + meta.setDefendedDamage(primaryDef, dmgType - defCalced); + } + dmgType = defCalced; + } + // Apply flat pen overflow amplification + if (overflowFlatPen > 0) { + // defense = original totalDef before flat pen; overflow = flatPen - defense + double bonus = evaluateOverflowFormula( + EngineCfg.COMBAT_OVERFLOW_PEN_FORMULA, dmgType, overflowFlatPen, preFlatPenDefVal); + if (bonus > 0) { + dmgType += bonus; + } + } + } else { + // Factor: 1:1, highest priority defense only (minecraft formula) + DefenseAttribute defAtt = dmgAtt != null ? dmgAtt.getAttachedDefense() : null; + if (defAtt != null && defenses.containsKey(defAtt)) { + double def = Math.max(0, defenses.get(defAtt) * pveDefenseMod * penetrateMod * perTypePenMod); + // Apply defense buff % from victim's equipment + for (DynamicBuffStat dBuff : ItemStats.getDefenseBuffs()) { + if (dBuff.isApplicableTo(defAtt.getId())) { + double buffPct = statsVictim.getDynamicBuff(dBuff); + if (buffPct != 0) def *= (1.0 + buffPct / 100.0); + } + } + double defCalced = Math.max(0, dmgType * (1 - Math.max(def / 5, def - 4 * dmgType / Math.max(1, toughness + 8)) * defAtt.getProtectionFactor() * 0.05)); + meta.setDefendedDamage(defAtt, dmgType - defCalced); + dmgType = defCalced; } - meta.setDefendedDamage(defAtt, dmgType - defCalced); - dmgType = defCalced; } } //Should we reactivate direct damage, remove directType here and deal the damage straight. @@ -292,15 +390,13 @@ public void onDamageRPGStart(@NotNull DivinityDamageEvent.Start e) { // Compare modified damage and invulnerable prot. If they're within 0.0001 of each other, set the damage to 0. // and cancel the event if (modifiedDamage + invulnerableProt < 0.001) { - if (e.getOriginalEvent().getEntity().getType() != EntityType.ARMOR_STAND) { - e.setCancelled(true); - e.getOriginalEvent().setCancelled(true); - } + e.setCancelled(true); + e.getOriginalEvent().setCancelled(true); return; } meta.setInvulnerableProtection(invulnerableProt); - double dmgTotal = meta.getTotalDamage(); + double dmgTotal = Math.round(meta.getTotalDamage() * 100.0) / 100.0; // Divinity.getInstance().getLogger().info("Damage total: " + dmgTotal); // Divinity.getInstance().getLogger().info("Defended: " + meta.getDefendedDamage()); orig.setDamage(DamageModifier.BASE, dmgTotal); @@ -346,13 +442,30 @@ private boolean handleDamageModifiers( } } + // SKILL_CRITICAL_RATE/DAMAGE apply only to skill damage (e.g. Fabled DamageMechanic). + // Autoattacks (melee, projectile) use only the standard CRITICAL_RATE/DAMAGE. + FabledHook skillApiHook = (FabledHook) Divinity.getInstance().getHook(EHook.SKILL_API); + boolean isSkillHit = skillApiHook != null && skillApiHook.isSkillDamage(); + double critRate = 0D; + if (!meta.isIgnoreCrit()) { + critRate += event.getDamagerItemStat(TypedStat.Type.CRITICAL_RATE); + } + if (isSkillHit && !meta.isIgnoreSkillCrit()) { + critRate += event.getDamagerItemStat(TypedStat.Type.SKILL_CRITICAL_RATE); + } double critModifier = 1D; - double critRate = event.getDamagerItemStat(TypedStat.Type.CRITICAL_RATE); if (critRate > 0 && Rnd.get(true) < critRate) { - critModifier = event.getDamagerItemStat(TypedStat.Type.CRITICAL_DAMAGE); - if (critModifier == 0D) { - critModifier = 1D; + // CRITICAL_DAMAGE has implicit 1.0 baseline (see EntityStats:950); strip it so both stats + // act as additive bonuses on top of the shared 1.0 baseline. Prevents critModifier < 1.0 + // when divinity-ignore-crit is set and only SKILL_CRITICAL_DAMAGE (< 1.0) contributes. + double critBonus = 0D; + if (!meta.isIgnoreCrit()) { + critBonus += event.getDamagerItemStat(TypedStat.Type.CRITICAL_DAMAGE) - 1D; + } + if (isSkillHit && !meta.isIgnoreSkillCrit()) { + critBonus += event.getDamagerItemStat(TypedStat.Type.SKILL_CRITICAL_DAMAGE); } + critModifier = 1D + Math.max(0D, critBonus); } meta.setCriticalModifier(critModifier); @@ -571,4 +684,37 @@ public void onDamage(DivinityDamageEvent.BeforeScale event) { } return success[0]; } + + private static double evaluateDefenseFormula(String formula, double damage, double defense, + double toughness, Map individualDefs) { + String expr = formula + .replace("damage", String.valueOf(damage)) + .replace("toughness", String.valueOf(toughness)); + // Replace individual defense placeholders BEFORE the sum placeholder + // because "defense" is a prefix of "defense_" + for (Map.Entry entry : individualDefs.entrySet()) { + expr = expr.replace("defense_" + entry.getKey(), String.valueOf(entry.getValue())); + } + expr = expr.replace("defense", String.valueOf(defense)); + return Evaluator.eval(expr, 1); + } + + /** + * Evaluates the overflow pen formula and returns the bonus damage to add. + * + * @param formula the formula string from config (overflow-pen-formula) + * @param damage incoming damage after defense reduction (post-formula) + * @param overflow flat pen amount that exceeded the target's total defense + * @param defense total defense before flat pen was applied + * @return bonus damage to add; 0 on invalid result (NaN / Infinity / negative) + */ + private static double evaluateOverflowFormula(String formula, double damage, + double overflow, double defense) { + String expr = formula + .replace("damage", String.valueOf(damage)) + .replace("overflow", String.valueOf(overflow)) + .replace("defense", String.valueOf(defense)); + double result = Evaluator.eval(expr, 1); + return Double.isFinite(result) ? Math.max(0, result) : 0; + } } diff --git a/src/main/java/studio/magemonkey/divinity/manager/damage/DamageMeta.java b/src/main/java/studio/magemonkey/divinity/manager/damage/DamageMeta.java index 10ee2cff..bc0be6b5 100644 --- a/src/main/java/studio/magemonkey/divinity/manager/damage/DamageMeta.java +++ b/src/main/java/studio/magemonkey/divinity/manager/damage/DamageMeta.java @@ -31,6 +31,8 @@ public class DamageMeta { private double pveDefModifier = 1D; private double directModifier = 0D; private double criticalModifier = 1D; + private boolean ignoreCrit = false; + private boolean ignoreSkillCrit = false; private double penetrateModifier = 1D; private double enchantProtectionModifier = 1D; @Getter @@ -208,6 +210,22 @@ public void setCriticalModifier(double critMod) { this.criticalModifier = critMod; } + public boolean isIgnoreCrit() { + return this.ignoreCrit; + } + + public void setIgnoreCrit(boolean ignoreCrit) { + this.ignoreCrit = ignoreCrit; + } + + public boolean isIgnoreSkillCrit() { + return this.ignoreSkillCrit; + } + + public void setIgnoreSkillCrit(boolean ignoreSkillCrit) { + this.ignoreSkillCrit = ignoreSkillCrit; + } + public double getDirectModifier() { return this.directModifier; } diff --git a/src/main/java/studio/magemonkey/divinity/manager/interactions/api/AnimatedSuccessBar.java b/src/main/java/studio/magemonkey/divinity/manager/interactions/api/AnimatedSuccessBar.java index fe4c36a7..8dfd9571 100644 --- a/src/main/java/studio/magemonkey/divinity/manager/interactions/api/AnimatedSuccessBar.java +++ b/src/main/java/studio/magemonkey/divinity/manager/interactions/api/AnimatedSuccessBar.java @@ -147,9 +147,11 @@ class Task extends ITask { Task() { super(AnimatedSuccessBar.this.plugin, AnimatedSuccessBar.this.fillInterval, true); - int calculatedResult = Math.round(Rnd.get(true)); - int iterations = (int) Math.ceil(100D / fillAmount); - // Map the iteration to the relative success for the result + int iterations = (int) Math.ceil(100D / fillAmount); + // At 100% chance the bar should animate fully green — no red segments + int calculatedResult = (chance >= 100) + ? iterations * fillAmount + 1 + : Math.round(Rnd.get(true)); for (int i = 0; i < iterations; i++) { mappedResult.add(i * fillAmount < calculatedResult); } diff --git a/src/main/java/studio/magemonkey/divinity/manager/listener/ListenerManager.java b/src/main/java/studio/magemonkey/divinity/manager/listener/ListenerManager.java index dbb20fe1..94d65295 100644 --- a/src/main/java/studio/magemonkey/divinity/manager/listener/ListenerManager.java +++ b/src/main/java/studio/magemonkey/divinity/manager/listener/ListenerManager.java @@ -1,9 +1,10 @@ package studio.magemonkey.divinity.manager.listener; import org.jetbrains.annotations.NotNull; -import studio.magemonkey.codex.core.Version; import studio.magemonkey.codex.manager.api.Loadable; import studio.magemonkey.divinity.Divinity; +import studio.magemonkey.divinity.config.Config; +import studio.magemonkey.divinity.config.EngineCfg; import studio.magemonkey.divinity.hooks.HookListener; import studio.magemonkey.divinity.manager.listener.object.*; import studio.magemonkey.divinity.stats.items.ItemStats; @@ -20,7 +21,6 @@ public class ListenerManager implements Loadable { private ItemUpdaterListener updater; private VanillaWrapperListener lisQuantum; private HookListener hookListener; - private GrindstoneListener grindstoneListener; public ListenerManager(@NotNull Divinity plugin) { this.plugin = plugin; @@ -47,19 +47,19 @@ public void setup() { this.lisDynamic = new DynamicStatListener(this.plugin); this.lisDynamic.registerListeners(); - this.lisQuantum = new VanillaWrapperListener(this.plugin); - this.lisQuantum.registerListeners(); + if(!EngineCfg.LEGACY_COMBAT) { + this.lisQuantum = new VanillaWrapperListener(this.plugin); + this.lisQuantum.registerListeners(); + Divinity.getInstance().getLogger().info("Loaded " + this.lisQuantum.getClass().getSimpleName()); + } else { + Divinity.getInstance().getLogger().info("Skipped " + VanillaWrapperListener.class.getSimpleName() + " due to legacy combat being enabled."); + } this.updater = new ItemUpdaterListener(this.plugin); this.updater.registerListeners(); this.hookListener = new HookListener(this.plugin); this.hookListener.registerListeners(); - - if (Version.CURRENT.isAtLeast(Version.V1_19_R3)) { - this.grindstoneListener = new GrindstoneListener(this.plugin); - this.grindstoneListener.registerListeners(); - } } @Override @@ -80,9 +80,5 @@ public void shutdown() { this.lisQuantum.unregisterListeners(); this.lisQuantum = null; } - if (this.grindstoneListener != null) { - this.grindstoneListener.unregisterListeners(); - this.grindstoneListener = null; - } } } diff --git a/src/main/java/studio/magemonkey/divinity/manager/listener/object/DynamicStatListener.java b/src/main/java/studio/magemonkey/divinity/manager/listener/object/DynamicStatListener.java index 2c2f021b..a79bafc8 100644 --- a/src/main/java/studio/magemonkey/divinity/manager/listener/object/DynamicStatListener.java +++ b/src/main/java/studio/magemonkey/divinity/manager/listener/object/DynamicStatListener.java @@ -13,6 +13,7 @@ import org.jetbrains.annotations.Nullable; import studio.magemonkey.codex.manager.IListener; import studio.magemonkey.divinity.Divinity; +import studio.magemonkey.divinity.config.EngineCfg; import studio.magemonkey.divinity.stats.items.ItemStats; import studio.magemonkey.divinity.stats.items.api.DynamicStat; @@ -27,6 +28,7 @@ public DynamicStatListener(@NotNull Divinity plugin) { } public static void updateItem(@Nullable Player p, @NotNull ItemStack item) { + if(EngineCfg.FULL_LEGACY) return; for (DynamicStat dynamicStat : ItemStats.getDynamicStats()) { dynamicStat.updateItem(p, item); } @@ -34,12 +36,14 @@ public static void updateItem(@Nullable Player p, @NotNull ItemStack item) { @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onDrop(PlayerDropItemEvent e) { + if(EngineCfg.FULL_LEGACY) return; ItemStack item = e.getItemDrop().getItemStack(); updateItem(null, item); } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onPick(EntityPickupItemEvent e) { + if(EngineCfg.FULL_LEGACY) return; LivingEntity entity = e.getEntity(); if (!(entity instanceof Player)) return; @@ -50,6 +54,7 @@ public void onPick(EntityPickupItemEvent e) { @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onInvOpen(InventoryOpenEvent e) { + if(EngineCfg.FULL_LEGACY) return; List list = new ArrayList<>(); Player player = (Player) e.getPlayer(); @@ -64,6 +69,7 @@ public void onInvOpen(InventoryOpenEvent e) { @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onInvClose(InventoryCloseEvent e) { + if(EngineCfg.FULL_LEGACY) return; Player player = (Player) e.getPlayer(); List list = new ArrayList<>(); diff --git a/src/main/java/studio/magemonkey/divinity/manager/listener/object/ItemDurabilityListener.java b/src/main/java/studio/magemonkey/divinity/manager/listener/object/ItemDurabilityListener.java index e9637e4d..203d2aa2 100644 --- a/src/main/java/studio/magemonkey/divinity/manager/listener/object/ItemDurabilityListener.java +++ b/src/main/java/studio/magemonkey/divinity/manager/listener/object/ItemDurabilityListener.java @@ -13,7 +13,6 @@ import org.bukkit.event.player.PlayerItemMendEvent; import org.bukkit.inventory.EntityEquipment; import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.Damageable; import org.jetbrains.annotations.NotNull; import studio.magemonkey.codex.manager.IListener; import studio.magemonkey.codex.util.ItemUT; @@ -41,6 +40,22 @@ public void onDuraItemDamage(PlayerItemDamageEvent e) { } } + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onMending(PlayerItemMendEvent e) { + ItemStack item = e.getItem(); + if (!ItemStats.hasStat(item, null, TypedStat.Type.DURABILITY)) return; + + e.setCancelled(true); // block vanilla mending + + double[] dur = duraStat.getRaw(item); + if (dur == null || dur[1] <= 0 || dur[0] >= dur[1]) return; // full, unbreakable, or no data + + int repairAmount = e.getRepairAmount(); + double newCurrent = Math.min(dur[0] + repairAmount, dur[1]); + duraStat.add(item, new double[]{newCurrent, dur[1]}, -1); + duraStat.syncVanillaBar(item); + } + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onDuraBreak(BlockBreakEvent e) { Player player = e.getPlayer(); @@ -115,48 +130,4 @@ public void onDuraHoe(PlayerInteractEvent e) { } } } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void onMend(PlayerItemMendEvent e) { - ItemStack item = e.getItem(); - - if (!ItemStats.hasStat(item, null, TypedStat.Type.DURABILITY)) return; - - double[] durability = duraStat.getRaw(item); - if (durability == null || duraStat.isUnbreakable(item)) return; - - double current = durability[0]; - double max = durability[1]; - int vanillaMax = item.getType().getMaxDurability(); - - if (vanillaMax <= 0) return; - - int repair = e.getRepairAmount(); - // Scale the repair amount to the durability max, so we can - // properly update the custom durability amount. - double customRepair = ((double) repair / vanillaMax) * max; - double newValue = current + customRepair; - - // Cap the durability at the max - if (newValue > max) { - newValue = max; - } - - newValue = Math.round(newValue * 100.0) / 100.0; - - duraStat.add(item, new double[]{newValue, max}, -1); - duraStat.syncVanillaBar(item, newValue, max); - - e.setCancelled(true); - - Damageable damageable = (Damageable) item.getItemMeta(); - if (damageable != null) { - int vanillaDamage = damageable.getDamage(); - if (vanillaDamage == 0) { - duraStat.add(item, new double[]{max, max}, -1); - duraStat.syncVanillaBar(item, max, max); - e.setCancelled(true); - } - } - } } diff --git a/src/main/java/studio/magemonkey/divinity/manager/listener/object/ItemUpdaterListener.java b/src/main/java/studio/magemonkey/divinity/manager/listener/object/ItemUpdaterListener.java index b21b19e9..7589a4a5 100644 --- a/src/main/java/studio/magemonkey/divinity/manager/listener/object/ItemUpdaterListener.java +++ b/src/main/java/studio/magemonkey/divinity/manager/listener/object/ItemUpdaterListener.java @@ -25,6 +25,7 @@ import studio.magemonkey.codex.manager.IListener; import studio.magemonkey.codex.util.DataUT; import studio.magemonkey.divinity.Divinity; +import studio.magemonkey.divinity.config.EngineCfg; import studio.magemonkey.divinity.stats.items.ItemStats; public class ItemUpdaterListener extends IListener { @@ -93,6 +94,7 @@ public void join(PlayerJoinEvent event) { } public void update(ItemStack item, @Nullable Player player) { + if(EngineCfg.LEGACY_COMBAT ||EngineCfg.FULL_LEGACY) return; if (item == null || item.getType() == Material.AIR) return; ItemType itemType = CodexEngine.get().getItemManager().getMainItemType(item); diff --git a/src/main/java/studio/magemonkey/divinity/manager/listener/object/VanillaWrapperListener.java b/src/main/java/studio/magemonkey/divinity/manager/listener/object/VanillaWrapperListener.java index 3a1680f5..f7d69c7d 100644 --- a/src/main/java/studio/magemonkey/divinity/manager/listener/object/VanillaWrapperListener.java +++ b/src/main/java/studio/magemonkey/divinity/manager/listener/object/VanillaWrapperListener.java @@ -13,6 +13,7 @@ import org.bukkit.event.entity.EntityShootBowEvent; import org.bukkit.event.entity.ProjectileLaunchEvent; import org.bukkit.event.inventory.PrepareAnvilEvent; +import org.bukkit.event.inventory.PrepareGrindstoneEvent; import org.bukkit.inventory.EntityEquipment; import org.bukkit.inventory.ItemStack; import org.bukkit.metadata.FixedMetadataValue; @@ -67,7 +68,7 @@ public void onVanillaShootBow(EntityShootBowEvent e) { Vector orig = pj.getVelocity(); double power = e.getForce(); - if (Version.CURRENT.isAtLeast(Version.V1_20_R4) && Version.CURRENT.isLower(Version.V1_21_R4)) { + if (Version.CURRENT.isAtLeast(Version.V1_20_R4)) { power /= 3; } @@ -166,6 +167,7 @@ public void onVanillaDamage(EntityDamageEvent e) { EntityStats statsDamager = null; EntityStats statsVictim = EntityStats.get(victim); + if (!(victim instanceof Player)) statsVictim.updateInventory(); DamageMeta meta = new DamageMeta(victim, damager, weapon, cause); statsVictim.setLastDamageMeta(meta); @@ -196,6 +198,7 @@ public void onVanillaDamage(EntityDamageEvent e) { meta.setDamager(damager); statsDamager = EntityStats.get(damager); + if (!(damager instanceof Player)) statsDamager.updateInventory(); statsDamager.setLastDamageMeta(meta); weapon = statsDamager.getItemInMainHand(); @@ -218,6 +221,7 @@ public void onVanillaDamage(EntityDamageEvent e) { damager = (LivingEntity) shooter; meta.setDamager(damager); statsDamager = EntityStats.get(damager); + if (!(damager instanceof Player)) statsDamager.updateInventory(); statsDamager.setLastDamageMeta(meta); weapon = ProjectileStats.getSrcWeapon(projectile); @@ -509,4 +513,11 @@ public void onEnchantingTable(PrepareItemEnchantEvent e) { ItemStack result = e.getItem(); ItemGeneratorManager.updateGeneratorItemLore(result); } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onGrindStone(PrepareGrindstoneEvent e) { + ItemStack result = e.getResult(); + if (result == null) return; + ItemGeneratorManager.updateGeneratorItemLore(result); + } } diff --git a/src/main/java/studio/magemonkey/divinity/modules/ModuleCache.java b/src/main/java/studio/magemonkey/divinity/modules/ModuleCache.java index 768b1431..33685bc6 100644 --- a/src/main/java/studio/magemonkey/divinity/modules/ModuleCache.java +++ b/src/main/java/studio/magemonkey/divinity/modules/ModuleCache.java @@ -245,4 +245,9 @@ public CombatLogManager getCombatLogManager() { public MoneyManager getMoneyManager() { return this.moneyManager; } + + @Nullable + public ConsumablesManager getConsumablesManager() { + return this.consumablesManager; + } } \ No newline at end of file diff --git a/src/main/java/studio/magemonkey/divinity/modules/ModuleItem.java b/src/main/java/studio/magemonkey/divinity/modules/ModuleItem.java index b40d17e1..5cd0010d 100644 --- a/src/main/java/studio/magemonkey/divinity/modules/ModuleItem.java +++ b/src/main/java/studio/magemonkey/divinity/modules/ModuleItem.java @@ -1,6 +1,7 @@ package studio.magemonkey.divinity.modules; import org.apache.commons.lang3.ArrayUtils; +import org.bukkit.Bukkit; import org.bukkit.Color; import org.bukkit.Material; import org.bukkit.NamespacedKey; @@ -8,6 +9,7 @@ import org.bukkit.attribute.AttributeModifier; import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.*; @@ -135,16 +137,25 @@ public ModuleItem(@NotNull Divinity plugin, @NotNull JYML cfg, @NotNull QModuleD this.attributes = new HashMap<>(); for (String attr : cfg.getSection("attributes")) { - String[] attrData = cfg.getString("attributes." + attr, "").split(":"); - double value = Double.parseDouble(attrData[0]); - String operation = attrData.length > 1 ? attrData[1] : "ADD_NUMBER"; - NBTAttribute nbtAttr = NBTAttribute.valueOf(attr.toUpperCase()); - AttributeModifier attrModifier = VersionManager.getCompat() - .createAttributeModifier(nbtAttr, value, AttributeModifier.Operation.valueOf(operation)); + String[] attrData = cfg.getString("attributes." + attr, "").split(":"); + double value = Double.parseDouble(attrData[0]); + String operation = attrData.length > 1 ? attrData[1] : "ADD_NUMBER"; + String equipmentSlot = attrData.length > 2 ? attrData[2] : null; + NBTAttribute nbtAttr = NBTAttribute.valueOf(attr.toUpperCase()); + + // Check attribute through compat support + AttributeModifier attrModifier = VersionManager.getCompat().createAttributeModifier(nbtAttr, value, AttributeModifier.Operation.valueOf(operation)); if (attrModifier == null) { Codex.warn("Invalid attribute provided: " + attr + " (" + cfg.getFile().getName() + ")"); continue; } + // If everything was fine and equipmentslot != null, recreate the modifier including the equipment slot + if(equipmentSlot != null) { + EquipmentSlot slot = EquipmentSlot.valueOf(equipmentSlot.toUpperCase()); + attrModifier = new AttributeModifier(attrModifier.getUniqueId(), attrModifier.getName(), attrModifier.getAmount(), attrModifier.getOperation(), slot); + // Debug + //Bukkit.getLogger().info("Registered attribute " + nbtAttr.name() + " with value " + value + ", operation " + operation + " and equipment slot " + equipmentSlot + " for item " + this.getId()); + } this.attributes.put(nbtAttr.getAttribute(), attrModifier); } @@ -277,6 +288,7 @@ protected ItemStack build(@NotNull ItemStack item) { for (Map.Entry attribute : this.attributes.entrySet()) { if (attribute != null) { + AttributeModifier mod = new AttributeModifier(attribute.getValue().getName(), attribute.getValue().getAmount(), attribute.getValue().getOperation()); meta.addAttributeModifier(attribute.getKey(), attribute.getValue()); } } diff --git a/src/main/java/studio/magemonkey/divinity/modules/api/QModule.java b/src/main/java/studio/magemonkey/divinity/modules/api/QModule.java index a5d649e9..18a49560 100644 --- a/src/main/java/studio/magemonkey/divinity/modules/api/QModule.java +++ b/src/main/java/studio/magemonkey/divinity/modules/api/QModule.java @@ -6,6 +6,7 @@ import studio.magemonkey.divinity.Divinity; import studio.magemonkey.divinity.modules.api.socketing.ModuleSocket; import studio.magemonkey.divinity.modules.command.*; +import studio.magemonkey.divinity.modules.list.itemgenerator.ItemGeneratorManager; public abstract class QModule extends IModule { @@ -29,6 +30,9 @@ protected void onPostSetup() { this.moduleCommand.addSubCommand(new MGiveCmd(md)); this.moduleCommand.addSubCommand(new MDropCmd(md)); this.moduleCommand.addSubCommand(new MListCmd(md)); + if (this instanceof ItemGeneratorManager) { + this.moduleCommand.addSubCommand(new MMobEquipCmd(md)); + } } this.moduleCommand.addSubCommand(new MReloadCmd(this)); } diff --git a/src/main/java/studio/magemonkey/divinity/modules/api/socketing/ModuleSocket.java b/src/main/java/studio/magemonkey/divinity/modules/api/socketing/ModuleSocket.java index bc4bfa2f..c2c4f5d7 100644 --- a/src/main/java/studio/magemonkey/divinity/modules/api/socketing/ModuleSocket.java +++ b/src/main/java/studio/magemonkey/divinity/modules/api/socketing/ModuleSocket.java @@ -160,7 +160,7 @@ protected boolean onDragDrop( @NotNull I mItem, @NotNull InventoryClickEvent e) { - if (!Perms.has(player, Perms.getSocketGuiUser(this))) { + if (!player.hasPermission(Perms.getSocketGuiUser(this))) { plugin.lang().Error_NoPerm.send(player); return false; } diff --git a/src/main/java/studio/magemonkey/divinity/modules/api/socketing/merchant/MerchantCmd.java b/src/main/java/studio/magemonkey/divinity/modules/api/socketing/merchant/MerchantCmd.java index 55e127f4..e9b5306b 100644 --- a/src/main/java/studio/magemonkey/divinity/modules/api/socketing/merchant/MerchantCmd.java +++ b/src/main/java/studio/magemonkey/divinity/modules/api/socketing/merchant/MerchantCmd.java @@ -40,7 +40,7 @@ public String description() { @Override @NotNull public List getTab(@NotNull Player player, int i, @NotNull String[] args) { - if (Perms.has(player, Perms.getSocketCmdMerchantOthers(this.module))) { + if (player.hasPermission(Perms.getSocketCmdMerchantOthers(this.module))) { if (i == 1) { return PlayerUT.getPlayerNames(); } @@ -57,7 +57,7 @@ protected void perform(@NotNull CommandSender sender, @NotNull String label, @No this.printUsage(sender); return; } - if (args.length > 1 && !Perms.has(sender, Perms.getSocketCmdMerchantOthers(this.module))) { + if (args.length > 1 && !sender.hasPermission(Perms.getSocketCmdMerchantOthers(this.module))) { this.errPerm(sender); return; } diff --git a/src/main/java/studio/magemonkey/divinity/modules/api/socketing/merchant/MerchantSocket.java b/src/main/java/studio/magemonkey/divinity/modules/api/socketing/merchant/MerchantSocket.java index 19b92190..ec4ce624 100644 --- a/src/main/java/studio/magemonkey/divinity/modules/api/socketing/merchant/MerchantSocket.java +++ b/src/main/java/studio/magemonkey/divinity/modules/api/socketing/merchant/MerchantSocket.java @@ -69,7 +69,7 @@ public void shutdown() { } public void openMerchantGUI(@NotNull Player player, boolean force) { - if (!force && !Perms.has(player, Perms.getSocketGuiMerchant(this.moduleSocket))) { + if (!force && !player.hasPermission(Perms.getSocketGuiMerchant(this.moduleSocket))) { plugin.lang().Error_NoPerm.send(player); return; } diff --git a/src/main/java/studio/magemonkey/divinity/modules/command/MDropCmd.java b/src/main/java/studio/magemonkey/divinity/modules/command/MDropCmd.java index e0b5ab02..62e1d27e 100644 --- a/src/main/java/studio/magemonkey/divinity/modules/command/MDropCmd.java +++ b/src/main/java/studio/magemonkey/divinity/modules/command/MDropCmd.java @@ -19,6 +19,8 @@ import studio.magemonkey.divinity.modules.list.itemgenerator.ItemGeneratorManager; import studio.magemonkey.divinity.modules.list.itemgenerator.ItemGeneratorManager.GeneratorItem; +import studio.magemonkey.divinity.utils.LoreUT; + import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -72,16 +74,20 @@ public List getTab(@NotNull Player player, int i, @NotNull String[] args if (i == 7) { return Arrays.asList("1", "10"); // Amount } - - // Support for material argument for ItemGenerator - if (i == 8 && this.module instanceof ItemGeneratorManager) { - ItemGeneratorManager itemGeneratorManager = (ItemGeneratorManager) this.module; - GeneratorItem generatorItem = itemGeneratorManager.getItemById(args[5]); - if (generatorItem != null) { - List list = generatorItem.getMaterialsList().stream() - .map(ItemType::getNamespacedID).collect(Collectors.toList()); - return list; + if (i == 8) { + List list = new java.util.ArrayList<>(); + if (this.module instanceof ItemGeneratorManager) { + ItemGeneratorManager itemGeneratorManager = (ItemGeneratorManager) this.module; + GeneratorItem generatorItem = itemGeneratorManager.getItemById(args[5]); + if (generatorItem != null) { + list.addAll(generatorItem.getMaterialsList().stream() + .map(ItemType::getNamespacedID).collect(Collectors.toList())); + } } + return list; + } + if (i == 9) { + return Arrays.asList("-noenchants"); } return super.getTab(player, i, args); } @@ -121,15 +127,20 @@ public void perform(@NotNull CommandSender sender, @NotNull String label, @NotNu amount = this.getNumI(sender, args[7], 1); } + boolean noEnchants = Arrays.stream(args).anyMatch(a -> a.equalsIgnoreCase("-noenchants")); + String id = args[5]; ItemStack item = null; Location loc = new Location(world, x, y, z); - ItemType material; - try { - material = args.length >= 9 ? CodexEngine.get().getItemManager().getItemType(args[8]) : null; - } catch (MissingProviderException | MissingItemException e) { - material = null; + // Find first material arg from position 8 onwards, skipping -noenchants + ItemType material = null; + for (int j = 8; j < args.length; j++) { + if (args[j].equalsIgnoreCase("-noenchants")) continue; + try { + material = CodexEngine.get().getItemManager().getItemType(args[j]); + break; + } catch (MissingProviderException | MissingItemException ignored) {} } ItemGeneratorManager itemGenerator = this.module instanceof ItemGeneratorManager ? (ItemGeneratorManager) this.module : null; @@ -146,7 +157,7 @@ public void perform(@NotNull CommandSender sender, @NotNull String label, @NotNu item = DivinityAPI.getItemByModule(this.module, id, iLevel, -1, -1); } if (item == null) continue; - + if (noEnchants) LoreUT.removeEnchants(item); world.dropItemNaturally(loc, item); String name = ItemUT.getItemName(item); diff --git a/src/main/java/studio/magemonkey/divinity/modules/command/MGiveCmd.java b/src/main/java/studio/magemonkey/divinity/modules/command/MGiveCmd.java index f75bfa0c..d44f0b62 100644 --- a/src/main/java/studio/magemonkey/divinity/modules/command/MGiveCmd.java +++ b/src/main/java/studio/magemonkey/divinity/modules/command/MGiveCmd.java @@ -17,6 +17,8 @@ import studio.magemonkey.divinity.modules.list.itemgenerator.ItemGeneratorManager; import studio.magemonkey.divinity.modules.list.itemgenerator.ItemGeneratorManager.GeneratorItem; +import studio.magemonkey.divinity.utils.LoreUT; + import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -61,17 +63,22 @@ public List getTab(@NotNull Player player, int i, @NotNull String[] args if (i == 4) { return Arrays.asList("1", "10"); } - - // Support for material argument for ItemGenerator - if (i == 5 && this.module instanceof ItemGeneratorManager) { - ItemGeneratorManager itemGeneratorManager = (ItemGeneratorManager) this.module; - GeneratorItem generatorItem = itemGeneratorManager.getItemById(args[2]); - if (generatorItem != null) { - List list = generatorItem.getMaterialsList().stream() - .map(ItemType::getNamespacedID).collect(Collectors.toList()); - return list; + if (i == 5) { + List list = new java.util.ArrayList<>(); + if (this.module instanceof ItemGeneratorManager) { + ItemGeneratorManager itemGeneratorManager = (ItemGeneratorManager) this.module; + GeneratorItem generatorItem = itemGeneratorManager.getItemById(args[2]); + if (generatorItem != null) { + list.addAll(generatorItem.getMaterialsList().stream() + .map(ItemType::getNamespacedID).collect(Collectors.toList())); + } } + return list; + } + if (i == 6) { + return Arrays.asList("-noenchants"); } + return super.getTab(player, i, args); } @@ -111,13 +118,18 @@ public void perform(@NotNull CommandSender sender, @NotNull String label, @NotNu amount = this.getNumI(sender, args[4], 1); } + boolean noEnchants = Arrays.stream(args).anyMatch(a -> a.equalsIgnoreCase("-noenchants")); + ItemStack item = null; - ItemType material; - try { - material = args.length >= 6 ? CodexEngine.get().getItemManager().getItemType(args[5]) : null; - } catch (MissingProviderException | MissingItemException e) { - material = null; + // Find first material arg from position 5 onwards, skipping -noenchants + ItemType material = null; + for (int j = 5; j < args.length; j++) { + if (args[j].equalsIgnoreCase("-noenchants")) continue; + try { + material = CodexEngine.get().getItemManager().getItemType(args[j]); + break; + } catch (MissingProviderException | MissingItemException ignored) {} } ItemGeneratorManager itemGenerator = this.module instanceof ItemGeneratorManager ? (ItemGeneratorManager) this.module : null; @@ -134,6 +146,7 @@ public void perform(@NotNull CommandSender sender, @NotNull String label, @NotNu item = DivinityAPI.getItemByModule(this.module, id, iLevel, -1, -1); } if (item == null) continue; + if (noEnchants) LoreUT.removeEnchants(item); ItemUT.addItem(p, item); String name = ItemUT.getItemName(item); diff --git a/src/main/java/studio/magemonkey/divinity/modules/command/MMobEquipCmd.java b/src/main/java/studio/magemonkey/divinity/modules/command/MMobEquipCmd.java new file mode 100644 index 00000000..399877f1 --- /dev/null +++ b/src/main/java/studio/magemonkey/divinity/modules/command/MMobEquipCmd.java @@ -0,0 +1,223 @@ +package studio.magemonkey.divinity.modules.command; + +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.inventory.ItemStack; +import studio.magemonkey.divinity.utils.LoreUT; +import org.bukkit.util.RayTraceResult; +import org.jetbrains.annotations.NotNull; +import studio.magemonkey.codex.CodexEngine; +import studio.magemonkey.codex.api.items.ItemType; +import studio.magemonkey.codex.api.items.exception.MissingItemException; +import studio.magemonkey.codex.api.items.exception.MissingProviderException; +import studio.magemonkey.codex.util.random.Rnd; +import studio.magemonkey.divinity.Perms; +import studio.magemonkey.divinity.api.DivinityAPI; +import studio.magemonkey.divinity.modules.api.QModuleDrop; +import studio.magemonkey.divinity.modules.list.itemgenerator.ItemGeneratorManager; +import studio.magemonkey.divinity.modules.list.itemgenerator.ItemGeneratorManager.GeneratorItem; +import studio.magemonkey.divinity.stats.EntityStats; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +public class MMobEquipCmd extends MCmd> { + + public MMobEquipCmd(@NotNull QModuleDrop m) { + super(m, new String[]{"mobequip"}, Perms.ADMIN); + } + + @Override + @NotNull + public String usage() { + return "/[module] mobequip [uuid] [amount] [material] [-noenchants]"; + } + + @Override + @NotNull + public String description() { + return "Equip a generated item on a mob. With [uuid]: targets by UUID (usable from console/MythicMobs). Without: targets the mob you are looking at (player only)."; + } + + @Override + public boolean playersOnly() { + return false; + } + + @Override + @NotNull + public List getTab(@NotNull Player player, int i, @NotNull String[] args) { + if (i == 1) { + return Arrays.asList("head", "chest", "legs", "feet", "hand", "offhand"); + } + if (i == 2) { + return module.getItemIds(); + } + if (i == 3) { + return Arrays.asList("[level]", "-1", "1"); + } + if (i == 4) { + return Arrays.asList("1"); + } + if (i == 5) { + List list = new java.util.ArrayList<>(); + if (this.module instanceof ItemGeneratorManager) { + ItemGeneratorManager igm = (ItemGeneratorManager) this.module; + GeneratorItem gi = igm.getItemById(args[2]); + if (gi != null) { + list.addAll(gi.getMaterialsList().stream() + .map(ItemType::getNamespacedID).collect(Collectors.toList())); + } + } + return list; + } + if (i == 6) { + return Arrays.asList("-noenchants"); + } + return super.getTab(player, i, args); + } + + @Override + public void perform(@NotNull CommandSender sender, @NotNull String label, @NotNull String[] args) { + if (args.length < 4) { + this.printUsage(sender); + return; + } + + // Detect UUID mode: args[1] looks like a UUID → console/MythicMobs targeting + // Otherwise: ray-trace mode (requires Player sender) + LivingEntity mob; + int argOffset; // index where starts + + UUID targetUuid = tryParseUUID(args[1]); + if (targetUuid != null) { + // UUID mode — works from console, MythicMobs, or player + if (args.length < 5) { + this.printUsage(sender); + return; + } + Entity entity = Bukkit.getEntity(targetUuid); + if (!(entity instanceof LivingEntity)) { + sender.sendMessage("§cNo living entity found with UUID §f" + targetUuid + "§c."); + return; + } + mob = (LivingEntity) entity; + argOffset = 2; // slot = args[2], id = args[3], level = args[4], ... + } else { + // Ray-trace mode — player only + if (!(sender instanceof Player)) { + sender.sendMessage("§cProvide a target UUID when running this command from console/MythicMobs."); + sender.sendMessage("§cUsage: " + this.usage()); + return; + } + Player player = (Player) sender; + RayTraceResult result = player.getWorld().rayTraceEntities( + player.getEyeLocation(), + player.getEyeLocation().getDirection(), + 10.0, + entity -> entity instanceof LivingEntity && !entity.equals(player) + ); + if (result == null || !(result.getHitEntity() instanceof LivingEntity)) { + sender.sendMessage("§cNo mob found in your line of sight (max 10 blocks)."); + return; + } + mob = (LivingEntity) result.getHitEntity(); + argOffset = 1; // slot = args[1], id = args[2], level = args[3], ... + } + + EntityEquipment equip = mob.getEquipment(); + if (equip == null) { + sender.sendMessage("§cThis entity does not support equipment."); + return; + } + + String slotArg = args[argOffset].toLowerCase(); + String id = args[argOffset + 1]; + int level = this.getNumI(sender, args[argOffset + 2], -1, true); + boolean noEnchants = Arrays.stream(args).anyMatch(a -> a.equalsIgnoreCase("-noenchants")); + + int amount = 1; + int amountArgIdx = argOffset + 3; + if (args.length > amountArgIdx && !args[amountArgIdx].equalsIgnoreCase("-noenchants")) { + amount = this.getNumI(sender, args[amountArgIdx], 1); + } + + // Find material arg (after amount, skipping -noenchants) + ItemType material = null; + for (int j = argOffset + 4; j < args.length; j++) { + if (args[j].equalsIgnoreCase("-noenchants")) continue; + try { + material = CodexEngine.get().getItemManager().getItemType(args[j]); + break; + } catch (MissingProviderException | MissingItemException ignored) {} + } + + ItemGeneratorManager igm = this.module instanceof ItemGeneratorManager + ? (ItemGeneratorManager) this.module : null; + GeneratorItem generatorItem = igm != null ? igm.getItemById(id) : null; + + for (int i = 0; i < amount; i++) { + int iLevel = (level == -1) ? Rnd.get(1, 100) : level; + ItemStack item; + if (material != null && generatorItem != null) { + item = generatorItem.create(iLevel, -1, material); + } else { + item = DivinityAPI.getItemByModule(this.module, id, iLevel, -1, -1); + } + if (item == null) { + sender.sendMessage("§cFailed to generate item '" + id + "'."); + return; + } + if (noEnchants) LoreUT.removeEnchants(item); + + switch (slotArg) { + case "head": + case "helmet": + equip.setHelmet(item); + break; + case "chest": + case "chestplate": + equip.setChestplate(item); + break; + case "legs": + case "leggings": + equip.setLeggings(item); + break; + case "feet": + case "boots": + equip.setBoots(item); + break; + case "hand": + case "mainhand": + equip.setItemInMainHand(item); + break; + case "offhand": + equip.setItemInOffHand(item); + break; + default: + sender.sendMessage("§cUnknown slot '" + slotArg + "'. Use: head, chest, legs, feet, hand, offhand."); + return; + } + } + + EntityStats.get(mob).updateInventory(); + + sender.sendMessage("§aEquipped §f" + id + "§a (lv §f" + level + "§a) on §f" + + (mob.getCustomName() != null ? mob.getCustomName() : mob.getType().name()) + + "§a in slot §f" + slotArg + "§a."); + } + + private static UUID tryParseUUID(String s) { + try { + return UUID.fromString(s); + } catch (IllegalArgumentException e) { + return null; + } + } +} diff --git a/src/main/java/studio/magemonkey/divinity/modules/list/classes/api/RPGClass.java b/src/main/java/studio/magemonkey/divinity/modules/list/classes/api/RPGClass.java index 2f71fa5f..43fc8add 100644 --- a/src/main/java/studio/magemonkey/divinity/modules/list/classes/api/RPGClass.java +++ b/src/main/java/studio/magemonkey/divinity/modules/list/classes/api/RPGClass.java @@ -213,7 +213,7 @@ public boolean hasPermission(@NotNull Player player) { if (!this.isPermissionRequired()) return true; String node = Perms.CLASS_CLASS + "." + this.getId(); - return Perms.has(player, node); + return player.hasPermission(node); } @NotNull diff --git a/src/main/java/studio/magemonkey/divinity/modules/list/customitems/CustomItemsManager.java b/src/main/java/studio/magemonkey/divinity/modules/list/customitems/CustomItemsManager.java index 79516e0c..3f4dcd37 100644 --- a/src/main/java/studio/magemonkey/divinity/modules/list/customitems/CustomItemsManager.java +++ b/src/main/java/studio/magemonkey/divinity/modules/list/customitems/CustomItemsManager.java @@ -1,5 +1,5 @@ /** - * © 2026 VoidEdge + * Copyright 2024 MageMonkeyStudio */ package studio.magemonkey.divinity.modules.list.customitems; diff --git a/src/main/java/studio/magemonkey/divinity/modules/list/dismantle/DismantleManager.java b/src/main/java/studio/magemonkey/divinity/modules/list/dismantle/DismantleManager.java index 2e456d2a..0fc70477 100644 --- a/src/main/java/studio/magemonkey/divinity/modules/list/dismantle/DismantleManager.java +++ b/src/main/java/studio/magemonkey/divinity/modules/list/dismantle/DismantleManager.java @@ -105,7 +105,7 @@ public void shutdown() { // METHODS public void openDismantleGUI(@NotNull Player player, boolean isForce) { - if (!isForce && !Perms.has(player, Perms.DISMANTLE_GUI)) { + if (!isForce && !player.hasPermission(Perms.DISMANTLE_GUI)) { plugin.lang().Error_NoPerm.send(player); return; } diff --git a/src/main/java/studio/magemonkey/divinity/modules/list/extractor/ExtractorManager.java b/src/main/java/studio/magemonkey/divinity/modules/list/extractor/ExtractorManager.java index 8fec8ec6..40c8196a 100644 --- a/src/main/java/studio/magemonkey/divinity/modules/list/extractor/ExtractorManager.java +++ b/src/main/java/studio/magemonkey/divinity/modules/list/extractor/ExtractorManager.java @@ -110,7 +110,7 @@ public final boolean openExtraction( boolean force ) { - if (!force && !Perms.has(player, Perms.EXTRACTOR_GUI)) { + if (!force && !player.hasPermission(Perms.EXTRACTOR_GUI)) { plugin.lang().Error_NoPerm.send(player); return false; } diff --git a/src/main/java/studio/magemonkey/divinity/modules/list/gems/GemManager.java b/src/main/java/studio/magemonkey/divinity/modules/list/gems/GemManager.java index fdb319de..c6b9ccf4 100644 --- a/src/main/java/studio/magemonkey/divinity/modules/list/gems/GemManager.java +++ b/src/main/java/studio/magemonkey/divinity/modules/list/gems/GemManager.java @@ -119,6 +119,9 @@ public Gem(@NotNull Divinity plugin, @NotNull JYML cfg) { bMap.loadStats(cfg, path + "item-stats"); bMap.loadDamages(cfg, path + "damage-types"); bMap.loadDefenses(cfg, path + "defense-types"); + bMap.loadDamageBuffs(cfg, path + "damage-buffs"); + bMap.loadDefenseBuffs(cfg, path + "defense-buffs"); + bMap.loadPenetrations(cfg, path + "penetrations"); this.bonusMap.put(lvl, bMap); diff --git a/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/ItemAbilityHandler.java b/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/ItemAbilityHandler.java index 2b5873ba..4c1f07e9 100644 --- a/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/ItemAbilityHandler.java +++ b/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/ItemAbilityHandler.java @@ -70,6 +70,14 @@ public void onPlayerJoin(PlayerJoinEvent event) { } } + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerQuit(PlayerQuitEvent event) { + FabledHook fabledHook = (FabledHook) this.plugin.getHook(EHook.SKILL_API); + if (fabledHook != null) { + fabledHook.clearFabledAttributes(event.getPlayer()); + } + } + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void playerDeath(PlayerDeathEvent event) { FabledHook fabledHook = (FabledHook) this.plugin.getHook(EHook.SKILL_API); diff --git a/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/ItemGeneratorManager.java b/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/ItemGeneratorManager.java index 00f4c4b8..4fd6c1c3 100644 --- a/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/ItemGeneratorManager.java +++ b/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/ItemGeneratorManager.java @@ -46,12 +46,15 @@ import studio.magemonkey.divinity.modules.list.itemgenerator.editor.AbstractEditorGUI; import studio.magemonkey.divinity.modules.list.itemgenerator.generators.AbilityGenerator; import studio.magemonkey.divinity.modules.list.itemgenerator.generators.AttributeGenerator; +import studio.magemonkey.divinity.modules.list.itemgenerator.generators.DuplicableStatGenerator; import studio.magemonkey.divinity.modules.list.itemgenerator.generators.SingleAttributeGenerator; import studio.magemonkey.divinity.modules.list.itemgenerator.generators.TypedStatGenerator; import studio.magemonkey.divinity.modules.list.sets.SetManager; import studio.magemonkey.divinity.stats.bonus.BonusMap; import studio.magemonkey.divinity.stats.bonus.StatBonus; import studio.magemonkey.divinity.stats.items.ItemStats; +import studio.magemonkey.divinity.stats.items.attributes.stats.DynamicBuffStat; +import studio.magemonkey.divinity.stats.items.attributes.stats.PenetrationStat; import studio.magemonkey.divinity.stats.items.ItemTags; import studio.magemonkey.divinity.stats.items.api.ItemLoreStat; import studio.magemonkey.divinity.stats.items.attributes.DamageAttribute; @@ -85,12 +88,15 @@ public class ItemGeneratorManager extends QModuleDrop { private static ResourceManager resourceManager; private ItemAbilityHandler abilityHandler; - public static final String PLACE_GEN_DAMAGE = "%GENERATOR_DAMAGE%"; - public static final String PLACE_GEN_DEFENSE = "%GENERATOR_DEFENSE%"; - public static final String PLACE_GEN_STATS = "%GENERATOR_STATS%"; - public static final String PLACE_GEN_SOCKETS = "%GENERATOR_SOCKETS_%TYPE%%"; - public static final String PLACE_GEN_ABILITY = "%GENERATOR_SKILLS%"; - public static final String PLACE_GEN_FABLED_ATTR = "%GENERATOR_FABLED_ATTR%"; + public static final String PLACE_GEN_DAMAGE = "%GENERATOR_DAMAGE%"; + public static final String PLACE_GEN_DEFENSE = "%GENERATOR_DEFENSE%"; + public static final String PLACE_GEN_STATS = "%GENERATOR_STATS%"; + public static final String PLACE_GEN_SOCKETS = "%GENERATOR_SOCKETS_%TYPE%%"; + public static final String PLACE_GEN_ABILITY = "%GENERATOR_SKILLS%"; + public static final String PLACE_GEN_FABLED_ATTR = "%GENERATOR_FABLED_ATTR%"; + public static final String PLACE_GEN_DAMAGE_BUFFS = "%GENERATOR_DAMAGE_BUFFS%"; + public static final String PLACE_GEN_DEFENSE_BUFFS = "%GENERATOR_DEFENSE_BUFFS%"; + public static final String PLACE_GEN_PENETRATION = "%GENERATOR_PENETRATION%"; public ItemGeneratorManager(@NotNull Divinity plugin) { super(plugin, GeneratorItem.class); @@ -617,6 +623,22 @@ public GeneratorItem(@NotNull Divinity plugin, @NotNull JYML cfg) { "generator.item-stats.", ItemStats.getStats(), ItemGeneratorManager.PLACE_GEN_STATS)); + + // DuplicableStatGenerators for damage buffs, defense buffs, and penetration. + // Each handles its own config section and auto-populates missing keys. + this.addAttributeGenerator(new DuplicableStatGenerator<>( + this.plugin, this, "generator.item-stats.", "list-damage-buffs", + ItemStats.getDamageBuffs(), DynamicBuffStat::getBuffId, + ItemGeneratorManager.PLACE_GEN_DAMAGE_BUFFS)); + this.addAttributeGenerator(new DuplicableStatGenerator<>( + this.plugin, this, "generator.item-stats.", "list-defense-buffs", + ItemStats.getDefenseBuffs(), DynamicBuffStat::getBuffId, + ItemGeneratorManager.PLACE_GEN_DEFENSE_BUFFS)); + this.addAttributeGenerator(new DuplicableStatGenerator<>( + this.plugin, this, "generator.item-stats.", "list-penetration", + ItemStats.getPenetrations(), PenetrationStat::getPenId, + ItemGeneratorManager.PLACE_GEN_PENETRATION)); + this.addAttributeGenerator( this.abilityGenerator = new AbilityGenerator(this.plugin, this, PLACE_GEN_ABILITY)); FabledHook fabledHook = (FabledHook) Divinity.getInstance().getHook(EHook.SKILL_API); @@ -956,6 +978,9 @@ protected ItemStack build(int itemLvl, int uses, @Nullable ItemType mat) { LoreUT.replacePlaceholder(item, PLACE_GEN_DAMAGE, null); LoreUT.replacePlaceholder(item, PLACE_GEN_DEFENSE, null); + LoreUT.replacePlaceholder(item, PLACE_GEN_DAMAGE_BUFFS, null); + LoreUT.replacePlaceholder(item, PLACE_GEN_DEFENSE_BUFFS, null); + LoreUT.replacePlaceholder(item, PLACE_GEN_PENETRATION, null); LevelRequirement reqLevel = ItemRequirements.getUserRequirement(LevelRequirement.class); if (reqLevel != null) { @@ -1015,6 +1040,15 @@ protected ItemStack build(int itemLvl, int uses, @Nullable ItemType mat) { for (ItemLoreStat at : ItemStats.getDefenses()) { lore.remove(at.getPlaceholder()); } + for (DynamicBuffStat at : ItemStats.getDamageBuffs()) { + lore.remove(at.getPlaceholder()); + } + for (DynamicBuffStat at : ItemStats.getDefenseBuffs()) { + lore.remove(at.getPlaceholder()); + } + for (PenetrationStat at : ItemStats.getPenetrations()) { + lore.remove(at.getPlaceholder()); + } FabledHook fabledHook = (FabledHook) Divinity.getInstance().getHook(EHook.SKILL_API); if (fabledHook != null) { diff --git a/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/editor/stats/MainStatsGUI.java b/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/editor/stats/MainStatsGUI.java index 456ab784..5f97fbf9 100644 --- a/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/editor/stats/MainStatsGUI.java +++ b/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/editor/stats/MainStatsGUI.java @@ -147,7 +147,11 @@ public void onRightClick() { "&eModify")) { @Override public void onLeftClick() { - openSubMenu(new StatListGUI(player, itemGenerator, itemType)); + if (itemType == EditorGUI.ItemType.ITEM_STATS) { + openSubMenu(new StatCategoryGUI(player, itemGenerator)); + } else { + openSubMenu(new StatListGUI(player, itemGenerator, itemType)); + } } }); } diff --git a/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/editor/stats/StatCategoryGUI.java b/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/editor/stats/StatCategoryGUI.java new file mode 100644 index 00000000..6ce0ff1f --- /dev/null +++ b/src/main/java/studio/magemonkey/divinity/modules/list/itemgenerator/editor/stats/StatCategoryGUI.java @@ -0,0 +1,77 @@ +package studio.magemonkey.divinity.modules.list.itemgenerator.editor.stats; + +import org.bukkit.Material; +import org.bukkit.entity.Player; +import studio.magemonkey.codex.manager.api.menu.Slot; +import studio.magemonkey.divinity.modules.list.itemgenerator.editor.AbstractEditorGUI; +import studio.magemonkey.divinity.modules.list.itemgenerator.editor.EditorGUI; + +/** + * Intermediate category-selection GUI shown when clicking the "List" button + * in the Item Stats editor (MainStatsGUI slot 3). + * + *

Presents four sub-categories: + *

    + *
  • General — classic TypedStat entries (list:)
  • + *
  • Damage % — DynamicBuffStat damage entries (list-damage-buffs:)
  • + *
  • Defense % — DynamicBuffStat defense entries (list-defense-buffs:)
  • + *
  • Penetration — PenetrationStat entries (list-penetration:)
  • + *
+ */ +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%'