diff --git a/src/main/java/dev/marcospaulo/canalhandia/XpAndItemMergeListener.java b/src/main/java/dev/marcospaulo/canalhandia/XpAndItemMergeListener.java index 1440181..633cae9 100644 --- a/src/main/java/dev/marcospaulo/canalhandia/XpAndItemMergeListener.java +++ b/src/main/java/dev/marcospaulo/canalhandia/XpAndItemMergeListener.java @@ -13,10 +13,18 @@ import org.bukkit.event.Listener; import org.bukkit.event.entity.ItemSpawnEvent; import org.bukkit.inventory.ItemStack; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.inventory.meta.Damageable; + +import java.util.ArrayList; +import java.util.List; + /** * Optimises XP and Item collection by instantly vacuuming nearby XP orbs into the player's - * XP bar (bypassing Vanilla's 2-tick per orb delay) and consolidating spawned item drops into - * full stacks. + * XP bar (bypassing Vanilla's 2-tick per orb delay), repairing Mending equipment first, + * and consolidating spawned item drops into full stacks. */ public final class XpAndItemMergeListener implements Listener { @@ -27,7 +35,7 @@ public final class XpAndItemMergeListener implements Listener { } /** - * Vacuums all experience orbs within radius instantly on player contact. + * Vacuums all experience orbs within radius instantly on player contact and repairs Mending items. */ @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onPlayerPickupXp(PlayerPickupExperienceEvent event) { @@ -49,7 +57,10 @@ public final class XpAndItemMergeListener implements Listener { } if (totalExp > 0) { - player.giveExp(totalExp); + int leftoverExp = applyMending(player, totalExp); + if (leftoverExp > 0) { + player.giveExp(leftoverExp); + } try { player.playSound(loc, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 0.5f, 1.2f); } catch (Exception ignored) { @@ -58,6 +69,73 @@ public final class XpAndItemMergeListener implements Listener { } } + /** + * Repairs equipped items with Mending enchantment using the collected XP. + * Returns remaining XP to be added to the player's experience bar. + */ + public static int applyMending(Player player, int totalExp) { + if (player == null || totalExp <= 0) { + return totalExp; + } + int remainingXp = totalExp; + PlayerInventory inv = player.getInventory(); + + List candidates = new ArrayList<>(); + ItemStack main = inv.getItemInMainHand(); + if (isDamagedMending(main)) candidates.add(main); + + ItemStack off = inv.getItemInOffHand(); + if (isDamagedMending(off)) candidates.add(off); + + for (ItemStack armor : inv.getArmorContents()) { + if (isDamagedMending(armor)) candidates.add(armor); + } + + for (ItemStack item : candidates) { + if (remainingXp <= 0) { + break; + } + if (item.getItemMeta() instanceof Damageable dmg && dmg.hasDamage()) { + int damage = dmg.getDamage(); + int[] repair = calculateMendingRepair(damage, remainingXp); + int repaired = repair[0]; + int xpUsed = repair[1]; + if (repaired > 0) { + dmg.setDamage(damage - repaired); + item.setItemMeta(dmg); + remainingXp -= xpUsed; + } + } + } + return remainingXp; + } + + private static boolean isDamagedMending(ItemStack item) { + if (item == null || item.getType() == Material.AIR || !item.hasItemMeta()) { + return false; + } + if (!item.containsEnchantment(Enchantment.MENDING)) { + return false; + } + if (item.getItemMeta() instanceof Damageable dmg) { + return dmg.hasDamage() && dmg.getDamage() > 0; + } + return false; + } + + /** + * Calculates repair amount and used XP for a given damage and available XP. + * Returns an int array: [durabilityRepaired, xpUsed]. + */ + public static int[] calculateMendingRepair(int damage, int availableXp) { + if (damage <= 0 || availableXp <= 0) { + return new int[]{0, 0}; + } + int durabilityToRepair = Math.min(damage, availableXp * 2); + int xpUsed = (durabilityToRepair + 1) / 2; + return new int[]{durabilityToRepair, xpUsed}; + } + /** * Consolidates spawned items of the same type in a 4-block radius into unified stacks. */ diff --git a/src/test/java/dev/marcospaulo/canalhandia/XpAndItemMergeTest.java b/src/test/java/dev/marcospaulo/canalhandia/XpAndItemMergeTest.java index 46bf14f..db70706 100644 --- a/src/test/java/dev/marcospaulo/canalhandia/XpAndItemMergeTest.java +++ b/src/test/java/dev/marcospaulo/canalhandia/XpAndItemMergeTest.java @@ -25,4 +25,31 @@ class XpAndItemMergeTest { // Target has 63, max is 64, source has 10 -> can transfer 1 assertEquals(1, XpAndItemMergeListener.calculateTransfer(63, 64, 10)); } + + @Test + void calculatesMendingRepairCorrectly() { + // Zero damage or zero XP -> 0 repair + int[] r1 = XpAndItemMergeListener.calculateMendingRepair(0, 50); + assertEquals(0, r1[0]); + assertEquals(0, r1[1]); + + int[] r2 = XpAndItemMergeListener.calculateMendingRepair(50, 0); + assertEquals(0, r2[0]); + assertEquals(0, r2[1]); + + // 10 damage, 100 XP -> repairs 10 durability, uses 5 XP + int[] r3 = XpAndItemMergeListener.calculateMendingRepair(10, 100); + assertEquals(10, r3[0]); + assertEquals(5, r3[1]); + + // 5 damage, 10 XP -> repairs 5 durability, uses 3 XP + int[] r4 = XpAndItemMergeListener.calculateMendingRepair(5, 10); + assertEquals(5, r4[0]); + assertEquals(3, r4[1]); + + // 10 damage, 2 XP -> repairs 4 durability (2 * 2), uses 2 XP + int[] r5 = XpAndItemMergeListener.calculateMendingRepair(10, 2); + assertEquals(4, r5[0]); + assertEquals(2, r5[1]); + } }