feat(xp-items): implement instant xp vacuum and spawned item consolidation listener
This commit is contained in:
@@ -161,6 +161,7 @@ public final class Canalhandia extends JavaPlugin implements Listener {
|
|||||||
getServer().getPluginManager().registerEvents(this, this);
|
getServer().getPluginManager().registerEvents(this, this);
|
||||||
getServer().getPluginManager().registerEvents(new TitleChatListener(this), this);
|
getServer().getPluginManager().registerEvents(new TitleChatListener(this), this);
|
||||||
getServer().getPluginManager().registerEvents(new ChunkLoaderListener(this), this);
|
getServer().getPluginManager().registerEvents(new ChunkLoaderListener(this), this);
|
||||||
|
getServer().getPluginManager().registerEvents(new XpAndItemMergeListener(this), this);
|
||||||
rescheduleTimer();
|
rescheduleTimer();
|
||||||
rescheduleMilestones();
|
rescheduleMilestones();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package dev.marcospaulo.canalhandia;
|
||||||
|
|
||||||
|
import com.destroystokyo.paper.event.player.PlayerPickupExperienceEvent;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.Sound;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.entity.ExperienceOrb;
|
||||||
|
import org.bukkit.entity.Item;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.entity.ItemSpawnEvent;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
public final class XpAndItemMergeListener implements Listener {
|
||||||
|
|
||||||
|
private final Canalhandia plugin;
|
||||||
|
|
||||||
|
public XpAndItemMergeListener(Canalhandia plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vacuums all experience orbs within radius instantly on player contact.
|
||||||
|
*/
|
||||||
|
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
|
||||||
|
public void onPlayerPickupXp(PlayerPickupExperienceEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
Location loc = player.getLocation();
|
||||||
|
if (loc.getWorld() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int totalExp = 0;
|
||||||
|
try {
|
||||||
|
for (Entity entity : loc.getWorld().getNearbyEntities(loc, 6.0, 6.0, 6.0)) {
|
||||||
|
if (entity instanceof ExperienceOrb orb && orb.isValid()) {
|
||||||
|
totalExp += orb.getExperience();
|
||||||
|
orb.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalExp > 0) {
|
||||||
|
player.giveExp(totalExp);
|
||||||
|
try {
|
||||||
|
player.playSound(loc, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 0.5f, 1.2f);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consolidates spawned items of the same type in a 4-block radius into unified stacks.
|
||||||
|
*/
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
|
public void onItemSpawn(ItemSpawnEvent event) {
|
||||||
|
Item item = event.getEntity();
|
||||||
|
ItemStack stack = item.getItemStack();
|
||||||
|
if (stack.getAmount() >= stack.getMaxStackSize() || item.getWorld() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (Entity e : item.getWorld().getNearbyEntities(item.getLocation(), 4.0, 4.0, 4.0)) {
|
||||||
|
if (e instanceof Item other && other != item && other.isValid()) {
|
||||||
|
ItemStack otherStack = other.getItemStack();
|
||||||
|
if (otherStack.isSimilar(stack)) {
|
||||||
|
int toTransfer = calculateTransfer(stack.getAmount(), stack.getMaxStackSize(), otherStack.getAmount());
|
||||||
|
if (toTransfer > 0) {
|
||||||
|
stack.setAmount(stack.getAmount() + toTransfer);
|
||||||
|
otherStack.setAmount(otherStack.getAmount() - toTransfer);
|
||||||
|
if (otherStack.getAmount() <= 0) {
|
||||||
|
other.remove();
|
||||||
|
} else {
|
||||||
|
other.setItemStack(otherStack);
|
||||||
|
}
|
||||||
|
item.setItemStack(stack);
|
||||||
|
if (stack.getAmount() >= stack.getMaxStackSize()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pure transfer amount calculator for stack consolidation. */
|
||||||
|
public static int calculateTransfer(int targetAmount, int targetMax, int sourceAmount) {
|
||||||
|
if (targetAmount < 0 || targetMax <= 0 || sourceAmount <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int canAdd = targetMax - targetAmount;
|
||||||
|
if (canAdd <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return Math.min(canAdd, sourceAmount);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package dev.marcospaulo.canalhandia;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
class XpAndItemMergeTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void handlesZeroOrNegativeAmountsGracefully() {
|
||||||
|
assertEquals(0, XpAndItemMergeListener.calculateTransfer(0, 0, 0));
|
||||||
|
assertEquals(0, XpAndItemMergeListener.calculateTransfer(-1, 16, 5));
|
||||||
|
assertEquals(0, XpAndItemMergeListener.calculateTransfer(16, 16, 5));
|
||||||
|
assertEquals(0, XpAndItemMergeListener.calculateTransfer(10, 16, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void calculatesTransfersUpToMaxCapacity() {
|
||||||
|
// Target has 10, max is 16, source has 10 -> can transfer 6
|
||||||
|
assertEquals(6, XpAndItemMergeListener.calculateTransfer(10, 16, 10));
|
||||||
|
|
||||||
|
// Target has 5, max is 64, source has 20 -> can transfer all 20
|
||||||
|
assertEquals(20, XpAndItemMergeListener.calculateTransfer(5, 64, 20));
|
||||||
|
|
||||||
|
// Target has 63, max is 64, source has 10 -> can transfer 1
|
||||||
|
assertEquals(1, XpAndItemMergeListener.calculateTransfer(63, 64, 10));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user