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