1
mirror of https://github.com/Shiewk/SModeration.git synced 2026-04-29 06:34:17 +02:00

38 Commits

Author SHA1 Message Date
Shiewk 039b40bd53 (1.6.0) Switch to Paper plugin & support Folia 2025-07-09 12:59:43 +02:00
Shiewk a69c7cb426 Clear player title after chat input 2025-07-09 11:48:00 +02:00
Shiewk 8a147eb7cf Use AsyncPlayerPreLoginEvent instead of PlayerLoginEvent
because of its deprecation in Paper 1.21.6, this event disabled re-configuration APIs when a listener is registered for it; we don't want that to happen
2025-07-09 11:44:23 +02:00
Shiewk ecdd688a79 Fix SMod menu opening in newer versions 2025-07-09 11:08:29 +02:00
Shiewk 26166b5a73 Use API version 1.21.3, fix invsee 2025-07-09 10:28:54 +02:00
Shiewk 27a1fd8244 1.5.0 2025-05-02 18:01:43 +02:00
Shiewk 66c113bad2 Save vanish state for next join 2025-05-02 17:55:10 +02:00
Shiewk d863622168 Add a config option to require a reason 2025-05-02 16:30:14 +02:00
Shiewk 361f66f645 Upgrade Paper to 1.21, use Brigadier command system 2025-05-01 19:55:55 +02:00
Shiewk fbaaa6ec89 Not using the constant value
how did I not notice
2025-05-01 11:43:15 +02:00
Shiewk 955ab8791a Allow registering skin texture provider for player heads 2025-05-01 11:25:49 +02:00
Shiewk 8128628b12 Fix pagination in SMod menu 2025-05-01 10:56:20 +02:00
Shiewk 02570993a7 Move SModeration for Paper to root project 2025-05-01 10:46:00 +02:00
Shiewk 0d095c40f0 Removed unnecessary log message, 1.4.2 2025-04-11 16:30:48 +02:00
Shiewk 8df1029d42 Add a VanishToggleEvent 2025-04-02 18:29:20 +02:00
Shiewk 2f66c6f12c move SModeration for Paper to subproject 2024-12-19 15:59:20 +01:00
Shiewk 1f9758f3d0 Use main branch for development from now on (#5) 2024-12-16 14:48:39 +01:00
Shiewk c7502fd661 add missing whitespace 2024-12-06 19:41:06 +01:00
Shiewk 1a9cf76a93 1.4.1 2024-08-25 11:12:39 +02:00
Shiewk b3ec076cae Fix chat input title delay 2024-08-25 10:59:29 +02:00
Shiewk 3cb3314f4c Allow opening own ender chest 2024-08-22 15:28:25 +02:00
Shiewk fb785fb53a Improve styling 2024-08-22 13:57:31 +02:00
Shiewk e44f6489af Update main branch to 1.4.0 (#4)
- Fixed a bug where unknown players would crash the SMod menu
- SocialSpy command and permissions
- Hide death messages from vanished players
2024-08-21 18:43:41 +02:00
Shiewk faa2eb0a43 1.4.0 2024-08-21 18:40:56 +02:00
Shiewk 8c00347ef2 Hide death messages from vanished players 2024-08-21 18:40:06 +02:00
Shiewk 8de4382ec4 SocialSpy command and permissions 2024-08-21 18:32:01 +02:00
Shiewk a7c487decd Update to version 1.3.1 2024-08-04 14:48:51 +02:00
Shiewk 22f42df1da Fixed a bug where unknown players would crash the SMod menu 2024-08-04 14:47:29 +02:00
Shiewk 7e90939b84 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
2024-07-29 17:41:04 +02:00
Shiewk 5c5eee0bca Update to version 1.3.0 2024-07-29 17:33:56 +02:00
Shiewk d7560d5ef3 Add ability to filter by type in SMod menu 2024-07-29 17:07:45 +02:00
Shiewk ce8a8a44cd Added Invsee for equipment 2024-07-29 16:27:42 +02:00
Shiewk 7902bb9a46 Refactor listeners 2024-07-29 15:12:23 +02:00
Shiewk b40e543f9e Fixed a bug where players could not put any items in their ender chests 2024-07-29 15:10:47 +02:00
Shiewk ab037716c4 Add search option in SMod menu with chat input 2024-07-29 13:22:00 +02:00
Shiewk 56aef4a2c9 Added /vanish list command and list vanished players on join 2024-07-28 16:10:22 +02:00
Shiewk 755b758b2f Move vanish-related methods to VanishCommand.java 2024-07-28 15:29:41 +02:00
Shiewk 4266b7ebb6 Update main branch to version 1.2.0
- Improved command registration
- Add permissions for mute/kick/ban immunity
- Add vanish command and permissions
- Update to version 1.2.0
2024-07-23 18:36:40 +02:00
65 changed files with 2470 additions and 1484 deletions
+2 -1
View File
@@ -14,7 +14,7 @@ SModeration provides a nice user interface that can be used instead of chat comm
![SMod Menu Sort](https://github.com/Shiewk/SModeration/assets/152653291/23e3862d-0915-47bd-9c47-6d8d10f8ab69) ![SMod Menu Sort](https://github.com/Shiewk/SModeration/assets/152653291/23e3862d-0915-47bd-9c47-6d8d10f8ab69)
It has helpful functions like filtering and sorting options. It has helpful functions like filtering, searching and sorting options.
## Commands ## Commands
This plugin has many commands, for a complete list of commands and their usage, please see [the commands list](https://github.com/Shiewk/SModeration/blob/main/docs/commands.md). This plugin has many commands, for a complete list of commands and their usage, please see [the commands list](https://github.com/Shiewk/SModeration/blob/main/docs/commands.md).
@@ -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.
+33 -14
View File
@@ -1,5 +1,6 @@
plugins { plugins {
id 'java' id 'java'
id("xyz.jpenilla.run-paper") version "2.3.1"
} }
group = 'de.shiewk' group = 'de.shiewk'
@@ -11,17 +12,44 @@ repositories {
name = "papermc-repo" name = "papermc-repo"
url = "https://repo.papermc.io/repository/maven-public/" url = "https://repo.papermc.io/repository/maven-public/"
} }
maven { }
name = "sonatype"
url = "https://oss.sonatype.org/content/groups/public/" processResources {
def props = [version: version]
inputs.properties props
filteringCharset 'UTF-8'
filesMatching('paper-plugin.yml') {
expand props
}
}
runPaper {
folia {
registerTask()
}
}
runServer {
minecraftVersion("1.21.7")
downloadPlugins {
// for testing from other client versions
modrinth("ViaVersion", "5.4.1")
modrinth("ViaBackwards", "5.4.1")
} }
} }
dependencies { dependencies {
compileOnly "io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT" //compileOnly "io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT"
compileOnly 'dev.folia:folia-api:1.21.4-R0.1-SNAPSHOT'
} }
def targetJavaVersion = 17 jar {
archiveBaseName.set('SModeration-Paper')
archiveVersion.set(pluginVersion)
}
def targetJavaVersion = 21
java { java {
def javaVersion = JavaVersion.toVersion(targetJavaVersion) def javaVersion = JavaVersion.toVersion(targetJavaVersion)
sourceCompatibility = javaVersion sourceCompatibility = javaVersion
@@ -38,12 +66,3 @@ tasks.withType(JavaCompile).configureEach {
options.release.set(targetJavaVersion) options.release.set(targetJavaVersion)
} }
} }
processResources {
def props = [version: version]
inputs.properties props
filteringCharset 'UTF-8'
filesMatching('plugin.yml') {
expand props
}
}
+16 -51
View File
@@ -1,53 +1,18 @@
# 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 \<players> | Toggles vanish mode for other players. | smod.vanish |
| /vanish list | Lists all vanished players. | smod.vanish.see |
Muted players can still join the server, but can't use the chat. | /socialspy | Enables SocialSpy mode where you can see other player's private messages. | smod.socialspy |
### /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.
+21 -18
View File
@@ -1,21 +1,24 @@
# 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.vanish**: Allows the player to enter and leave vanish mode. | smod.enderchestsee | Allows the player to view other players ender chests. |
- **smod.vanish.see**: Allows the player to see vanished players | 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 |
| smod.socialspy | Allows the player to enable SocialSpy |
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.1 pluginVersion = 1.6.0
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
@@ -1,137 +0,0 @@
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.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;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import static net.kyori.adventure.text.Component.text;
import static org.bukkit.Bukkit.getPluginManager;
public final class SModeration extends JavaPlugin {
public static final PunishmentContainer container = new PunishmentContainer();
public static ComponentLogger LOGGER = null;
public static SModeration PLUGIN = null;
public static File SAVE_FILE = null;
public static final TextColor PRIMARY_COLOR = TextColor.color(212, 0, 255);
public static final TextColor SECONDARY_COLOR = TextColor.color(52, 143, 255);
public static final TextColor INACTIVE_COLOR = NamedTextColor.GRAY;
public static final TextColor FAIL_COLOR = NamedTextColor.RED;
public static final TextComponent CHAT_PREFIX = text("SM \u00BB ").color(PRIMARY_COLOR);
@Override
public void onLoad() {
LOGGER = getComponentLogger();
PLUGIN = this;
SAVE_FILE = new File(this.getDataFolder().getAbsolutePath() + "/container.gz");
}
@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 VanishListener(), this);
registerCommand("mute", new MuteCommand());
registerCommand("ban", new BanCommand());
registerCommand("kick", new KickCommand());
registerCommand("smod", new SModCommand());
registerCommand("modlogs", new ModLogsCommand());
registerCommand("unmute", new UnmuteCommand());
registerCommand("unban", new UnbanCommand());
registerCommand("invsee", new InvseeCommand());
registerCommand("enderchestsee", new EnderchestSeeCommand());
registerCommand("vanish", new VanishCommand());
container.load(SAVE_FILE);
}
private void registerCommand(String label, TabExecutor executor){
final PluginCommand command = getCommand(label);
if (command != null) {
command.setExecutor(executor);
command.setTabCompleter(executor);
} else {
LOGGER.warn("Command %s failed to register: This command does not exist".formatted(label));
}
}
@Override
public void onDisable() {
SModeration.container.save(SModeration.SAVE_FILE);
for (Player player : Bukkit.getOnlinePlayers()) {
// in case players are still vanished when the server shuts down
if (isVanished(player)){
toggleVanish(player);
}
}
}
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,124 +0,0 @@
package de.shiewk.smoderation.command;
import de.shiewk.smoderation.SModeration;
import de.shiewk.smoderation.punishments.Punishment;
import de.shiewk.smoderation.util.PlayerUtil;
import de.shiewk.smoderation.util.TimeUtil;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import org.bukkit.util.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import static net.kyori.adventure.text.Component.text;
public class BanCommand implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length < 2){
return false;
} else {
UUID senderUUID;
if (sender instanceof ConsoleCommandSender){
senderUUID = PlayerUtil.UUID_CONSOLE;
} else if (sender instanceof Player pl){
senderUUID = pl.getUniqueId();
} else if (sender instanceof BlockCommandSender){
sender.sendMessage(Component.text("Blocks can't execute this command.").color(NamedTextColor.RED));
return true;
} else {
sender.sendMessage(Component.text("Your command sender type is unknown (%s).".formatted(sender.getClass().getName())).color(NamedTextColor.RED));
return true;
}
String playerName = args[0];
UUID uuid = PlayerUtil.offlinePlayerUUIDByName(playerName);
if (senderUUID.equals(uuid)) {
sender.sendMessage(Component.text("You can't ban yourself.").color(NamedTextColor.RED));
return true;
}
if (uuid == null) {
sender.sendMessage(Component.text("This player is either offline or was never on this server.").color(NamedTextColor.RED));
return true;
}
final Player toPlayer = Bukkit.getPlayer(uuid);
if (toPlayer != null && toPlayer.hasPermission("smod.preventban")){
sender.sendMessage(text().content("This player can't be banned.").color(NamedTextColor.RED));
return true;
}
long duration = 0;
int p = 1;
for (int i = 1 /* start with index 1 to avoid player name */; i < args.length; i++) {
String arg = args[i];
long parsedDuration = TimeUtil.parseDurationMillisSafely(arg);
if (parsedDuration == -1){
p = i;
break;
} else {
duration += parsedDuration;
}
if (i == args.length - 1){ p = args.length; }
}
if (duration == 0){
sender.sendMessage(Component.text("Please provide a valid duration.").color(NamedTextColor.RED));
return false;
}
if (duration < 0){
sender.sendMessage(Component.text("Please provide a duration that's longer than 0ms.").color(NamedTextColor.RED));
return false;
}
StringBuilder reason = new StringBuilder();
for (int i = p; i < args.length; i++) {
if (!reason.isEmpty()){
reason.append(" ");
}
reason.append(args[i]);
}
final Punishment punishment = Punishment.ban(System.currentTimeMillis(), System.currentTimeMillis() + duration, senderUUID, uuid, reason.isEmpty() ? Punishment.DEFAULT_REASON : reason.toString());
Punishment.issue(punishment, SModeration.container);
return true;
}
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length < 2){
String toComplete = args.length > 0 ? args[0] : "";
ArrayList<String> names = new ArrayList<>();
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
names.add(onlinePlayer.getName());
}
ArrayList<String> completions = new ArrayList<>();
StringUtil.copyPartialMatches(toComplete, names, completions);
return completions;
} else {
for (int i = 1; i < args.length; i++) {
if (TimeUtil.parseDurationMillisSafely(args[i]) == -1){
try {
Long.parseLong(args[i]);
} catch (NumberFormatException ignored){
if (i != 1){
return List.of();
}
}
}
}
return List.of(
"100ms",
"15s", // some sample completions for duration
"30min", // you can input your own ones as well
"6h",
"1d",
"2w",
"3mo",
"1y"
);
}
}
}
@@ -1,63 +0,0 @@
package de.shiewk.smoderation.command;
import de.shiewk.smoderation.util.PlayerUtil;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.util.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import static de.shiewk.smoderation.SModeration.*;
public class EnderchestSeeCommand implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length < 1) {
return false;
}
if (sender instanceof HumanEntity human){
final Player player = PlayerUtil.findOnlinePlayer(args[0]);
if (player != null) {
if (human.getUniqueId().equals(player.getUniqueId())){
human.sendMessage(Component.text("You can't open your own ender chest.").color(FAIL_COLOR));
} else {
human.sendMessage(CHAT_PREFIX.append(
Component.text("Opening ender chest of ").color(PRIMARY_COLOR)
.append(Component.text(player.getName()).color(SECONDARY_COLOR))
.append(Component.text("."))
));
human.openInventory(player.getEnderChest());
}
} else {
human.sendMessage(Component.text("This player is not online.").color(FAIL_COLOR));
}
} else {
sender.sendMessage(Component.text("Only an entity that can open inventories can execute this command!").color(FAIL_COLOR));
}
return true;
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length > 1){
return List.of();
}
List<String> available = new ArrayList<>();
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
available.add(onlinePlayer.getName());
}
List<String> completions = new ArrayList<>();
StringUtil.copyPartialMatches(args.length > 0 ? args[0] : "", available, completions);
return completions;
}
}
@@ -1,63 +0,0 @@
package de.shiewk.smoderation.command;
import de.shiewk.smoderation.util.PlayerUtil;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.util.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import static de.shiewk.smoderation.SModeration.*;
public class InvseeCommand implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length < 1) {
return false;
}
if (sender instanceof HumanEntity human){
final Player player = PlayerUtil.findOnlinePlayer(args[0]);
if (player != null) {
if (human.getUniqueId().equals(player.getUniqueId())){
human.sendMessage(Component.text("You can't open your own inventory.").color(FAIL_COLOR));
} else {
human.sendMessage(CHAT_PREFIX.append(
Component.text("Opening inventory of ").color(PRIMARY_COLOR)
.append(Component.text(player.getName()).color(SECONDARY_COLOR))
.append(Component.text("."))
));
human.openInventory(player.getInventory());
}
} else {
human.sendMessage(Component.text("This player is not online.").color(FAIL_COLOR));
}
} else {
sender.sendMessage(Component.text("Only an entity that can open inventories can execute this command!").color(FAIL_COLOR));
}
return true;
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length > 1){
return List.of();
}
List<String> available = new ArrayList<>();
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
available.add(onlinePlayer.getName());
}
List<String> completions = new ArrayList<>();
StringUtil.copyPartialMatches(args.length > 0 ? args[0] : "", available, completions);
return completions;
}
}
@@ -1,81 +0,0 @@
package de.shiewk.smoderation.command;
import de.shiewk.smoderation.SModeration;
import de.shiewk.smoderation.punishments.Punishment;
import de.shiewk.smoderation.util.PlayerUtil;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import org.bukkit.util.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import static net.kyori.adventure.text.Component.text;
public class KickCommand implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length < 1){
return false;
} else {
UUID senderUUID;
if (sender instanceof ConsoleCommandSender){
senderUUID = PlayerUtil.UUID_CONSOLE;
} else if (sender instanceof Player pl){
senderUUID = pl.getUniqueId();
} else if (sender instanceof BlockCommandSender){
sender.sendMessage(Component.text("Blocks can't execute this command.").color(NamedTextColor.RED));
return true;
} else {
sender.sendMessage(Component.text("Your command sender type is unknown (%s).".formatted(sender.getClass().getName())).color(NamedTextColor.RED));
return true;
}
String playerName = args[0];
Player player = Bukkit.getPlayer(playerName);
if (player == null) {
sender.sendMessage(Component.text("This player is not online.").color(NamedTextColor.RED));
return true;
}
UUID uuid = player.getUniqueId();
if (senderUUID.equals(uuid)) {
sender.sendMessage(Component.text("You can't kick yourself.").color(NamedTextColor.RED));
return true;
}
if (player.hasPermission("smod.preventkick")){
sender.sendMessage(text().content("This player can't be kicked.").color(NamedTextColor.RED));
return true;
}
StringBuilder reason = new StringBuilder();
for (int i = 1; i < args.length; i++) {
if (!reason.isEmpty()){
reason.append(" ");
}
reason.append(args[i]);
}
final Punishment punishment = Punishment.kick(System.currentTimeMillis(), senderUUID, uuid, reason.isEmpty() ? Punishment.DEFAULT_REASON : reason.toString());
Punishment.issue(punishment, SModeration.container);
return true;
}
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length < 2){
String toComplete = args.length > 0 ? args[0] : "";
ArrayList<String> names = new ArrayList<>();
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
names.add(onlinePlayer.getName());
}
ArrayList<String> completions = new ArrayList<>();
StringUtil.copyPartialMatches(toComplete, names, completions);
return completions;
}
return List.of();
}
}
@@ -1,79 +0,0 @@
package de.shiewk.smoderation.command;
import de.shiewk.smoderation.SModeration;
import de.shiewk.smoderation.punishments.Punishment;
import de.shiewk.smoderation.punishments.PunishmentType;
import de.shiewk.smoderation.util.PlayerUtil;
import de.shiewk.smoderation.util.TimeUtil;
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;
import org.bukkit.entity.Player;
import org.bukkit.util.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import static de.shiewk.smoderation.SModeration.*;
public class ModLogsCommand implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length < 1){
return false;
} else {
String playername = args[0];
String name;
UUID uuid;
try {
uuid = UUID.fromString(playername);
} catch (IllegalArgumentException ignored){
uuid = PlayerUtil.offlinePlayerUUIDByName(playername);
}
if (uuid == null){
sender.sendMessage(Component.text("This player was not found. Try running /%s with an UUID instead.".formatted(label)).color(NamedTextColor.RED));
return true;
}
name = PlayerUtil.offlinePlayerName(uuid);
sender.sendMessage(CHAT_PREFIX.append(Component.text("Player ").color(PRIMARY_COLOR)
.append(Component.text(name).color(SECONDARY_COLOR))
.append(Component.text(" (%s)".formatted(uuid)).color(INACTIVE_COLOR))));
UUID finalUuid = uuid;
final List<Punishment> punishments = SModeration.container.findAll(p -> p.to.equals(finalUuid) && p.isActive());
for (Punishment punishment : punishments) {
sender.sendMessage(Component.text("- is currently ").color(PRIMARY_COLOR)
.append(Component.text(punishment.type == PunishmentType.BAN ? "banned" : "muted").color(SECONDARY_COLOR))
.append(Component.text(" until ").color(PRIMARY_COLOR))
.append(Component.text(TimeUtil.calendarTimestamp(punishment.until)).color(SECONDARY_COLOR))
.append(Component.text(" (in %s)".formatted(TimeUtil.formatTimeLong(punishment.until - System.currentTimeMillis()))).color(INACTIVE_COLOR))
.append(Component.text(". Reason: ").color(PRIMARY_COLOR))
.append(Component.text(punishment.reason).color(SECONDARY_COLOR)));
}
if (punishments.isEmpty()){
sender.sendMessage(Component.text("- has no punishments.").color(PRIMARY_COLOR));
}
return true;
}
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length > 1){
return List.of();
}
String search = args.length > 0 ? args[0] : "";
List<String> playernames = new ArrayList<>();
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
playernames.add(onlinePlayer.getName());
}
List<String> completions = new ArrayList<>();
StringUtil.copyPartialMatches(search, playernames, completions);
return completions;
}
}
@@ -1,123 +0,0 @@
package de.shiewk.smoderation.command;
import de.shiewk.smoderation.SModeration;
import de.shiewk.smoderation.punishments.Punishment;
import de.shiewk.smoderation.util.PlayerUtil;
import de.shiewk.smoderation.util.TimeUtil;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import org.bukkit.util.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import static net.kyori.adventure.text.Component.text;
public class MuteCommand implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length < 2){
return false;
} else {
UUID senderUUID;
if (sender instanceof ConsoleCommandSender){
senderUUID = PlayerUtil.UUID_CONSOLE;
} else if (sender instanceof Player pl){
senderUUID = pl.getUniqueId();
} else if (sender instanceof BlockCommandSender){
sender.sendMessage(text("Blocks can't execute this command.").color(NamedTextColor.RED));
return true;
} else {
sender.sendMessage(text("Your command sender type is unknown (%s).".formatted(sender.getClass().getName())).color(NamedTextColor.RED));
return true;
}
String playerName = args[0];
UUID uuid = PlayerUtil.offlinePlayerUUIDByName(playerName);
if (senderUUID.equals(uuid)) {
sender.sendMessage(text("You can't mute yourself.").color(NamedTextColor.RED));
return true;
}
if (uuid == null) {
sender.sendMessage(text("This player is either offline or was never on this server.").color(NamedTextColor.RED));
return true;
}
final Player toPlayer = Bukkit.getPlayer(uuid);
if (toPlayer != null && toPlayer.hasPermission("smod.preventmute")){
sender.sendMessage(text().content("This player can't be muted.").color(NamedTextColor.RED));
return true;
}
long duration = 0;
int p = 1;
for (int i = 1 /* start with index 1 to avoid player name */; i < args.length; i++) {
String arg = args[i];
long parsedDuration = TimeUtil.parseDurationMillisSafely(arg);
if (parsedDuration == -1){
p = i;
break;
} else {
duration += parsedDuration;
}
if (i == args.length - 1){ p = args.length; }
}
if (duration == 0){
sender.sendMessage(text("Please provide a valid duration.").color(NamedTextColor.RED));
return false;
}
if (duration < 0){
sender.sendMessage(text("Please provide a duration that's longer than 0ms.").color(NamedTextColor.RED));
return false;
}
StringBuilder reason = new StringBuilder();
for (int i = p; i < args.length; i++) {
if (!reason.isEmpty()){
reason.append(" ");
}
reason.append(args[i]);
}
final Punishment punishment = Punishment.mute(System.currentTimeMillis(), System.currentTimeMillis() + duration, senderUUID, uuid, reason.isEmpty() ? Punishment.DEFAULT_REASON : reason.toString());
Punishment.issue(punishment, SModeration.container);
return true;
}
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length < 2){
String toComplete = args.length > 0 ? args[0] : "";
ArrayList<String> names = new ArrayList<>();
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
names.add(onlinePlayer.getName());
}
ArrayList<String> completions = new ArrayList<>();
StringUtil.copyPartialMatches(toComplete, names, completions);
return completions;
} else {
for (int i = 1; i < args.length; i++) {
if (TimeUtil.parseDurationMillisSafely(args[i]) == -1){
try {
Long.parseLong(args[i]);
} catch (NumberFormatException ignored){
if (i != 1){
return List.of();
}
}
}
}
return List.of(
"100ms",
"15s", // some sample completions for duration
"30min", // you can input your own ones as well
"6h",
"1d",
"2w",
"3mo",
"1y"
);
}
}
}
@@ -1,27 +0,0 @@
package de.shiewk.smoderation.command;
import de.shiewk.smoderation.SModeration;
import de.shiewk.smoderation.inventory.SModMenu;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class SModCommand implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String[] strings) {
if (commandSender instanceof Player player){
new SModMenu(player, SModeration.container).open();
}
return true;
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String[] strings) {
return List.of();
}
}
@@ -1,64 +0,0 @@
package de.shiewk.smoderation.command;
import de.shiewk.smoderation.SModeration;
import de.shiewk.smoderation.punishments.Punishment;
import de.shiewk.smoderation.punishments.PunishmentType;
import de.shiewk.smoderation.util.PlayerUtil;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.UUID;
public class UnbanCommand implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length < 1){
return false;
} else {
UUID senderUUID;
if (sender instanceof ConsoleCommandSender){
senderUUID = PlayerUtil.UUID_CONSOLE;
} else if (sender instanceof Player pl){
senderUUID = pl.getUniqueId();
} else if (sender instanceof BlockCommandSender){
sender.sendMessage(Component.text("Blocks can't execute this command.").color(NamedTextColor.RED));
return true;
} else {
sender.sendMessage(Component.text("Your command sender type is unknown (%s).".formatted(sender.getClass().getName())).color(NamedTextColor.RED));
return true;
}
String nameArg = args[0];
UUID uuid;
try {
uuid = UUID.fromString(nameArg);
} catch (IllegalArgumentException ignored){
uuid = PlayerUtil.offlinePlayerUUIDByName(nameArg);
}
if (uuid == null){
sender.sendMessage(Component.text("This player was not found. Try running /%s with an UUID instead.".formatted(label)).color(NamedTextColor.RED));
return true;
}
UUID finalUuid = uuid;
final Punishment punishment = SModeration.container.find(p -> p.to.equals(finalUuid) && p.isActive() && p.type == PunishmentType.BAN);
if (punishment != null) {
punishment.undo(senderUUID);
punishment.broadcastUndo(SModeration.container);
} else {
sender.sendMessage(Component.text("This player is not banned.").color(NamedTextColor.RED));
}
return true;
}
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
return List.of();
}
}
@@ -1,64 +0,0 @@
package de.shiewk.smoderation.command;
import de.shiewk.smoderation.SModeration;
import de.shiewk.smoderation.punishments.Punishment;
import de.shiewk.smoderation.punishments.PunishmentType;
import de.shiewk.smoderation.util.PlayerUtil;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.UUID;
public class UnmuteCommand implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length < 1){
return false;
} else {
UUID senderUUID;
if (sender instanceof ConsoleCommandSender){
senderUUID = PlayerUtil.UUID_CONSOLE;
} else if (sender instanceof Player pl){
senderUUID = pl.getUniqueId();
} else if (sender instanceof BlockCommandSender){
sender.sendMessage(Component.text("Blocks can't execute this command.").color(NamedTextColor.RED));
return true;
} else {
sender.sendMessage(Component.text("Your command sender type is unknown (%s).".formatted(sender.getClass().getName())).color(NamedTextColor.RED));
return true;
}
String nameArg = args[0];
UUID uuid;
try {
uuid = UUID.fromString(nameArg);
} catch (IllegalArgumentException ignored){
uuid = PlayerUtil.offlinePlayerUUIDByName(nameArg);
}
if (uuid == null){
sender.sendMessage(Component.text("This player was not found. Try running /%s with an UUID instead.".formatted(label)).color(NamedTextColor.RED));
return true;
}
UUID finalUuid = uuid;
final Punishment punishment = SModeration.container.find(p -> p.to.equals(finalUuid) && p.isActive() && p.type == PunishmentType.MUTE);
if (punishment != null) {
punishment.undo(senderUUID);
punishment.broadcastUndo(SModeration.container);
} else {
sender.sendMessage(Component.text("This player is not muted.").color(NamedTextColor.RED));
}
return true;
}
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
return List.of();
}
}
@@ -1,39 +0,0 @@
package de.shiewk.smoderation.command;
import de.shiewk.smoderation.SModeration;
import de.shiewk.smoderation.util.PlayerUtil;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class VanishCommand implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
Player player = null;
if (args.length > 0){
player = PlayerUtil.findOnlinePlayer(args[0]);
} else if (sender instanceof Player){
player = (Player) sender;
}
if (player != null){
SModeration.toggleVanish(player);
return true;
} else {
return false;
}
}
@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();
}
}
@@ -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);
}
}
}
@@ -1,286 +0,0 @@
package de.shiewk.smoderation.inventory;
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.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Predicate;
import static de.shiewk.smoderation.SModeration.*;
public class SModMenu extends PageableCustomInventory {
public enum Filter {
ACTIVE("Active punishments", Punishment::isActive),
OLD("Old punishments", p -> !p.isActive()),
ALL("All punishments", p -> true);
public static final Material ICON = Material.HOPPER;
public final String name;
public final Predicate<Punishment> filter;
Filter(String name, Predicate<Punishment> filter) {
this.name = name;
this.filter = filter;
}
}
public enum Sort {
EXPIRY("Expiry", Comparator.comparingLong(p -> p.until)),
TIME("Date", Comparator.comparingLong(p -> p.time)),
PLAYER_NAME("Player name", (p1, p2) -> String.CASE_INSENSITIVE_ORDER.compare(PlayerUtil.offlinePlayerName(p1.to), PlayerUtil.offlinePlayerName(p2.to))),
MODERATOR_NAME("Moderator name", (p1, p2) -> String.CASE_INSENSITIVE_ORDER.compare(PlayerUtil.offlinePlayerName(p1.by), PlayerUtil.offlinePlayerName(p2.by)));
public static final Material ICON = Material.COMPARATOR;
public final String name;
public final Comparator<Punishment> comparator;
Sort(String name, Comparator<Punishment> comparator) {
this.name = name;
this.comparator = comparator;
}
}
private static final NamespacedKey PUNISHMENT_STORE_KEY = new NamespacedKey("smod", "punishmentid");
private final Inventory inventory;
private final Player player;
private final PunishmentContainer container;
private List<Punishment> punishments;
private ItemStack sortStack = null;
private ItemStack filterStack = null;
private int sort = 0;
private int filter = 0;
public SModMenu(Player player, PunishmentContainer container) {
this.player = player;
this.container = container;
this.inventory = Bukkit.createInventory(this, 54, Component.text("SMod Menu"));
reload();
}
public Sort getSort(){
return Sort.values()[sort];
}
public Filter getFilter(){
return Filter.values()[filter];
}
private void reload(){
this.punishments = container.copy().stream().filter(getFilter().filter).sorted(getSort().comparator).toList();
}
@Override
public int lastPage() {
return Math.max((punishments.size() - 1) / 45, 0);
}
public void cycleFilter(boolean backwards){
player.playSound(player, Sound.UI_BUTTON_CLICK, 1f, backwards ? 0.8f : 2f);
if (backwards){
if (filter <= 0){
filter = Filter.values().length-1;
} else {
filter--;
}
} else {
if (filter >= Filter.values().length-1){
filter = 0;
} else {
filter++;
}
}
reload();
refresh();
}
public void cycleSort(boolean backwards){
player.playSound(player, Sound.UI_BUTTON_CLICK, 1f, backwards ? 0.8f : 2f);
if (backwards){
if (sort <= 0){
sort = Sort.values().length-1;
} else {
sort--;
}
} else {
if (sort >= Sort.values().length-1){
sort = 0;
} else {
sort++;
}
}
reload();
refresh();
}
@Override
public void switchPage() {
player.playSound(player, Sound.BLOCK_STONE_HIT, 0.75f, 1f);
}
private ItemStack createFilterItem(){
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)));
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));
lore.add(filterText);
}
lore.add(Component.empty());
lore.add(applyFormatting(Component.text("\u00BB Click to switch filter").color(NamedTextColor.GOLD)));
meta.lore(lore);
});
filterStack = stack;
return 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)));
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));
lore.add(sortText);
}
lore.add(Component.empty());
lore.add(applyFormatting(Component.text("\u00BB Click to switch sorting option").color(NamedTextColor.GOLD)));
meta.lore(lore);
});
sortStack = stack;
return 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)));
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))));
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))));
long remainingTime = punishment.until - System.currentTimeMillis();
final String expires;
if (remainingTime > 0){
expires = "in " + TimeUtil.formatTimeLong(remainingTime);
} else {
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(Component.text("Reason: ").color(SECONDARY_COLOR).append(Component.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))));
} 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)));
}
}
meta.lore(lore);
});
return stack;
}
@Override
public void refresh() {
for (int i = 45; i < 54; i++) {
inventory.setItem(i, createEmptyStack());
}
inventory.setItem(45, createPreviousPageStack());
inventory.setItem(53, createNextPageStack());
inventory.setItem(50, createFilterItem());
inventory.setItem(48, createSortItem());
for (int i = 0; i < 45; i++) {
int ci = i + (getPage() * 45);
if (punishments.size() > ci){
final Punishment punishment = punishments.get(ci);
final ItemStack item = createPunishmentItem(punishment);
if (punishment.isActive()){
if ((punishment.type == PunishmentType.BAN && player.hasPermission("smod.unban")) || (punishment.type == PunishmentType.MUTE && player.hasPermission("smod.unmute"))) {
item.editMeta(meta -> meta.getPersistentDataContainer().set(PUNISHMENT_STORE_KEY, PersistentDataType.LONG, punishment.time));
}
}
inventory.setItem(i, item);
} else {
inventory.setItem(i, new ItemStack(Material.AIR));
}
}
}
@Override
public void click(ItemStack stack, InventoryClickEvent event) {
super.click(stack, event);
if (stack != null) {
if (stack.equals(filterStack)){
cycleFilter(event.isRightClick());
} else if (stack.equals(sortStack)){
cycleSort(event.isRightClick());
}
final ItemMeta itemMeta = stack.getItemMeta();
if (itemMeta != null) {
final PersistentDataContainer persistentDataContainer = itemMeta.getPersistentDataContainer();
final Long timestamp = persistentDataContainer.get(PUNISHMENT_STORE_KEY, PersistentDataType.LONG);
if (timestamp != null) {
final Punishment punishment = container.findByTimestamp(timestamp);
if (punishment != null) {
new ConfirmationInventory(player, "Do you want to undo this punishment?", () -> {
punishment.undo(player.getUniqueId());
punishment.broadcastUndo(container);
player.playSound(player, Sound.BLOCK_NOTE_BLOCK_PLING, 1f, 2f);
this.open();
}, this::open, false).open();
}
}
}
}
}
@Override
public void open() {
reload();
refresh();
player.openInventory(this.inventory);
}
@Override
public @NotNull Inventory getInventory() {
return inventory;
}
}
@@ -1,37 +0,0 @@
package de.shiewk.smoderation.listener;
import de.shiewk.smoderation.SModeration;
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.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
public class VanishListener implements Listener {
@EventHandler public void onPlayerQuit(PlayerQuitEvent event){
final Player player = event.getPlayer();
if (SModeration.isVanished(player)){
SModeration.toggleVanish(player);
}
for (Player vanishedPlayer : SModeration.getVanishedPlayers()) {
// to clean up visibility status
player.hideEntity(SModeration.PLUGIN, vanishedPlayer);
}
}
@EventHandler(priority = EventPriority.MONITOR) public void onPlayerJoin(PlayerJoinEvent event){
Bukkit.getScheduler().scheduleSyncDelayedTask(SModeration.PLUGIN, () -> {
final Player player = event.getPlayer();
if (player.hasPermission("smod.vanish.see")){
for (Player vanishedPlayer : SModeration.getVanishedPlayers()) {
// to show visible vanished players
player.showEntity(SModeration.PLUGIN, vanishedPlayer);
}
}
});
}
}
@@ -0,0 +1,111 @@
package de.shiewk.smoderation.paper;
import de.shiewk.smoderation.paper.command.*;
import de.shiewk.smoderation.paper.config.SModerationConfig;
import de.shiewk.smoderation.paper.input.ChatInput;
import de.shiewk.smoderation.paper.input.ChatInputListener;
import de.shiewk.smoderation.paper.listener.*;
import de.shiewk.smoderation.paper.storage.PunishmentContainer;
import de.shiewk.smoderation.paper.util.SchedulerUtil;
import io.papermc.paper.command.brigadier.Commands;
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
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.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import static de.shiewk.smoderation.paper.command.VanishCommand.isVanished;
import static de.shiewk.smoderation.paper.command.VanishCommand.toggleVanish;
import static net.kyori.adventure.text.Component.text;
import static org.bukkit.Bukkit.getPluginManager;
public final class SModerationPaper extends JavaPlugin {
public static final PunishmentContainer container = new PunishmentContainer();
public static ComponentLogger LOGGER = null;
public static SModerationPaper PLUGIN = null;
public static SModerationConfig CONFIG = null;
public static File SAVE_FILE = null;
private static SkinTextureProvider textureProvider = null;
public static final TextColor PRIMARY_COLOR = TextColor.color(212, 0, 255);
public static final TextColor SECONDARY_COLOR = TextColor.color(52, 143, 255);
public static final TextColor INACTIVE_COLOR = NamedTextColor.GRAY;
public static final TextComponent CHAT_PREFIX = text("SM \u00BB ").color(PRIMARY_COLOR);
@Override
public void onLoad() {
LOGGER = getComponentLogger();
PLUGIN = this;
CONFIG = new SModerationConfig(this.getConfig(), this);
SAVE_FILE = new File(this.getDataFolder().getAbsolutePath() + "/container.gz");
}
@Override
public void onEnable() {
CONFIG.reload();
getPluginManager().registerEvents(new PunishmentListener(), 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);
getPluginManager().registerEvents(new SocialSpyListener(), this);
getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> {
Commands commands = event.registrar();
registerCommand(commands, new KickCommand());
registerCommand(commands, new ModLogsCommand());
registerCommand(commands, new SModCommand());
registerCommand(commands, new InvseeCommand());
registerCommand(commands, new EnderchestSeeCommand());
registerCommand(commands, new SocialSpyCommand());
registerCommand(commands, new VanishCommand());
registerCommand(commands, new UnmuteCommand());
registerCommand(commands, new UnbanCommand());
registerCommand(commands, new MuteCommand());
registerCommand(commands, new BanCommand());
});
SchedulerUtil.scheduleGlobalRepeating(PLUGIN, CustomInventoryListener::onTick, 1, 1);
SchedulerUtil.scheduleGlobalRepeating(PLUGIN, ChatInput::tickAll, 1, 1);
container.load(SAVE_FILE);
LOGGER.info("Folia: {}", SchedulerUtil.isFolia ? "yes" : "no");
}
private void registerCommand(Commands commands, CommandProvider provider){
commands.register(
provider.getCommandNode(),
provider.getCommandDescription(),
provider.getAliases()
);
}
@Override
public void onDisable() {
SModerationPaper.container.save(SModerationPaper.SAVE_FILE);
for (Player player : Bukkit.getOnlinePlayers()) {
// in case players are still vanished when the server shuts down
if (isVanished(player)){
toggleVanish(player);
}
}
}
public static SkinTextureProvider getTextureProvider() {
return textureProvider;
}
public static void setTextureProvider(SkinTextureProvider textureProvider) {
SModerationPaper.textureProvider = textureProvider;
}
}
@@ -0,0 +1,10 @@
package de.shiewk.smoderation.paper;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public interface SkinTextureProvider {
CompletableFuture<String> textureProperty(UUID player);
}
@@ -0,0 +1,98 @@
package de.shiewk.smoderation.paper.command;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.tree.LiteralCommandNode;
import de.shiewk.smoderation.paper.SModerationPaper;
import de.shiewk.smoderation.paper.command.argument.DurationArgument;
import de.shiewk.smoderation.paper.command.argument.PlayerUUIDArgument;
import de.shiewk.smoderation.paper.punishments.Punishment;
import de.shiewk.smoderation.paper.util.CommandUtil;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import static io.papermc.paper.command.brigadier.Commands.argument;
import static io.papermc.paper.command.brigadier.Commands.literal;
public final class BanCommand implements CommandProvider {
@Override
public LiteralCommandNode<CommandSourceStack> getCommandNode() {
return literal("ban")
.requires(CommandUtil.requirePermission("smod.ban"))
.then(argument("player", new PlayerUUIDArgument())
.then(argument("duration", new DurationArgument())
.executes(this::banWithoutReason)
.then(argument("reason", StringArgumentType.greedyString())
.executes(this::banWithReason)
)
)
)
.build();
}
private int banWithoutReason(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
if (SModerationPaper.CONFIG.shouldForceReason()){
CommandUtil.error("Please provide a reason.");
}
UUID sender = CommandUtil.getSenderUUID(context.getSource());
UUID target = context.getArgument("player", UUID.class);
long duration = context.getArgument("duration", Long.class);
executeBan(sender, target, duration, Punishment.DEFAULT_REASON);
return Command.SINGLE_SUCCESS;
}
private int banWithReason(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
UUID sender = CommandUtil.getSenderUUID(context.getSource());
UUID target = context.getArgument("player", UUID.class);
long duration = context.getArgument("duration", Long.class);
String reason = StringArgumentType.getString(context, "reason");
executeBan(sender, target, duration, reason);
return Command.SINGLE_SUCCESS;
}
public static void executeBan(UUID sender, UUID target, long duration, String reason) throws CommandSyntaxException {
Player targetPlayer = Bukkit.getPlayer(target);
if (duration == 0){
if (targetPlayer == null){
CommandUtil.error("You can't ban an offline player for less than 1ms.");
} else {
KickCommand.executeKick(sender, targetPlayer, reason);
}
return;
}
if (sender.equals(target)) {
CommandUtil.error("You can't ban yourself.");
} else {
if (targetPlayer != null && targetPlayer.hasPermission("smod.preventban")){
CommandUtil.error("This player can't be banned.");
} else {
final Punishment punishment = Punishment.ban(
System.currentTimeMillis(),
System.currentTimeMillis() + duration,
sender,
target,
reason
);
Punishment.issue(punishment, SModerationPaper.container);
}
}
}
@Override
public String getCommandDescription() {
return "Bans a player for a customizable duration.";
}
@Override
public Collection<String> getAliases() {
return List.of("smodban");
}
}
@@ -0,0 +1,19 @@
package de.shiewk.smoderation.paper.command;
import com.mojang.brigadier.tree.LiteralCommandNode;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import java.util.Collection;
import java.util.List;
public interface CommandProvider {
LiteralCommandNode<CommandSourceStack> getCommandNode();
String getCommandDescription();
default Collection<String> getAliases(){
return List.of();
}
}
@@ -0,0 +1,53 @@
package de.shiewk.smoderation.paper.command;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.tree.LiteralCommandNode;
import de.shiewk.smoderation.paper.util.CommandUtil;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import io.papermc.paper.command.brigadier.argument.ArgumentTypes;
import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player;
import java.util.Collection;
import java.util.List;
import static de.shiewk.smoderation.paper.SModerationPaper.*;
import static io.papermc.paper.command.brigadier.Commands.argument;
import static io.papermc.paper.command.brigadier.Commands.literal;
public final class EnderchestSeeCommand implements CommandProvider {
@Override
public LiteralCommandNode<CommandSourceStack> getCommandNode() {
return literal("enderchestsee")
.requires(CommandUtil.requirePermission("smod.enderchestsee"))
.then(argument("player", ArgumentTypes.player())
.executes(this::openEnderChest)
)
.build();
}
private int openEnderChest(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
Player sender = CommandUtil.getExecutingPlayer(context.getSource());
Player target = CommandUtil.getPlayerSingle(context, "player");
sender.sendMessage(CHAT_PREFIX.append(
Component.text("Opening ender chest of ").color(PRIMARY_COLOR)
.append(target.teamDisplayName().colorIfAbsent(SECONDARY_COLOR))
.append(Component.text("."))
));
sender.openInventory(target.getEnderChest());
return Command.SINGLE_SUCCESS;
}
@Override
public String getCommandDescription() {
return "Views the ender chest of a player.";
}
@Override
public Collection<String> getAliases() {
return List.of("ecsee");
}
}
@@ -0,0 +1,82 @@
package de.shiewk.smoderation.paper.command;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.tree.LiteralCommandNode;
import de.shiewk.smoderation.paper.inventory.InvSeeEquipmentInventory;
import de.shiewk.smoderation.paper.inventory.InvSeeInventory;
import de.shiewk.smoderation.paper.util.CommandUtil;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import io.papermc.paper.command.brigadier.argument.ArgumentTypes;
import org.bukkit.entity.Player;
import java.util.Collection;
import java.util.List;
import static de.shiewk.smoderation.paper.SModerationPaper.*;
import static io.papermc.paper.command.brigadier.Commands.argument;
import static io.papermc.paper.command.brigadier.Commands.literal;
import static net.kyori.adventure.text.Component.text;
public final class InvseeCommand implements CommandProvider {
@Override
public LiteralCommandNode<CommandSourceStack> getCommandNode() {
return literal("invsee")
.requires(CommandUtil.requirePermission("smod.invsee"))
.then(argument("player", ArgumentTypes.player())
.executes(this::invseeInventory)
.then(literal("inventory")
.executes(this::invseeInventory)
)
.then(literal("armor")
.executes(this::invseeEquipment)
)
.then(literal("equipment")
.executes(this::invseeEquipment)
)
)
.build();
}
private int invseeInventory(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
Player sender = CommandUtil.getExecutingPlayer(context.getSource());
Player target = CommandUtil.getPlayerSingle(context, "player");
if (sender.equals(target)){
CommandUtil.error("You can't open your own inventory.");
}
sender.sendMessage(CHAT_PREFIX.append(
text("Opening inventory of ").color(PRIMARY_COLOR)
.append(target.teamDisplayName().colorIfAbsent(SECONDARY_COLOR))
.append(text("."))
));
new InvSeeInventory(sender, target).open();
return Command.SINGLE_SUCCESS;
}
private int invseeEquipment(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
Player sender = CommandUtil.getExecutingPlayer(context.getSource());
Player target = CommandUtil.getPlayerSingle(context, "player");
if (sender.equals(target)){
CommandUtil.error("You can't open your own inventory.");
}
sender.sendMessage(CHAT_PREFIX.append(
text("Opening inventory of ").color(PRIMARY_COLOR)
.append(target.teamDisplayName().colorIfAbsent(SECONDARY_COLOR))
.append(text("."))
));
new InvSeeEquipmentInventory(sender, target).open();
return Command.SINGLE_SUCCESS;
}
@Override
public String getCommandDescription() {
return "Views the inventory of another player.";
}
@Override
public Collection<String> getAliases() {
return List.of("sinvsee", "smodinvsee", "invs");
}
}
@@ -0,0 +1,80 @@
package de.shiewk.smoderation.paper.command;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.tree.LiteralCommandNode;
import de.shiewk.smoderation.paper.SModerationPaper;
import de.shiewk.smoderation.paper.punishments.Punishment;
import de.shiewk.smoderation.paper.util.CommandUtil;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import io.papermc.paper.command.brigadier.argument.ArgumentTypes;
import org.bukkit.entity.Player;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import static io.papermc.paper.command.brigadier.Commands.argument;
import static io.papermc.paper.command.brigadier.Commands.literal;
public final class KickCommand implements CommandProvider {
@Override
public LiteralCommandNode<CommandSourceStack> getCommandNode() {
return literal("kick")
.requires(CommandUtil.requirePermission("smod.kick"))
.then(argument("player", ArgumentTypes.player())
.executes(this::kickWithoutReason)
.then(argument("reason", StringArgumentType.greedyString())
.executes(this::kickWithReason)
)
)
.build();
}
private int kickWithReason(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
UUID sender = CommandUtil.getSenderUUID(context.getSource());
Player target = CommandUtil.getPlayerSingle(context, "player");
String reason = StringArgumentType.getString(context, "reason");
executeKick(sender, target, reason);
return Command.SINGLE_SUCCESS;
}
private int kickWithoutReason(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
if (SModerationPaper.CONFIG.shouldForceReason()){
CommandUtil.error("Please provide a reason.");
}
UUID sender = CommandUtil.getSenderUUID(context.getSource());
Player target = CommandUtil.getPlayerSingle(context, "player");
executeKick(sender, target, Punishment.DEFAULT_REASON);
return Command.SINGLE_SUCCESS;
}
public static void executeKick(UUID sender, Player target, String reason) throws CommandSyntaxException {
UUID targetId = target.getUniqueId();
if (sender.equals(targetId)) {
CommandUtil.error("You can't kick yourself.");
} else if (target.hasPermission("smod.preventkick")){
CommandUtil.error("This player can't be kicked.");
}
final Punishment punishment = Punishment.kick(
System.currentTimeMillis(),
sender,
targetId,
reason
);
Punishment.issue(punishment, SModerationPaper.container);
}
@Override
public String getCommandDescription() {
return "Kicks a player";
}
@Override
public Collection<String> getAliases() {
return List.of("smodkick");
}
}
@@ -0,0 +1,69 @@
package de.shiewk.smoderation.paper.command;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.tree.LiteralCommandNode;
import de.shiewk.smoderation.paper.SModerationPaper;
import de.shiewk.smoderation.paper.command.argument.PlayerUUIDArgument;
import de.shiewk.smoderation.paper.punishments.Punishment;
import de.shiewk.smoderation.paper.punishments.PunishmentType;
import de.shiewk.smoderation.paper.util.CommandUtil;
import de.shiewk.smoderation.paper.util.PlayerUtil;
import de.shiewk.smoderation.paper.util.TimeUtil;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import net.kyori.adventure.text.Component;
import org.bukkit.command.CommandSender;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import static de.shiewk.smoderation.paper.SModerationPaper.*;
import static io.papermc.paper.command.brigadier.Commands.argument;
import static io.papermc.paper.command.brigadier.Commands.literal;
public final class ModLogsCommand implements CommandProvider {
@Override
public LiteralCommandNode<CommandSourceStack> getCommandNode() {
return literal("modlogs")
.requires(CommandUtil.requirePermission("smod.logs"))
.then(argument("player", new PlayerUUIDArgument())
.executes(this::showModLogs)
)
.build();
}
private int showModLogs(CommandContext<CommandSourceStack> context) {
CommandSender sender = context.getSource().getSender();
UUID uuid = context.getArgument("player", UUID.class);
String name = PlayerUtil.offlinePlayerName(uuid);
sender.sendMessage(CHAT_PREFIX.append(Component.text("Player ").color(PRIMARY_COLOR)
.append(Component.text(name).color(SECONDARY_COLOR))
.append(Component.text(" (%s)".formatted(uuid)).color(INACTIVE_COLOR))));
final List<Punishment> punishments = SModerationPaper.container.findAll(p -> p.to.equals(uuid) && p.isActive());
for (Punishment punishment : punishments) {
sender.sendMessage(Component.text("- is currently ").color(PRIMARY_COLOR)
.append(Component.text(punishment.type == PunishmentType.BAN ? "banned" : "muted").color(SECONDARY_COLOR))
.append(Component.text(" until ").color(PRIMARY_COLOR))
.append(Component.text(TimeUtil.calendarTimestamp(punishment.until)).color(SECONDARY_COLOR))
.append(Component.text(" (in %s)".formatted(TimeUtil.formatTimeLong(punishment.until - System.currentTimeMillis()))).color(INACTIVE_COLOR))
.append(Component.text(". Reason: ").color(PRIMARY_COLOR))
.append(Component.text(punishment.reason).color(SECONDARY_COLOR)));
}
if (punishments.isEmpty()){
sender.sendMessage(Component.text("- has no punishments.").color(PRIMARY_COLOR));
}
return Command.SINGLE_SUCCESS;
}
@Override
public String getCommandDescription() {
return "Views all current punishments of a player.";
}
@Override
public Collection<String> getAliases() {
return List.of("logs", "seen", "smodlogs");
}
}
@@ -0,0 +1,90 @@
package de.shiewk.smoderation.paper.command;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.tree.LiteralCommandNode;
import de.shiewk.smoderation.paper.SModerationPaper;
import de.shiewk.smoderation.paper.command.argument.DurationArgument;
import de.shiewk.smoderation.paper.command.argument.PlayerUUIDArgument;
import de.shiewk.smoderation.paper.punishments.Punishment;
import de.shiewk.smoderation.paper.util.CommandUtil;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import static io.papermc.paper.command.brigadier.Commands.argument;
import static io.papermc.paper.command.brigadier.Commands.literal;
public final class MuteCommand implements CommandProvider {
@Override
public LiteralCommandNode<CommandSourceStack> getCommandNode() {
return literal("mute")
.requires(CommandUtil.requirePermission("smod.mute"))
.then(argument("player", new PlayerUUIDArgument())
.then(argument("duration", new DurationArgument())
.executes(this::muteWithoutReason)
.then(argument("reason", StringArgumentType.greedyString())
.executes(this::muteWithReason)
)
)
)
.build();
}
private int muteWithoutReason(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
if (SModerationPaper.CONFIG.shouldForceReason()){
CommandUtil.error("Please provide a reason.");
}
UUID sender = CommandUtil.getSenderUUID(context.getSource());
UUID target = context.getArgument("player", UUID.class);
long duration = context.getArgument("duration", Long.class);
executeMute(sender, target, duration, Punishment.DEFAULT_REASON);
return Command.SINGLE_SUCCESS;
}
private int muteWithReason(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
UUID sender = CommandUtil.getSenderUUID(context.getSource());
UUID target = context.getArgument("player", UUID.class);
long duration = context.getArgument("duration", Long.class);
String reason = StringArgumentType.getString(context, "reason");
executeMute(sender, target, duration, reason);
return Command.SINGLE_SUCCESS;
}
public static void executeMute(UUID sender, UUID target, long duration, String reason) throws CommandSyntaxException {
if (sender.equals(target)) {
CommandUtil.error("You can't mute yourself.");
} else {
Player targetPlayer = Bukkit.getPlayer(target);
if (targetPlayer != null && targetPlayer.hasPermission("smod.preventmute")){
CommandUtil.error("This player can't be muted.");
} else {
final Punishment punishment = Punishment.mute(
System.currentTimeMillis(),
System.currentTimeMillis() + duration,
sender,
target,
reason
);
Punishment.issue(punishment, SModerationPaper.container);
}
}
}
@Override
public String getCommandDescription() {
return "Mutes a player for a customizable duration.";
}
@Override
public Collection<String> getAliases() {
return List.of("smodmute");
}
}
@@ -0,0 +1,43 @@
package de.shiewk.smoderation.paper.command;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.tree.LiteralCommandNode;
import de.shiewk.smoderation.paper.SModerationPaper;
import de.shiewk.smoderation.paper.inventory.SModMenu;
import de.shiewk.smoderation.paper.util.CommandUtil;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import org.bukkit.entity.Player;
import java.util.Collection;
import java.util.List;
import static io.papermc.paper.command.brigadier.Commands.literal;
public final class SModCommand implements CommandProvider {
@Override
public LiteralCommandNode<CommandSourceStack> getCommandNode() {
return literal("smod")
.requires(CommandUtil.requirePermission("smod.menu"))
.executes(this::openMenu)
.build();
}
private int openMenu(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
Player player = CommandUtil.getExecutingPlayer(context.getSource());
new SModMenu(player, SModerationPaper.container).open();
return Command.SINGLE_SUCCESS;
}
@Override
public String getCommandDescription() {
return "Shows the SModeration menu.";
}
@Override
public Collection<String> getAliases() {
return List.of("smodmenu", "smoderation");
}
}
@@ -0,0 +1,49 @@
package de.shiewk.smoderation.paper.command;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.tree.LiteralCommandNode;
import de.shiewk.smoderation.paper.listener.SocialSpyListener;
import de.shiewk.smoderation.paper.util.CommandUtil;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.CommandSender;
import java.util.Collection;
import java.util.List;
import static de.shiewk.smoderation.paper.SModerationPaper.CHAT_PREFIX;
import static io.papermc.paper.command.brigadier.Commands.literal;
import static net.kyori.adventure.text.Component.text;
public final class SocialSpyCommand implements CommandProvider {
@Override
public LiteralCommandNode<CommandSourceStack> getCommandNode() {
return literal("socialspy")
.requires(CommandUtil.requirePermission("smod.socialspy"))
.executes(this::toggleSocialSpy)
.build();
}
private int toggleSocialSpy(CommandContext<CommandSourceStack> context) {
CommandSender sender = context.getSource().getSender();
final boolean enabled = SocialSpyListener.toggle(sender);
sender.sendMessage(CHAT_PREFIX.append(text("SocialSpy ").append(
enabled ?
text("enabled").color(NamedTextColor.GREEN) :
text("disabled").color(NamedTextColor.RED)
).append(text("."))));
return Command.SINGLE_SUCCESS;
}
@Override
public String getCommandDescription() {
return "Enables socialspy mode (you can see private messages of other players)";
}
@Override
public Collection<String> getAliases() {
return List.of("smodsocialspy");
}
}
@@ -0,0 +1,56 @@
package de.shiewk.smoderation.paper.command;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.tree.LiteralCommandNode;
import de.shiewk.smoderation.paper.SModerationPaper;
import de.shiewk.smoderation.paper.command.argument.PlayerUUIDArgument;
import de.shiewk.smoderation.paper.punishments.Punishment;
import de.shiewk.smoderation.paper.punishments.PunishmentType;
import de.shiewk.smoderation.paper.util.CommandUtil;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import static io.papermc.paper.command.brigadier.Commands.argument;
import static io.papermc.paper.command.brigadier.Commands.literal;
public final class UnbanCommand implements CommandProvider {
@Override
public LiteralCommandNode<CommandSourceStack> getCommandNode() {
return literal("unban")
.requires(CommandUtil.requirePermission("smod.unban"))
.then(argument("player", new PlayerUUIDArgument())
.executes(this::unbanPlayer)
)
.build();
}
private int unbanPlayer(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
UUID senderUUID = CommandUtil.getSenderUUID(context.getSource());
UUID target = context.getArgument("player", UUID.class);
final Punishment punishment = SModerationPaper.container.find(
p -> p.to.equals(target) && p.isActive() && p.type == PunishmentType.BAN
);
if (punishment != null) {
punishment.undo(senderUUID);
punishment.broadcastUndo(SModerationPaper.container);
} else {
CommandUtil.error("This player is not banned.");
}
return com.mojang.brigadier.Command.SINGLE_SUCCESS;
}
@Override
public String getCommandDescription() {
return "Unbans a banned player.";
}
@Override
public Collection<String> getAliases() {
return List.of("smodunban");
}
}
@@ -0,0 +1,57 @@
package de.shiewk.smoderation.paper.command;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.tree.LiteralCommandNode;
import de.shiewk.smoderation.paper.SModerationPaper;
import de.shiewk.smoderation.paper.command.argument.PlayerUUIDArgument;
import de.shiewk.smoderation.paper.punishments.Punishment;
import de.shiewk.smoderation.paper.punishments.PunishmentType;
import de.shiewk.smoderation.paper.util.CommandUtil;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import static io.papermc.paper.command.brigadier.Commands.argument;
import static io.papermc.paper.command.brigadier.Commands.literal;
public final class UnmuteCommand implements CommandProvider {
@Override
public LiteralCommandNode<CommandSourceStack> getCommandNode() {
return literal("unmute")
.requires(CommandUtil.requirePermission("smod.unmute"))
.then(argument("player", new PlayerUUIDArgument())
.executes(this::unmutePlayer)
)
.build();
}
private int unmutePlayer(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
UUID senderUUID = CommandUtil.getSenderUUID(context.getSource());
UUID target = context.getArgument("player", UUID.class);
final Punishment punishment = SModerationPaper.container.find(
p -> p.to.equals(target) && p.isActive() && p.type == PunishmentType.MUTE
);
if (punishment != null) {
punishment.undo(senderUUID);
punishment.broadcastUndo(SModerationPaper.container);
} else {
CommandUtil.error("This player is not muted.");
}
return Command.SINGLE_SUCCESS;
}
@Override
public String getCommandDescription() {
return "Unmutes a muted player.";
}
@Override
public Collection<String> getAliases() {
return List.of("smodunmute");
}
}
@@ -0,0 +1,177 @@
package de.shiewk.smoderation.paper.command;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.tree.LiteralCommandNode;
import de.shiewk.smoderation.paper.SModerationPaper;
import de.shiewk.smoderation.paper.event.VanishToggleEvent;
import de.shiewk.smoderation.paper.util.CommandUtil;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import io.papermc.paper.command.brigadier.argument.ArgumentTypes;
import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectListIterator;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.Collection;
import java.util.List;
import static de.shiewk.smoderation.paper.SModerationPaper.*;
import static io.papermc.paper.command.brigadier.Commands.argument;
import static io.papermc.paper.command.brigadier.Commands.literal;
import static net.kyori.adventure.text.Component.text;
public final class VanishCommand implements CommandProvider {
public static final NamespacedKey KEY_VANISHED = new NamespacedKey("smoderation", "vanished");
@Override
public LiteralCommandNode<CommandSourceStack> getCommandNode() {
return literal("vanish")
.requires(CommandUtil.requirePermission("smod.vanish"))
.executes(this::toggleVanishSelf)
.then(literal("toggle")
.executes(this::toggleVanishSelf)
.then(argument("targets", ArgumentTypes.players())
.executes(this::toggleVanishForTargets)
)
)
.then(literal("list")
.requires(CommandUtil.requirePermission("smod.vanish.see"))
.executes(this::listVanishedPlayers)
)
.build();
}
private int toggleVanishForTargets(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
List<Player> targets = context.getArgument("targets", PlayerSelectorArgumentResolver.class).resolve(context.getSource());
if (targets.isEmpty()){
CommandUtil.error("No player was found.");
} else {
for (Player target : targets) {
toggleVanish(target);
}
}
return Command.SINGLE_SUCCESS;
}
private int toggleVanishSelf(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
toggleVanish(CommandUtil.getExecutingPlayer(context.getSource()));
return Command.SINGLE_SUCCESS;
}
private int listVanishedPlayers(CommandContext<CommandSourceStack> context) {
listVanishedPlayersTo(context.getSource().getSender());
return Command.SINGLE_SUCCESS;
}
@Override
public String getCommandDescription() {
return "Toggles vanish mode which prevents other players from seeing you're online";
}
@Override
public Collection<String> getAliases() {
return List.of("smvanish", "smodvanish", "v", "smv");
}
private static final ObjectArrayList<Player> vanishedPlayers = new ObjectArrayList<>(1);
public static void toggleVanish(Player player){
final boolean newStatus = !isVanished(player);
VanishToggleEvent event = new VanishToggleEvent(player, newStatus);
Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled()){
return;
}
if (newStatus){
vanishedPlayers.add(player);
for (CommandSender sender : SModerationPaper.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 toggleVanishSilent(Player player, boolean force){
final boolean newStatus = !isVanished(player);
VanishToggleEvent event = new VanishToggleEvent(player, newStatus);
Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled() && !force){
return false;
}
if (newStatus){
vanishedPlayers.add(player);
player.setVisibleByDefault(false);
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
if (onlinePlayer.hasPermission("smod.vanish.see")){
onlinePlayer.showEntity(PLUGIN, player);
}
}
} else {
vanishedPlayers.remove(player);
player.setVisibleByDefault(true);
}
return 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);
}
}
}
@@ -0,0 +1,74 @@
package de.shiewk.smoderation.paper.command.argument;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import de.shiewk.smoderation.paper.util.CommandUtil;
import io.papermc.paper.command.brigadier.argument.CustomArgumentType;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.MatchResult;
import java.util.regex.Pattern;
public final class DurationArgument implements CustomArgumentType.Converted<Long, String> {
public static final Pattern DURATION_PATTERN = Pattern.compile("([0-9]{1,9})(ms|s|min|h|d|w|mo|y)");
public static final Pattern VALIDATION_PATTERN = Pattern.compile("(([0-9]{1,9})(ms|s|min|h|d|w|mo|y))+");
@Override
public @NotNull Long convert(@NotNull String nativeType) throws CommandSyntaxException {
if (!VALIDATION_PATTERN.matcher(nativeType).matches()){
CommandUtil.error("Please provide a valid duration, e.g. '1d6h30min'");
}
AtomicLong totalDuration = new AtomicLong();
for (MatchResult result : DURATION_PATTERN.matcher(nativeType).results().toList()) {
long amount = Long.parseLong(result.group(1));
long timeSpan = switch (result.group(2)) {
case "ms" -> 1;
case "s" -> 1000;
case "min" -> 60_000;
case "h" -> 3600_000;
case "d" -> 86400_000;
case "w" -> 604800_000;
case "mo" -> 2_592_000_000L;
case "y" -> 31_536_000_000L;
default -> {
CommandUtil.error("Invalid time span '%s'".formatted(result.group(2)));
throw new UnknownError(); // can't happen
}
};
totalDuration.addAndGet(amount*timeSpan);
}
return totalDuration.get();
}
@Override
public @NotNull ArgumentType<String> getNativeType() {
return StringArgumentType.word();
}
@Override
public @NotNull <S> CompletableFuture<Suggestions> listSuggestions(@NotNull CommandContext<S> context, @NotNull SuggestionsBuilder builder) {
if (builder.getRemaining().isBlank()){
List.of(
"100ms",
"15s",
"2min",
"3h",
"7d",
"1w",
"3mo",
"1y",
"1mo15d",
"2h30min"
).forEach(builder::suggest);
}
return builder.buildFuture();
}
}
@@ -0,0 +1,51 @@
package de.shiewk.smoderation.paper.command.argument;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import de.shiewk.smoderation.paper.util.CommandUtil;
import io.papermc.paper.command.brigadier.argument.CustomArgumentType;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public final class PlayerUUIDArgument implements CustomArgumentType.Converted<UUID, String> {
@Override
public @NotNull UUID convert(@NotNull String nativeType) throws CommandSyntaxException {
try {
return UUID.fromString(nativeType);
} catch (IllegalArgumentException e) {
OfflinePlayer player = Bukkit.getOfflinePlayerIfCached(nativeType);
if (player != null){
return player.getUniqueId();
} else {
CommandUtil.error("This player is not cached. Try providing an UUID instead.");
throw new UnknownError(); // can't happen
}
}
}
@Override
public @NotNull ArgumentType<String> getNativeType() {
return StringArgumentType.word();
}
@Override
public @NotNull <S> CompletableFuture<Suggestions> listSuggestions(@NotNull CommandContext<S> context, @NotNull SuggestionsBuilder builder) {
Bukkit.getOnlinePlayers()
.stream()
.map(Player::getName)
.filter(name -> name.toLowerCase().startsWith(builder.getRemainingLowerCase()))
.forEach(builder::suggest);
return builder.buildFuture();
}
}
@@ -0,0 +1,60 @@
package de.shiewk.smoderation.paper.config;
import de.shiewk.smoderation.paper.SModerationPaper;
import org.bukkit.configuration.file.FileConfiguration;
import java.util.List;
public class SModerationConfig {
private final FileConfiguration config;
private final SModerationPaper plugin;
public SModerationConfig(FileConfiguration config, SModerationPaper plugin) {
this.config = config;
this.plugin = plugin;
}
private List<String> socialSpyCommands = List.of(
"w",
"tell",
"msg",
"teammsg",
"tm",
"minecraft:w",
"minecraft:tell",
"minecraft:msg",
"minecraft:teammsg",
"minecraft:tm"
);
private boolean forceReason = false;
public void reload(){
socialSpyCommands = loadOrSetStringList("socialspy-commands", socialSpyCommands);
forceReason = loadOrSetBoolean("force-reason", forceReason);
}
private List<String> loadOrSetStringList(String path, List<String> defaultValue) {
if (!config.contains(path)){
config.set(path, defaultValue);
plugin.saveConfig();
}
return config.getStringList(path);
}
private boolean loadOrSetBoolean(String path, boolean defaultValue) {
if (!config.contains(path)){
config.set(path, defaultValue);
plugin.saveConfig();
}
return config.getBoolean(path);
}
public List<String> getSocialSpyCommands(){
return socialSpyCommands;
}
public boolean shouldForceReason(){
return forceReason;
}
}
@@ -1,7 +1,7 @@
package de.shiewk.smoderation.event; package de.shiewk.smoderation.paper.event;
import de.shiewk.smoderation.punishments.Punishment; import de.shiewk.smoderation.paper.punishments.Punishment;
import de.shiewk.smoderation.storage.PunishmentContainer; import de.shiewk.smoderation.paper.storage.PunishmentContainer;
import org.bukkit.event.Cancellable; import org.bukkit.event.Cancellable;
import org.bukkit.event.Event; import org.bukkit.event.Event;
import org.bukkit.event.HandlerList; import org.bukkit.event.HandlerList;
@@ -0,0 +1,42 @@
package de.shiewk.smoderation.paper.event;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.jetbrains.annotations.NotNull;
public class VanishToggleEvent extends PlayerEvent implements Cancellable {
private static final HandlerList handlerList = new HandlerList();
private final boolean state;
private boolean cancelled;
public VanishToggleEvent(Player player, boolean state) {
super(player);
this.state = state;
}
public boolean getState() {
return state;
}
@Override
public @NotNull HandlerList getHandlers() {
return handlerList;
}
public static HandlerList getHandlerList() {
return handlerList;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
}
@@ -0,0 +1,70 @@
package de.shiewk.smoderation.paper.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.paper.SModerationPaper.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 + 1;
}
public 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,27 @@
package de.shiewk.smoderation.paper.input;
import io.papermc.paper.event.player.AsyncChatEvent;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
import static de.shiewk.smoderation.paper.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());
event.getPlayer().clearTitle();
}
}
@EventHandler public void onPlayerQuit(PlayerQuitEvent event){
runningInputs.remove(event.getPlayer());
}
}
@@ -0,0 +1,3 @@
package de.shiewk.smoderation.paper.inventory;
public interface AutoUpdatingCustomInventory extends CustomInventory { }
@@ -1,4 +1,4 @@
package de.shiewk.smoderation.inventory; package de.shiewk.smoderation.paper.inventory;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
@@ -1,4 +1,4 @@
package de.shiewk.smoderation.inventory; package de.shiewk.smoderation.paper.inventory;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.text.format.TextDecoration;
@@ -0,0 +1,67 @@
package de.shiewk.smoderation.paper.inventory;
import de.shiewk.smoderation.paper.SModerationPaper;
import de.shiewk.smoderation.paper.util.SchedulerUtil;
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;
SchedulerUtil.scheduleGlobal(SModerationPaper.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;
}
}
@@ -0,0 +1,70 @@
package de.shiewk.smoderation.paper.inventory;
import de.shiewk.smoderation.paper.SModerationPaper;
import de.shiewk.smoderation.paper.util.SchedulerUtil;
import org.bukkit.Bukkit;
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.inventory.InventoryClickEvent;
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 InvSeeInventory implements AutoUpdatingCustomInventory {
private final HumanEntity viewer;
private final HumanEntity subject;
private final Inventory subjectInventory;
private final Inventory inventory;
private boolean changing = false;
public InvSeeInventory(HumanEntity viewer, HumanEntity subject) {
this.viewer = viewer;
this.subject = subject;
this.subjectInventory = subject.getInventory();
this.inventory = Bukkit.createInventory(this, 36, text("Player inventory"));
}
@Override
public void refresh() {
if (!changing){
loadContents();
}
}
private void loadContents() {
for (int i = 0; i < inventory.getSize(); i++) {
inventory.setItem(i, subjectInventory.getItem(i));
}
}
@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;
SchedulerUtil.scheduleGlobal(SModerationPaper.PLUGIN, () -> {
changing = false;
applyChanges();
});
}
}
private void applyChanges() {
for (int i = 0; i < inventory.getSize(); i++) {
subjectInventory.setItem(i, inventory.getItem(i));
}
}
@Override
public @NotNull Inventory getInventory() {
return inventory;
}
}
@@ -1,4 +1,4 @@
package de.shiewk.smoderation.inventory; package de.shiewk.smoderation.paper.inventory;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
@@ -0,0 +1,439 @@
package de.shiewk.smoderation.paper.inventory;
import com.destroystokyo.paper.profile.PlayerProfile;
import com.destroystokyo.paper.profile.ProfileProperty;
import de.shiewk.smoderation.paper.SkinTextureProvider;
import de.shiewk.smoderation.paper.input.ChatInput;
import de.shiewk.smoderation.paper.punishments.Punishment;
import de.shiewk.smoderation.paper.punishments.PunishmentType;
import de.shiewk.smoderation.paper.storage.PunishmentContainer;
import de.shiewk.smoderation.paper.util.PlayerUtil;
import de.shiewk.smoderation.paper.util.SchedulerUtil;
import de.shiewk.smoderation.paper.util.TimeUtil;
import io.papermc.paper.datacomponent.DataComponentTypes;
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;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Predicate;
import static de.shiewk.smoderation.paper.SModerationPaper.*;
import static net.kyori.adventure.text.Component.text;
public class SModMenu extends PageableCustomInventory {
public enum Filter {
ACTIVE("Active punishments", Punishment::isActive),
OLD("Old punishments", p -> !p.isActive()),
ALL("All punishments", p -> true);
public static final Material ICON = Material.HOPPER;
public final String name;
public final Predicate<Punishment> filter;
Filter(String name, Predicate<Punishment> filter) {
this.name = name;
this.filter = filter;
}
}
public enum Sort {
EXPIRY("Expiry", Comparator.comparingLong(p -> p.until)),
TIME("Date", Comparator.comparingLong(p -> p.time)),
PLAYER_NAME("Player name", (p1, p2) -> String.CASE_INSENSITIVE_ORDER.compare(PlayerUtil.offlinePlayerName(p1.to), PlayerUtil.offlinePlayerName(p2.to))),
MODERATOR_NAME("Moderator name", (p1, p2) -> String.CASE_INSENSITIVE_ORDER.compare(PlayerUtil.offlinePlayerName(p1.by), PlayerUtil.offlinePlayerName(p2.by)));
public static final Material ICON = Material.COMPARATOR;
public final String name;
public final Comparator<Punishment> comparator;
Sort(String name, Comparator<Punishment> comparator) {
this.name = name;
this.comparator = comparator;
}
}
private static final NamespacedKey PUNISHMENT_STORE_KEY = new NamespacedKey("smod", "punishmentid");
private final Inventory inventory;
private final Player player;
private final PunishmentContainer container;
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, text("SMod Menu"));
reload();
}
public Sort getSort(){
return Sort.values()[sort];
}
public Filter getFilter(){
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)
.filter(p -> getType() == null || p.type == getType())
.filter(p -> p.matchesSearchQuery(searchQuery))
.sorted(getSort().comparator).toList();
}
public void promptSearchQuery(){
SchedulerUtil.scheduleForEntity(PLUGIN, player, player::closeInventory);
ChatInput.prompt(player, component -> {
if (component instanceof TextComponent text){
this.searchQuery = text.content();
// chat event is async
SchedulerUtil.scheduleForEntity(PLUGIN, player, this::open);
}
}, text("Enter your search query in chat").color(SECONDARY_COLOR), 30);
}
@Override
public int lastPage() {
return Math.max((punishments.size() - 1) / 45, 0);
}
public void cycleFilter(boolean backwards){
player.playSound(player, Sound.UI_BUTTON_CLICK, 1f, backwards ? 0.8f : 2f);
if (backwards){
if (filter <= 0){
filter = Filter.values().length-1;
} else {
filter--;
}
} else {
if (filter >= Filter.values().length-1){
filter = 0;
} else {
filter++;
}
}
reload();
refresh();
}
public void cycleSort(boolean backwards){
player.playSound(player, Sound.UI_BUTTON_CLICK, 1f, backwards ? 0.8f : 2f);
if (backwards){
if (sort <= 0){
sort = Sort.values().length-1;
} else {
sort--;
}
} else {
if (sort >= Sort.values().length-1){
sort = 0;
} else {
sort++;
}
}
reload();
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);
}
private ItemStack createFilterItem(){
final Filter filter = getFilter();
final ItemStack stack = new ItemStack(Filter.ICON);
stack.editMeta(meta -> {
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(text((selected ? "\u00BB " : "") + value.name).color(selected ? SECONDARY_COLOR : INACTIVE_COLOR));
lore.add(filterText);
}
lore.add(Component.empty());
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(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(text((selected ? "\u00BB " : "") + value.name).color(selected ? SECONDARY_COLOR : INACTIVE_COLOR));
lore.add(sortText);
}
lore.add(Component.empty());
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 -> {
try {
stack.setData(DataComponentTypes.HIDE_ADDITIONAL_TOOLTIP);
} catch (NoSuchFieldError e) {
// that component is no longer present under that name,
// we just create the stack without it instead of throwing
}
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 CompletableFuture<ItemStack> createPunishmentItem(Punishment punishment){
SkinTextureProvider provider = getTextureProvider();
if (provider != null) {
return provider.textureProperty(punishment.to)
.thenApply(texture -> {
ItemStack stack = new ItemStack(Material.PLAYER_HEAD);
stack.editMeta(meta -> {
if (meta instanceof SkullMeta skullMeta){
PlayerProfile profile = Bukkit.createProfile(punishment.to);
profile.setProperty(new ProfileProperty(
"textures",
texture
));
skullMeta.setPlayerProfile(profile);
}
addPunishmentInfo(punishment, meta);
});
return stack;
});
} else {
ItemStack stack = new ItemStack(Material.PLAYER_HEAD);
stack.editMeta(meta -> {
if (meta instanceof SkullMeta skullMeta){
try {
skullMeta.setOwningPlayer(Bukkit.getOfflinePlayer(punishment.to));
} catch (NullPointerException e) {
LOGGER.warn("Player {} has a punishment but was never on this server!", punishment.to);
}
}
addPunishmentInfo(punishment, meta);
});
return CompletableFuture.completedFuture(stack);
}
}
private void addPunishmentInfo(Punishment punishment, ItemMeta meta) {
meta.displayName(applyFormatting(text(punishment.type.name).color(NamedTextColor.RED).decorate(TextDecoration.BOLD)));
ArrayList<Component> lore = new ArrayList<>();
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(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){
expires = "in " + TimeUtil.formatTimeLong(remainingTime);
} else {
remainingTime *= -1;
expires = TimeUtil.formatTimeLong(remainingTime) + " ago";
}
lore.add(applyFormatting(text("Expires: ").color(SECONDARY_COLOR).append(text(expires).color(PRIMARY_COLOR))));
}
lore.add(applyFormatting(text("Reason: ").color(SECONDARY_COLOR).append(text(punishment.reason).color(PRIMARY_COLOR))));
if (punishment.wasUndone()){
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(text("\u00BB Click to undo punishment").color(NamedTextColor.GOLD)));
}
}
meta.lore(lore);
}
private int rfId = 0;
@Override
public void refresh() {
int rfId = ++this.rfId;
while (getPage() > lastPage()){
previousPage();
}
inventory.clear();
for (int i = 45; i < 54; i++) {
inventory.setItem(i, createEmptyStack());
}
inventory.setItem(45, createPreviousPageStack());
inventory.setItem(53, createNextPageStack());
inventory.setItem(47, createSearchItem());
inventory.setItem(48, createTypeItem());
inventory.setItem(50, createFilterItem());
inventory.setItem(51, createSortItem());
for (int i = 0; i < 45; i++) {
int ci = i + (getPage() * 45);
if (punishments.size() > ci){
final Punishment punishment = punishments.get(ci);
int slot = i;
createPunishmentItem(punishment).thenAccept(item -> {
if (rfId != this.rfId) return;
if (punishment.isActive()){
if ((punishment.type == PunishmentType.BAN && player.hasPermission("smod.unban")) || (punishment.type == PunishmentType.MUTE && player.hasPermission("smod.unmute"))) {
item.editMeta(meta -> meta.getPersistentDataContainer().set(PUNISHMENT_STORE_KEY, PersistentDataType.LONG, punishment.time));
}
}
inventory.setItem(slot, item);
}).exceptionally(x -> {
LOGGER.warn("Error creating punishment item", x);
return null;
});
} else {
inventory.setItem(i, new ItemStack(Material.AIR));
}
}
}
@Override
public void click(ItemStack stack, InventoryClickEvent event) {
super.click(stack, event);
if (stack != null) {
if (stack.equals(filterStack)){
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) {
final PersistentDataContainer persistentDataContainer = itemMeta.getPersistentDataContainer();
final Long timestamp = persistentDataContainer.get(PUNISHMENT_STORE_KEY, PersistentDataType.LONG);
if (timestamp != null) {
final Punishment punishment = container.findByTimestamp(timestamp);
if (punishment != null) {
new ConfirmationInventory(player, "Do you want to undo this punishment?", () -> {
punishment.undo(player.getUniqueId());
punishment.broadcastUndo(container);
player.playSound(player, Sound.BLOCK_NOTE_BLOCK_PLING, 1f, 2f);
this.open();
}, this::open, false).open();
}
}
}
}
}
@Override
public void open() {
reload();
refresh();
player.openInventory(this.inventory);
}
@Override
public @NotNull Inventory getInventory() {
return inventory;
}
}
@@ -0,0 +1,35 @@
package de.shiewk.smoderation.paper.listener;
import de.shiewk.smoderation.paper.inventory.AutoUpdatingCustomInventory;
import de.shiewk.smoderation.paper.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);
}
}
public static void onTick(){
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.paper.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 && humanHolder.getEnderChest().equals(clicked)){ 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.paper.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,38 +1,41 @@
package de.shiewk.smoderation.listener; package de.shiewk.smoderation.paper.listener;
import de.shiewk.smoderation.SModeration; import de.shiewk.smoderation.paper.SModerationPaper;
import de.shiewk.smoderation.event.PunishmentIssueEvent; import de.shiewk.smoderation.paper.event.PunishmentIssueEvent;
import de.shiewk.smoderation.punishments.Punishment; import de.shiewk.smoderation.paper.punishments.Punishment;
import de.shiewk.smoderation.punishments.PunishmentType; import de.shiewk.smoderation.paper.punishments.PunishmentType;
import de.shiewk.smoderation.storage.PunishmentContainer; import de.shiewk.smoderation.paper.storage.PunishmentContainer;
import io.papermc.paper.event.player.AsyncChatEvent; import io.papermc.paper.event.player.AsyncChatEvent;
import org.bukkit.Bukkit; 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;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerLoginEvent; import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
import org.bukkit.event.world.WorldSaveEvent; import org.bukkit.event.world.WorldSaveEvent;
import static de.shiewk.smoderation.SModeration.CHAT_PREFIX; import static de.shiewk.smoderation.paper.SModerationPaper.CHAT_PREFIX;
public class PunishmentListener implements Listener { public class PunishmentListener implements Listener {
@EventHandler(priority = EventPriority.LOW) @EventHandler(priority = EventPriority.LOW)
public void onPlayerLogin(PlayerLoginEvent event){ public void onPlayerLogin(AsyncPlayerPreLoginEvent event){
Punishment punishment = SModeration.container.find(p -> // Have to use AsyncPlayerPreLoginEvent since PlayerLoginEvent is deprecated in newer versions
// I would use the new PlayerConnectionValidateLoginEvent but there is literally no API available
// there to get player's UUIDs
Punishment punishment = SModerationPaper.container.find(p ->
p.type == PunishmentType.BAN p.type == PunishmentType.BAN
&& p.to.equals(event.getPlayer().getUniqueId()) && p.to.equals(event.getUniqueId())
&& p.isActive()); && p.isActive());
if (punishment != null){ if (punishment != null){
event.disallow(PlayerLoginEvent.Result.KICK_BANNED, CHAT_PREFIX.append(punishment.playerMessage())); event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_BANNED, CHAT_PREFIX.append(punishment.playerMessage()));
} }
} }
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onPlayerChat(AsyncChatEvent event){ public void onPlayerChat(AsyncChatEvent event){
final Player player = event.getPlayer(); final Player player = event.getPlayer();
final Punishment punishment = SModeration.container.find(p -> final Punishment punishment = SModerationPaper.container.find(p ->
p.type == PunishmentType.MUTE p.type == PunishmentType.MUTE
&& p.to.equals(player.getUniqueId()) && p.to.equals(player.getUniqueId())
&& p.isActive()); && p.isActive());
@@ -63,8 +66,8 @@ public class PunishmentListener implements Listener {
@EventHandler @EventHandler
public void onWorldSave(WorldSaveEvent event){ public void onWorldSave(WorldSaveEvent event){
if (event.getWorld().equals(Bukkit.getServer().getWorlds().get(0))){ if (event.getWorld().equals(Bukkit.getServer().getWorlds().getFirst())){
SModeration.container.save(SModeration.SAVE_FILE); SModerationPaper.container.save(SModerationPaper.SAVE_FILE);
} }
} }
} }
@@ -0,0 +1,84 @@
package de.shiewk.smoderation.paper.listener;
import de.shiewk.smoderation.paper.SModerationPaper;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import org.bukkit.NamespacedKey;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import java.util.List;
import static de.shiewk.smoderation.paper.SModerationPaper.PRIMARY_COLOR;
import static de.shiewk.smoderation.paper.SModerationPaper.SECONDARY_COLOR;
import static net.kyori.adventure.text.Component.text;
public class SocialSpyListener implements Listener {
private static final NamespacedKey SAVE_KEY = new NamespacedKey("smoderation", "socialspy");
private static final ObjectArrayList<CommandSender> targets = new ObjectArrayList<>();
public static boolean toggle(CommandSender sender) {
boolean enabledNow = isEnabled(sender);
if (enabledNow){
targets.remove(sender);
if (sender instanceof Player player){
player.getPersistentDataContainer().set(SAVE_KEY, PersistentDataType.BOOLEAN, false);
}
return false;
} else {
targets.add(sender);
if (sender instanceof Player player){
player.getPersistentDataContainer().set(SAVE_KEY, PersistentDataType.BOOLEAN, true);
}
return true;
}
}
@EventHandler public void onPlayerJoin(PlayerJoinEvent event){
final PersistentDataContainer pdc = event.getPlayer().getPersistentDataContainer();
if (Boolean.TRUE.equals(pdc.get(SAVE_KEY, PersistentDataType.BOOLEAN))){
targets.add(event.getPlayer());
}
}
@EventHandler public void onPlayerQuit(PlayerQuitEvent event){
targets.remove(event.getPlayer());
}
public static boolean isEnabled(CommandSender sender){
return targets.contains(sender);
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.NORMAL)
public void onPlayerSendCommand(PlayerCommandPreprocessEvent event){
List<String> socialSpyCommands = SModerationPaper.CONFIG.getSocialSpyCommands();
final String message = event.getMessage();
if (socialSpyCommands.stream().anyMatch(str ->
message.startsWith("/"+str+" ")
|| message.startsWith(str+" ")
)){
SocialSpyListener.command(event.getPlayer(), message);
}
}
public static void command(Player player, String command){
for (CommandSender target : targets) {
target.sendMessage(text("[", PRIMARY_COLOR)
.append(text("SocialSpy", SECONDARY_COLOR))
.append(text("] "))
.append(player.displayName().colorIfAbsent(PRIMARY_COLOR))
.append(text(": ", PRIMARY_COLOR))
.append(text(command, SECONDARY_COLOR))
);
}
}
}
@@ -0,0 +1,103 @@
package de.shiewk.smoderation.paper.listener;
import de.shiewk.smoderation.paper.SModerationPaper;
import de.shiewk.smoderation.paper.command.VanishCommand;
import de.shiewk.smoderation.paper.util.SchedulerUtil;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextDecoration;
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.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.persistence.PersistentDataType;
import static de.shiewk.smoderation.paper.SModerationPaper.SECONDARY_COLOR;
import static net.kyori.adventure.text.Component.text;
public class VanishListener implements Listener {
public static final Component PREFIX = text("[VANISH] ").color(SECONDARY_COLOR);
@EventHandler(priority = EventPriority.HIGH) public void onPlayerQuit(PlayerQuitEvent event){
final Player player = event.getPlayer();
if (VanishCommand.isVanished(player)){
player.getPersistentDataContainer().set(VanishCommand.KEY_VANISHED, PersistentDataType.BOOLEAN, true);
VanishCommand.toggleVanishSilent(player, true);
Component message = event.quitMessage();
event.quitMessage(null);
if (message != null){
broadcast(message.color(null));
}
} else {
player.getPersistentDataContainer().remove(VanishCommand.KEY_VANISHED);
}
for (Player vanishedPlayer : VanishCommand.getVanishedPlayers()) {
// to clean up visibility status
player.hideEntity(SModerationPaper.PLUGIN, vanishedPlayer);
}
}
@EventHandler(priority = EventPriority.MONITOR) public void onPlayerJoin(PlayerJoinEvent event){
final Player player = event.getPlayer();
if (player.getPersistentDataContainer().has(VanishCommand.KEY_VANISHED)){
boolean state = VanishCommand.toggleVanishSilent(player, false);
if (state){
Component message = event.joinMessage();
event.joinMessage(null);
if (message != null){
broadcast(message.color(null));
}
SchedulerUtil.scheduleForEntity(SModerationPaper.PLUGIN, player, () -> {
player.sendMessage(SModerationPaper.CHAT_PREFIX
.decorate(TextDecoration.BOLD)
.append(
text("You are still vanished!")
));
player.playSound(Sound.sound(
Key.key("minecraft", "block.beacon.power_select"),
Sound.Source.MASTER,
1f,
1f
), player);
}, 20);
} else {
player.getPersistentDataContainer().remove(VanishCommand.KEY_VANISHED);
}
}
SchedulerUtil.scheduleForEntity(SModerationPaper.PLUGIN, player, () -> {
if (player.hasPermission("smod.vanish.see")){
for (Player vanishedPlayer : VanishCommand.getVanishedPlayers()) {
// to show visible vanished players
player.showEntity(SModerationPaper.PLUGIN, vanishedPlayer);
}
VanishCommand.listVanishedPlayersTo(player);
}
});
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPlayerDeath(PlayerDeathEvent event){
final Component message = event.deathMessage();
if (VanishCommand.isVanished(event.getPlayer()) && message != null){
event.deathMessage(null);
broadcast(message);
}
}
private static void broadcast(Component message) {
Component result = PREFIX.append(message);
SModerationPaper.LOGGER.info(result);
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
if (onlinePlayer.hasPermission("smod.vanish.see")){
onlinePlayer.sendMessage(result);
}
}
}
}
@@ -1,10 +1,10 @@
package de.shiewk.smoderation.punishments; package de.shiewk.smoderation.paper.punishments;
import de.shiewk.smoderation.event.PunishmentIssueEvent; import de.shiewk.smoderation.paper.event.PunishmentIssueEvent;
import de.shiewk.smoderation.storage.PunishmentContainer; import de.shiewk.smoderation.paper.storage.PunishmentContainer;
import de.shiewk.smoderation.util.ByteUtil; import de.shiewk.smoderation.paper.util.ByteUtil;
import de.shiewk.smoderation.util.PlayerUtil; import de.shiewk.smoderation.paper.util.PlayerUtil;
import de.shiewk.smoderation.util.TimeUtil; import de.shiewk.smoderation.paper.util.TimeUtil;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@@ -15,7 +15,7 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.UUID; import java.util.UUID;
import static de.shiewk.smoderation.SModeration.*; import static de.shiewk.smoderation.paper.SModerationPaper.*;
public class Punishment { public class Punishment {
public static final String DEFAULT_REASON = "No reason provided."; public static final String DEFAULT_REASON = "No reason provided.";
@@ -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);
}
} }
@@ -1,4 +1,4 @@
package de.shiewk.smoderation.punishments; package de.shiewk.smoderation.paper.punishments;
public enum PunishmentType { public enum PunishmentType {
MUTE("Mute"), MUTE("Mute"),
@@ -1,7 +1,7 @@
package de.shiewk.smoderation.storage; package de.shiewk.smoderation.paper.storage;
import de.shiewk.smoderation.SModeration; import de.shiewk.smoderation.paper.SModerationPaper;
import de.shiewk.smoderation.punishments.Punishment; import de.shiewk.smoderation.paper.punishments.Punishment;
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.CommandSender;
@@ -37,7 +37,7 @@ public class PunishmentContainer {
} }
public @Nullable Punishment find(Predicate<Punishment> predicate){ public @Nullable Punishment find(Predicate<Punishment> predicate){
for (Punishment punishment : new CopyOnWriteArrayList<>(punishments)) { for (Punishment punishment : punishments) {
if (predicate.test(punishment)){ if (predicate.test(punishment)){
return punishment; return punishment;
} }
@@ -47,7 +47,7 @@ public class PunishmentContainer {
public @NotNull List<Punishment> findAll(Predicate<Punishment> predicate){ public @NotNull List<Punishment> findAll(Predicate<Punishment> predicate){
List<Punishment> found = new ArrayList<>(); List<Punishment> found = new ArrayList<>();
for (Punishment punishment : new CopyOnWriteArrayList<>(punishments)) { for (Punishment punishment : punishments) {
if (predicate.test(punishment)){ if (predicate.test(punishment)){
found.add(punishment); found.add(punishment);
} }
@@ -74,8 +74,8 @@ public class PunishmentContainer {
return new ArrayList<>(punishments); return new ArrayList<>(punishments);
} }
public void load(File file){ public synchronized void load(File file){
final ComponentLogger logger = SModeration.LOGGER; final ComponentLogger logger = SModerationPaper.LOGGER;
try { try {
logger.info("Loading from {}", file.getPath()); logger.info("Loading from {}", file.getPath());
if (!file.isFile()){ if (!file.isFile()){
@@ -99,10 +99,9 @@ public class PunishmentContainer {
} }
} }
public void save(File file) { public synchronized void save(File file) {
final ComponentLogger logger = SModeration.LOGGER; final ComponentLogger logger = SModerationPaper.LOGGER;
try { try {
logger.info("Saving to {}", file.getPath());
if (!file.isFile()){ if (!file.isFile()){
file.mkdirs(); file.mkdirs();
file.delete(); file.delete();
@@ -115,7 +114,6 @@ public class PunishmentContainer {
} }
gzout.close(); gzout.close();
} }
logger.info("Successfully saved.");
} catch (IOException e){ } catch (IOException e){
logger.error("An error occurred while saving: {}", e.toString()); logger.error("An error occurred while saving: {}", e.toString());
for (StackTraceElement stackTraceElement : e.getStackTrace()) { for (StackTraceElement stackTraceElement : e.getStackTrace()) {
@@ -1,4 +1,4 @@
package de.shiewk.smoderation.util; package de.shiewk.smoderation.paper.util;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.UUID; import java.util.UUID;
@@ -0,0 +1,66 @@
package de.shiewk.smoderation.paper.util;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import io.papermc.paper.command.brigadier.MessageComponentSerializer;
import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver;
import net.kyori.adventure.text.Component;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.UUID;
import java.util.function.Predicate;
import static de.shiewk.smoderation.paper.util.PlayerUtil.UUID_CONSOLE;
public abstract class CommandUtil {
private CommandUtil(){}
public static Predicate<CommandSourceStack> requirePermission(String permission) {
return stack -> stack.getSender().hasPermission(permission);
}
public static Player getExecutingPlayer(CommandSourceStack stack) throws CommandSyntaxException {
CommandSender sender = stack.getSender();
if (sender instanceof Player player) {
return player;
} else {
error("Only players can execute this command.");
throw new UnknownError(); // can't happen
}
}
public static UUID getSenderUUID(CommandSourceStack stack) throws CommandSyntaxException {
CommandSender sender = stack.getSender();
if (sender instanceof Player player) {
return player.getUniqueId();
} else if (sender instanceof ConsoleCommandSender){
return UUID_CONSOLE;
} else {
error("Only players and the console can execute this command.");
throw new UnknownError(); // can't happen
}
}
public static Player getPlayerSingle(CommandContext<CommandSourceStack> context, String name) throws CommandSyntaxException {
@NotNull List<Player> players = context.getArgument(name, PlayerSelectorArgumentResolver.class).resolve(context.getSource());
if (players.isEmpty()){
CommandUtil.error("Please provide a valid player.");
}
return players.getFirst();
}
public static void error(String message) throws CommandSyntaxException {
throw new CommandSyntaxException(
new SimpleCommandExceptionType(null),
MessageComponentSerializer.message().serialize(
Component.text(message)
)
);
}
}
@@ -1,11 +1,12 @@
package de.shiewk.smoderation.util; package de.shiewk.smoderation.paper.util;
import de.shiewk.smoderation.SModeration; import de.shiewk.smoderation.paper.SModerationPaper;
import de.shiewk.smoderation.punishments.Punishment; import de.shiewk.smoderation.paper.punishments.Punishment;
import org.bukkit.Bukkit; 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;
@@ -24,7 +25,7 @@ public abstract class PlayerUtil {
return "CONSOLE"; return "CONSOLE";
} }
OfflinePlayer player = Bukkit.getOfflinePlayer(uuid); OfflinePlayer player = Bukkit.getOfflinePlayer(uuid);
return player.getName() == null ? uuid.toString() : player.getName(); return player.getName() == null ? "Unknown Player " + uuid : player.getName();
} }
public static @Nullable UUID offlinePlayerUUIDByName(String name){ public static @Nullable UUID offlinePlayerUUIDByName(String name){
@@ -33,7 +34,7 @@ public abstract class PlayerUtil {
return offlinePlayer.getUniqueId(); return offlinePlayer.getUniqueId();
} else { } else {
// try to find uuid by searching through punishments // try to find uuid by searching through punishments
final Punishment punishment = SModeration.container.find(p -> offlinePlayerName(p.to).equalsIgnoreCase(name)); final Punishment punishment = SModerationPaper.container.find(p -> offlinePlayerName(p.to).equalsIgnoreCase(name));
if (punishment != null) { if (punishment != null) {
return punishment.to; return punishment.to;
} }
@@ -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<>());
}
} }
@@ -0,0 +1,58 @@
package de.shiewk.smoderation.paper.util;
import org.bukkit.Bukkit;
import org.bukkit.entity.Entity;
import org.bukkit.plugin.Plugin;
/**
* This class provides some convenience methods that make Folia support easier
*/
public final class SchedulerUtil {
private SchedulerUtil(){}
public static final boolean isFolia;
static {
boolean folia;
try {
Class.forName("io.papermc.paper.threadedregions.RegionizedServer");
folia = true;
} catch (ClassNotFoundException e) {
folia = false;
}
isFolia = folia;
}
public static void scheduleForEntity(Plugin plugin, Entity entity, Runnable task, int delayTicks){
if (isFolia){
entity.getScheduler().execute(plugin, task, null, delayTicks);
} else {
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, task, delayTicks);
}
}
public static void scheduleForEntity(Plugin plugin, Entity entity, Runnable task){
if (isFolia){
entity.getScheduler().run(plugin, t -> task.run(), null);
} else {
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, task);
}
}
public static void scheduleGlobalRepeating(Plugin plugin, Runnable task, int delay, int interval){
if (isFolia){
Bukkit.getGlobalRegionScheduler().runAtFixedRate(plugin, t -> task.run(), delay, interval);
} else {
Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, task, delay, interval);
}
}
public static void scheduleGlobal(Plugin plugin, Runnable task){
if (isFolia){
Bukkit.getGlobalRegionScheduler().run(plugin, t -> task.run());
} else {
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, task);
}
}
}
@@ -1,4 +1,4 @@
package de.shiewk.smoderation.util; package de.shiewk.smoderation.paper.util;
import org.jetbrains.annotations.Range; import org.jetbrains.annotations.Range;
+74
View File
@@ -0,0 +1,74 @@
name: SModeration
version: '${version}'
main: de.shiewk.smoderation.paper.SModerationPaper
website: https://github.com/Shiewk/SModeration
api-version: '1.21.3'
load: STARTUP
authors:
- Shiewk
description: "SModeration is an easy-to-use minecraft plugin for moderating your server."
folia-supported: true
permissions:
smod.mute:
default: op
description: Allows the player to mute other players.
smod.ban:
default: op
description: Allows the player to ban and kick other players.
children:
- smod.kick
smod.kick:
default: op
description: Allows the player to kick other players.
smod.menu:
default: op
description: Allows the player to use the SModeration menu.
smod.notifications:
default: op
description: Allows the player to be notified when a punishment is issued.
smod.unmute:
default: op
description: Allows the player to unmute other players.
smod.unban:
default: op
description: Allows the player to unban other players.
smod.logs:
default: op
description: Allows the player to view mod logs.
smod.invsee:
default: op
description: Allows the player to view other players inventories.
smod.invsee.modify:
default: op
description: Allows the player to view and modify other players inventories.
children:
- smod.invsee
smod.invsee.preventmodify:
default: op
description: When giving this permission to a player, prevents their inventory from being modified.
smod.enderchestsee:
default: op
description: Allows the player to view other players ender chests.
smod.enderchestsee.modify:
default: op
description: Allows the player to view and modify other players ender chests.
children:
- smod.enderchestsee
smod.preventmute:
default: op
description: Prevents the player from being muted (if online)
smod.preventkick:
default: op
description: Prevents the player from being kicked (if online)
smod.preventban:
default: op
description: Prevents the player from being banned (if online)
smod.vanish:
default: op
description: Allows the player to use /vanish
smod.vanish.see:
default: op
description: Allows the player to see vanished players
smod.socialspy:
default: op
description: Allows the player to enable SocialSpy
-140
View File
@@ -1,140 +0,0 @@
name: SModeration
version: '${version}'
main: de.shiewk.smoderation.SModeration
api-version: '1.20'
load: STARTUP
authors:
- Shiewk
description: "SModeration is an easy-to-use minecraft plugin for moderating your server."
commands:
modlogs:
usage: "§cUsage: /modlogs <player|uuid>"
aliases:
- logs
- seen
- smodlogs
permission: smod.logs
mute:
usage: "§cUsage: /mute <player> <duration> <reason>"
aliases:
- smodmute
permission: smod.mute
description: Mutes a player, either temporarily or permanently.
ban:
usage: "§cUsage: /ban <player> <duration> <reason>"
aliases:
- smodban
- tempban
permission: smod.ban
description: Bans a player, either temporarily or permanently.
kick:
usage: "§cUsage: /kick <player> <reason>"
aliases:
- smodkick
permission: smod.kick
description: Kicks a player
smod:
usage: "§cUsage: /smod"
aliases:
- smodmenu
- smoderation
permission: smod.menu
description: Shows the SModeration menu.
unmute:
usage: "§cUsage: /unmute <player|uuid>"
aliases:
- sunmute
permission: smod.unmute
description: Unmutes a muted player.
unban:
usage: "§cUsage: /unban <player|uuid>"
aliases:
- sunban
- pardon
- spardon
permission: smod.unban
description: Unbans a banned player.
invsee:
usage: "§cUsage: /invsee <player>"
aliases:
- sinvsee
- smodinvsee
- invs
permission: smod.invsee
description: Views the inventory of another player.
enderchestsee:
usage: "§cUsage: /enderchestsee <player>"
aliases:
- secsee
- senderchestsee
- ecsee
- ecs
permission: smod.enderchestsee
description: Views the ender chest of another player.
vanish:
usage: "§cUsage: /vanish <player>"
aliases:
- smvanish
permission: smod.vanish
description: Toggles vanish mode which prevents other players from seeing you're online
permissions:
smod.mute:
default: op
description: Allows the player to mute other players.
smod.ban:
default: op
description: Allows the player to ban and kick other players.
children:
- smod.kick
smod.kick:
default: op
description: Allows the player to kick other players.
smod.menu:
default: op
description: Allows the player to use the SModeration menu.
smod.notifications:
default: op
description: Allows the player to be notified when a punishment is issued.
smod.unmute:
default: op
description: Allows the player to unmute other players.
smod.unban:
default: op
description: Allows the player to unban other players.
smod.logs:
default: op
description: Allows the player to view mod logs.
smod.invsee:
default: op
description: Allows the player to view other players inventories.
smod.invsee.modify:
default: op
description: Allows the player to view and modify other players inventories.
children:
- smod.invsee
smod.invsee.preventmodify:
default: op
description: When giving this permission to a player, prevents their inventory from being modified.
smod.enderchestsee:
default: op
description: Allows the player to view other players ender chests.
smod.enderchestsee.modify:
default: op
description: Allows the player to view and modify other players ender chests.
children:
- smod.enderchestsee
smod.preventmute:
default: op
description: Prevents the player from being muted (if online)
smod.preventkick:
default: op
description: Prevents the player from being muted (if online)
smod.preventban:
default: op
description: Prevents the player from being muted (if online)
smod.vanish:
default: op
description: Allows the player to use /vanish
smod.vanish.see:
default: op
description: Allows the player to see vanished players