diff --git a/src/main/java/com/yourname/sellplugin/SellPlugin.java b/src/main/java/com/yourname/sellplugin/SellPlugin.java index 2bb669a..beb29a7 100644 --- a/src/main/java/com/yourname/sellplugin/SellPlugin.java +++ b/src/main/java/com/yourname/sellplugin/SellPlugin.java @@ -9,6 +9,7 @@ import com.yourname.sellplugin.economy.EconomyManager; import com.yourname.sellplugin.gui.GUIListener; import com.yourname.sellplugin.listener.WorthPacketListener; +import com.yourname.sellplugin.listener.WorthRefreshListener; import com.yourname.sellplugin.manager.ConfigManager; import com.yourname.sellplugin.manager.DailyBonusManager; import com.yourname.sellplugin.manager.MultiplierManager; @@ -52,6 +53,7 @@ public void onEnable() { getCommand("sellmulti").setExecutor(new SellMultiCommand(this)); getCommand("sellworth").setExecutor(new WorthCommand(this)); getServer().getPluginManager().registerEvents(new GUIListener(this), this); + getServer().getPluginManager().registerEvents(new WorthRefreshListener(this), this); worthPacketListener = new WorthPacketListener(this); worthPacketListener.register(); diff --git a/src/main/java/com/yourname/sellplugin/listener/WorthPacketListener.java b/src/main/java/com/yourname/sellplugin/listener/WorthPacketListener.java index dbf8c71..8b45b8d 100644 --- a/src/main/java/com/yourname/sellplugin/listener/WorthPacketListener.java +++ b/src/main/java/com/yourname/sellplugin/listener/WorthPacketListener.java @@ -30,8 +30,17 @@ public class WorthPacketListener { + // Invisible marker prefixed to the worth line we inject. It is built from + // valid formatting codes only, so it renders no glyphs, but lets us reliably + // recognise (and strip) our own line — both to avoid duplicates and to keep + // it out of items the client sends back to the server (creative mode). + private static final char SECTION = '\u00A7'; + private static final String WORTH_MARKER = + "" + SECTION + '9' + SECTION + '8' + SECTION + '9' + SECTION + '8' + SECTION + 'r'; + private final SellPlugin plugin; private PacketListener packetListener; + private PacketListener creativeListener; public WorthPacketListener(SellPlugin plugin) { this.plugin = plugin; @@ -81,35 +90,117 @@ public void onPacketSending(PacketEvent event) { } }; protocolManager.addPacketListener(packetListener); + + // Creative-mode clients echo the items they see back to the server. Strip + // our injected worth line from those inbound items so it never gets baked + // into the real ItemStack (which would otherwise produce duplicate lines). + creativeListener = new PacketAdapter(plugin, ListenerPriority.NORMAL, + PacketType.Play.Client.SET_CREATIVE_SLOT) { + + @Override + public void onPacketReceiving(PacketEvent event) { + if (event.getPacket().getItemModifier().size() <= 0) return; + ItemStack item = event.getPacket().getItemModifier().read(0); + ItemStack cleaned = stripWorthLore(item); + if (cleaned != item) { + event.getPacket().getItemModifier().write(0, cleaned); + } + } + }; + protocolManager.addPacketListener(creativeListener); } public void unregister() { - if (packetListener == null) return; - ProtocolLibrary.getProtocolManager().removePacketListener(packetListener); - packetListener = null; + ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager(); + if (packetListener != null) { + protocolManager.removePacketListener(packetListener); + packetListener = null; + } + if (creativeListener != null) { + protocolManager.removePacketListener(creativeListener); + creativeListener = null; + } } private ItemStack addWorthLore(Player player, ItemStack original) { if (original == null || original.getType().isAir()) return original; double worth = plugin.getSellManager().calculateItemWorth(player, original); - if (worth <= 0) return original; + + ItemMeta meta = original.getItemMeta(); + boolean hadWorthLine = meta != null && meta.hasLore() && loreHasWorthLine(meta.getLore()); + + // Nothing to add and nothing stale to clean up -> leave the item untouched. + if (worth <= 0 && !hadWorthLine) return original; ItemStack clone = original.clone(); - ItemMeta meta = clone.getItemMeta(); + meta = clone.getItemMeta(); if (meta == null) return original; + // Always start from lore without any previously injected/baked worth line + // so we never stack duplicates. List lore = meta.hasLore() ? new ArrayList<>(meta.getLore()) : new ArrayList<>(); - if (!lore.isEmpty()) { - lore.add(""); + removeWorthLines(lore); + + if (worth > 0) { + if (!lore.isEmpty()) { + lore.add(""); + } + lore.add(WORTH_MARKER + plugin.getConfigManager().getWorthFormat() + .replace("{worth}", NumberFormatter.format(worth))); } - lore.add(plugin.getConfigManager().getWorthFormat() - .replace("{worth}", NumberFormatter.format(worth))); - meta.setLore(lore); + + meta.setLore(lore.isEmpty() ? null : lore); clone.setItemMeta(meta); return clone; } + /** + * Returns a copy of {@code item} with any injected worth line removed, or the + * original reference if it carried none. + */ + private ItemStack stripWorthLore(ItemStack item) { + if (item == null || item.getType().isAir()) return item; + + ItemMeta meta = item.getItemMeta(); + if (meta == null || !meta.hasLore()) return item; + + List lore = new ArrayList<>(meta.getLore()); + if (!removeWorthLines(lore)) return item; + + ItemStack clone = item.clone(); + ItemMeta cloneMeta = clone.getItemMeta(); + cloneMeta.setLore(lore.isEmpty() ? null : lore); + clone.setItemMeta(cloneMeta); + return clone; + } + + private boolean loreHasWorthLine(List lore) { + for (String line : lore) { + if (line != null && line.startsWith(WORTH_MARKER)) return true; + } + return false; + } + + /** + * Removes every injected worth line (and the blank separator we place before + * it) from {@code lore} in place. Returns {@code true} if anything changed. + */ + private boolean removeWorthLines(List lore) { + boolean changed = false; + for (int i = lore.size() - 1; i >= 0; i--) { + String line = lore.get(i); + if (line == null || !line.startsWith(WORTH_MARKER)) continue; + lore.remove(i); + changed = true; + // Drop the blank separator we added directly before the worth line. + if (i - 1 >= 0 && lore.get(i - 1).isEmpty()) { + lore.remove(i - 1); + } + } + return changed; + } + private boolean shouldDecorate(Player player) { Inventory topInventory = player.getOpenInventory().getTopInventory(); InventoryHolder holder = topInventory.getHolder(); diff --git a/src/main/java/com/yourname/sellplugin/listener/WorthRefreshListener.java b/src/main/java/com/yourname/sellplugin/listener/WorthRefreshListener.java new file mode 100644 index 0000000..f2dbbfb --- /dev/null +++ b/src/main/java/com/yourname/sellplugin/listener/WorthRefreshListener.java @@ -0,0 +1,57 @@ +package com.yourname.sellplugin.listener; + +import com.yourname.sellplugin.SellPlugin; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityPickupItemEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.player.PlayerJoinEvent; + +/** + * The worth line is applied purely through outgoing item packets, so it only + * appears on items the client is (re)sent while a decoratable inventory is open. + * Items that arrive in other situations — picked up, bought from a shop GUI, + * handed over with /give while a menu is open — would otherwise stay bare until + * the next full inventory resend (i.e. a relog). + * + *

This listener nudges the client to redraw shortly after those moments by + * re-sending the player's inventory, which the packet listener then decorates. + */ +public class WorthRefreshListener implements Listener { + + private final SellPlugin plugin; + + public WorthRefreshListener(SellPlugin plugin) { + this.plugin = plugin; + } + + @EventHandler + public void onPickup(EntityPickupItemEvent event) { + if (event.getEntity() instanceof Player player) { + refresh(player); + } + } + + @EventHandler + public void onClose(InventoryCloseEvent event) { + if (event.getPlayer() instanceof Player player) { + refresh(player); + } + } + + @EventHandler + public void onJoin(PlayerJoinEvent event) { + refresh(event.getPlayer()); + } + + private void refresh(Player player) { + if (!plugin.getConfigManager().isWorthEnabled()) return; + // Run next tick so the inventory reflects the change that triggered us. + plugin.getServer().getScheduler().runTaskLater(plugin, () -> { + if (player.isOnline()) { + player.updateInventory(); + } + }, 1L); + } +}