diff --git a/pom.xml b/pom.xml index a007f03..5cb84a8 100644 --- a/pom.xml +++ b/pom.xml @@ -45,6 +45,13 @@ provided + + net.dmulloy2 + ProtocolLib + 5.4.0 + provided + + su.nightexpress.nightcore NightCore diff --git a/src/main/java/com/yourname/sellplugin/SellPlugin.java b/src/main/java/com/yourname/sellplugin/SellPlugin.java index 5762d2d..e766b20 100644 --- a/src/main/java/com/yourname/sellplugin/SellPlugin.java +++ b/src/main/java/com/yourname/sellplugin/SellPlugin.java @@ -2,9 +2,11 @@ import com.yourname.sellplugin.command.SellAllCommand; import com.yourname.sellplugin.command.SellCommand; +import com.yourname.sellplugin.command.FastSellAllCommand; import com.yourname.sellplugin.command.TopSellCommand; import com.yourname.sellplugin.economy.EconomyManager; import com.yourname.sellplugin.gui.GUIListener; +import com.yourname.sellplugin.listener.WorthPacketListener; import com.yourname.sellplugin.manager.ConfigManager; import com.yourname.sellplugin.manager.DailyBonusManager; import com.yourname.sellplugin.manager.MultiplierManager; @@ -20,6 +22,7 @@ public class SellPlugin extends JavaPlugin { private MultiplierManager multiplierManager; private DailyBonusManager dailyBonusManager; private SellManager sellManager; + private WorthPacketListener worthPacketListener; @Override public void onEnable() { @@ -42,9 +45,13 @@ public void onEnable() { getCommand("sell").setExecutor(new SellCommand(this)); getCommand("sellall").setExecutor(new SellAllCommand(this)); + getCommand("fastsellall").setExecutor(new FastSellAllCommand(this)); getCommand("topsell").setExecutor(new TopSellCommand(this)); getServer().getPluginManager().registerEvents(new GUIListener(this), this); + worthPacketListener = new WorthPacketListener(this); + worthPacketListener.register(); + getLogger().info("SellPlugin has been enabled successfully."); } @@ -53,6 +60,9 @@ public void onDisable() { if (multiplierManager != null) { multiplierManager.saveAll(); } + if (worthPacketListener != null) { + worthPacketListener.unregister(); + } getLogger().info("SellPlugin has been disabled."); } diff --git a/src/main/java/com/yourname/sellplugin/command/FastSellAllCommand.java b/src/main/java/com/yourname/sellplugin/command/FastSellAllCommand.java new file mode 100644 index 0000000..592fe25 --- /dev/null +++ b/src/main/java/com/yourname/sellplugin/command/FastSellAllCommand.java @@ -0,0 +1,32 @@ +package com.yourname.sellplugin.command; + +import com.yourname.sellplugin.SellPlugin; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class FastSellAllCommand implements CommandExecutor { + + private final SellPlugin plugin; + + public FastSellAllCommand(SellPlugin plugin) { + this.plugin = plugin; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!(sender instanceof Player player)) { + sender.sendMessage("Only players can use this command."); + return true; + } + + if (!player.hasPermission("sellplugin.use")) { + player.sendMessage(plugin.getConfigManager().getMessage("no-permission")); + return true; + } + + plugin.getSellManager().sellAll(player); + return true; + } +} diff --git a/src/main/java/com/yourname/sellplugin/gui/CategoryItemsGUI.java b/src/main/java/com/yourname/sellplugin/gui/CategoryItemsGUI.java index d4dc68e..a1c1848 100644 --- a/src/main/java/com/yourname/sellplugin/gui/CategoryItemsGUI.java +++ b/src/main/java/com/yourname/sellplugin/gui/CategoryItemsGUI.java @@ -3,6 +3,7 @@ import com.yourname.sellplugin.SellPlugin; import com.yourname.sellplugin.manager.ConfigManager; import com.yourname.sellplugin.manager.PriceManager; +import com.yourname.sellplugin.util.ItemNameFormatter; import com.yourname.sellplugin.util.NumberFormatter; import com.yourname.sellplugin.util.SmallCaps; import org.bukkit.Bukkit; @@ -141,7 +142,7 @@ private void populate() { int catCount = plugin.getSellManager().countCategoryItems(player, categoryId); List sellLore = new ArrayList<>(); sellLore.add(ChatColor.GRAY + " ▸ " + SmallCaps.convert("category: ") - + ChatColor.WHITE + categoryId); + + ChatColor.WHITE + ChatColor.stripColor(cfg.getCategoryDisplayName(categoryId))); if (catCount > 0) { sellLore.add(ChatColor.GRAY + " ▸ " + SmallCaps.convert("items: ") + ChatColor.WHITE + NumberFormatter.format(catCount)); @@ -161,10 +162,11 @@ private void populate() { private ItemStack buildItemDisplay(String itemKey) { PriceManager pm = plugin.getPriceManager(); double base = pm.getPrice(itemKey); - double earned = plugin.getMultiplierManager().getMultiplier(player, categoryId); - double daily = plugin.getDailyBonusManager().getDailyBonus(categoryId); - double mult = earned + daily; - double effective = base * mult; + String itemCategory = pm.getCategory(itemKey); + double earned = plugin.getMultiplierManager().getMultiplier(player, itemCategory); + double daily = plugin.getDailyBonusManager().getDailyBonus(itemCategory); + double effectiveMultiplier = plugin.getMultiplierManager().getEffectiveMultiplier(player, itemCategory); + double effective = base * effectiveMultiplier; // Build correct ItemStack (handles potions with PotionMeta) ItemStack item = resolveItemStack(itemKey); @@ -179,13 +181,13 @@ private ItemStack buildItemDisplay(String itemKey) { + ChatColor.GOLD + " (+" + String.format("%.2fx", daily) + " today)"); } else { lore.add(ChatColor.GRAY + " ▸ " + ChatColor.WHITE + "Mult: " - + ChatColor.AQUA + String.format("%.2fx", mult)); + + ChatColor.AQUA + String.format("%.2fx", effectiveMultiplier)); } lore.add(ChatColor.GRAY + " ▸ " + ChatColor.WHITE + "Price: " + ChatColor.GREEN + "$" + NumberFormatter.format(effective)); lore.add(ChatColor.DARK_GRAY + "━━━━━━━━━━━━━━━━━━━━━"); - String displayName = ChatColor.WHITE + formatItemName(itemKey); + String displayName = ChatColor.WHITE + ItemNameFormatter.formatKey(itemKey); ItemMeta meta = item.getItemMeta(); if (meta != null) { @@ -224,10 +226,6 @@ private ItemStack resolveItemStack(String itemKey) { return new ItemStack(mat != null ? mat : Material.BARRIER); } - private String formatItemName(String key) { - return key.replace("_", " ").replace(":", " – "); - } - // ── Item clicked ───────────────────────────────────────────────────────── /** @@ -286,4 +284,3 @@ public int getPage() { return page; } } - diff --git a/src/main/java/com/yourname/sellplugin/gui/ConfirmSellGUI.java b/src/main/java/com/yourname/sellplugin/gui/ConfirmSellGUI.java index 4e3cdad..e1e4da9 100644 --- a/src/main/java/com/yourname/sellplugin/gui/ConfirmSellGUI.java +++ b/src/main/java/com/yourname/sellplugin/gui/ConfirmSellGUI.java @@ -31,10 +31,12 @@ public class ConfirmSellGUI implements InventoryHolder { private final Inventory inv; private final SellPlugin plugin; private final String categoryId; + private final int returnPage; - public ConfirmSellGUI(SellPlugin plugin, Player player, String categoryId) { + public ConfirmSellGUI(SellPlugin plugin, Player player, String categoryId, int returnPage) { this.plugin = plugin; this.categoryId = categoryId; + this.returnPage = returnPage; ConfigManager cfg = plugin.getConfigManager(); String title = ChatColor.DARK_GRAY + "" + ChatColor.BOLD @@ -113,5 +115,8 @@ public void open(Player p) { public String getCategoryId() { return categoryId; } -} + public int getReturnPage() { + return returnPage; + } +} diff --git a/src/main/java/com/yourname/sellplugin/gui/GUIListener.java b/src/main/java/com/yourname/sellplugin/gui/GUIListener.java index 2b24944..bd86855 100644 --- a/src/main/java/com/yourname/sellplugin/gui/GUIListener.java +++ b/src/main/java/com/yourname/sellplugin/gui/GUIListener.java @@ -81,7 +81,7 @@ public void onClick(InventoryClickEvent e) { e.setCancelled(true); String catId = shopGUI.getCategoryAtSlot(slot); if (catId != null) { - new CategoryProgressGUI(plugin, player, catId).open(player); + new CategoryItemsGUI(plugin, player, catId, 0).open(player); } return; } @@ -130,7 +130,7 @@ public void onClick(InventoryClickEvent e) { } if (slot == ConfirmSellGUI.SLOT_CANCEL) { - new CategoryProgressGUI(plugin, player, confirmGUI.getCategoryId()).open(player); + new CategoryItemsGUI(plugin, player, confirmGUI.getCategoryId(), confirmGUI.getReturnPage()).open(player); return; } return; @@ -145,7 +145,7 @@ public void onClick(InventoryClickEvent e) { int slot = e.getSlot(); if (slot == CategoryItemsGUI.SLOT_BACK) { - new CategoryProgressGUI(plugin, player, catItemsGUI.getCategoryId()).open(player); + new ShopMainGUI(plugin, player).open(player); return; } @@ -160,8 +160,7 @@ public void onClick(InventoryClickEvent e) { } if (slot == CategoryItemsGUI.SLOT_SELL_ALL) { - player.closeInventory(); - plugin.getSellManager().sellCategory(player, catItemsGUI.getCategoryId()); + new ConfirmSellGUI(plugin, player, catItemsGUI.getCategoryId(), catItemsGUI.getPage()).open(player); return; } @@ -307,4 +306,3 @@ private void returnItem(Player player, ItemStack item) { } } } - diff --git a/src/main/java/com/yourname/sellplugin/gui/ShopMainGUI.java b/src/main/java/com/yourname/sellplugin/gui/ShopMainGUI.java index f800808..79c6576 100644 --- a/src/main/java/com/yourname/sellplugin/gui/ShopMainGUI.java +++ b/src/main/java/com/yourname/sellplugin/gui/ShopMainGUI.java @@ -80,7 +80,7 @@ private ItemStack buildCategoryButton(String catId) { + ChatColor.YELLOW + "+" + String.format("%.2f", dailyBonus) + "x"); } lore.add(ChatColor.DARK_GRAY + "━━━━━━━━━━━━━━━━━━━"); - lore.add(ChatColor.YELLOW + " ✦ " + SmallCaps.convert("click to view progress!")); + lore.add(ChatColor.YELLOW + " ✦ " + SmallCaps.convert("click to view items & prices!")); List extraLore = cfg.getCategoryLore(catId); if (!extraLore.isEmpty()) lore.addAll(extraLore); diff --git a/src/main/java/com/yourname/sellplugin/listener/WorthPacketListener.java b/src/main/java/com/yourname/sellplugin/listener/WorthPacketListener.java new file mode 100644 index 0000000..dbf8c71 --- /dev/null +++ b/src/main/java/com/yourname/sellplugin/listener/WorthPacketListener.java @@ -0,0 +1,147 @@ +package com.yourname.sellplugin.listener; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.ProtocolManager; +import com.comphenix.protocol.events.ListenerPriority; +import com.comphenix.protocol.events.PacketAdapter; +import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.events.PacketListener; +import com.yourname.sellplugin.SellPlugin; +import com.yourname.sellplugin.gui.CategoryItemsGUI; +import com.yourname.sellplugin.gui.CategoryProgressGUI; +import com.yourname.sellplugin.gui.ConfirmSellAllGUI; +import com.yourname.sellplugin.gui.ConfirmSellGUI; +import com.yourname.sellplugin.gui.SellAllGUI; +import com.yourname.sellplugin.gui.ShopMainGUI; +import com.yourname.sellplugin.gui.TopSellGUI; +import com.yourname.sellplugin.util.NumberFormatter; +import org.bukkit.block.DoubleChest; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.BlockInventoryHolder; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.ArrayList; +import java.util.List; + +public class WorthPacketListener { + + private final SellPlugin plugin; + private PacketListener packetListener; + + public WorthPacketListener(SellPlugin plugin) { + this.plugin = plugin; + } + + public void register() { + if (plugin.getServer().getPluginManager().getPlugin("ProtocolLib") == null) { + plugin.getLogger().warning("ProtocolLib not found; sell worth tooltips are disabled."); + return; + } + + ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager(); + packetListener = new PacketAdapter(plugin, ListenerPriority.NORMAL, + PacketType.Play.Server.SET_SLOT, + PacketType.Play.Server.WINDOW_ITEMS) { + + @Override + public void onPacketSending(PacketEvent event) { + if (!WorthPacketListener.this.plugin.getConfigManager().isWorthEnabled()) return; + if (!shouldDecorate(event.getPlayer())) return; + + if (event.getPacketType() == PacketType.Play.Server.SET_SLOT) { + if (event.getPacket().getItemModifier().size() <= 0) return; + ItemStack item = event.getPacket().getItemModifier().read(0); + ItemStack updated = addWorthLore(event.getPlayer(), item); + if (updated != item) { + event.getPacket().getItemModifier().write(0, updated); + } + return; + } + + if (event.getPacket().getItemListModifier().size() <= 0) return; + List items = event.getPacket().getItemListModifier().read(0); + if (items == null || items.isEmpty()) return; + + boolean changed = false; + List updatedItems = new ArrayList<>(items.size()); + for (ItemStack item : items) { + ItemStack updated = addWorthLore(event.getPlayer(), item); + updatedItems.add(updated); + changed |= updated != item; + } + + if (changed) { + event.getPacket().getItemListModifier().write(0, updatedItems); + } + } + }; + protocolManager.addPacketListener(packetListener); + } + + public void unregister() { + if (packetListener == null) return; + ProtocolLibrary.getProtocolManager().removePacketListener(packetListener); + packetListener = 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; + + ItemStack clone = original.clone(); + ItemMeta meta = clone.getItemMeta(); + if (meta == null) return original; + + List lore = meta.hasLore() ? new ArrayList<>(meta.getLore()) : new ArrayList<>(); + if (!lore.isEmpty()) { + lore.add(""); + } + lore.add(plugin.getConfigManager().getWorthFormat() + .replace("{worth}", NumberFormatter.format(worth))); + meta.setLore(lore); + clone.setItemMeta(meta); + return clone; + } + + private boolean shouldDecorate(Player player) { + Inventory topInventory = player.getOpenInventory().getTopInventory(); + InventoryHolder holder = topInventory.getHolder(); + + if (isPluginGui(holder)) return false; + + InventoryType type = topInventory.getType(); + if (type == InventoryType.CRAFTING + || type == InventoryType.CREATIVE + || type == InventoryType.PLAYER) { + return true; + } + + if (holder instanceof BlockInventoryHolder || holder instanceof DoubleChest) { + return true; + } + + return switch (type) { + case ANVIL, BEACON, BLAST_FURNACE, BREWING, CARTOGRAPHY, CRAFTER, + ENCHANTING, FURNACE, GRINDSTONE, LOOM, MERCHANT, + SMITHING, SMOKER, STONECUTTER -> true; + default -> false; + }; + } + + private boolean isPluginGui(InventoryHolder holder) { + return holder instanceof ShopMainGUI + || holder instanceof CategoryProgressGUI + || holder instanceof CategoryItemsGUI + || holder instanceof SellAllGUI + || holder instanceof ConfirmSellGUI + || holder instanceof ConfirmSellAllGUI + || holder instanceof TopSellGUI; + } +} diff --git a/src/main/java/com/yourname/sellplugin/manager/ConfigManager.java b/src/main/java/com/yourname/sellplugin/manager/ConfigManager.java index 060d709..b402892 100644 --- a/src/main/java/com/yourname/sellplugin/manager/ConfigManager.java +++ b/src/main/java/com/yourname/sellplugin/manager/ConfigManager.java @@ -85,6 +85,14 @@ public Material getFillerBlock() { return mat != null ? mat : Material.BLACK_STAINED_GLASS_PANE; } + public boolean isWorthEnabled() { + return plugin.getConfig().getBoolean("worth.enabled", true); + } + + public String getWorthFormat() { + return color(plugin.getConfig().getString("worth.format", "&7Worth &a&l${worth}")); + } + // ---- SellAll GUI (simple /sellall GUI) -------------------------------- public String getSellAllGuiTitle() { return color(plugin.getConfig().getString("sell-all-gui.title", "&8&lSell All Items")); diff --git a/src/main/java/com/yourname/sellplugin/manager/SellManager.java b/src/main/java/com/yourname/sellplugin/manager/SellManager.java index 70a382c..d7a9a00 100644 --- a/src/main/java/com/yourname/sellplugin/manager/SellManager.java +++ b/src/main/java/com/yourname/sellplugin/manager/SellManager.java @@ -221,6 +221,24 @@ public double calculateCategoryValue(Player player, String category) { return total; } + public double calculateItemWorth(Player player, ItemStack item) { + if (item == null || item.getType() == Material.AIR) return 0.0; + + if (isShulkerBox(item)) { + return peekShulkerContents(player, item, null, null).earned; + } + + String key = plugin.getPriceManager().getItemKey(item); + if (key == null) return 0.0; + + double base = plugin.getPriceManager().getPrice(key); + if (base <= 0) return 0.0; + + String category = plugin.getPriceManager().getCategory(key); + double multiplier = plugin.getMultiplierManager().getEffectiveMultiplier(player, category); + return base * multiplier * item.getAmount(); + } + // --------------------------------------------------------------- // Finalize a sell operation // --------------------------------------------------------------- diff --git a/src/main/java/com/yourname/sellplugin/util/ItemNameFormatter.java b/src/main/java/com/yourname/sellplugin/util/ItemNameFormatter.java new file mode 100644 index 0000000..fd93919 --- /dev/null +++ b/src/main/java/com/yourname/sellplugin/util/ItemNameFormatter.java @@ -0,0 +1,31 @@ +package com.yourname.sellplugin.util; + +import java.util.Locale; + +public final class ItemNameFormatter { + + private ItemNameFormatter() { + } + + public static String formatKey(String key) { + if (key == null || key.isBlank()) return ""; + + String[] parts = key.split(":", 2); + String materialName = formatWords(parts[0]); + if (parts.length == 1) return materialName; + + return materialName + " (" + formatWords(parts[1]) + ")"; + } + + private static String formatWords(String raw) { + String[] words = raw.toLowerCase(Locale.ENGLISH).split("[_ ]+"); + StringBuilder builder = new StringBuilder(); + for (String word : words) { + if (word.isEmpty()) continue; + if (!builder.isEmpty()) builder.append(' '); + builder.append(Character.toUpperCase(word.charAt(0))); + builder.append(word.substring(1)); + } + return builder.toString(); + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 3af36ce..0016b74 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -57,6 +57,13 @@ coinsengine-currency-id: coins # Filler block used as background in GUIs (must be a valid Material name) filler-block: "BLACK_STAINED_GLASS_PANE" +# ---- Sell worth tooltip -------------------------------------- # +# Adds a client-side worth line to sellable items using packets. +# Supports the {worth} placeholder. +worth: + enabled: true + format: "&7Worth &a&l${worth}" + # ---- Main Shop GUI (/sell) ---------------------------------- # # This opens the 9x5 category browsing menu. diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 078d553..9f1829a 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -2,7 +2,7 @@ name: SellPlugin version: 2.1.0 main: com.yourname.sellplugin.SellPlugin api-version: 1.13 -softdepend: [Vault, CoinsEngine] +softdepend: [Vault, CoinsEngine, ProtocolLib] commands: sell: @@ -14,6 +14,10 @@ commands: description: Opens the quick sell-all GUI. usage: /sellall permission: sellplugin.use + fastsellall: + description: Instantly sells all sellable items without opening a GUI. + usage: /fastsellall + permission: sellplugin.use topsell: description: Opens the top sellers leaderboard.