1
mirror of https://github.com/Shiewk/SModeration.git synced 2026-04-28 05:54:16 +02:00

Add search option in SMod menu with chat input

This commit is contained in:
Shy
2024-07-29 13:22:00 +02:00
parent 56aef4a2c9
commit ab037716c4
5 changed files with 189 additions and 19 deletions
@@ -4,6 +4,7 @@ import de.shiewk.smoderation.command.*;
import de.shiewk.smoderation.event.CustomInventoryEvents; import de.shiewk.smoderation.event.CustomInventoryEvents;
import de.shiewk.smoderation.event.EnderchestSeeEvents; import de.shiewk.smoderation.event.EnderchestSeeEvents;
import de.shiewk.smoderation.event.InvSeeEvents; import de.shiewk.smoderation.event.InvSeeEvents;
import de.shiewk.smoderation.input.ChatInputListener;
import de.shiewk.smoderation.listener.PunishmentListener; import de.shiewk.smoderation.listener.PunishmentListener;
import de.shiewk.smoderation.listener.VanishListener; import de.shiewk.smoderation.listener.VanishListener;
import de.shiewk.smoderation.storage.PunishmentContainer; import de.shiewk.smoderation.storage.PunishmentContainer;
@@ -51,6 +52,7 @@ public final class SModeration extends JavaPlugin {
getPluginManager().registerEvents(new InvSeeEvents(), this); getPluginManager().registerEvents(new InvSeeEvents(), this);
getPluginManager().registerEvents(new EnderchestSeeEvents(), this); getPluginManager().registerEvents(new EnderchestSeeEvents(), this);
getPluginManager().registerEvents(new VanishListener(), this); getPluginManager().registerEvents(new VanishListener(), this);
getPluginManager().registerEvents(new ChatInputListener(), this);
registerCommand("mute", new MuteCommand()); registerCommand("mute", new MuteCommand());
registerCommand("ban", new BanCommand()); registerCommand("ban", new BanCommand());
@@ -0,0 +1,70 @@
package de.shiewk.smoderation.input;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.title.Title;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import static de.shiewk.smoderation.SModeration.CHAT_PREFIX;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
public class ChatInput {
private final Player player;
private final Component prompt;
private final Consumer<Component> action;
private int remainingTicks;
private ChatInput(@NotNull Player player, @NotNull Component prompt, @NotNull Consumer<Component> action, int remainingSeconds){
Objects.requireNonNull(action);
Objects.requireNonNull(prompt);
Objects.requireNonNull(player);
this.player = player;
this.prompt = prompt;
this.action = action;
this.remainingTicks = remainingSeconds * 20;
}
static void tickAll() {
runningInputs.values().forEach(ChatInput::tick);
}
void tick(){
remainingTicks--;
if (remainingTicks <= 0){
runningInputs.remove(player);
return;
}
if (remainingTicks % 20 == 0){
player.showTitle(Title.title(
text().content(getRemainingTicks() / 20 + " seconds").color(GRAY).build(),
getPrompt(),
Title.Times.times(Duration.ZERO, Duration.ofSeconds(2), Duration.ZERO)
));
}
}
final static ConcurrentHashMap<Player, ChatInput> runningInputs = new ConcurrentHashMap<>();
public static void prompt(Player player, Consumer<Component> consumer, Component prompt, int timeSeconds){
runningInputs.put(player, new ChatInput(player, prompt, consumer, timeSeconds));
player.sendMessage(CHAT_PREFIX.append(prompt));
}
public Component getPrompt() {
return prompt;
}
public Consumer<Component> getAction() {
return action;
}
public int getRemainingTicks() {
return remainingTicks;
}
}
@@ -0,0 +1,32 @@
package de.shiewk.smoderation.input;
import com.destroystokyo.paper.event.server.ServerTickStartEvent;
import io.papermc.paper.event.player.AsyncChatEvent;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
import static de.shiewk.smoderation.input.ChatInput.runningInputs;
public class ChatInputListener implements Listener {
@EventHandler
public void onAsyncChat(AsyncChatEvent event){
final ChatInput input = runningInputs.remove(event.getPlayer());
if (input != null){
event.setCancelled(true);
input.getAction().accept(event.message());
}
}
@EventHandler public void onPlayerQuit(PlayerQuitEvent event){
runningInputs.remove(event.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR) public void onServerTickStart(ServerTickStartEvent event){
ChatInput.tickAll();
}
}
@@ -1,11 +1,13 @@
package de.shiewk.smoderation.inventory; package de.shiewk.smoderation.inventory;
import de.shiewk.smoderation.input.ChatInput;
import de.shiewk.smoderation.punishments.Punishment; import de.shiewk.smoderation.punishments.Punishment;
import de.shiewk.smoderation.punishments.PunishmentType; import de.shiewk.smoderation.punishments.PunishmentType;
import de.shiewk.smoderation.storage.PunishmentContainer; import de.shiewk.smoderation.storage.PunishmentContainer;
import de.shiewk.smoderation.util.PlayerUtil; import de.shiewk.smoderation.util.PlayerUtil;
import de.shiewk.smoderation.util.TimeUtil; import de.shiewk.smoderation.util.TimeUtil;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.text.format.TextDecoration;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@@ -15,6 +17,7 @@ import org.bukkit.Sound;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.SkullMeta; import org.bukkit.inventory.meta.SkullMeta;
@@ -28,6 +31,7 @@ import java.util.List;
import java.util.function.Predicate; import java.util.function.Predicate;
import static de.shiewk.smoderation.SModeration.*; import static de.shiewk.smoderation.SModeration.*;
import static net.kyori.adventure.text.Component.text;
public class SModMenu extends PageableCustomInventory { public class SModMenu extends PageableCustomInventory {
@@ -70,13 +74,15 @@ public class SModMenu extends PageableCustomInventory {
private List<Punishment> punishments; private List<Punishment> punishments;
private ItemStack sortStack = null; private ItemStack sortStack = null;
private ItemStack filterStack = null; private ItemStack filterStack = null;
private ItemStack searchStack = null;
private int sort = 0; private int sort = 0;
private int filter = 0; private int filter = 0;
private String searchQuery = null;
public SModMenu(Player player, PunishmentContainer container) { public SModMenu(Player player, PunishmentContainer container) {
this.player = player; this.player = player;
this.container = container; this.container = container;
this.inventory = Bukkit.createInventory(this, 54, Component.text("SMod Menu")); this.inventory = Bukkit.createInventory(this, 54, text("SMod Menu"));
reload(); reload();
} }
@@ -89,7 +95,18 @@ public class SModMenu extends PageableCustomInventory {
} }
private void reload(){ private void reload(){
this.punishments = container.copy().stream().filter(getFilter().filter).sorted(getSort().comparator).toList(); this.punishments = container.copy().stream().filter(getFilter().filter).filter(p -> p.matchesSearchQuery(searchQuery)).sorted(getSort().comparator).toList();
}
public void promptSearchQuery(){
Bukkit.getScheduler().scheduleSyncDelayedTask(PLUGIN, player::closeInventory);
ChatInput.prompt(player, component -> {
if (component instanceof TextComponent text){
this.searchQuery = text.content();
// chat event is async
Bukkit.getScheduler().scheduleSyncDelayedTask(PLUGIN, this::open);
}
}, text("Enter your search query in chat").color(SECONDARY_COLOR), 30);
} }
@Override @Override
@@ -144,16 +161,16 @@ public class SModMenu extends PageableCustomInventory {
final Filter filter = getFilter(); final Filter filter = getFilter();
final ItemStack stack = new ItemStack(Filter.ICON); final ItemStack stack = new ItemStack(Filter.ICON);
stack.editMeta(meta -> { stack.editMeta(meta -> {
meta.displayName(applyFormatting(Component.text("Filter: " + filter.name).color(SECONDARY_COLOR))); meta.displayName(applyFormatting(text("Filter: " + filter.name).color(PRIMARY_COLOR)));
ArrayList<Component> lore = new ArrayList<>(); ArrayList<Component> lore = new ArrayList<>();
lore.add(Component.empty()); lore.add(Component.empty());
for (Filter value : Filter.values()) { for (Filter value : Filter.values()) {
final boolean selected = filter == value; final boolean selected = filter == value;
Component filterText = applyFormatting(Component.text((selected ? "\u00BB " : "") + value.name).color(selected ? PRIMARY_COLOR : INACTIVE_COLOR)); Component filterText = applyFormatting(text((selected ? "\u00BB " : "") + value.name).color(selected ? SECONDARY_COLOR : INACTIVE_COLOR));
lore.add(filterText); lore.add(filterText);
} }
lore.add(Component.empty()); lore.add(Component.empty());
lore.add(applyFormatting(Component.text("\u00BB Click to switch filter").color(NamedTextColor.GOLD))); lore.add(applyFormatting(text("\u00BB Click to switch filter").color(NamedTextColor.GOLD)));
meta.lore(lore); meta.lore(lore);
}); });
filterStack = stack; filterStack = stack;
@@ -164,35 +181,54 @@ public class SModMenu extends PageableCustomInventory {
final Sort sort = getSort(); final Sort sort = getSort();
final ItemStack stack = new ItemStack(Sort.ICON); final ItemStack stack = new ItemStack(Sort.ICON);
stack.editMeta(meta -> { stack.editMeta(meta -> {
meta.displayName(applyFormatting(Component.text("Sort by: " + sort.name).color(PRIMARY_COLOR))); meta.displayName(applyFormatting(text("Sort by: " + sort.name).color(PRIMARY_COLOR)));
ArrayList<Component> lore = new ArrayList<>(); ArrayList<Component> lore = new ArrayList<>();
lore.add(Component.empty()); lore.add(Component.empty());
for (Sort value : Sort.values()) { for (Sort value : Sort.values()) {
final boolean selected = sort == value; final boolean selected = sort == value;
Component sortText = applyFormatting(Component.text((selected ? "\u00BB " : "") + value.name).color(selected ? SECONDARY_COLOR : INACTIVE_COLOR)); Component sortText = applyFormatting(text((selected ? "\u00BB " : "") + value.name).color(selected ? SECONDARY_COLOR : INACTIVE_COLOR));
lore.add(sortText); lore.add(sortText);
} }
lore.add(Component.empty()); lore.add(Component.empty());
lore.add(applyFormatting(Component.text("\u00BB Click to switch sorting option").color(NamedTextColor.GOLD))); lore.add(applyFormatting(text("\u00BB Click to switch sorting option").color(NamedTextColor.GOLD)));
meta.lore(lore); meta.lore(lore);
}); });
sortStack = stack; sortStack = stack;
return stack; return stack;
} }
private ItemStack createSearchItem(){
final ItemStack stack = new ItemStack(Material.FLOWER_BANNER_PATTERN);
stack.editMeta(meta -> {
meta.addItemFlags(ItemFlag.HIDE_ITEM_SPECIFICS);
meta.displayName(applyFormatting(text("Search").color(PRIMARY_COLOR)));
final ArrayList<Component> lore = new ArrayList<>(List.of(
Component.empty(),
applyFormatting(text("Current search query: %s".formatted(searchQuery == null ? "None" : "\"" + searchQuery + "\"")).color(SECONDARY_COLOR)),
Component.empty(),
applyFormatting(text("\u00BB Click to enter new search query").color(NamedTextColor.GOLD))
));
if (searchQuery != null){
lore.add(applyFormatting(text("\u00BB Right click to remove search query").color(NamedTextColor.GOLD)));
}
meta.lore(lore);
});
return searchStack = stack;
}
private ItemStack createPunishmentItem(Punishment punishment){ private ItemStack createPunishmentItem(Punishment punishment){
ItemStack stack = new ItemStack(Material.PLAYER_HEAD); ItemStack stack = new ItemStack(Material.PLAYER_HEAD);
stack.editMeta(meta -> { stack.editMeta(meta -> {
if (meta instanceof SkullMeta skullMeta){ if (meta instanceof SkullMeta skullMeta){
skullMeta.setOwningPlayer(Bukkit.getOfflinePlayer(punishment.to)); skullMeta.setOwningPlayer(Bukkit.getOfflinePlayer(punishment.to));
} }
meta.displayName(applyFormatting(Component.text(punishment.type.name).color(NamedTextColor.RED).decorate(TextDecoration.BOLD))); meta.displayName(applyFormatting(text(punishment.type.name).color(NamedTextColor.RED).decorate(TextDecoration.BOLD)));
ArrayList<Component> lore = new ArrayList<>(); ArrayList<Component> lore = new ArrayList<>();
lore.add(applyFormatting(Component.text("Player: ").color(SECONDARY_COLOR).append(Component.text(PlayerUtil.offlinePlayerName(punishment.to)).color(PRIMARY_COLOR)))); lore.add(applyFormatting(text("Player: ").color(SECONDARY_COLOR).append(text(PlayerUtil.offlinePlayerName(punishment.to)).color(PRIMARY_COLOR))));
lore.add(applyFormatting(Component.text("Punished by: ").color(SECONDARY_COLOR).append(Component.text(PlayerUtil.offlinePlayerName(punishment.by)).color(PRIMARY_COLOR)))); lore.add(applyFormatting(text("Punished by: ").color(SECONDARY_COLOR).append(text(PlayerUtil.offlinePlayerName(punishment.by)).color(PRIMARY_COLOR))));
lore.add(applyFormatting(Component.text("Timestamp: ").color(SECONDARY_COLOR).append(Component.text(TimeUtil.calendarTimestamp(punishment.time)).color(PRIMARY_COLOR)))); lore.add(applyFormatting(text("Timestamp: ").color(SECONDARY_COLOR).append(text(TimeUtil.calendarTimestamp(punishment.time)).color(PRIMARY_COLOR))));
if (punishment.type != PunishmentType.KICK){ if (punishment.type != PunishmentType.KICK){
lore.add(applyFormatting(Component.text("Duration: ").color(SECONDARY_COLOR).append(Component.text(TimeUtil.formatTimeLong(punishment.until - punishment.time)).color(PRIMARY_COLOR)))); lore.add(applyFormatting(text("Duration: ").color(SECONDARY_COLOR).append(text(TimeUtil.formatTimeLong(punishment.until - punishment.time)).color(PRIMARY_COLOR))));
long remainingTime = punishment.until - System.currentTimeMillis(); long remainingTime = punishment.until - System.currentTimeMillis();
final String expires; final String expires;
if (remainingTime > 0){ if (remainingTime > 0){
@@ -201,15 +237,15 @@ public class SModMenu extends PageableCustomInventory {
remainingTime *= -1; remainingTime *= -1;
expires = TimeUtil.formatTimeLong(remainingTime) + " ago"; expires = TimeUtil.formatTimeLong(remainingTime) + " ago";
} }
lore.add(applyFormatting(Component.text("Expires: ").color(SECONDARY_COLOR).append(Component.text(expires).color(PRIMARY_COLOR)))); lore.add(applyFormatting(text("Expires: ").color(SECONDARY_COLOR).append(text(expires).color(PRIMARY_COLOR))));
} }
lore.add(applyFormatting(Component.text("Reason: ").color(SECONDARY_COLOR).append(Component.text(punishment.reason).color(PRIMARY_COLOR)))); lore.add(applyFormatting(text("Reason: ").color(SECONDARY_COLOR).append(text(punishment.reason).color(PRIMARY_COLOR))));
if (punishment.wasUndone()){ if (punishment.wasUndone()){
lore.add(applyFormatting(Component.text("Undone by: ").color(NamedTextColor.RED).append(Component.text(PlayerUtil.offlinePlayerName(punishment.undoneBy())).color(NamedTextColor.GOLD)))); lore.add(applyFormatting(text("Undone by: ").color(NamedTextColor.RED).append(text(PlayerUtil.offlinePlayerName(punishment.undoneBy())).color(NamedTextColor.GOLD))));
} else if (punishment.isActive()) { } else if (punishment.isActive()) {
if ((punishment.type == PunishmentType.BAN && player.hasPermission("smod.unban")) || (punishment.type == PunishmentType.MUTE && player.hasPermission("smod.unmute"))){ if ((punishment.type == PunishmentType.BAN && player.hasPermission("smod.unban")) || (punishment.type == PunishmentType.MUTE && player.hasPermission("smod.unmute"))){
lore.add(Component.empty()); lore.add(Component.empty());
lore.add(applyFormatting(Component.text("\u00BB Click to undo punishment").color(NamedTextColor.GOLD))); lore.add(applyFormatting(text("\u00BB Click to undo punishment").color(NamedTextColor.GOLD)));
} }
} }
meta.lore(lore); meta.lore(lore);
@@ -224,8 +260,9 @@ public class SModMenu extends PageableCustomInventory {
} }
inventory.setItem(45, createPreviousPageStack()); inventory.setItem(45, createPreviousPageStack());
inventory.setItem(53, createNextPageStack()); inventory.setItem(53, createNextPageStack());
inventory.setItem(50, createFilterItem()); inventory.setItem(51, createFilterItem());
inventory.setItem(48, createSortItem()); inventory.setItem(49, createSortItem());
inventory.setItem(47, createSearchItem());
for (int i = 0; i < 45; i++) { for (int i = 0; i < 45; i++) {
int ci = i + (getPage() * 45); int ci = i + (getPage() * 45);
@@ -252,6 +289,16 @@ public class SModMenu extends PageableCustomInventory {
cycleFilter(event.isRightClick()); cycleFilter(event.isRightClick());
} else if (stack.equals(sortStack)){ } else if (stack.equals(sortStack)){
cycleSort(event.isRightClick()); cycleSort(event.isRightClick());
} else if (stack.equals(searchStack)){
if (event.isRightClick() && searchQuery != null){
player.playSound(player, Sound.UI_BUTTON_CLICK, 1f, 0.8f);
searchQuery = null;
reload();
refresh();
} else {
player.playSound(player, Sound.UI_BUTTON_CLICK, 1f, 2f);
promptSearchQuery();
}
} }
final ItemMeta itemMeta = stack.getItemMeta(); final ItemMeta itemMeta = stack.getItemMeta();
if (itemMeta != null) { if (itemMeta != null) {
@@ -239,4 +239,23 @@ public class Punishment {
default -> throw new IllegalStateException("Unknown punishment type " + type); default -> throw new IllegalStateException("Unknown punishment type " + type);
} }
} }
public boolean matchesSearchQuery(String searchQuery) {
if (searchQuery == null) return true;
searchQuery = searchQuery.toLowerCase();
return reason.toLowerCase().contains(searchQuery)
|| by.toString().equalsIgnoreCase(searchQuery)
|| to.toString().equalsIgnoreCase(searchQuery)
|| getPlayerName().toLowerCase().contains(searchQuery)
|| getModeratorName().toLowerCase().contains(searchQuery);
}
private String getPlayerName() {
return PlayerUtil.offlinePlayerName(to);
}
private String getModeratorName() {
return PlayerUtil.offlinePlayerName(by);
}
} }