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
- /unmute & /unban
- /invsee & /enderchestsee
- /vanish
## Permissions
This plugin uses Bukkit permissions for commands and other actions.
+15 -51
View File
@@ -1,53 +1,17 @@
# SModeration commands
### /smod
The /smod command just opens the SMod menu. It takes no arguments.
### /mute
The /mute command is used to mute players.
It requires 2 arguments:
- Player name
- Duration
If you want to, you can also add a **reason**.
For example, if you want to mute a player for breaking server rules, just use **/mute playername 1h 30min Breaking server rules.**
The player will be muted for 1 hour and 30 minutes with the reason "Breaking server rules.".
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.
| Command | Description | Permission |
|----------------------------------------|------------------------------------------------------------------------------|---------------------|
| /smod | Opens the SMod menu. | smod.menu |
| /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 |
| /kick \<player> \<reason?> | Kicks a player from the server. | smod.kick |
| /modlogs <player\|uuid> | Lists a player's active punishments in chat. | smod.logs |
| /unmute <player> | Clears a player's mute status. | smod.unmute |
| /unban <player> | Clears a player's ban status. | smod.unban |
| /invsee \<player> inventory | Views another player's inventory. | smod.invsee |
| /invsee \<player> equipment | Views another player's equipment (armor and offhand). | smod.invsee |
| /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 |
| /vanish toggle \<player> | Toggles vanish mode for another player. | smod.vanish |
| /vanish list | Lists all vanished players. | smod.vanish.see |
+20 -16
View File
@@ -1,19 +1,23 @@
# SModeration permissions
- **smod.mute**: Allows the player to mute other players.
- **smod.preventmute**: Prevents the player from being muted (if they are online).
- **smod.ban**: Allows the player to ban and kick other players.
- **smod.preventban**: Prevents the player from being banned (if they are online).
- **smod.kick**: Allows the player to kick other players.
- **smod.preventkick**: Prevents the player from being kicked (if they are online).
- **smod.menu**: Allows the player to use the SModeration menu.
- **smod.notifications**: Allows the player to be notified when a punishment is issued.
- **smod.unmute**: Allows the player to unmute other players.
- **smod.unban**: Allows the player to unban other players.
- **smod.logs**: Allows the player to view mod logs.
- **smod.invsee**: Allows the player to view other players inventories.
- **smod.invsee.modify**: Allows the player to view and modify other players inventories.
- **smod.invsee.preventmodify**: When giving this permission to a player, prevents their 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.
| Permission | Description |
|---------------------------|------------------------------------------------------------------|
| smod.mute | Allows the player to mute other players. |
| smod.preventmute | Prevents the player from being muted (if they are online). |
| smod.ban | Allows the player to ban and kick other players. |
| smod.preventban | Prevents the player from being banned (if they are online). |
| smod.kick | Allows the player to kick other players. |
| smod.preventkick | Prevents the player from being kicked (if they are online). |
| smod.menu | Allows the player to use the SModeration menu. |
| smod.notifications | Allows the player to be notified when a punishment is issued. |
| smod.unmute | Allows the player to unmute other players. |
| smod.unban | Allows the player to unban other players. |
| smod.logs | Allows the player to view mod logs. |
| smod.invsee | Allows the player to view other players inventories. |
| smod.invsee.modify | Allows the player to view and modify other players inventories. |
| 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.
+1 -1
View File
@@ -1 +1 @@
pluginVersion = 1.2.0
pluginVersion = 1.3.0
@@ -1,19 +1,14 @@
package de.shiewk.smoderation;
import de.shiewk.smoderation.command.*;
import de.shiewk.smoderation.event.CustomInventoryEvents;
import de.shiewk.smoderation.event.EnderchestSeeEvents;
import de.shiewk.smoderation.event.InvSeeEvents;
import de.shiewk.smoderation.listener.PunishmentListener;
import de.shiewk.smoderation.listener.VanishListener;
import de.shiewk.smoderation.input.ChatInputListener;
import de.shiewk.smoderation.listener.*;
import de.shiewk.smoderation.storage.PunishmentContainer;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
@@ -21,6 +16,8 @@ import org.bukkit.plugin.java.JavaPlugin;
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 org.bukkit.Bukkit.getPluginManager;
@@ -47,10 +44,11 @@ public final class SModeration extends JavaPlugin {
@Override
public void onEnable() {
getPluginManager().registerEvents(new PunishmentListener(), this);
getPluginManager().registerEvents(new CustomInventoryEvents(), this);
getPluginManager().registerEvents(new InvSeeEvents(), this);
getPluginManager().registerEvents(new EnderchestSeeEvents(), this);
getPluginManager().registerEvents(new CustomInventoryListener(), this);
getPluginManager().registerEvents(new InvSeeListener(), this);
getPluginManager().registerEvents(new EnderchestSeeListener(), this);
getPluginManager().registerEvents(new VanishListener(), this);
getPluginManager().registerEvents(new ChatInputListener(), this);
registerCommand("mute", new MuteCommand());
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;
import de.shiewk.smoderation.inventory.InvSeeEquipmentInventory;
import de.shiewk.smoderation.util.PlayerUtil;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
@@ -19,16 +20,30 @@ import static de.shiewk.smoderation.SModeration.*;
public class InvseeCommand implements TabExecutor {
private enum InvseeType {
INVENTORY,
EQUIPMENT
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length < 1) {
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){
final Player player = PlayerUtil.findOnlinePlayer(args[0]);
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));
} else {
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("."))
));
human.openInventory(player.getInventory());
switch (type){
case INVENTORY -> human.openInventory(player.getInventory());
case EQUIPMENT -> new InvSeeEquipmentInventory(human, player).open();
}
}
} else {
human.sendMessage(Component.text("This player is not online.").color(FAIL_COLOR));
@@ -49,8 +67,10 @@ public class InvseeCommand implements TabExecutor {
@Override
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();
} else if (args.length > 1){
return List.of("armor", "equipment", "inventory");
}
List<String> available = new ArrayList<>();
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
@@ -2,6 +2,11 @@ package de.shiewk.smoderation.command;
import de.shiewk.smoderation.SModeration;
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.CommandSender;
import org.bukkit.command.TabExecutor;
@@ -11,18 +16,32 @@ import org.jetbrains.annotations.Nullable;
import java.util.List;
import static de.shiewk.smoderation.SModeration.*;
import static net.kyori.adventure.text.Component.text;
public class VanishCommand implements TabExecutor {
@Override
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;
if (args.length > 0){
player = PlayerUtil.findOnlinePlayer(args[0]);
if (args.length > 1){
player = PlayerUtil.findOnlinePlayer(args[1]);
} else if (sender instanceof Player){
player = (Player) sender;
}
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;
} else {
return false;
@@ -32,8 +51,83 @@ public class VanishCommand implements TabExecutor {
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
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();
}
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;
import de.shiewk.smoderation.input.ChatInput;
import de.shiewk.smoderation.punishments.Punishment;
import de.shiewk.smoderation.punishments.PunishmentType;
import de.shiewk.smoderation.storage.PunishmentContainer;
import de.shiewk.smoderation.util.PlayerUtil;
import de.shiewk.smoderation.util.TimeUtil;
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.TextDecoration;
import org.bukkit.Bukkit;
@@ -15,6 +17,7 @@ import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.SkullMeta;
@@ -25,9 +28,11 @@ import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
import static de.shiewk.smoderation.SModeration.*;
import static net.kyori.adventure.text.Component.text;
public class SModMenu extends PageableCustomInventory {
@@ -70,13 +75,17 @@ public class SModMenu extends PageableCustomInventory {
private List<Punishment> punishments;
private ItemStack sortStack = null;
private ItemStack filterStack = null;
private ItemStack searchStack = null;
private ItemStack typeStack = null;
private int sort = 0;
private int filter = 0;
private int type = -1;
private String searchQuery = null;
public SModMenu(Player player, PunishmentContainer container) {
this.player = player;
this.container = container;
this.inventory = Bukkit.createInventory(this, 54, Component.text("SMod Menu"));
this.inventory = Bukkit.createInventory(this, 54, text("SMod Menu"));
reload();
}
@@ -88,8 +97,23 @@ public class SModMenu extends PageableCustomInventory {
return Filter.values()[filter];
}
public PunishmentType getType(){
return type == -1 ? null : PunishmentType.values()[type];
}
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
@@ -135,6 +159,25 @@ public class SModMenu extends PageableCustomInventory {
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
public void switchPage() {
player.playSound(player, Sound.BLOCK_STONE_HIT, 0.75f, 1f);
@@ -144,55 +187,97 @@ public class SModMenu extends PageableCustomInventory {
final Filter filter = getFilter();
final ItemStack stack = new ItemStack(Filter.ICON);
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<>();
lore.add(Component.empty());
for (Filter value : Filter.values()) {
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(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);
});
filterStack = 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(){
final Sort sort = getSort();
final ItemStack stack = new ItemStack(Sort.ICON);
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<>();
lore.add(Component.empty());
for (Sort value : Sort.values()) {
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(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);
});
sortStack = 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){
ItemStack stack = new ItemStack(Material.PLAYER_HEAD);
stack.editMeta(meta -> {
if (meta instanceof SkullMeta skullMeta){
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<>();
lore.add(applyFormatting(Component.text("Player: ").color(SECONDARY_COLOR).append(Component.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(Component.text("Timestamp: ").color(SECONDARY_COLOR).append(Component.text(TimeUtil.calendarTimestamp(punishment.time)).color(PRIMARY_COLOR))));
lore.add(applyFormatting(text("Player: ").color(SECONDARY_COLOR).append(text(PlayerUtil.offlinePlayerName(punishment.to)).color(PRIMARY_COLOR))));
lore.add(applyFormatting(text("Punished by: ").color(SECONDARY_COLOR).append(text(PlayerUtil.offlinePlayerName(punishment.by)).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){
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();
final String expires;
if (remainingTime > 0){
@@ -201,15 +286,15 @@ public class SModMenu extends PageableCustomInventory {
remainingTime *= -1;
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()){
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()) {
if ((punishment.type == PunishmentType.BAN && player.hasPermission("smod.unban")) || (punishment.type == PunishmentType.MUTE && player.hasPermission("smod.unmute"))){
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);
@@ -224,8 +309,10 @@ public class SModMenu extends PageableCustomInventory {
}
inventory.setItem(45, createPreviousPageStack());
inventory.setItem(53, createNextPageStack());
inventory.setItem(47, createSearchItem());
inventory.setItem(48, createTypeItem());
inventory.setItem(50, createFilterItem());
inventory.setItem(48, createSortItem());
inventory.setItem(51, createSortItem());
for (int i = 0; i < 45; i++) {
int ci = i + (getPage() * 45);
@@ -252,6 +339,18 @@ public class SModMenu extends PageableCustomInventory {
cycleFilter(event.isRightClick());
} else if (stack.equals(sortStack)){
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();
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.event.EventHandler;
@@ -8,14 +8,14 @@ import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.PlayerInventory;
public class EnderchestSeeEvents implements Listener {
public class EnderchestSeeListener implements Listener {
@EventHandler
public void onInventoryClick(InventoryClickEvent event){
final Inventory clicked = event.getView().getTopInventory();
if (!(clicked instanceof PlayerInventory)){
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")){
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.event.EventHandler;
@@ -9,7 +9,7 @@ import org.bukkit.inventory.PlayerInventory;
import java.util.Objects;
public class InvSeeEvents implements Listener {
public class InvSeeListener implements Listener {
@EventHandler
public void onInventoryClick(InventoryClickEvent event){
@@ -1,6 +1,8 @@
package de.shiewk.smoderation.listener;
import de.shiewk.smoderation.SModeration;
import de.shiewk.smoderation.command.VanishCommand;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
@@ -12,23 +14,27 @@ public class VanishListener implements Listener {
@EventHandler public void onPlayerQuit(PlayerQuitEvent event){
final Player player = event.getPlayer();
if (SModeration.isVanished(player)){
SModeration.toggleVanish(player);
if (VanishCommand.isVanished(player)){
VanishCommand.toggleVanish(player);
}
for (Player vanishedPlayer : SModeration.getVanishedPlayers()) {
for (Player vanishedPlayer : VanishCommand.getVanishedPlayers()) {
// to clean up visibility status
player.hideEntity(SModeration.PLUGIN, vanishedPlayer);
}
}
@EventHandler(priority = EventPriority.HIGHEST) public void onPlayerJoin(PlayerJoinEvent event){
final Player player = event.getPlayer();
@EventHandler(priority = EventPriority.MONITOR) public void onPlayerJoin(PlayerJoinEvent event){
Bukkit.getScheduler().scheduleSyncDelayedTask(SModeration.PLUGIN, () -> {
final Player player = event.getPlayer().getPlayer();
assert player != null;
if (player.hasPermission("smod.vanish.see")){
for (Player vanishedPlayer : SModeration.getVanishedPlayers()) {
for (Player vanishedPlayer : VanishCommand.getVanishedPlayers()) {
// to show visible vanished players
player.showEntity(SModeration.PLUGIN, vanishedPlayer);
}
VanishCommand.listVanishedPlayersTo(player);
}
});
}
}
@@ -239,4 +239,23 @@ public class Punishment {
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.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.util.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -71,4 +72,8 @@ public abstract class PlayerUtil {
}
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
description: Views the ender chest of another player.
vanish:
usage: "§cUsage: /vanish <player>"
usage: "§cUsage: /vanish list or /vanish toggle <player>"
aliases:
- smvanish
permission: smod.vanish