diff --git a/src/main/java/studio/magemonkey/divinity/config/EngineCfg.java b/src/main/java/studio/magemonkey/divinity/config/EngineCfg.java index 5757107d..3f6cb524 100644 --- a/src/main/java/studio/magemonkey/divinity/config/EngineCfg.java +++ b/src/main/java/studio/magemonkey/divinity/config/EngineCfg.java @@ -46,6 +46,7 @@ public EngineCfg(@NotNull Divinity plugin) throws InvalidConfigurationException public static boolean ATTRIBUTES_EFFECTIVE_FOR_MOBS; public static boolean ATTRIBUTES_EFFECTIVE_IN_OFFHAND; public static boolean ATTRIBUTES_ALLOW_HOLD_REQUIREMENTS; + public static boolean ATTRIBUTES_HIDE_FLAGS; public static boolean ATTRIBUTES_DURABILITY_BREAK_ITEMS; public static boolean ATTRIBUTES_DURABILITY_REDUCE_FOR_MOBS; @@ -62,6 +63,7 @@ public EngineCfg(@NotNull Divinity plugin) throws InvalidConfigurationException public static boolean COMBAT_BOWS_DO_FULL_MELEE_DAMAGE; public static double COMBAT_DAMAGE_MODIFIER_FOR_COOLDOWN; public static double COMBAT_MAX_GET_TARGET_DISTANCE; + public static boolean VANILLA_ONLY_DAMAGE_HANDLING; public static String LORE_CHAR_PERCENT; public static String LORE_CHAR_NEGATIVE; @@ -113,6 +115,9 @@ public EngineCfg(@NotNull Divinity plugin) throws InvalidConfigurationException public static String LORE_STYLE_FABLED_ATTRIBUTE_FORMAT; + public static boolean VANILLA_ONLY_ENTITY_STATS; + public static boolean VANILLA_ONLY_ITEM_STATS; + public void setup() { this.plugin.info("Loading engine configuration..."); @@ -174,10 +179,11 @@ public void setup() { // A T T R I B U T E S // path = "attributes."; + cfg.addMissing(path + "hide-flags", true); 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"); EngineCfg.ATTRIBUTES_DURABILITY_REDUCE_FOR_MOBS = cfg.getBoolean(path + "effective-for.mobs"); @@ -424,6 +430,17 @@ public void setup() { EngineCfg.LORE_STYLE_FABLED_ATTRIBUTE_FORMAT = StringUT.color(cfg.getString(path, "&7%attrPre%&3%name%&7%attrPost%")); + + path = "vanilla-only.entity-stats"; + cfg.addMissing(path, false); + EngineCfg.VANILLA_ONLY_ENTITY_STATS = cfg.getBoolean(path, false); + path = "vanilla-only.item-stats"; + cfg.addMissing(path, false); + EngineCfg.VANILLA_ONLY_ITEM_STATS = cfg.getBoolean(path, false); + path = "vanilla-only.damage-handling"; + cfg.addMissing(path, false); + EngineCfg.VANILLA_ONLY_DAMAGE_HANDLING = cfg.getBoolean(path, false); + cfg.saveChanges(); } diff --git a/src/main/java/studio/magemonkey/divinity/manager/EntityManager.java b/src/main/java/studio/magemonkey/divinity/manager/EntityManager.java index ddc45f9f..af4f2bcc 100644 --- a/src/main/java/studio/magemonkey/divinity/manager/EntityManager.java +++ b/src/main/java/studio/magemonkey/divinity/manager/EntityManager.java @@ -2,6 +2,7 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.entity.CreatureSpawnEvent; @@ -19,6 +20,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 +92,7 @@ public void shutdown() { @EventHandler(priority = EventPriority.MONITOR) public void onStatsDeath(EntityDeathEvent e) { + if(EngineCfg.VANILLA_ONLY_ENTITY_STATS) return; LivingEntity entity = e.getEntity(); previousEquipment.remove(e.getEntity().getUniqueId()); EntityStats.get(entity).handleDeath(); @@ -98,22 +101,26 @@ public void onStatsDeath(EntityDeathEvent e) { // Clear stats on player exit @EventHandler(priority = EventPriority.HIGHEST) public void onStatsQuit(PlayerQuitEvent e) { + if(EngineCfg.VANILLA_ONLY_ENTITY_STATS) return; EntityStats.purge(e.getPlayer()); } @EventHandler(priority = EventPriority.HIGHEST) public void onStatsJoin(PlayerJoinEvent e) { + if(EngineCfg.VANILLA_ONLY_ENTITY_STATS) return; EntityStats.get(e.getPlayer()); this.pushToUpdate(e.getPlayer(), 1D); } @EventHandler public void quit(PlayerQuitEvent event) { + if(EngineCfg.VANILLA_ONLY_ENTITY_STATS) return; previousEquipment.remove(event.getPlayer().getUniqueId()); } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onStatsRegen(EntityRegainHealthEvent e) { + if(EngineCfg.VANILLA_ONLY_ENTITY_STATS) return; Entity e1 = e.getEntity(); if (!(e1 instanceof LivingEntity)) return; @@ -124,6 +131,7 @@ public void onStatsRegen(EntityRegainHealthEvent e) { @EventHandler(ignoreCancelled = true) public void onPickup(EntityPickupItemEvent e) { + if(EngineCfg.VANILLA_ONLY_ENTITY_STATS) return; if (!ProjectileStats.isPickable(e.getItem())) { e.setCancelled(true); } @@ -138,6 +146,7 @@ public void onPickup(EntityPickupItemEvent e) { } private final void pushToUpdate(@NotNull LivingEntity entity, double time) { + if(EngineCfg.VANILLA_ONLY_ENTITY_STATS) return; EntityEquipment equip = new EntityEquipmentSnapshot(entity); previousEquipment.put(entity.getUniqueId(), equip); if (time <= 0D) { @@ -152,7 +161,25 @@ public void run() { }.runTask(Divinity.getInstance()); } + private void updateVanillaItemAttributes(@NotNull LivingEntity entity) { + if (EngineCfg.VANILLA_ONLY_ENTITY_STATS) return; + + EntityEquipment equipment = entity.getEquipment(); + if (equipment == null) return; + + for (ItemStack item : equipment.getArmorContents()) { + if (item != null) ItemStats.updateVanillaAttributes(item, entity instanceof Player ? (Player) entity : null); + } + + ItemStack main = equipment.getItemInMainHand(); + if (main != null) ItemStats.updateVanillaAttributes(main, entity instanceof Player ? (Player) entity : null); + + ItemStack off = equipment.getItemInOffHand(); + if (off != null) ItemStats.updateVanillaAttributes(off, entity instanceof Player ? (Player) entity : null); + } + private final void addDuplicatorFixer(@NotNull Entity entity) { + if(EngineCfg.VANILLA_ONLY_ENTITY_STATS) return; entity.setMetadata(PACKET_DUPLICATOR_FIXER, new FixedMetadataValue(plugin, "fixed")); } @@ -197,6 +224,7 @@ public void onStatsUpdatePlayerHeld(PlayerItemHeldEvent e) { @EventHandler(priority = EventPriority.NORMAL) public void onEntityUpdateEquipmentChange(EntityEquipmentChangeEvent e) { + this.updateVanillaItemAttributes(e.getEntity()); this.pushToUpdate(e.getEntity(), 0.5D); } } 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..4e455347 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.VANILLA_ONLY_ITEM_STATS) return; for (DynamicStat dynamicStat : ItemStats.getDynamicStats()) { dynamicStat.updateItem(p, item); } 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..ab8a41d6 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,8 +25,12 @@ import studio.magemonkey.codex.manager.IListener; import studio.magemonkey.codex.util.DataUT; import studio.magemonkey.divinity.Divinity; +import studio.magemonkey.divinity.api.DivinityAPI; +import studio.magemonkey.divinity.config.EngineCfg; import studio.magemonkey.divinity.stats.items.ItemStats; +import java.util.Objects; + public class ItemUpdaterListener extends IListener { @@ -106,8 +110,20 @@ public void update(ItemStack item, @Nullable Player player) { if (fixed) { DataUT.removeData(item, key); meta.removeItemFlags(ItemFlag.HIDE_ATTRIBUTES); - } else { + } else if(EngineCfg.ATTRIBUTES_HIDE_FLAGS) { meta.addItemFlags(ItemFlag.values()); + } else { + // Reapply only the required flags on from a copied item + try { + var itemCopy = Objects.requireNonNull(CodexEngine.get().getItemManager().getMainItemType(item)).create(); + if(itemCopy == null) return; + var itemCopyMeta = itemCopy.getItemMeta(); + if(itemCopyMeta == null) return; + meta.removeItemFlags(ItemFlag.values()); + meta.addItemFlags(itemCopyMeta.getItemFlags().toArray(new ItemFlag[0])); + } catch (Exception e) { + return; + } } item.setItemMeta(meta); } 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 e609e643..a3acfca3 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 @@ -139,6 +139,8 @@ public void onVanillaProjectLaunch(ProjectileLaunchEvent e) { @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onVanillaDamage(EntityDamageEvent e) { + if (EngineCfg.VANILLA_ONLY_DAMAGE_HANDLING) return; + boolean isEde = e instanceof EntityDamageByEntityEvent; if (isEde && plugin.getPluginManager().isPluginEnabled("Fabled")) { EntityDamageByEntityEvent ede = (EntityDamageByEntityEvent) e; @@ -376,16 +378,23 @@ public void onVanillaDamage(EntityDamageEvent e) { // +----------------------------------------------------+ // Divinity.getInstance().info("Damage Final Check: " + e.getFinalDamage() + "/" + e.getDamage()); if (e.getFinalDamage() != e.getDamage()) { - double absorption = Math.min(e.getDamage(), victim.getAbsorptionAmount()); + double damageBeforeAbsorption = e.getDamage(); + if (e.isApplicable(DamageModifier.RESISTANCE)) { + damageBeforeAbsorption += e.getDamage(DamageModifier.RESISTANCE); + } + double absorption = Math.min(Math.max(0D, damageBeforeAbsorption), victim.getAbsorptionAmount()); for (DamageModifier dmgModifier : DamageModifier.values()) { - if (dmgModifier == DamageModifier.ABSORPTION) continue; if (e.isApplicable(dmgModifier)) { if (dmgModifier == DamageModifier.BASE) { // Divinity.getInstance().info("FINAL - " + dmgModifier.name() + ": " + e.getDamage()); - e.setDamage(dmgModifier, e.getDamage() - absorption); + e.setDamage(dmgModifier, e.getDamage()); } else if (dmgModifier == DamageModifier.ABSORPTION) { - e.setDamage(dmgModifier, absorption); - } else if (!dmgModifier.name().equals("INVULNERABILITY_REDUCTION")) + // Bukkit represents absorption as a negative damage modifier. + // Keep it in the final damage calculation instead of subtracting + // it from BASE (which caused the absorption modifier to be applied + // twice or not at all depending on the damage source). + e.setDamage(dmgModifier, -absorption); + } else if (!dmgModifier.name().equals("INVULNERABILITY_REDUCTION") && !dmgModifier.name().equals("RESISTANCE")) e.setDamage(dmgModifier, 0); // Fix } } diff --git a/src/main/java/studio/magemonkey/divinity/stats/EntityStats.java b/src/main/java/studio/magemonkey/divinity/stats/EntityStats.java index 5320a32a..8ace575a 100644 --- a/src/main/java/studio/magemonkey/divinity/stats/EntityStats.java +++ b/src/main/java/studio/magemonkey/divinity/stats/EntityStats.java @@ -438,6 +438,7 @@ private void updateInventory() { } public void updateAll() { + if(EngineCfg.VANILLA_ONLY_ENTITY_STATS) return; if (!EngineCfg.ATTRIBUTES_EFFECTIVE_FOR_MOBS && !this.isPlayer()) { return; } 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..5bcdaa80 100644 --- a/src/main/java/studio/magemonkey/divinity/stats/items/ItemStats.java +++ b/src/main/java/studio/magemonkey/divinity/stats/items/ItemStats.java @@ -20,6 +20,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; @@ -308,6 +309,8 @@ public static boolean hasStat(@NotNull ItemStack item, @Nullable Player player, // ----------------------------------------------------------------- // public static void updateVanillaAttributes(@NotNull ItemStack item, @Nullable Player player) { + if (EngineCfg.VANILLA_ONLY_ITEM_STATS) 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)); diff --git a/src/main/resources/engine.yml b/src/main/resources/engine.yml index 81ca70bf..13598b15 100644 --- a/src/main/resources/engine.yml +++ b/src/main/resources/engine.yml @@ -63,6 +63,8 @@ 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 @@ -230,3 +232,11 @@ lore: main: '&c▸ %name% %value%' max-roman: 10 fabled-attribute-format: '&7%attrPre%&3%name%&7%attrPost%' + +vanilla-only: + # When enabled, Divinity will not replace vanilla entity-stat handling. + entity-stats: false + # When enabled, Divinity will not add generated vanilla item attributes. + item-stats: false + # When enabled, Divinity leaves damage events to Minecraft's vanilla handling. + damage-handling: false diff --git a/src/test/java/studio/magemonkey/divinity/manager/EntityManagerTest.java b/src/test/java/studio/magemonkey/divinity/manager/EntityManagerTest.java new file mode 100644 index 00000000..0d533496 --- /dev/null +++ b/src/test/java/studio/magemonkey/divinity/manager/EntityManagerTest.java @@ -0,0 +1,46 @@ +package studio.magemonkey.divinity.manager; + +import org.bukkit.event.player.PlayerToggleSprintEvent; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockbukkit.mockbukkit.entity.PlayerMock; +import studio.magemonkey.divinity.config.EngineCfg; +import studio.magemonkey.divinity.testutil.MockedTest; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class EntityManagerTest extends MockedTest { + private PlayerMock player; + + @BeforeEach + void setup() { + player = genPlayer("Travja"); + } + + @AfterEach + void resetFlag() { + EngineCfg.VANILLA_ONLY_ENTITY_STATS = false; + } + + @Test + void entityStatsHandlingAppliesDuplicatorFixerByDefault() { + EngineCfg.VANILLA_ONLY_ENTITY_STATS = false; + + server.getPluginManager().callEvent(new PlayerToggleSprintEvent(player, true)); + + assertTrue(EntityManager.isPacketDuplicatorFixed(player), + "Duplicator fixer metadata should be applied when legacy vanilla entity stats is disabled"); + } + + @Test + void legacyVanillaEntityStatsSkipsDuplicatorFixer() { + EngineCfg.VANILLA_ONLY_ENTITY_STATS = true; + + server.getPluginManager().callEvent(new PlayerToggleSprintEvent(player, true)); + + assertFalse(EntityManager.isPacketDuplicatorFixed(player), + "Duplicator fixer metadata should not be applied when legacy vanilla entity stats is enabled"); + } +} diff --git a/src/test/java/studio/magemonkey/divinity/manager/listener/object/VanillaWrapperListenerTest.java b/src/test/java/studio/magemonkey/divinity/manager/listener/object/VanillaWrapperListenerTest.java index 19102fc0..802c7c26 100644 --- a/src/test/java/studio/magemonkey/divinity/manager/listener/object/VanillaWrapperListenerTest.java +++ b/src/test/java/studio/magemonkey/divinity/manager/listener/object/VanillaWrapperListenerTest.java @@ -3,6 +3,10 @@ import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.entity.Trident; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityDamageEvent.DamageCause; +import org.bukkit.event.entity.EntityDamageEvent.DamageModifier; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.persistence.PersistentDataType; @@ -17,9 +21,15 @@ import studio.magemonkey.divinity.api.event.DivinityDamageEvent; import studio.magemonkey.divinity.testutil.MockedTest; +import com.google.common.base.Function; + +import java.util.EnumMap; +import java.util.Map; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +@SuppressWarnings("deprecation") public class VanillaWrapperListenerTest extends MockedTest { private PlayerMock damager; private PlayerMock target; @@ -103,6 +113,101 @@ void tridentUsesSavedCustomWeaponDamageAfterSwap() { }); } + @Test + void resistancePreservedInsteadOfBeingZeroedOut() { + final double base = 10D; + final double resistance = -2D; + + EntityDamageByEntityEvent event = buildDamageEvent(base, resistance, null); + + assertEquals(resistance, + event.getDamage(DamageModifier.RESISTANCE), + 0.001, + "Resistance modifier should be preserved rather than zeroed out"); + assertEquals(base + resistance, + event.getFinalDamage(), + 0.001, + "Final damage should reflect the resistance reduction"); + } + + @Test + void absorptionCappedToVictimsAvailableAmountWhenHitIsLarger() { + target.setAbsorptionAmount(4D); // 2 golden hearts + final double base = 10D; + + EntityDamageByEntityEvent event = buildDamageEvent(base, null, -10D); + + assertEquals(base, + event.getDamage(DamageModifier.BASE), + 0.001, + "Base damage should be left untouched; absorption should not also be subtracted from it"); + assertEquals(-4D, + event.getDamage(DamageModifier.ABSORPTION), + 0.001, + "Absorption modifier should be capped to the victim's available absorption amount"); + assertEquals(6D, event.getFinalDamage(), 0.001, "Absorption should only reduce final damage once"); + } + + @Test + void absorptionNotConsumedBeyondTheIncomingDamage() { + target.setAbsorptionAmount(20D); // 10 golden hearts + final double base = 2D; + + EntityDamageByEntityEvent event = buildDamageEvent(base, null, -20D); + + assertEquals(-2D, + event.getDamage(DamageModifier.ABSORPTION), + 0.001, + "A small hit should only consume absorption equal to its own damage, not drain the whole pool"); + assertEquals(0D, event.getFinalDamage(), 0.001); + } + + @Test + void resistanceIsFactoredInBeforeAbsorptionIsCalculated() { + target.setAbsorptionAmount(4D); // 2 golden hearts + final double base = 10D; + final double resistance = -4D; + + EntityDamageByEntityEvent event = buildDamageEvent(base, resistance, -10D); + + assertEquals(resistance, event.getDamage(DamageModifier.RESISTANCE), 0.001); + assertEquals(-4D, + event.getDamage(DamageModifier.ABSORPTION), + 0.001, + "Absorption should be computed off the damage remaining after resistance, then capped"); + assertEquals(base + resistance - 4D, event.getFinalDamage(), 0.001); + } + + /** + * Fires an {@link EntityDamageByEntityEvent} carrying only the given modifiers (mirroring how the + * vanilla server would populate them before Divinity's listener runs), so the resistance/absorption + * fix in {@code VanillaWrapperListener#onVanillaDamage} can be exercised in isolation. + */ + private EntityDamageByEntityEvent buildDamageEvent(double base, Double resistance, Double absorption) { + Map modifiers = new EnumMap<>(DamageModifier.class); + Map> functions = new EnumMap<>(DamageModifier.class); + + modifiers.put(DamageModifier.BASE, base); + functions.put(DamageModifier.BASE, d -> base); + + if (resistance != null) { + modifiers.put(DamageModifier.RESISTANCE, resistance); + functions.put(DamageModifier.RESISTANCE, d -> resistance); + } + if (absorption != null) { + modifiers.put(DamageModifier.ABSORPTION, absorption); + functions.put(DamageModifier.ABSORPTION, d -> absorption); + } + + EntityDamageByEntityEvent event = new EntityDamageByEntityEvent(damager, + target, + DamageCause.ENTITY_ATTACK, + modifiers, + functions); + server.getPluginManager().callEvent(event); + return event; + } + private double getTotalDamage(Start event) { return event.getDamageMap().values().stream().mapToDouble(Double::doubleValue).sum(); } diff --git a/src/test/java/studio/magemonkey/divinity/stats/items/ItemStatsTest.java b/src/test/java/studio/magemonkey/divinity/stats/items/ItemStatsTest.java new file mode 100644 index 00000000..29ca5cc4 --- /dev/null +++ b/src/test/java/studio/magemonkey/divinity/stats/items/ItemStatsTest.java @@ -0,0 +1,40 @@ +package studio.magemonkey.divinity.stats.items; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockbukkit.mockbukkit.entity.PlayerMock; +import studio.magemonkey.divinity.config.EngineCfg; +import studio.magemonkey.divinity.testutil.MockedTest; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ItemStatsTest extends MockedTest { + private PlayerMock player; + + @BeforeEach + void setup() { + player = genPlayer("Travja"); + } + + @AfterEach + void resetFlag() { + EngineCfg.VANILLA_ONLY_ITEM_STATS = false; + } + + @Test + void legacyVanillaItemStatsLeavesItemCompletelyUntouched() { + EngineCfg.VANILLA_ONLY_ITEM_STATS = true; + + ItemStack item = new ItemStack(Material.DIAMOND_SWORD); + ItemStack before = item.clone(); + + ItemStats.updateVanillaAttributes(item, player); + + assertEquals(before, + item, + "Item should not be modified at all while legacy vanilla item stats is enabled"); + } +}