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.event.entity.ItemSpawnEvent;
|
||||||
import org.bukkit.inventory.ItemStack;
|
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
|
* 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
|
* XP bar (bypassing Vanilla's 2-tick per orb delay), repairing Mending equipment first,
|
||||||
* full stacks.
|
* and consolidating spawned item drops into full stacks.
|
||||||
*/
|
*/
|
||||||
public final class XpAndItemMergeListener implements Listener {
|
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)
|
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
|
||||||
public void onPlayerPickupXp(PlayerPickupExperienceEvent event) {
|
public void onPlayerPickupXp(PlayerPickupExperienceEvent event) {
|
||||||
@@ -49,7 +57,10 @@ public final class XpAndItemMergeListener implements Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (totalExp > 0) {
|
if (totalExp > 0) {
|
||||||
player.giveExp(totalExp);
|
int leftoverExp = applyMending(player, totalExp);
|
||||||
|
if (leftoverExp > 0) {
|
||||||
|
player.giveExp(leftoverExp);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
player.playSound(loc, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 0.5f, 1.2f);
|
player.playSound(loc, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 0.5f, 1.2f);
|
||||||
} catch (Exception ignored) {
|
} 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.
|
* 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
|
// Target has 63, max is 64, source has 10 -> can transfer 1
|
||||||
assertEquals(1, XpAndItemMergeListener.calculateTransfer(63, 64, 10));
|
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