diff --git a/pom.xml b/pom.xml index 3f811ff..0de4591 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ uk.co.hopperelec.mc itemrace - 1.3.0 + 1.4.0 jar ItemRace @@ -53,7 +53,20 @@ co.aikar.locales uk.co.hopperelec.mc.itemrace.locales + + net.wesjd.anvilgui + uk.co.hopperelec.mc.anvilgui + + + + *:* + false + + net/wesjd/anvilgui/version/Wrapper1_21_R1 + + + @@ -82,6 +95,10 @@ aikar https://repo.aikar.co/content/groups/aikar/ + + codemc-snapshots + https://repo.codemc.io/repository/maven-snapshots/ + @@ -101,5 +118,10 @@ acf-paper 0.5.1-SNAPSHOT + + net.wesjd + anvilgui + 1.10.1-SNAPSHOT + diff --git a/src/main/java/uk/co/hopperelec/mc/itemrace/ItemRaceUtils.java b/src/main/java/uk/co/hopperelec/mc/itemrace/ItemRaceUtils.java index b124159..ce7a544 100644 --- a/src/main/java/uk/co/hopperelec/mc/itemrace/ItemRaceUtils.java +++ b/src/main/java/uk/co/hopperelec/mc/itemrace/ItemRaceUtils.java @@ -1,8 +1,10 @@ package uk.co.hopperelec.mc.itemrace; +import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TranslatableComponent; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import net.kyori.adventure.translation.GlobalTranslator; +import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.Damageable; import org.jetbrains.annotations.NotNull; @@ -19,4 +21,13 @@ public final class ItemRaceUtils { public static boolean isDamaged(@NotNull ItemStack item) { return item.getItemMeta() instanceof Damageable itemMeta && itemMeta.hasDamage(); } + + // Returns true if it couldn't translate the item name + public static boolean itemNameContains(@NotNull Material itemType, @NotNull String query, @NotNull Locale locale) { + final String translationKey = itemType.getItemTranslationKey(); + if (translationKey == null) throw new IllegalArgumentException("Item type " + itemType + " has no translation key"); + return serializeTranslatable( + Component.translatable(translationKey), locale + ).toLowerCase(locale).contains(query.toLowerCase(locale)); + } } diff --git a/src/main/java/uk/co/hopperelec/mc/itemrace/gui/AutoDepositItemsGUI.java b/src/main/java/uk/co/hopperelec/mc/itemrace/gui/AutoDepositItemsGUI.java index 98dc2d8..efb47e4 100644 --- a/src/main/java/uk/co/hopperelec/mc/itemrace/gui/AutoDepositItemsGUI.java +++ b/src/main/java/uk/co/hopperelec/mc/itemrace/gui/AutoDepositItemsGUI.java @@ -13,6 +13,8 @@ import java.util.Arrays; import java.util.Set; +import static uk.co.hopperelec.mc.itemrace.ItemRaceUtils.itemNameContains; + public class AutoDepositItemsGUI extends PaginatedGUI { private final @NotNull ManualDepositHandler manualDepositHandler; @@ -20,7 +22,11 @@ public AutoDepositItemsGUI( @NotNull ItemRacePlugin plugin, @NotNull Player viewer ) { - super(plugin, viewer, Component.translatable("gui.auto_deposit_items.title")); + super( + plugin, viewer, + Component.translatable("gui.auto_deposit_items.title"), + Component.translatable("gui.items.search.title") + ); if (!(plugin.pointsHandler instanceof ManualDepositHandler)) throw new IllegalStateException("Auto-deposit items GUI is only available when using a manual points award mode"); this.manualDepositHandler = (ManualDepositHandler) plugin.pointsHandler; @@ -55,4 +61,9 @@ public void onClick(@NotNull InventoryClickEvent event) { } } } + + @Override + public boolean filter(@NotNull ItemStack itemStack, @NotNull String query) { + return itemNameContains(itemStack.getType(), query, viewer.locale()); + } } diff --git a/src/main/java/uk/co/hopperelec/mc/itemrace/gui/DepositedItemsGUI.java b/src/main/java/uk/co/hopperelec/mc/itemrace/gui/DepositedItemsGUI.java index 72e5104..7c32a4e 100644 --- a/src/main/java/uk/co/hopperelec/mc/itemrace/gui/DepositedItemsGUI.java +++ b/src/main/java/uk/co/hopperelec/mc/itemrace/gui/DepositedItemsGUI.java @@ -13,6 +13,8 @@ import java.util.*; +import static uk.co.hopperelec.mc.itemrace.ItemRaceUtils.itemNameContains; + public class DepositedItemsGUI extends PaginatedGUI { public final @NotNull OfflinePlayer player; public final static int PLAYERS_BUTTON_SLOT = 45; @@ -22,9 +24,12 @@ public DepositedItemsGUI( @NotNull Player viewer, @NotNull OfflinePlayer player ) { - super(plugin, viewer, Component.translatable("gui.deposited_items.title", - Component.text(Objects.requireNonNull(player.getName())) - )); + super( + plugin, viewer, + Component.translatable("gui.deposited_items.title", + Component.text(Objects.requireNonNull(player.getName())) + ), Component.translatable("gui.items.search.title") + ); this.player = player; final boolean canViewOtherInventories = viewer.hasPermission("itemrace.inventory"); @@ -49,6 +54,11 @@ public void onRefreshDepositedItems(@NotNull OfflinePlayer player) { if (this.player == player) setItems(); } + @Override + public boolean filter(@NotNull ItemStack itemStack, @NotNull String query) { + return itemNameContains(itemStack.getType(), query, viewer.locale()); + } + private void setItems() { final List items = new ArrayList<>(); plugin.pointsHandler.getItems(player).entrySet().stream() diff --git a/src/main/java/uk/co/hopperelec/mc/itemrace/gui/ItemRaceGUI.java b/src/main/java/uk/co/hopperelec/mc/itemrace/gui/ItemRaceGUI.java index 2503612..1e0e017 100644 --- a/src/main/java/uk/co/hopperelec/mc/itemrace/gui/ItemRaceGUI.java +++ b/src/main/java/uk/co/hopperelec/mc/itemrace/gui/ItemRaceGUI.java @@ -18,7 +18,11 @@ public abstract class ItemRaceGUI implements InventoryHolder { public ItemRaceGUI(@NotNull ItemRacePlugin plugin, @NotNull Player viewer, int slots, @NotNull Component title) { this.plugin = plugin; this.viewer = viewer; - inventory = plugin.getServer().createInventory(this, slots, title); + this.inventory = plugin.getServer().createInventory(this, slots, title); + show(); + } + + public void show() { viewer.openInventory(inventory); plugin.guiListener.registerGUI(this); } diff --git a/src/main/java/uk/co/hopperelec/mc/itemrace/gui/PaginatedGUI.java b/src/main/java/uk/co/hopperelec/mc/itemrace/gui/PaginatedGUI.java index ae76028..64b3b96 100644 --- a/src/main/java/uk/co/hopperelec/mc/itemrace/gui/PaginatedGUI.java +++ b/src/main/java/uk/co/hopperelec/mc/itemrace/gui/PaginatedGUI.java @@ -1,13 +1,16 @@ package uk.co.hopperelec.mc.itemrace.gui; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.translation.GlobalTranslator; +import net.wesjd.anvilgui.AnvilGUI; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import uk.co.hopperelec.mc.itemrace.ItemRacePlugin; import java.util.ArrayList; @@ -19,50 +22,87 @@ public abstract class PaginatedGUI extends ItemRaceGUI { public final static int NUM_ITEMS_PER_PAGE = 45; public final static int PREVIOUS_BUTTON_SLOT = 48; public final static int NEXT_BUTTON_SLOT = 50; + public final static int SEARCH_BUTTON_SLOT = 53; + private @Nullable String searchQuery; + public final @NotNull Component searchTitle; private int page = 1; private int numPages = 1; - private List items; + private List unfilteredItems; + private List filteredItems; - public PaginatedGUI(@NotNull ItemRacePlugin plugin, @NotNull Player viewer, @NotNull Component title) { + public PaginatedGUI(@NotNull ItemRacePlugin plugin, @NotNull Player viewer, @NotNull Component title, @NotNull Component searchTitle) { super(plugin, viewer, SLOTS, title); + this.searchTitle = searchTitle; } - public List getItems() { - return new ArrayList<>(items); + public int getCurrentPage() { + return page; } - public int getNumItems() { - return items.size(); + public int getNumPages() { + return numPages; } - public void setItems(@NotNull Collection items) { - this.items = new ArrayList<>(items); - numPages = Math.max(1, Math.ceilDiv(items.size(), NUM_ITEMS_PER_PAGE)); + public @NotNull List getItems() { + return new ArrayList<>(unfilteredItems); + } + + public @NotNull List getItemsShown() { + return new ArrayList<>(filteredItems); + } + + public int getTotalNumItems() { + return unfilteredItems.size(); + } + + public int getNumItemsShown() { + return filteredItems.size(); + } + + private void refreshItems() { + filteredItems = searchQuery == null ? unfilteredItems : unfilteredItems.stream() + .filter(itemStack -> filter(itemStack, searchQuery)) + .toList(); + numPages = Math.max(1, Math.ceilDiv(filteredItems.size(), NUM_ITEMS_PER_PAGE)); if (page > numPages) page = numPages; draw(); } + public void setItems(@NotNull Collection items) { + this.unfilteredItems = new ArrayList<>(items); + refreshItems(); + } + public void addItem(@NotNull ItemStack item) { - items.add(item); - if (items.size() != 1 && numPages % NUM_ITEMS_PER_PAGE == 1) { - if (page == numPages++) drawNextArrow(); - } else if (page == numPages) getInventory().addItem(item); + unfilteredItems.add(item); + if (searchQuery == null || filter(item, searchQuery)) { + filteredItems.add(item); + if (filteredItems.size() != 1 && numPages % NUM_ITEMS_PER_PAGE == 1) { + if (page == numPages++) drawNextArrow(); + } else if (page == numPages) getInventory().addItem(item); + } } public void replaceItem(int index, @NotNull ItemStack item) { - items.set(index, item); - if (index >= (page - 1) * NUM_ITEMS_PER_PAGE && index < page * NUM_ITEMS_PER_PAGE) - getInventory().setItem(index % NUM_ITEMS_PER_PAGE, item); + final ItemStack oldItem = unfilteredItems.set(index, item); + for (int j = 0; j < filteredItems.size(); j++) { + if (filteredItems.get(j).equals(oldItem)) { + filteredItems.set(j, item); + if (j >= (page - 1) * NUM_ITEMS_PER_PAGE && j < page * NUM_ITEMS_PER_PAGE) + getInventory().setItem(j % NUM_ITEMS_PER_PAGE, item); + return; + } + } } - private void draw() { + public void draw() { // Add paginated items final int startOfPage = (page - 1) * NUM_ITEMS_PER_PAGE; int pagedIndex = 0; int itemsIndex = startOfPage + pagedIndex; - while (pagedIndex < NUM_ITEMS_PER_PAGE && itemsIndex < items.size()) { - getInventory().setItem(pagedIndex++, items.get(itemsIndex++)); + while (pagedIndex < NUM_ITEMS_PER_PAGE && itemsIndex < filteredItems.size()) { + getInventory().setItem(pagedIndex++, filteredItems.get(itemsIndex++)); } while (pagedIndex < NUM_ITEMS_PER_PAGE) { getInventory().clear(pagedIndex++); @@ -73,6 +113,24 @@ private void draw() { else getInventory().setItem(PREVIOUS_BUTTON_SLOT, createArrow("gui.button.previous")); if (page == numPages) getInventory().clear(NEXT_BUTTON_SLOT); else drawNextArrow(); + + final ItemStack searchButton = new ItemStack(Material.ARROW); + final ItemMeta searchButtonMeta = searchButton.getItemMeta(); + searchButtonMeta.itemName( + GlobalTranslator.render( + Component.translatable("gui.button.search"), + viewer.locale() + ) + ); + if (searchQuery != null) + searchButtonMeta.lore(List.of( + GlobalTranslator.render( + Component.translatable("gui.button.search.lore", Component.text(searchQuery)), + viewer.locale() + ) + )); + searchButton.setItemMeta(searchButtonMeta); + getInventory().setItem(SEARCH_BUTTON_SLOT, searchButton); } private void drawNextArrow() { @@ -87,6 +145,18 @@ private void drawNextArrow() { return itemStack; } + public @Nullable String getSearchQuery() { + return searchQuery; + } + + public void search(@Nullable String query) { + page = 1; + searchQuery = query == null || query.isEmpty() ? null : query.toLowerCase(); + refreshItems(); + } + + public abstract boolean filter(@NotNull ItemStack itemStack, @NotNull String query); + @Override public void onClick(@NotNull InventoryClickEvent event) { if (event.getClick().isMouseClick()) { @@ -100,6 +170,24 @@ public void onClick(@NotNull InventoryClickEvent event) { page++; draw(); } + } else if (event.getSlot() == SEARCH_BUTTON_SLOT) { + final ItemStack barrier = new ItemStack(Material.BARRIER); + final ItemMeta barrierMeta = barrier.getItemMeta(); + barrierMeta.displayName(Component.empty()); + barrier.setItemMeta(barrierMeta); + new AnvilGUI.Builder() + .plugin(plugin) + .jsonTitle(GsonComponentSerializer.gson().serialize(searchTitle)) + .itemLeft(barrier) + .itemRight(barrier) + .itemOutput(barrier) + .onClick((slot, stateSnapshot) -> List.of()) + .onClose(stateSnapshot -> { + search(stateSnapshot.getText()); + // Not sure why but, if I don't delay this, then GUIListener isn't able to register itself + plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, this::show); + }) + .open(viewer); } } event.setCancelled(true); diff --git a/src/main/java/uk/co/hopperelec/mc/itemrace/gui/PlayersGUI.java b/src/main/java/uk/co/hopperelec/mc/itemrace/gui/PlayersGUI.java index 0443bc1..6cc0331 100644 --- a/src/main/java/uk/co/hopperelec/mc/itemrace/gui/PlayersGUI.java +++ b/src/main/java/uk/co/hopperelec/mc/itemrace/gui/PlayersGUI.java @@ -16,7 +16,11 @@ public class PlayersGUI extends PaginatedGUI { public PlayersGUI(@NotNull ItemRacePlugin plugin, @NotNull Player viewer) { - super(plugin, viewer, Component.translatable("gui.players.title")); + super( + plugin, viewer, + Component.translatable("gui.players.title"), + Component.translatable("gui.players.search.title") + ); final AtomicInteger index = new AtomicInteger(); setItems( plugin.pointsHandler.getEligiblePlayers().stream() @@ -39,7 +43,7 @@ public void onClick(@NotNull InventoryClickEvent event) { @Override public void onAddEligiblePlayer(@NotNull OfflinePlayer player) { - addItem(createSkull(player, getNumItems())); + addItem(createSkull(player, getTotalNumItems())); } @Override @@ -57,6 +61,15 @@ public void onRemoveEligiblePlayer(@NotNull OfflinePlayer player) { setItems(newItems); } + @Override + public boolean filter(@NotNull ItemStack itemStack, @NotNull String query) { + final OfflinePlayer skullOwner = ((SkullMeta) itemStack.getItemMeta()).getOwningPlayer(); + if (skullOwner == null) return true; + final String skullOwnerName = skullOwner.getName(); + if (skullOwnerName == null) return true; + return skullOwnerName.toLowerCase().contains(query); + } + private @NotNull ItemStack createSkull(@NotNull OfflinePlayer player, int index) { final ItemStack itemStack = new ItemStack(Material.PLAYER_HEAD); final SkullMeta itemMeta = (SkullMeta) itemStack.getItemMeta(); diff --git a/src/main/resources/uk/co/hopperelec/mc/itemrace/en_US.properties b/src/main/resources/uk/co/hopperelec/mc/itemrace/en_US.properties index a2c47a5..c655e9d 100644 --- a/src/main/resources/uk/co/hopperelec/mc/itemrace/en_US.properties +++ b/src/main/resources/uk/co/hopperelec/mc/itemrace/en_US.properties @@ -3,12 +3,17 @@ scoreboard.title=ItemRace scores # GUIs gui.button.next=Next page gui.button.previous=Previous page +gui.button.search=Search +gui.button.search.lore=Searching for '{0}' + +gui.items.search.title=Search for an item gui.deposited_items.title={0}'s deposited items gui.deposited_items.item_name={0} x{1} gui.deposited_items.players_button=View other inventories gui.players.title=ItemRace inventories +gui.players.search.title=Search for a player gui.players.skull=View {0}'s deposited items gui.deposit.title=Move all items to deposit here