Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/main/java/com/yourname/sellplugin/SellPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down
111 changes: 101 additions & 10 deletions src/main/java/com/yourname/sellplugin/listener/WorthPacketListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String> 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<String> 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<String> 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<String> 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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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).
*
* <p>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);
}
}
Loading