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

Update main branch to version 1.3.0 (#3)

- Fixed EnderchestSee bug
- Fixed vanish bug
- Moved vanish-related methods to VanishCommand.java
- Added /vanish list command and list vanished players on join
- Added search option in SMod menu with chat input
- Refactored listeners
- Added Invsee for equipment
- Added ability to filter by type in SMod menu
- Updated docs and README to version 1.3.0
This commit is contained in:
Shy
2024-07-29 17:41:04 +02:00
committed by GitHub
20 changed files with 542 additions and 189 deletions
+1
View File
@@ -25,6 +25,7 @@ The most important ones are:
- /smod - /smod
- /unmute & /unban - /unmute & /unban
- /invsee & /enderchestsee - /invsee & /enderchestsee
- /vanish
## Permissions ## Permissions
This plugin uses Bukkit permissions for commands and other actions. This plugin uses Bukkit permissions for commands and other actions.
+15 -51
View File
@@ -1,53 +1,17 @@
# SModeration commands # SModeration commands
### /smod | Command | Description | Permission |
The /smod command just opens the SMod menu. It takes no arguments. |----------------------------------------|------------------------------------------------------------------------------|---------------------|
### /mute | /smod | Opens the SMod menu. | smod.menu |
The /mute command is used to mute players. | /mute \<player> \<duration> \<reason?> | Mutes a player so that they can't type in chat for the specified duration. | smod.mute |
| /ban \<player> \<duration> \<reason?> | Bans a player so that they can't join the server for the specified duration. | smod.ban |
It requires 2 arguments: | /kick \<player> \<reason?> | Kicks a player from the server. | smod.kick |
- Player name | /modlogs <player\|uuid> | Lists a player's active punishments in chat. | smod.logs |
- Duration | /unmute <player> | Clears a player's mute status. | smod.unmute |
| /unban <player> | Clears a player's ban status. | smod.unban |
If you want to, you can also add a **reason**. | /invsee \<player> inventory | Views another player's inventory. | smod.invsee |
| /invsee \<player> equipment | Views another player's equipment (armor and offhand). | smod.invsee |
For example, if you want to mute a player for breaking server rules, just use **/mute playername 1h 30min Breaking server rules.** | /enderchestsee <player> | Views another player's ender chest. | smod.enderchestsee |
| /vanish | Toggles vanish mode so that other players can't see you're online. | smod.vanish |
The player will be muted for 1 hour and 30 minutes with the reason "Breaking server rules.". | /vanish toggle \<player> | Toggles vanish mode for another player. | smod.vanish |
| /vanish list | Lists all vanished players. | smod.vanish.see |
Muted players can still join the server, but can't use the chat.
### /ban
The /ban command works the same as the /mute command, with one important difference:
Banned players **can't even join** the server until the ban expires.
### /kick
The /kick command is a bit different. It does not require a duration because kicks are instant. Instead, you can use the command like this: **/kick playername reason**
### /modlogs
The /modlogs command can be used when the /smod menu is unavailable. It displays information about a player in chat instead of a menu.
Example: **/modlogs playername** shows you a message in chat that tells you whether the player is muted or banned.
### /unmute & /unban
The /unmute and /unban commands only take one argument, the player name.
The specified player will then be unmuted or unbanned.
### /invsee
The /invsee command can be used to view the inventory of another player.
It takes one argument: the player name.
The player has to be online.
### /enderchestsee
The /enderchestsee command can, similarly to /invsee, be used to view the ender chest of another player.
It takes one argument: the player name.
The player has to be online.
### /vanish
The /vanish command is used to toggle vanish mode.
In vanish mode, other players (who don't have the necessary permissions) can't see that you're online.
To toggle vanish mode for yourself, use **/vanish**.
To toggle vanish mode for someone else, use **/vanish <playername>**.
Vanish status is not saved, so you re-appear when you leave the server.
+20 -16
View File
@@ -1,19 +1,23 @@
# SModeration permissions # SModeration permissions
- **smod.mute**: Allows the player to mute other players. | Permission | Description |
- **smod.preventmute**: Prevents the player from being muted (if they are online). |---------------------------|------------------------------------------------------------------|
- **smod.ban**: Allows the player to ban and kick other players. | smod.mute | Allows the player to mute other players. |
- **smod.preventban**: Prevents the player from being banned (if they are online). | smod.preventmute | Prevents the player from being muted (if they are online). |
- **smod.kick**: Allows the player to kick other players. | smod.ban | Allows the player to ban and kick other players. |
- **smod.preventkick**: Prevents the player from being kicked (if they are online). | smod.preventban | Prevents the player from being banned (if they are online). |
- **smod.menu**: Allows the player to use the SModeration menu. | smod.kick | Allows the player to kick other players. |
- **smod.notifications**: Allows the player to be notified when a punishment is issued. | smod.preventkick | Prevents the player from being kicked (if they are online). |
- **smod.unmute**: Allows the player to unmute other players. | smod.menu | Allows the player to use the SModeration menu. |
- **smod.unban**: Allows the player to unban other players. | smod.notifications | Allows the player to be notified when a punishment is issued. |
- **smod.logs**: Allows the player to view mod logs. | smod.unmute | Allows the player to unmute other players. |
- **smod.invsee**: Allows the player to view other players inventories. | smod.unban | Allows the player to unban other players. |
- **smod.invsee.modify**: Allows the player to view and modify other players inventories. | smod.logs | Allows the player to view mod logs. |
- **smod.invsee.preventmodify**: When giving this permission to a player, prevents their inventory from being modified. | smod.invsee | Allows the player to view other players inventories. |
- **smod.enderchestsee**: Allows the player to view other players ender chests. | smod.invsee.modify | Allows the player to view and modify other players inventories. |
- **smod.enderchestsee.modify**: Allows the player to view and modify other players ender chests. | smod.invsee.preventmodify | Prevents the player's inventory from being modified. |
| smod.enderchestsee | Allows the player to view other players ender chests. |
| smod.enderchestsee.modify | Allows the player to view and modify other players ender chests. |
| smod.vanish | Allows the player to enter and leave vanish mode. |
| smod.vanish.see | Allows the player to see vanished players |
All of these permissions are granted by default if the player is a server operator. All of these permissions are granted by default if the player is a server operator.
+1 -1
View File
@@ -1 +1 @@
pluginVersion = 1.2.0 pluginVersion = 1.3.0
@@ -1,19 +1,14 @@
package de.shiewk.smoderation; package de.shiewk.smoderation;
import de.shiewk.smoderation.command.*; import de.shiewk.smoderation.command.*;
import de.shiewk.smoderation.event.CustomInventoryEvents; import de.shiewk.smoderation.input.ChatInputListener;
import de.shiewk.smoderation.event.EnderchestSeeEvents; import de.shiewk.smoderation.listener.*;
import de.shiewk.smoderation.event.InvSeeEvents;
import de.shiewk.smoderation.listener.PunishmentListener;
import de.shiewk.smoderation.listener.VanishListener;
import de.shiewk.smoderation.storage.PunishmentContainer; import de.shiewk.smoderation.storage.PunishmentContainer;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.kyori.adventure.text.TextComponent; 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.TextColor; import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger; import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginCommand; import org.bukkit.command.PluginCommand;
import org.bukkit.command.TabExecutor; import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -21,6 +16,8 @@ import org.bukkit.plugin.java.JavaPlugin;
import java.io.File; import java.io.File;
import static de.shiewk.smoderation.command.VanishCommand.isVanished;
import static de.shiewk.smoderation.command.VanishCommand.toggleVanish;
import static net.kyori.adventure.text.Component.text; import static net.kyori.adventure.text.Component.text;
import static org.bukkit.Bukkit.getPluginManager; import static org.bukkit.Bukkit.getPluginManager;
@@ -47,10 +44,11 @@ public final class SModeration extends JavaPlugin {
@Override @Override
public void onEnable() { public void onEnable() {
getPluginManager().registerEvents(new PunishmentListener(), this); getPluginManager().registerEvents(new PunishmentListener(), this);
getPluginManager().registerEvents(new CustomInventoryEvents(), this); getPluginManager().registerEvents(new CustomInventoryListener(), this);
getPluginManager().registerEvents(new InvSeeEvents(), this); getPluginManager().registerEvents(new InvSeeListener(), this);
getPluginManager().registerEvents(new EnderchestSeeEvents(), this); getPluginManager().registerEvents(new EnderchestSeeListener(), 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());
@@ -86,52 +84,4 @@ public final class SModeration extends JavaPlugin {
} }
} }
} }
private static final ObjectArrayList<Player> vanishedPlayers = new ObjectArrayList<>();
public static void toggleVanish(Player player){
final boolean newStatus = !isVanished(player);
if (newStatus){
vanishedPlayers.add(player);
for (CommandSender sender : container.collectBroadcastTargets()) {
sender.sendMessage(CHAT_PREFIX.append(
player.displayName()
.colorIfAbsent(SECONDARY_COLOR)
).append(
text()
.content(" vanished.")
.color(PRIMARY_COLOR)
));
}
player.sendMessage(CHAT_PREFIX.append(text("You are now vanished.").color(PRIMARY_COLOR)));
player.setVisibleByDefault(false);
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
if (onlinePlayer.hasPermission("smod.vanish.see")){
onlinePlayer.showEntity(PLUGIN, player);
}
}
} else {
vanishedPlayers.remove(player);
for (CommandSender sender : container.collectBroadcastTargets()) {
sender.sendMessage(CHAT_PREFIX.append(
player.displayName()
.colorIfAbsent(SECONDARY_COLOR)
).append(
text()
.content(" re-appeared.")
.color(PRIMARY_COLOR)
));
}
player.sendMessage(CHAT_PREFIX.append(text("You are no longer vanished.").color(PRIMARY_COLOR)));
player.setVisibleByDefault(true);
}
}
public static boolean isVanished(Player player){
return vanishedPlayers.contains(player);
}
public static ObjectArrayList<Player> getVanishedPlayers() {
return vanishedPlayers.clone();
}
} }
@@ -1,5 +1,6 @@
package de.shiewk.smoderation.command; package de.shiewk.smoderation.command;
import de.shiewk.smoderation.inventory.InvSeeEquipmentInventory;
import de.shiewk.smoderation.util.PlayerUtil; import de.shiewk.smoderation.util.PlayerUtil;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@@ -19,16 +20,30 @@ import static de.shiewk.smoderation.SModeration.*;
public class InvseeCommand implements TabExecutor { public class InvseeCommand implements TabExecutor {
private enum InvseeType {
INVENTORY,
EQUIPMENT
}
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length < 1) { if (args.length < 1) {
return false; return false;
} }
final InvseeType type;
if (args.length > 1){
switch (args[1].toLowerCase()){
case "armor", "equipment" -> type = InvseeType.EQUIPMENT;
default -> type = InvseeType.INVENTORY;
}
} else {
type = InvseeType.INVENTORY;
}
if (sender instanceof HumanEntity human){ if (sender instanceof HumanEntity human){
final Player player = PlayerUtil.findOnlinePlayer(args[0]); final Player player = PlayerUtil.findOnlinePlayer(args[0]);
if (player != null) { if (player != null) {
if (human.getUniqueId().equals(player.getUniqueId())){ if (human.getUniqueId().equals(player.getUniqueId()) && type != InvseeType.EQUIPMENT){
human.sendMessage(Component.text("You can't open your own inventory.").color(FAIL_COLOR)); human.sendMessage(Component.text("You can't open your own inventory.").color(FAIL_COLOR));
} else { } else {
human.sendMessage(CHAT_PREFIX.append( human.sendMessage(CHAT_PREFIX.append(
@@ -36,7 +51,10 @@ public class InvseeCommand implements TabExecutor {
.append(Component.text(player.getName()).color(SECONDARY_COLOR)) .append(Component.text(player.getName()).color(SECONDARY_COLOR))
.append(Component.text(".")) .append(Component.text("."))
)); ));
human.openInventory(player.getInventory()); switch (type){
case INVENTORY -> human.openInventory(player.getInventory());
case EQUIPMENT -> new InvSeeEquipmentInventory(human, player).open();
}
} }
} else { } else {
human.sendMessage(Component.text("This player is not online.").color(FAIL_COLOR)); human.sendMessage(Component.text("This player is not online.").color(FAIL_COLOR));
@@ -49,8 +67,10 @@ public class InvseeCommand implements TabExecutor {
@Override @Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length > 1){ if (args.length > 2){
return List.of(); return List.of();
} else if (args.length > 1){
return List.of("armor", "equipment", "inventory");
} }
List<String> available = new ArrayList<>(); List<String> available = new ArrayList<>();
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
@@ -2,6 +2,11 @@ package de.shiewk.smoderation.command;
import de.shiewk.smoderation.SModeration; import de.shiewk.smoderation.SModeration;
import de.shiewk.smoderation.util.PlayerUtil; import de.shiewk.smoderation.util.PlayerUtil;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectListIterator;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor; import org.bukkit.command.TabExecutor;
@@ -11,18 +16,32 @@ import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.List;
import static de.shiewk.smoderation.SModeration.*;
import static net.kyori.adventure.text.Component.text;
public class VanishCommand implements TabExecutor { public class VanishCommand implements TabExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length == 0 || args[0].equalsIgnoreCase("toggle")){
Player player = null; Player player = null;
if (args.length > 0){ if (args.length > 1){
player = PlayerUtil.findOnlinePlayer(args[0]); player = PlayerUtil.findOnlinePlayer(args[1]);
} else if (sender instanceof Player){ } else if (sender instanceof Player){
player = (Player) sender; player = (Player) sender;
} }
if (player != null){ if (player != null){
SModeration.toggleVanish(player); toggleVanish(player);
return true;
} else {
return false;
}
} else if (args[0].equalsIgnoreCase("list")) {
if (sender.hasPermission("smod.vanish.see")){
listVanishedPlayersTo(sender);
} else {
sender.sendMessage(text().color(NamedTextColor.RED).content("You do not have permission to list all vanished players."));
}
return true; return true;
} else { } else {
return false; return false;
@@ -32,8 +51,83 @@ public class VanishCommand implements TabExecutor {
@Override @Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length < 2){ if (args.length < 2){
return PlayerUtil.listPlayerNames(); return List.of("list", "toggle");
}
if (args.length < 3 && args[0].equalsIgnoreCase("toggle")){
return PlayerUtil.listPlayerNames(args[1]);
} }
return List.of(); return List.of();
} }
private static final ObjectArrayList<Player> vanishedPlayers = new ObjectArrayList<>();
public static void toggleVanish(Player player){
final boolean newStatus = !isVanished(player);
if (newStatus){
vanishedPlayers.add(player);
for (CommandSender sender : SModeration.container.collectBroadcastTargets()) {
sender.sendMessage(CHAT_PREFIX.append(
player.displayName()
.colorIfAbsent(SECONDARY_COLOR)
).append(
text()
.content(" vanished.")
.color(PRIMARY_COLOR)
));
}
player.sendMessage(CHAT_PREFIX.append(text("You are now vanished.").color(PRIMARY_COLOR)));
player.setVisibleByDefault(false);
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
if (onlinePlayer.hasPermission("smod.vanish.see")){
onlinePlayer.showEntity(PLUGIN, player);
}
}
} else {
vanishedPlayers.remove(player);
for (CommandSender sender : container.collectBroadcastTargets()) {
sender.sendMessage(CHAT_PREFIX.append(
player.displayName()
.colorIfAbsent(SECONDARY_COLOR)
).append(
text()
.content(" re-appeared.")
.color(PRIMARY_COLOR)
));
}
player.sendMessage(CHAT_PREFIX.append(text("You are no longer vanished.").color(PRIMARY_COLOR)));
player.setVisibleByDefault(true);
}
}
public static boolean isVanished(Player player){
return vanishedPlayers.contains(player);
}
public static ObjectArrayList<Player> getVanishedPlayers() {
return vanishedPlayers.clone();
}
public static void listVanishedPlayersTo(CommandSender receiver){
if (vanishedPlayers.isEmpty()){
receiver.sendMessage(CHAT_PREFIX.append(
text().content("No players are currently vanished.").color(PRIMARY_COLOR)
));
} else {
Component vanishList = CHAT_PREFIX.append(
text().content("The following players are currently vanished: ").color(PRIMARY_COLOR)
);
for (ObjectListIterator<Player> iterator = vanishedPlayers.iterator(); iterator.hasNext(); ) {
Player vanishedPlayer = iterator.next();
vanishList = vanishList.append(
vanishedPlayer.displayName().colorIfAbsent(SECONDARY_COLOR)
);
if (iterator.hasNext()){
vanishList = vanishList.append(
text().content(", ").color(PRIMARY_COLOR)
);
}
}
receiver.sendMessage(vanishList);
}
}
} }
@@ -1,17 +0,0 @@
package de.shiewk.smoderation.event;
import de.shiewk.smoderation.inventory.CustomInventory;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
public class CustomInventoryEvents implements Listener {
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onInventoryClick(InventoryClickEvent event){
if (event.getInventory().getHolder() instanceof CustomInventory customInventory){
customInventory.click(event.getCurrentItem(), event);
event.setCancelled(true);
}
}
}
@@ -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();
}
}
@@ -0,0 +1,3 @@
package de.shiewk.smoderation.inventory;
public interface AutoUpdatingCustomInventory extends CustomInventory { }
@@ -0,0 +1,66 @@
package de.shiewk.smoderation.inventory;
import de.shiewk.smoderation.SModeration;
import org.bukkit.Bukkit;
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.EntityEquipment;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import static net.kyori.adventure.text.Component.text;
public class InvSeeEquipmentInventory implements AutoUpdatingCustomInventory {
private final HumanEntity viewer;
private final HumanEntity subject;
private final Inventory inventory = Bukkit.createInventory(this, InventoryType.HOPPER, text("Player equipment"));
private boolean changing = false;
public InvSeeEquipmentInventory(HumanEntity viewer, HumanEntity subject) {
this.viewer = viewer;
this.subject = subject;
}
@Override
public void refresh() {
if (!changing){
final EntityEquipment equipment = subject.getEquipment();
inventory.setItem(0, equipment.getHelmet());
inventory.setItem(1, equipment.getChestplate());
inventory.setItem(2, equipment.getLeggings());
inventory.setItem(3, equipment.getBoots());
inventory.setItem(4, equipment.getItemInOffHand());
}
}
@Override
public void open() {
refresh();
viewer.openInventory(getInventory());
}
@Override
public void click(ItemStack stack, InventoryClickEvent event) {
if (viewer.hasPermission("smod.invsee.modify") && !subject.hasPermission("smod.invsee.preventmodify")){
event.setCancelled(false);
changing = true;
Bukkit.getScheduler().scheduleSyncDelayedTask(SModeration.PLUGIN, () -> {
changing = false;
final EntityEquipment equipment = subject.getEquipment();
equipment.setHelmet(inventory.getItem(0));
equipment.setChestplate(inventory.getItem(1));
equipment.setLeggings(inventory.getItem(2));
equipment.setBoots(inventory.getItem(3));
equipment.setItemInOffHand(inventory.getItem(4));
});
}
}
@Override
public @NotNull Inventory getInventory() {
return inventory;
}
}
@@ -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;
@@ -25,9 +28,11 @@ import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.function.Consumer;
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 +75,17 @@ 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 ItemStack typeStack = null;
private int sort = 0; private int sort = 0;
private int filter = 0; private int filter = 0;
private int type = -1;
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();
} }
@@ -88,8 +97,23 @@ public class SModMenu extends PageableCustomInventory {
return Filter.values()[filter]; return Filter.values()[filter];
} }
public PunishmentType getType(){
return type == -1 ? null : PunishmentType.values()[type];
}
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 -> getType() == null || p.type == getType()).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
@@ -135,6 +159,25 @@ public class SModMenu extends PageableCustomInventory {
refresh(); refresh();
} }
public void cycleType(boolean backwards){
player.playSound(player, Sound.UI_BUTTON_CLICK, 1f, backwards ? 0.8f : 2f);
if (backwards){
if (type <= -1){
type = PunishmentType.values().length-1;
} else {
type--;
}
} else {
if (type >= PunishmentType.values().length-1){
type = -1;
} else {
type++;
}
}
reload();
refresh();
}
@Override @Override
public void switchPage() { public void switchPage() {
player.playSound(player, Sound.BLOCK_STONE_HIT, 0.75f, 1f); player.playSound(player, Sound.BLOCK_STONE_HIT, 0.75f, 1f);
@@ -144,55 +187,97 @@ 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;
return stack; return stack;
} }
private ItemStack createTypeItem(){
final PunishmentType type = getType();
final ItemStack stack = new ItemStack(Material.CHEST);
stack.editMeta(meta -> {
meta.displayName(applyFormatting(text("Type: " + (type == null ? "All" : type.name)).color(PRIMARY_COLOR)));
ArrayList<Component> lore = new ArrayList<>();
lore.add(Component.empty());
final Consumer<PunishmentType> addToLore = value -> {
final boolean selected = type == value;
Component typeText = applyFormatting(text((selected ? "\u00BB " : "") + (value == null ? "All" : value.name)).color(selected ? SECONDARY_COLOR : INACTIVE_COLOR));
lore.add(typeText);
};
addToLore.accept(null);
for (PunishmentType value : PunishmentType.values()) {
addToLore.accept(value);
}
lore.add(Component.empty());
lore.add(applyFormatting(text("\u00BB Click to switch type").color(NamedTextColor.GOLD)));
meta.lore(lore);
});
return typeStack = stack;
}
private ItemStack createSortItem(){ private ItemStack createSortItem(){
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 +286,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 +309,10 @@ public class SModMenu extends PageableCustomInventory {
} }
inventory.setItem(45, createPreviousPageStack()); inventory.setItem(45, createPreviousPageStack());
inventory.setItem(53, createNextPageStack()); inventory.setItem(53, createNextPageStack());
inventory.setItem(47, createSearchItem());
inventory.setItem(48, createTypeItem());
inventory.setItem(50, createFilterItem()); inventory.setItem(50, createFilterItem());
inventory.setItem(48, createSortItem()); inventory.setItem(51, createSortItem());
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 +339,18 @@ 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();
}
} else if (stack.equals(typeStack)) {
cycleType(event.isRightClick());
} }
final ItemMeta itemMeta = stack.getItemMeta(); final ItemMeta itemMeta = stack.getItemMeta();
if (itemMeta != null) { if (itemMeta != null) {
@@ -0,0 +1,37 @@
package de.shiewk.smoderation.listener;
import com.destroystokyo.paper.event.server.ServerTickEndEvent;
import de.shiewk.smoderation.inventory.AutoUpdatingCustomInventory;
import de.shiewk.smoderation.inventory.CustomInventory;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryDragEvent;
public class CustomInventoryListener implements Listener {
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onInventoryClick(InventoryClickEvent event){
if (event.getInventory().getHolder() instanceof CustomInventory customInventory){
event.setCancelled(true);
customInventory.click(event.getCurrentItem(), event);
}
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onInventoryDrag(InventoryDragEvent event){
if (event.getInventory().getHolder() instanceof CustomInventory){
event.setCancelled(true);
}
}
@EventHandler(priority = EventPriority.LOWEST)
public void onServerTickEnd(ServerTickEndEvent event){
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
if (onlinePlayer.getOpenInventory().getTopInventory().getHolder() instanceof AutoUpdatingCustomInventory ci) {
ci.refresh();
}
}
}
}
@@ -1,4 +1,4 @@
package de.shiewk.smoderation.event; package de.shiewk.smoderation.listener;
import org.bukkit.entity.HumanEntity; import org.bukkit.entity.HumanEntity;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
@@ -8,14 +8,14 @@ import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.PlayerInventory; import org.bukkit.inventory.PlayerInventory;
public class EnderchestSeeEvents implements Listener { public class EnderchestSeeListener implements Listener {
@EventHandler @EventHandler
public void onInventoryClick(InventoryClickEvent event){ public void onInventoryClick(InventoryClickEvent event){
final Inventory clicked = event.getView().getTopInventory(); final Inventory clicked = event.getView().getTopInventory();
if (!(clicked instanceof PlayerInventory)){ if (!(clicked instanceof PlayerInventory)){
final InventoryHolder holder = clicked.getHolder(); final InventoryHolder holder = clicked.getHolder();
if (holder instanceof HumanEntity humanHolder){ if (holder instanceof HumanEntity humanHolder && humanHolder.getEnderChest().equals(clicked) && !humanHolder.equals(event.getWhoClicked())){
if (!event.getWhoClicked().hasPermission("smod.enderchestsee.modify")){ if (!event.getWhoClicked().hasPermission("smod.enderchestsee.modify")){
event.setCancelled(true); event.setCancelled(true);
} }
@@ -1,4 +1,4 @@
package de.shiewk.smoderation.event; package de.shiewk.smoderation.listener;
import org.bukkit.entity.HumanEntity; import org.bukkit.entity.HumanEntity;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
@@ -9,7 +9,7 @@ import org.bukkit.inventory.PlayerInventory;
import java.util.Objects; import java.util.Objects;
public class InvSeeEvents implements Listener { public class InvSeeListener implements Listener {
@EventHandler @EventHandler
public void onInventoryClick(InventoryClickEvent event){ public void onInventoryClick(InventoryClickEvent event){
@@ -1,6 +1,8 @@
package de.shiewk.smoderation.listener; package de.shiewk.smoderation.listener;
import de.shiewk.smoderation.SModeration; import de.shiewk.smoderation.SModeration;
import de.shiewk.smoderation.command.VanishCommand;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
@@ -12,23 +14,27 @@ public class VanishListener implements Listener {
@EventHandler public void onPlayerQuit(PlayerQuitEvent event){ @EventHandler public void onPlayerQuit(PlayerQuitEvent event){
final Player player = event.getPlayer(); final Player player = event.getPlayer();
if (SModeration.isVanished(player)){ if (VanishCommand.isVanished(player)){
SModeration.toggleVanish(player); VanishCommand.toggleVanish(player);
} }
for (Player vanishedPlayer : SModeration.getVanishedPlayers()) { for (Player vanishedPlayer : VanishCommand.getVanishedPlayers()) {
// to clean up visibility status // to clean up visibility status
player.hideEntity(SModeration.PLUGIN, vanishedPlayer); player.hideEntity(SModeration.PLUGIN, vanishedPlayer);
} }
} }
@EventHandler(priority = EventPriority.HIGHEST) public void onPlayerJoin(PlayerJoinEvent event){ @EventHandler(priority = EventPriority.MONITOR) public void onPlayerJoin(PlayerJoinEvent event){
final Player player = event.getPlayer(); Bukkit.getScheduler().scheduleSyncDelayedTask(SModeration.PLUGIN, () -> {
final Player player = event.getPlayer().getPlayer();
assert player != null;
if (player.hasPermission("smod.vanish.see")){ if (player.hasPermission("smod.vanish.see")){
for (Player vanishedPlayer : SModeration.getVanishedPlayers()) { for (Player vanishedPlayer : VanishCommand.getVanishedPlayers()) {
// to show visible vanished players // to show visible vanished players
player.showEntity(SModeration.PLUGIN, vanishedPlayer); player.showEntity(SModeration.PLUGIN, vanishedPlayer);
} }
VanishCommand.listVanishedPlayersTo(player);
} }
});
} }
} }
@@ -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);
}
} }
@@ -6,6 +6,7 @@ import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.util.StringUtil;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -71,4 +72,8 @@ public abstract class PlayerUtil {
} }
return List.copyOf(names); return List.copyOf(names);
} }
public static List<String> listPlayerNames(String search) {
return StringUtil.copyPartialMatches(search, listPlayerNames(), new ArrayList<>());
}
} }
+1 -1
View File
@@ -72,7 +72,7 @@ commands:
permission: smod.enderchestsee permission: smod.enderchestsee
description: Views the ender chest of another player. description: Views the ender chest of another player.
vanish: vanish:
usage: "§cUsage: /vanish <player>" usage: "§cUsage: /vanish list or /vanish toggle <player>"
aliases: aliases:
- smvanish - smvanish
permission: smod.vanish permission: smod.vanish