package com.yourname.premiumah.gui; import com.yourname.premiumah.config.ButtonConfig; import com.yourname.premiumah.config.ConfigManager; import com.yourname.premiumah.config.MessageManager; import com.yourname.premiumah.manager.AuctionHouseManager; import com.yourname.premiumah.manager.ClaimManager; import com.yourname.premiumah.manager.ListingManager; import com.yourname.premiumah.model.ActionResult; import com.yourname.premiumah.model.ClaimRecord; import com.yourname.premiumah.model.ClaimType; import com.yourname.premiumah.model.Listing; import com.yourname.premiumah.model.ListingCreationResult; import com.yourname.premiumah.model.SortMode; import com.yourname.premiumah.util.InventoryUtil; import com.yourname.premiumah.util.MaterialUtil; import com.yourname.premiumah.util.TextUtil; import io.papermc.paper.event.player.AsyncChatEvent; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.Registry; import org.bukkit.Sound; import org.bukkit.entity.HumanEntity; import org.bukkit.entity.Player; import org.bukkit.event.inventory.ClickType; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryDragEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.plugin.java.JavaPlugin; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; public final class GuiManager { private static final int SELL_ITEM_SLOT = 22; private final JavaPlugin plugin; private final ConfigManager config; private final MessageManager messages; private final ListingManager listings; private final ClaimManager claims; private final AuctionHouseManager auctionHouse; private final Map browseStates = new ConcurrentHashMap<>(); private final Map sellSessions = new ConcurrentHashMap<>(); private final Map clickDebounce = new ConcurrentHashMap<>(); public GuiManager(JavaPlugin plugin, ConfigManager config, MessageManager messages, ListingManager listings, ClaimManager claims, AuctionHouseManager auctionHouse) { this.plugin = plugin; this.config = config; this.messages = messages; this.listings = listings; this.claims = claims; this.auctionHouse = auctionHouse; } public void openMain(Player player) { GuiHolder holder = new GuiHolder(GuiType.MAIN, player.getUniqueId(), 0, null, null, null); Inventory inventory = create(holder, config.guiSize("main"), config.guiTitle("main"), Map.of()); fill(inventory); inventory.setItem(11, button("browse", Map.of())); inventory.setItem(13, button("sell", Map.of())); inventory.setItem(15, button("my-listings", Map.of())); inventory.setItem(29, button("claims", Map.of())); if (player.hasPermission("premiumah.admin")) { inventory.setItem(31, button("admin", Map.of())); } inventory.setItem(33, button("close", Map.of())); player.openInventory(inventory); play(player, "open"); } public void openBrowse(Player player, int requestedPage) { BrowseState state = browseState(player); List active = listings.activeListings(state.sortMode(), state.filter()); openListingView(player, GuiType.BROWSE, active, requestedPage, "browse", null, state.sortMode(), state.filter()); } public void openMyListings(Player player, int requestedPage) { List own = listings.activeListingsBySeller(player.getUniqueId(), SortMode.NEWEST); openListingView(player, GuiType.MY_LISTINGS, own, requestedPage, "my-listings", null, SortMode.NEWEST, null); } public void openAdmin(Player player, int requestedPage) { BrowseState state = browseState(player); List active = listings.activeListings(state.sortMode(), state.filter()); openListingView(player, GuiType.ADMIN, active, requestedPage, "admin", null, state.sortMode(), state.filter()); } public void openAdminForSeller(Player player, String sellerName, int requestedPage) { List active = listings.activeListingsBySellerName(sellerName, SortMode.NEWEST); openListingView(player, GuiType.ADMIN, active, requestedPage, "admin", sellerName, SortMode.NEWEST, null); } public void openClaims(Player player, int requestedPage) { List playerClaims = claims.claimsFor(player.getUniqueId()); int page = normalizePage(requestedPage, playerClaims.size()); GuiHolder holder = new GuiHolder(GuiType.CLAIMS, player.getUniqueId(), page, null, SortMode.NEWEST, null); Inventory inventory = create(holder, config.guiSize("claims"), config.guiTitle("claims"), Map.of("page", String.valueOf(page + 1))); fill(inventory); List slots = config.listingSlots(); int start = page * slots.size(); for (int i = 0; i < slots.size(); i++) { int index = start + i; if (index >= playerClaims.size()) { break; } ClaimRecord claim = playerClaims.get(index); int slot = slots.get(i); holder.claimSlots().put(slot, claim.id()); inventory.setItem(slot, claimItem(claim)); } addPagedControls(inventory, page, playerClaims.size(), slots.size(), "back"); player.openInventory(inventory); play(player, "open"); } public void openSell(Player player, boolean takeHeldItem) { if (!player.hasPermission("premiumah.sell")) { messages.send(player, "no-permission"); play(player, "fail"); return; } SellSession session = sellSessions.computeIfAbsent(player.getUniqueId(), ignored -> new SellSession()); if (takeHeldItem && InventoryUtil.isAir(session.rawItem())) { ItemStack held = player.getInventory().getItemInMainHand(); if (!InventoryUtil.isAir(held)) { session.item(held); player.getInventory().setItemInMainHand(null); } } session.awaitingPrice(false); session.completed(false); GuiHolder holder = new GuiHolder(GuiType.SELL, player.getUniqueId(), 0, null, null, null); Inventory inventory = create(holder, config.guiSize("sell"), config.guiTitle("sell"), Map.of()); fill(inventory); inventory.setItem(SELL_ITEM_SLOT, session.item()); inventory.setItem(20, customItem(Material.GOLD_INGOT, "<#f5d58a>Set Price", List.of( "<#9ca3af>Current: <#ffffff>" + (session.price() <= 0.0D ? "Not set" : auctionHouse.formatMoney(session.price())), "<#6ee7b7>Click to type a price." ), Map.of())); inventory.setItem(24, button("confirm", Map.of())); inventory.setItem(36, button("back", Map.of())); inventory.setItem(40, button("cancel", Map.of())); player.openInventory(inventory); play(player, "open"); } public void openConfirmBuy(Player player, String listingId) { Listing listing = listings.get(listingId).orElse(null); if (listing == null || !listing.isActive(System.currentTimeMillis())) { messages.send(player, "listing-no-longer-available"); play(player, "fail"); openBrowse(player, 0); return; } GuiHolder holder = new GuiHolder(GuiType.CONFIRM_BUY, player.getUniqueId(), 0, listing.id(), null, null); Inventory inventory = create(holder, config.guiSize("confirm-buy"), config.guiTitle("confirm-buy"), Map.of()); fill(inventory); inventory.setItem(11, button("confirm", Map.of())); inventory.setItem(13, listingItem(listing, "confirm-buy")); inventory.setItem(15, button("cancel", Map.of())); player.openInventory(inventory); play(player, "open"); } public void handleClick(InventoryClickEvent event) { if (!(event.getWhoClicked() instanceof Player player) || !(event.getView().getTopInventory().getHolder() instanceof GuiHolder holder)) { return; } int topSize = event.getView().getTopInventory().getSize(); int rawSlot = event.getRawSlot(); if (holder.type() != GuiType.SELL && rawSlot >= topSize) { if (event.isShiftClick()) { event.setCancelled(true); } return; } if (holder.type() == GuiType.SELL && rawSlot >= topSize) { if (event.isShiftClick()) { event.setCancelled(true); } return; } if (holder.type() == GuiType.SELL && rawSlot == SELL_ITEM_SLOT) { event.setCancelled(false); Bukkit.getScheduler().runTask(plugin, () -> syncSellSlot(player, event.getView().getTopInventory())); return; } event.setCancelled(true); if (rawSlot < 0 || !canClick(player)) { return; } switch (holder.type()) { case MAIN -> handleMainClick(player, rawSlot); case BROWSE -> handleBrowseClick(player, holder, rawSlot, event.getClick()); case MY_LISTINGS -> handleMyListingsClick(player, holder, rawSlot); case CLAIMS -> handleClaimsClick(player, holder, rawSlot); case SELL -> handleSellClick(player, event.getView().getTopInventory(), rawSlot); case CONFIRM_BUY -> handleConfirmClick(player, holder, rawSlot); case ADMIN -> handleAdminClick(player, holder, rawSlot, event.getClick()); } } public void handleDrag(InventoryDragEvent event) { if (!(event.getWhoClicked() instanceof Player player) || !(event.getView().getTopInventory().getHolder() instanceof GuiHolder holder)) { return; } int topSize = event.getView().getTopInventory().getSize(); boolean touchesTop = event.getRawSlots().stream().anyMatch(slot -> slot < topSize); if (!touchesTop) { return; } if (holder.type() == GuiType.SELL && event.getRawSlots().stream().allMatch(slot -> slot == SELL_ITEM_SLOT || slot >= topSize)) { Bukkit.getScheduler().runTask(plugin, () -> syncSellSlot(player, event.getView().getTopInventory())); return; } event.setCancelled(true); } public void handleClose(InventoryCloseEvent event) { if (!(event.getPlayer() instanceof Player player) || !(event.getInventory().getHolder() instanceof GuiHolder holder)) { return; } if (holder.type() != GuiType.SELL) { return; } SellSession session = sellSessions.get(player.getUniqueId()); if (session == null || session.awaitingPrice() || session.completed()) { return; } ItemStack item = event.getInventory().getItem(SELL_ITEM_SLOT); if (InventoryUtil.isAir(item)) { item = session.item(); } returnItem(player, item); sellSessions.remove(player.getUniqueId()); } public void handleChat(AsyncChatEvent event) { Player player = event.getPlayer(); SellSession session = sellSessions.get(player.getUniqueId()); if (session == null || !session.awaitingPrice()) { return; } event.setCancelled(true); String input = PlainTextComponentSerializer.plainText().serialize(event.message()).trim(); Bukkit.getScheduler().runTask(plugin, () -> completePricePrompt(player, input)); } public void setSort(Player player, SortMode sortMode) { browseState(player).sortMode(sortMode); } public void shutdown() { for (Player player : Bukkit.getOnlinePlayers()) { if (player.getOpenInventory().getTopInventory().getHolder() instanceof GuiHolder) { player.closeInventory(); } } for (UUID uuid : new ArrayList<>(sellSessions.keySet())) { Player player = Bukkit.getPlayer(uuid); SellSession session = sellSessions.remove(uuid); if (player != null && session != null && !InventoryUtil.isAir(session.rawItem())) { returnItem(player, session.rawItem()); } if (session != null) { session.cancelTimeout(); } } sellSessions.clear(); browseStates.clear(); clickDebounce.clear(); } public void handleQuit(Player player) { SellSession session = sellSessions.remove(player.getUniqueId()); if (session != null) { session.cancelTimeout(); if (!InventoryUtil.isAir(session.rawItem())) { returnItem(player, session.rawItem()); } } browseStates.remove(player.getUniqueId()); clickDebounce.remove(player.getUniqueId()); } private void handleMainClick(Player player, int slot) { play(player, "click"); if (slot == 11) { openBrowse(player, 0); } else if (slot == 13) { openSell(player, true); } else if (slot == 15) { openMyListings(player, 0); } else if (slot == 29) { openClaims(player, 0); } else if (slot == 31 && player.hasPermission("premiumah.admin")) { openAdmin(player, 0); } else if (slot == 33) { player.closeInventory(); } } private void handleBrowseClick(Player player, GuiHolder holder, int slot, ClickType click) { if (holder.listingSlots().containsKey(slot)) { Listing listing = listings.get(holder.listingSlots().get(slot)).orElse(null); if (listing == null) { messages.send(player, "listing-no-longer-available"); play(player, "fail"); openBrowse(player, holder.page()); return; } if (click.isRightClick() && player.hasPermission("premiumah.admin.remove")) { ActionResult result = auctionHouse.adminRemoveListing(listing.id()); messages.send(player, result.messageKey(), result.placeholders()); play(player, result.success() ? "success" : "fail"); openBrowse(player, holder.page()); return; } if (click.isShiftClick()) { browseState(player).filter(listing.rawItem().getType()); openBrowse(player, 0); return; } openConfirmBuy(player, listing.id()); return; } handlePagedControl(player, holder, slot, GuiType.BROWSE); } private void handleMyListingsClick(Player player, GuiHolder holder, int slot) { if (holder.listingSlots().containsKey(slot)) { ActionResult result = auctionHouse.cancelListing(player, holder.listingSlots().get(slot)); messages.send(player, result.messageKey(), result.placeholders()); play(player, result.success() ? "success" : "fail"); openMyListings(player, holder.page()); return; } handlePagedControl(player, holder, slot, GuiType.MY_LISTINGS); } private void handleClaimsClick(Player player, GuiHolder holder, int slot) { if (holder.claimSlots().containsKey(slot)) { ActionResult result = auctionHouse.claim(player, holder.claimSlots().get(slot)); messages.send(player, result.messageKey(), result.placeholders()); play(player, result.success() ? "success" : "fail"); openClaims(player, holder.page()); return; } handlePagedControl(player, holder, slot, GuiType.CLAIMS); } private void handleSellClick(Player player, Inventory inventory, int slot) { play(player, "click"); if (slot == 20) { beginPricePrompt(player, inventory); } else if (slot == 24) { confirmSell(player, inventory); } else if (slot == 36) { closeSellReturningItem(player, inventory, true); openMain(player); } else if (slot == 40) { closeSellReturningItem(player, inventory, false); } } private void handleConfirmClick(Player player, GuiHolder holder, int slot) { if (slot == 11) { ActionResult result = auctionHouse.buyListing(player, holder.context()); messages.send(player, result.messageKey(), result.placeholders()); play(player, result.success() ? "success" : "fail"); if (result.success()) { openBrowse(player, 0); } else { player.closeInventory(); } } else if (slot == 15) { play(player, "click"); openBrowse(player, 0); } } private void handleAdminClick(Player player, GuiHolder holder, int slot, ClickType click) { if (holder.listingSlots().containsKey(slot)) { Listing listing = listings.get(holder.listingSlots().get(slot)).orElse(null); if (listing == null) { messages.send(player, "listing-no-longer-available"); openAdmin(player, holder.page()); return; } if (click.isRightClick() || click.isShiftClick()) { ActionResult result = auctionHouse.adminRemoveListing(listing.id()); messages.send(player, result.messageKey(), result.placeholders()); play(player, result.success() ? "success" : "fail"); openAdmin(player, holder.page()); } else { openConfirmBuy(player, listing.id()); } return; } handlePagedControl(player, holder, slot, GuiType.ADMIN); } private void handlePagedControl(Player player, GuiHolder holder, int slot, GuiType type) { play(player, "click"); if (slot == 45) { openByType(player, type, Math.max(0, holder.page() - 1), holder.context()); } else if (slot == 49) { openMain(player); } else if (slot == 53) { openByType(player, type, holder.page() + 1, holder.context()); } else if (slot == 50 && (type == GuiType.BROWSE || type == GuiType.ADMIN)) { BrowseState state = browseState(player); state.sortMode(state.sortMode().next()); openByType(player, type, 0, holder.context()); } else if (slot == 48 && (type == GuiType.BROWSE || type == GuiType.ADMIN)) { browseState(player).filter(null); openByType(player, type, 0, holder.context()); } } private void openByType(Player player, GuiType type, int page, String context) { switch (type) { case BROWSE -> openBrowse(player, page); case MY_LISTINGS -> openMyListings(player, page); case CLAIMS -> openClaims(player, page); case ADMIN -> { if (context == null || context.isBlank()) { openAdmin(player, page); } else { openAdminForSeller(player, context, page); } } default -> openMain(player); } } private void openListingView(Player player, GuiType type, List source, int requestedPage, String titleKey, String context, SortMode sortMode, Material filter) { int page = normalizePage(requestedPage, source.size()); GuiHolder holder = new GuiHolder(type, player.getUniqueId(), page, context, sortMode, filter); Inventory inventory = create(holder, config.guiSize(titleKey), config.guiTitle(titleKey), Map.of("page", String.valueOf(page + 1))); fill(inventory); List slots = config.listingSlots(); int start = page * slots.size(); String loreKey = type == GuiType.MY_LISTINGS ? "my-listing" : "listing"; for (int i = 0; i < slots.size(); i++) { int index = start + i; if (index >= source.size()) { break; } Listing listing = source.get(index); int slot = slots.get(i); holder.listingSlots().put(slot, listing.id()); inventory.setItem(slot, listingItem(listing, loreKey)); } addPagedControls(inventory, page, source.size(), slots.size(), "back"); if (type == GuiType.BROWSE || type == GuiType.ADMIN) { BrowseState state = browseState(player); inventory.setItem(48, button("filter", Map.of("filter", state.filter() == null ? "All" : MaterialUtil.pretty(state.filter())))); inventory.setItem(50, button("sort", Map.of("sort", state.sortMode().displayName()))); } player.openInventory(inventory); play(player, "open"); } private void addPagedControls(Inventory inventory, int page, int totalItems, int pageSize, String backKey) { if (page > 0) { inventory.setItem(45, button("previous", Map.of())); } inventory.setItem(49, button(backKey, Map.of())); if ((page + 1) * pageSize < totalItems) { inventory.setItem(53, button("next", Map.of())); } } private Inventory create(GuiHolder holder, int size, String title, Map placeholders) { Inventory inventory = Bukkit.createInventory(holder, size, messages.component(messages.apply(title, placeholders))); holder.inventory(inventory); return inventory; } private void fill(Inventory inventory) { if (!config.fillerEnabled()) { return; } ItemStack filler = customItem(config.fillerMaterial(), config.fillerName(), List.of(), Map.of()); for (int i = 0; i < inventory.getSize(); i++) { if (inventory.getItem(i) == null) { inventory.setItem(i, filler); } } } private ItemStack button(String key, Map placeholders) { ButtonConfig button = config.button(key); return customItem(button.material(), button.name(), button.lore(), placeholders); } private ItemStack customItem(Material material, String name, List lore, Map placeholders) { ItemStack item = new ItemStack(material); ItemMeta meta = item.getItemMeta(); if (meta != null) { meta.displayName(messages.component(messages.apply(name, placeholders))); if (lore != null && !lore.isEmpty()) { meta.lore(lore.stream().map(line -> messages.component(messages.apply(line, placeholders))).toList()); } meta.addItemFlags(ItemFlag.values()); item.setItemMeta(meta); } return item; } private ItemStack listingItem(Listing listing, String loreKey) { ItemStack item = listing.item(); ItemMeta meta = item.getItemMeta(); if (meta != null) { List lore = new ArrayList<>(); if (meta.lore() != null && !meta.lore().isEmpty()) { lore.addAll(meta.lore()); lore.add(TextUtil.component("")); } lore.addAll(messages.guiLore(loreKey, listingPlaceholders(listing))); meta.lore(lore); item.setItemMeta(meta); } return item; } private ItemStack claimItem(ClaimRecord claim) { ItemStack item; if (claim.type() == ClaimType.MONEY) { item = customItem(Material.EMERALD, "<#6ee7b7>Sale Payment <#ffffff>{amount}", messages.rawGuiLore("claim"), claimPlaceholders(claim)); } else { item = claim.item(); ItemMeta meta = item.getItemMeta(); if (meta != null) { List lore = new ArrayList<>(); if (meta.lore() != null && !meta.lore().isEmpty()) { lore.addAll(meta.lore()); lore.add(TextUtil.component("")); } lore.addAll(messages.guiLore("claim", claimPlaceholders(claim))); meta.lore(lore); item.setItemMeta(meta); } } return item; } private Map listingPlaceholders(Listing listing) { return Map.of( "seller", listing.sellerName(), "price", auctionHouse.formatMoney(listing.price()), "remaining", com.yourname.premiumah.util.TimeUtil.remaining(listing.expiresAt()), "id", listing.id() ); } private Map claimPlaceholders(ClaimRecord claim) { String itemName = claim.type() == ClaimType.MONEY ? auctionHouse.formatMoney(claim.moneyAmount()) : auctionHouse.itemName(claim.item()); Map placeholders = new HashMap<>(); placeholders.put("reason", claim.reason().displayName()); placeholders.put("id", claim.listingId() == null ? "N/A" : claim.listingId()); placeholders.put("age", com.yourname.premiumah.util.TimeUtil.age(claim.createdAt())); placeholders.put("item", itemName); placeholders.put("amount", itemName); return placeholders; } private BrowseState browseState(Player player) { return browseStates.computeIfAbsent(player.getUniqueId(), ignored -> new BrowseState(config.defaultSort(), null)); } private int normalizePage(int requestedPage, int totalItems) { int pageSize = Math.max(1, config.listingSlots().size()); int maxPage = Math.max(0, (int) Math.ceil(totalItems / (double) pageSize) - 1); return Math.max(0, Math.min(requestedPage, maxPage)); } private boolean canClick(Player player) { long now = System.currentTimeMillis(); long last = clickDebounce.getOrDefault(player.getUniqueId(), 0L); if (now - last < config.clickDebounceMillis()) { return false; } clickDebounce.put(player.getUniqueId(), now); return true; } private void syncSellSlot(Player player, Inventory inventory) { SellSession session = sellSessions.get(player.getUniqueId()); if (session != null) { session.item(inventory.getItem(SELL_ITEM_SLOT)); } } private void beginPricePrompt(Player player, Inventory inventory) { SellSession session = sellSessions.computeIfAbsent(player.getUniqueId(), ignored -> new SellSession()); session.item(inventory.getItem(SELL_ITEM_SLOT)); inventory.setItem(SELL_ITEM_SLOT, null); session.awaitingPrice(true); session.completed(false); session.cancelTimeout(); session.timeoutTask(Bukkit.getScheduler().runTaskLater(plugin, () -> { SellSession current = sellSessions.get(player.getUniqueId()); if (current != null && current.awaitingPrice()) { current.awaitingPrice(false); returnItem(player, current.rawItem()); current.cancelTimeout(); sellSessions.remove(player.getUniqueId()); messages.send(player, "price-prompt-timeout"); play(player, "fail"); } }, config.chatPriceTimeoutSeconds() * 20L)); player.closeInventory(); messages.send(player, "price-prompt"); } private void completePricePrompt(Player player, String input) { SellSession session = sellSessions.get(player.getUniqueId()); if (session == null || !session.awaitingPrice()) { return; } session.cancelTimeout(); if (input.equalsIgnoreCase("cancel")) { session.awaitingPrice(false); returnItem(player, session.rawItem()); sellSessions.remove(player.getUniqueId()); messages.send(player, "price-prompt-cancelled"); play(player, "click"); return; } double price; try { price = Double.parseDouble(input.replace(",", "")); } catch (NumberFormatException exception) { messages.send(player, "invalid-price"); play(player, "fail"); beginPricePrompt(player, createTempSellInventory(player, session)); return; } if (!Double.isFinite(price) || price <= 0.0D) { messages.send(player, "invalid-price"); play(player, "fail"); beginPricePrompt(player, createTempSellInventory(player, session)); return; } session.price(price); session.awaitingPrice(false); messages.send(player, "price-set", Map.of("price", auctionHouse.formatMoney(price))); openSell(player, false); } private Inventory createTempSellInventory(Player player, SellSession session) { GuiHolder holder = new GuiHolder(GuiType.SELL, player.getUniqueId(), 0, null, null, null); Inventory inventory = create(holder, config.guiSize("sell"), config.guiTitle("sell"), Map.of()); inventory.setItem(SELL_ITEM_SLOT, session.item()); return inventory; } private void confirmSell(Player player, Inventory inventory) { SellSession session = sellSessions.computeIfAbsent(player.getUniqueId(), ignored -> new SellSession()); ItemStack item = inventory.getItem(SELL_ITEM_SLOT); if (InventoryUtil.isAir(item)) { messages.send(player, "no-item-in-hand"); play(player, "fail"); return; } if (session.price() <= 0.0D) { messages.send(player, "invalid-price"); play(player, "fail"); return; } inventory.setItem(SELL_ITEM_SLOT, null); ListingCreationResult result = auctionHouse.createListing(player, item, session.price()); if (!result.success()) { inventory.setItem(SELL_ITEM_SLOT, item); messages.send(player, result.messageKey(), result.placeholders()); play(player, "fail"); return; } session.completed(true); session.cancelTimeout(); sellSessions.remove(player.getUniqueId()); messages.send(player, result.messageKey(), result.placeholders()); play(player, "success"); openBrowse(player, 0); } private void closeSellReturningItem(Player player, Inventory inventory, boolean keepOpen) { SellSession session = sellSessions.remove(player.getUniqueId()); ItemStack item = inventory.getItem(SELL_ITEM_SLOT); inventory.setItem(SELL_ITEM_SLOT, null); if (session != null) { session.completed(true); session.cancelTimeout(); if (InventoryUtil.isAir(item)) { item = session.rawItem(); } } returnItem(player, item); if (!keepOpen) { player.closeInventory(); } } private void returnItem(Player player, ItemStack item) { if (InventoryUtil.isAir(item)) { return; } Map leftovers = InventoryUtil.addItem(player.getInventory(), item); for (ItemStack leftover : leftovers.values()) { player.getWorld().dropItemNaturally(player.getLocation(), leftover); } } private void play(Player player, String key) { if (!config.soundsEnabled()) { return; } String soundName = config.soundName(key); if (soundName == null || soundName.isBlank()) { return; } try { String soundKey = soundName.toLowerCase(Locale.ROOT).replace('_', '.'); Sound sound = Registry.SOUNDS.get(NamespacedKey.minecraft(soundKey)); if (sound == null) { return; } player.playSound(player.getLocation(), sound, 0.7F, 1.0F); } catch (IllegalArgumentException ignored) { } } }