fix(xp): prioritize repairing equipped mending items before giving leftover xp to player
This commit is contained in:
@@ -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<ItemStack> 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.
|
||||
*/
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user