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

7 Commits

Author SHA1 Message Date
Shiewk fecd21bf19 Fix hardcoded page switching strings; add German translations 2026-04-03 16:52:11 +02:00
Shiewk fc8c1c9f26 Move some versions to gradle.properties; update versions 2026-04-03 16:42:48 +02:00
Shiewk f7f541c8a4 Stop warning about experimental APIs 2026-04-03 15:59:52 +02:00
Shiewk a8f836d94c Remove issue templates
They don't seem really useful to me
2026-04-03 15:53:48 +02:00
Shiewk fcd2a513aa Remove the chat prefix 2026-04-03 15:53:07 +02:00
Shiewk 5c4feea042 Amend custom inventory logic
- Make click events based on slot numbers instead of comparing item stacks
- Removed ItemStack parameter from CustomInventory#click as it is no longer needed
2026-04-03 15:33:32 +02:00
Shiewk c4e1aedeca Add comments to config file 2026-04-02 19:57:27 +02:00
37 changed files with 301 additions and 196 deletions
-34
View File
@@ -1,34 +0,0 @@
---
name: Bug report
about: Report bugs to help us make the plugin better and more reliable.
title: "[BUG]"
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Server**
Please provide information about the plugin version and the server.
This section should include:
1. Server Software
2. Server version
3. Plugin Version you are using
**Additional context**
Add any other context about the problem here.
-20
View File
@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this plugin
title: "[REQUEST]"
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
+6 -6
View File
@@ -1,6 +1,6 @@
plugins {
id 'java'
id("xyz.jpenilla.run-paper") version "2.3.1"
id("xyz.jpenilla.run-paper") version "${runPaperVersion}"
}
group = 'de.shiewk'
@@ -30,17 +30,17 @@ runPaper {
}
runServer {
minecraftVersion("1.21.10")
minecraftVersion("1.21.11")
downloadPlugins {
// for testing from other client versions
modrinth("ViaVersion", "5.5.1")
modrinth("ViaBackwards", "5.5.1")
modrinth("ViaVersion", viaVersionVersion)
modrinth("ViaBackwards", viaVersionVersion)
}
}
dependencies {
//compileOnly "io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT"
compileOnly 'dev.folia:folia-api:1.21.4-R0.1-SNAPSHOT'
//compileOnly "io.papermc.paper:paper-api:${apiVersion}"
compileOnly "dev.folia:folia-api:${apiVersion}"
}
jar {
+3
View File
@@ -1 +1,4 @@
pluginVersion = 1.8.2
apiVersion = 1.21.4-R0.1-SNAPSHOT
runPaperVersion = 3.0.2
viaVersionVersion = 5.8.0
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
@@ -11,7 +11,6 @@ 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.key.Key;
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;
@@ -28,15 +27,22 @@ import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
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;
@SuppressWarnings("UnstableApiUsage") // Paper Brigadier API
public final class SModerationPaper extends JavaPlugin {
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 Gson gson = new Gson();
public static final PunishmentContainer container = new PunishmentContainer();
public static ComponentLogger LOGGER = null;
@@ -44,17 +50,13 @@ public final class SModerationPaper extends JavaPlugin {
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);
private final TranslatorManager translatorManager = new TranslatorManager(
Key.key("smoderation", "translations"),
createMiniMessage(),
"smoderation/translations/",
new Locale[] {
Locale.forLanguageTag("en-US")
Locale.forLanguageTag("en-US"),
Locale.forLanguageTag("de-DE")
}
);
@@ -158,7 +160,6 @@ public final class SModerationPaper extends JavaPlugin {
private MiniMessage createMiniMessage() {
return MiniMessage.builder()
.tags(TagResolver.builder()
.resolver(TagResolver.resolver("prefix", Tag.inserting(CHAT_PREFIX)))
.resolver(TagResolver.resolver("primary", Tag.styling(style -> style.color(PRIMARY_COLOR))))
.resolver(TagResolver.resolver("secondary", Tag.styling(style -> style.color(SECONDARY_COLOR))))
.resolver(TagResolver.standard())
@@ -181,14 +182,29 @@ public final class SModerationPaper extends JavaPlugin {
boolean changedSomething = false;
for (String key : defaultConfig.getKeys(true)) {
if (!config.contains(key)) { // There's a new key in the default config
if (!config.contains(key)) {
// There's a new key in the default config
config.set(key, defaultConfig.get(key));
changedSomething = true;
}
List<String> defaultComments = new ArrayList<>(defaultConfig.getComments(key));
List<String> comments = new ArrayList<>(config.getComments(key));
defaultComments.removeIf(Objects::isNull);
comments.removeIf(Objects::isNull);
if (!defaultComments.equals(comments)) {
// Comments changed
config.setComments(key, defaultConfig.getComments(key));
changedSomething = true;
}
}
// Save the updated configuration file
if (changedSomething) saveConfig();
if (changedSomething){
LOGGER.info("Changing config file to add new options/documentation");
saveConfig();
}
} catch (Exception e) {
throw new RuntimeException("Could not update config", e);
@@ -21,6 +21,7 @@ import java.util.UUID;
import static io.papermc.paper.command.brigadier.Commands.argument;
import static io.papermc.paper.command.brigadier.Commands.literal;
@SuppressWarnings("UnstableApiUsage") // Paper Brigadier API
public final class BanCommand implements CommandProvider {
@Override
@@ -6,6 +6,7 @@ import io.papermc.paper.command.brigadier.CommandSourceStack;
import java.util.Collection;
import java.util.List;
@SuppressWarnings("UnstableApiUsage") // Paper Brigadier API
public interface CommandProvider {
LiteralCommandNode<CommandSourceStack> getCommandNode();
@@ -16,6 +16,7 @@ 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.translatable;
@SuppressWarnings("UnstableApiUsage") // Paper Brigadier API
public final class EnderchestSeeCommand implements CommandProvider {
@Override
@@ -18,6 +18,7 @@ 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.translatable;
@SuppressWarnings("UnstableApiUsage") // Paper Brigadier API
public final class InvseeCommand implements CommandProvider {
@Override
@@ -19,6 +19,7 @@ import java.util.UUID;
import static io.papermc.paper.command.brigadier.Commands.argument;
import static io.papermc.paper.command.brigadier.Commands.literal;
@SuppressWarnings("UnstableApiUsage") // Paper Brigadier API
public final class KickCommand implements CommandProvider {
@Override
@@ -21,6 +21,7 @@ import static io.papermc.paper.command.brigadier.Commands.literal;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.Component.translatable;
@SuppressWarnings("UnstableApiUsage") // Paper Brigadier API
public final class ModLogsCommand implements CommandProvider {
@Override
@@ -21,6 +21,7 @@ import java.util.UUID;
import static io.papermc.paper.command.brigadier.Commands.argument;
import static io.papermc.paper.command.brigadier.Commands.literal;
@SuppressWarnings("UnstableApiUsage") // Paper Brigadier API
public final class MuteCommand implements CommandProvider {
@Override
@@ -21,6 +21,7 @@ import static io.papermc.paper.command.brigadier.Commands.literal;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.Component.translatable;
@SuppressWarnings("UnstableApiUsage") // Paper Brigadier API
public final class OfflineTPCommand implements CommandProvider {
@Override
@@ -15,6 +15,7 @@ import java.util.List;
import static io.papermc.paper.command.brigadier.Commands.literal;
@SuppressWarnings("UnstableApiUsage") // Paper Brigadier API
public final class SModCommand implements CommandProvider {
@Override
@@ -14,6 +14,7 @@ import java.util.List;
import static io.papermc.paper.command.brigadier.Commands.literal;
import static net.kyori.adventure.text.Component.translatable;
@SuppressWarnings("UnstableApiUsage") // Paper Brigadier API
public final class SocialSpyCommand implements CommandProvider {
@Override
@@ -18,6 +18,7 @@ import java.util.UUID;
import static io.papermc.paper.command.brigadier.Commands.argument;
import static io.papermc.paper.command.brigadier.Commands.literal;
@SuppressWarnings("UnstableApiUsage") // Paper Brigadier API
public final class UnbanCommand implements CommandProvider {
@Override
@@ -18,6 +18,7 @@ import java.util.UUID;
import static io.papermc.paper.command.brigadier.Commands.argument;
import static io.papermc.paper.command.brigadier.Commands.literal;
@SuppressWarnings("UnstableApiUsage") // Paper Brigadier API
public final class UnmuteCommand implements CommandProvider {
@Override
@@ -26,6 +26,7 @@ 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.*;
@SuppressWarnings("UnstableApiUsage") // Paper Brigadier API
public final class VanishCommand implements CommandProvider {
public static final NamespacedKey KEY_VANISHED = new NamespacedKey("smoderation", "vanished");
@@ -16,6 +16,7 @@ import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.MatchResult;
import java.util.regex.Pattern;
@SuppressWarnings("UnstableApiUsage") // Paper Brigadier API
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)");
@@ -11,13 +11,15 @@ import io.papermc.paper.command.brigadier.argument.CustomArgumentType;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NonNull;
import java.util.concurrent.CompletableFuture;
@SuppressWarnings("UnstableApiUsage") // Paper Brigadier API
public final class OfflinePlayerArgument implements CustomArgumentType.Converted<OfflinePlayer, String> {
@Override
public OfflinePlayer convert(@NotNull String nativeType) throws CommandSyntaxException {
public @NonNull OfflinePlayer convert(@NotNull String nativeType) throws CommandSyntaxException {
OfflinePlayer player = Bukkit.getOfflinePlayerIfCached(nativeType);
if (player != null){
return player;
@@ -16,6 +16,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
@SuppressWarnings("UnstableApiUsage") // Paper Brigadier API
public final class PlayerUUIDArgument implements CustomArgumentType.Converted<UUID, String> {
@Override
@@ -10,7 +10,7 @@ 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 de.shiewk.smoderation.paper.SModerationPaper.PRIMARY_COLOR;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.Component.translatable;
@@ -53,7 +53,7 @@ public class ChatInput {
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));
player.sendMessage(prompt.colorIfAbsent(PRIMARY_COLOR));
}
public Component getPrompt() {
@@ -15,37 +15,35 @@ import org.jetbrains.annotations.NotNull;
import static de.shiewk.smoderation.paper.inventory.CustomInventory.renderComponent;
import static net.kyori.adventure.text.Component.translatable;
@SuppressWarnings("UnstableApiUsage") // Paper Brigadier API
public class ConfirmationInventory implements CustomInventory {
private final Inventory inventory;
private final Player player;
private final Component prompt;
private final ItemStack yesStack;
private final ItemStack noStack;
private final Runnable onAccept;
private final Runnable onReject;
private final boolean reversed;
public ConfirmationInventory(Player player, Component prompt, Runnable onAccept, Runnable onReject, boolean reversed) {
public ConfirmationInventory(Player player, Component prompt, Runnable onAccept, Runnable onReject) {
this.player = player;
this.prompt = prompt;
this.onAccept = onAccept;
this.onReject = onReject;
this.reversed = reversed;
inventory = Bukkit.createInventory(this, InventoryType.HOPPER, this.prompt);
yesStack = new ItemStack(Material.LIME_STAINED_GLASS_PANE);
noStack = new ItemStack(Material.RED_STAINED_GLASS_PANE);
}
@Override
public void refresh() {
yesStack.setData(DataComponentTypes.ITEM_NAME, renderComponent(player, applyFormatting(translatable("smod.confirm.yes"))));
noStack.setData(DataComponentTypes.ITEM_NAME, renderComponent(player, applyFormatting(translatable("smod.confirm.no"))));
ItemStack accept = new ItemStack(Material.LIME_STAINED_GLASS_PANE);
ItemStack confirmation = new ItemStack(Material.PAPER);
confirmation.setData(DataComponentTypes.ITEM_NAME, renderComponent(player, prompt.colorIfAbsent(NamedTextColor.GOLD)));
ItemStack reject = new ItemStack(Material.RED_STAINED_GLASS_PANE);
inventory.setItem(reversed ? 4 : 0, noStack);
accept.setData(DataComponentTypes.ITEM_NAME, renderComponent(player, applyFormatting(translatable("smod.confirm.yes"))));
confirmation.setData(DataComponentTypes.ITEM_NAME, renderComponent(player, prompt.colorIfAbsent(NamedTextColor.GOLD)));
reject.setData(DataComponentTypes.ITEM_NAME, renderComponent(player, applyFormatting(translatable("smod.confirm.no"))));
inventory.setItem(0, accept);
inventory.setItem(2, confirmation);
inventory.setItem(reversed ? 0 : 4, yesStack);
inventory.setItem(4, reject);
}
@Override
@@ -55,11 +53,11 @@ public class ConfirmationInventory implements CustomInventory {
}
@Override
public void click(ItemStack stack, InventoryClickEvent event) {
if (yesStack.equals(stack)){
public void click(InventoryClickEvent event) {
if (event.getSlot() == 0){
inventory.close();
onAccept.run();
} else if (noStack.equals(stack)) {
} else if (event.getSlot() == 4) {
inventory.close();
onReject.run();
}
@@ -16,7 +16,7 @@ public interface CustomInventory extends InventoryHolder {
void refresh();
void open();
void click(ItemStack stack, InventoryClickEvent event);
void click(InventoryClickEvent event);
default ItemStack createEmptyStack(){
ItemStack stack = new ItemStack(Material.BLACK_STAINED_GLASS_PANE);
@@ -8,7 +8,6 @@ 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;
@@ -44,7 +43,7 @@ public class InvSeeEquipmentInventory implements AutoUpdatingCustomInventory {
}
@Override
public void click(ItemStack stack, InventoryClickEvent event) {
public void click(InventoryClickEvent event) {
if (viewer.hasPermission("smod.invsee.modify") && !subject.hasPermission("smod.invsee.preventmodify")){
event.setCancelled(false);
changing = true;
@@ -6,7 +6,6 @@ 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;
@@ -46,7 +45,7 @@ public class InvSeeInventory implements AutoUpdatingCustomInventory {
}
@Override
public void click(ItemStack stack, InventoryClickEvent event) {
public void click(InventoryClickEvent event) {
if (viewer.hasPermission("smod.invsee.modify") && !subject.hasPermission("smod.invsee.preventmodify")){
event.setCancelled(false);
changing = true;
@@ -1,33 +1,42 @@
package de.shiewk.smoderation.paper.inventory;
import net.kyori.adventure.text.Component;
import io.papermc.paper.datacomponent.DataComponentTypes;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.Component.translatable;
@SuppressWarnings("UnstableApiUsage") // Paper Data Component API
public abstract class PageableCustomInventory implements CustomInventory {
public abstract int lastPage();
public abstract void switchPage();
private ItemStack previousStack = null;
private ItemStack nextStack = null;
protected final int prevSlot, nextSlot;
private int page = 0;
public PageableCustomInventory(int prevSlot, int nextSlot) {
this.prevSlot = prevSlot;
this.nextSlot = nextSlot;
}
public int getPage(){
return page;
}
public abstract int lastPage();
public abstract void switchPage();
@Override
public void click(ItemStack stack, InventoryClickEvent event) {
if (stack != null){
if (stack.equals(previousStack)){
public void click(InventoryClickEvent event) {
if (event.getSlot() == prevSlot) {
previousPage();
} else if (stack.equals(nextStack)) {
} else if (event.getSlot() == nextSlot) {
nextPage();
}
}
}
public void nextPage(){
if (page < lastPage()){
@@ -45,23 +54,21 @@ public abstract class PageableCustomInventory implements CustomInventory {
}
}
public ItemStack createPreviousPageStack(){
public ItemStack createPreviousPageStack(Player viewer){
boolean allowed = page > 0;
TextColor color = allowed ? NamedTextColor.GREEN : NamedTextColor.RED;
int skip = allowed ? page : page+1;
ItemStack stack = new ItemStack(allowed ? Material.GREEN_STAINED_GLASS_PANE : Material.RED_STAINED_GLASS_PANE);
stack.editMeta(meta -> meta.displayName(applyFormatting(Component.text("Previous page (%s/%s)".formatted(skip, lastPage()+1)).color(color))));
previousStack = stack;
stack.setData(DataComponentTypes.ITEM_NAME, CustomInventory.renderComponent(viewer, applyFormatting(translatable("smod.inventory.previous", text(skip), text(lastPage()+1)).color(color))));
return stack;
}
public ItemStack createNextPageStack(){
public ItemStack createNextPageStack(Player viewer){
boolean allowed = page < lastPage();
TextColor color = allowed ? NamedTextColor.GREEN : NamedTextColor.RED;
int skip = allowed ? page+2 : page+1;
ItemStack stack = new ItemStack(allowed ? Material.GREEN_STAINED_GLASS_PANE : Material.RED_STAINED_GLASS_PANE);
stack.editMeta(meta -> meta.displayName(applyFormatting(Component.text("Next page (%s/%s)".formatted(skip, lastPage()+1)).color(color))));
nextStack = stack;
stack.setData(DataComponentTypes.ITEM_NAME, CustomInventory.renderComponent(viewer, applyFormatting(translatable("smod.inventory.next", text(skip), text(lastPage()+1)).color(color))));
return stack;
}
}
@@ -12,22 +12,19 @@ import de.shiewk.smoderation.paper.util.SchedulerUtil;
import de.shiewk.smoderation.paper.util.TimeUtil;
import io.papermc.paper.datacomponent.DataComponentTypes;
import io.papermc.paper.datacomponent.item.ItemLore;
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
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.Comparator;
@@ -40,6 +37,7 @@ import static de.shiewk.smoderation.paper.SModerationPaper.*;
import static de.shiewk.smoderation.paper.inventory.CustomInventory.renderComponent;
import static net.kyori.adventure.text.Component.*;
@SuppressWarnings("UnstableApiUsage") // Paper Brigadier API, Paper Data Component API
public class SModMenu extends PageableCustomInventory {
public enum Filter {
@@ -73,22 +71,20 @@ public class SModMenu extends PageableCustomInventory {
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 final Int2ObjectArrayMap<Punishment> slotMap = new Int2ObjectArrayMap<>(45);
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 int rfId = 0;
private String searchQuery = null;
public SModMenu(Player player, PunishmentContainer container) {
super(45, 53);
this.player = player;
this.container = container;
this.inventory = Bukkit.createInventory(this, 54, translatable("smod.menu"));
@@ -209,7 +205,7 @@ public class SModMenu extends PageableCustomInventory {
loreBuilder.addLine(renderComponent(player, applyFormatting(translatable("smod.menu.filter.switch", NamedTextColor.GOLD))));
stack.setData(DataComponentTypes.LORE, loreBuilder.build());
return filterStack = stack;
return stack;
}
private ItemStack createTypeItem(){
@@ -233,7 +229,7 @@ public class SModMenu extends PageableCustomInventory {
loreBuilder.addLine(renderComponent(player, applyFormatting(translatable("smod.menu.type.switch", NamedTextColor.GOLD))));
stack.setData(DataComponentTypes.LORE, loreBuilder);
return typeStack = stack;
return stack;
}
private ItemStack createSortItem(){
@@ -254,7 +250,7 @@ public class SModMenu extends PageableCustomInventory {
loreBuilder.addLine(renderComponent(player, applyFormatting(translatable("smod.menu.sort.switch", NamedTextColor.GOLD))));
stack.setData(DataComponentTypes.LORE, loreBuilder);
return sortStack = stack;
return stack;
}
private ItemStack createSearchItem(){
@@ -280,7 +276,7 @@ public class SModMenu extends PageableCustomInventory {
loreBuilder.addLine(renderComponent(player, applyFormatting(translatable("smod.menu.search.remove", NamedTextColor.GOLD))));
}
stack.setData(DataComponentTypes.LORE, loreBuilder);
return searchStack = stack;
return stack;
}
private CompletableFuture<ItemStack> createPunishmentItem(Punishment punishment){
@@ -349,7 +345,6 @@ public class SModMenu extends PageableCustomInventory {
stack.setData(DataComponentTypes.LORE, lore);
}
private int rfId = 0;
@Override
public void refresh() {
int rfId = ++this.rfId;
@@ -357,11 +352,12 @@ public class SModMenu extends PageableCustomInventory {
previousPage();
}
inventory.clear();
slotMap.clear();
for (int i = 45; i < 54; i++) {
inventory.setItem(i, createEmptyStack());
}
inventory.setItem(45, createPreviousPageStack());
inventory.setItem(53, createNextPageStack());
inventory.setItem(prevSlot, createPreviousPageStack(player));
inventory.setItem(nextSlot, createNextPageStack(player));
inventory.setItem(47, createSearchItem());
inventory.setItem(48, createTypeItem());
inventory.setItem(50, createFilterItem());
@@ -372,13 +368,9 @@ public class SModMenu extends PageableCustomInventory {
if (punishments.size() > ci){
final Punishment punishment = punishments.get(ci);
int slot = i;
slotMap.put(slot, punishment);
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);
@@ -391,14 +383,10 @@ public class SModMenu extends PageableCustomInventory {
}
@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)){
public void click(InventoryClickEvent event) {
super.click(event);
int slot = event.getSlot();
if (slot == 47) {
if (event.isRightClick() && searchQuery != null){
player.playSound(player, Sound.UI_BUTTON_CLICK, 1f, 0.8f);
searchQuery = null;
@@ -408,22 +396,23 @@ public class SModMenu extends PageableCustomInventory {
player.playSound(player, Sound.UI_BUTTON_CLICK, 1f, 2f);
promptSearchQuery();
}
} else if (stack.equals(typeStack)) {
} else if (slot == 48) {
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);
} else if (slot == 50) {
cycleFilter(event.isRightClick());
} else if (slot == 51) {
cycleSort(event.isRightClick());
} else {
Punishment punishment = slotMap.get(slot);
if (punishment != null){
if (punishment.isActive()){
if ((punishment.type == PunishmentType.BAN && player.hasPermission("smod.unban")) || (punishment.type == PunishmentType.MUTE && player.hasPermission("smod.unmute"))) {
new ConfirmationInventory(player, translatable("smod.menu.undoConfirmation"), () -> {
punishment.undo(player.getUniqueId());
punishment.broadcastUndo(container);
player.playSound(player, Sound.BLOCK_NOTE_BLOCK_PLING, 1f, 2f);
this.open();
}, this::open, false).open();
}, this::open).open();
}
}
}
@@ -15,7 +15,7 @@ public class CustomInventoryListener implements Listener {
public void onInventoryClick(InventoryClickEvent event){
if (event.getInventory().getHolder() instanceof CustomInventory customInventory){
event.setCancelled(true);
customInventory.click(event.getCurrentItem(), event);
customInventory.click(event);
}
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
@@ -18,7 +18,7 @@ import org.bukkit.event.world.WorldSaveEvent;
import java.util.List;
import static de.shiewk.smoderation.paper.SModerationPaper.CHAT_PREFIX;
import static de.shiewk.smoderation.paper.SModerationPaper.PRIMARY_COLOR;
import static net.kyori.adventure.text.Component.translatable;
public class PunishmentListener implements Listener {
@@ -33,7 +33,7 @@ public class PunishmentListener implements Listener {
&& p.to.equals(event.getUniqueId())
&& p.isActive());
if (punishment != null){
event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_BANNED, CHAT_PREFIX.append(punishment.playerMessage()));
event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_BANNED, punishment.playerMessage().colorIfAbsent(PRIMARY_COLOR));
}
}
@@ -46,7 +46,7 @@ public class PunishmentListener implements Listener {
&& p.isActive());
if (punishment != null) {
event.setCancelled(true);
player.sendMessage(CHAT_PREFIX.append(punishment.playerMessage()));
player.sendMessage(punishment.playerMessage().colorIfAbsent(PRIMARY_COLOR));
}
}
@@ -66,7 +66,7 @@ public class PunishmentListener implements Listener {
|| message.toLowerCase().startsWith(str.toLowerCase()+" ")
)){
Bukkit.getConsoleSender().sendMessage(player.getName() + " tried to run forbidden command while muted");
player.sendMessage(CHAT_PREFIX.append(translatable("smod.punishment.playerMessage.mute.chat")));
player.sendMessage(translatable("smod.punishment.playerMessage.mute.chat", PRIMARY_COLOR));
event.setCancelled(true);
}
}
@@ -86,7 +86,7 @@ public class PunishmentListener implements Listener {
case KICK, BAN -> {
final Player player = Bukkit.getPlayer(punishment.to);
if (player != null) {
player.kick(CustomInventory.renderComponent(player, CHAT_PREFIX.append(punishment.playerMessage())));
player.kick(CustomInventory.renderComponent(player, punishment.playerMessage().colorIfAbsent(PRIMARY_COLOR)));
}
}
}
@@ -57,7 +57,7 @@ public class VanishListener implements Listener {
broadcast(message.color(null));
}
SchedulerUtil.scheduleForEntity(SModerationPaper.PLUGIN, player, () -> {
player.sendMessage(translatable("smod.vanish.stillEnabled"));
player.sendMessage(translatable("smod.command.vanish.stillEnabled"));
player.playSound(Sound.sound(
Key.key("minecraft", "block.beacon.power_select"),
Sound.Source.MASTER,
@@ -120,7 +120,7 @@ public class Punishment {
public void broadcastUndo(PunishmentContainer container){
for (CommandSender sender : container.collectBroadcastTargets()) {
sender.sendMessage(CHAT_PREFIX.append(undoMessage()));
sender.sendMessage(undoMessage().colorIfAbsent(PRIMARY_COLOR));
}
}
@@ -158,7 +158,7 @@ public class Punishment {
private void broadcastIssue(PunishmentContainer container){
for (CommandSender sender : container.collectBroadcastTargets()) {
sender.sendMessage(CHAT_PREFIX.append(broadcastMessage()));
sender.sendMessage(broadcastMessage().colorIfAbsent(PRIMARY_COLOR));
}
}
@@ -167,7 +167,7 @@ public class Punishment {
case MUTE, BAN -> {
final CommandSender sender = PlayerUtil.senderByUUID(to);
if (sender != null) {
sender.sendMessage(CHAT_PREFIX.append(playerMessage()));
sender.sendMessage(playerMessage().colorIfAbsent(PRIMARY_COLOR));
}
}
}
@@ -20,6 +20,7 @@ import java.util.function.Predicate;
import static de.shiewk.smoderation.paper.util.PlayerUtil.UUID_CONSOLE;
import static net.kyori.adventure.text.Component.translatable;
@SuppressWarnings("UnstableApiUsage") // Paper Brigadier API
public final class CommandUtil {
private CommandUtil(){}
+26 -9
View File
@@ -1,3 +1,27 @@
# If enabled, punishments can no longer be issued without providing a reason.
force-reason: false
# Allows you to toggle specific plugin features.
features:
# Should punishments be tracked and executed?
# Note that disabling this will also disable the SMod menu and
# /modlogs command since their only use is viewing punishments
punishments: true
# Should commands for opening the SMod menu be registered?
smodmenu: true
# Should commands for viewing player inventories be registered?
invsee: true
# Should commands for viewing player ender chests be registered?
enderchestsee: true
# Should commands for teleporting to offline players be registered?
offlinetp: true
# Should commands for viewing other players' messaging commands be registered?
socialspy: true
# Should players be able to enable vanish mode?
vanish: true
# A list of commands which will be captured and broadcast
# to players which have SocialSpy enabled (/socialspy).
socialspy-commands:
- w
- tell
@@ -9,7 +33,8 @@ socialspy-commands:
- minecraft:msg
- minecraft:teammsg
- minecraft:tm
force-reason: false
# A list of commands which muted players will not be able to run.
muted-forbidden-commands:
- w
- tell
@@ -23,11 +48,3 @@ muted-forbidden-commands:
- minecraft:teammsg
- minecraft:tm
- minecraft:me
features:
punishments: true
smodmenu: true
invsee: true
enderchestsee: true
offlinetp: true
socialspy: true
vanish: true
@@ -0,0 +1,111 @@
{
"smod.argument.duration.fail.invalid": "Ungültige Zeit '<arg:0>'",
"smod.argument.duration.fail.pattern": "Bitte gib eine gültige Zeit an, z.B. '1d6h30min'",
"smod.argument.offlinePlayer.fail.notCached": "Dieser Spieler ist nicht gespeichert.",
"smod.argument.uuid.fail.notCached": "Dieser Spieler ist nicht gespeichert. Versuche, stattdessen seine UUID anzugeben.",
"smod.chatInput.remainingTime": "<gray><arg:0> Sekunden",
"smod.command.ban.fail.forceReason": "Bitte gib einen Grund an.",
"smod.command.ban.fail.protect": "Dieser Spieler kann nicht gebannt werden.",
"smod.command.ban.fail.self": "Du kannst dich nicht selbst bannen.",
"smod.command.ban.fail.tooShort": "Du kannst Spieler, die nicht online sind, nicht so kurz bannen.",
"smod.command.ecsee.opening": "<primary>Enderkiste von <secondary><arg:0></secondary> wird geöffnet.",
"smod.command.fail.invalidPlayer": "Bitte gib einen gültigen Spieler an.",
"smod.command.fail.players": "Nur Spieler können diesen Befehl ausführen.",
"smod.command.fail.playersConsole": "Nur Spieler und die Konsole können diesen Befehl ausführen.",
"smod.command.invsee.fail.self": "Du kannst dein eigenes Inventar nicht öffnen.",
"smod.command.invsee.opening": "<primary>Inventar von <secondary><arg:0></secondary> wird geöffnet.",
"smod.command.kick.fail.forceReason": "Bitte gib einen Grund an.",
"smod.command.kick.fail.protect": "Dieser Spieler kann nicht gekickt werden.",
"smod.command.kick.fail.self": "Du kannst dich nicht selbst kicken.",
"smod.command.modlogs.ban": "<primary>- ist bis <secondary><arg:0></secondary> <gray>(in <arg:1>)</gray> gebannt. Grund: <secondary><arg:2>",
"smod.command.modlogs.heading": "<primary>Spieler <secondary><arg:0> <gray>(<arg:1>)",
"smod.command.modlogs.mute": "<primary>- ist bis <secondary><arg:0></secondary> <gray>(in <arg:1>)</gray> stummgeschaltet. Grund: <secondary><arg:2>",
"smod.command.modlogs.none": "<primary>- ist momentan nicht gebannt oder stummgeschaltet.",
"smod.command.mute.fail.forceReason": "Bitte gib einen Grund an.",
"smod.command.mute.fail.protect": "Dieser Spieler kann nicht stummgeschaltet werden.",
"smod.command.mute.fail.self": "Du kannst dich nicht selbst stummschalten.",
"smod.command.mute.fail.tooShort": "Du kannst Spieler nicht so kurz stummschalten.",
"smod.command.offlinetp.fail.unknown": "Die Position des Spielers ist nicht bekannt.",
"smod.command.offlinetp.teleporting": "<primary>Du wirst zu <secondary><arg:0></secondary> teleportiert.",
"smod.command.socialspy.disabled": "<primary>SocialSpy <red>deaktiviert</red>.",
"smod.command.socialspy.enabled": "<primary>SocialSpy <green>aktiviert</green>.",
"smod.command.unban.fail.notBanned": "Dieser Spieler ist nicht gebannt.",
"smod.command.unmute.fail.notMuted": "Dieser Spieler ist nicht stummgeschaltet.",
"smod.command.vanish.broadcast.off": "<primary><secondary><arg:0></secondary> ist wieder erschienen.",
"smod.command.vanish.broadcast.on": "<primary><secondary><arg:0></secondary> ist verschwunden.",
"smod.command.vanish.fail.noPlayersFound": "Kein Spieler wurde gefunden.",
"smod.command.vanish.list": "<primary>Diese Spieler sind momentan versteckt: <arg:0>",
"smod.command.vanish.list.none": "<primary>Keine Spieler sind gerade versteckt.",
"smod.command.vanish.stillEnabled": "<bold><primary>Du bist noch unsichtbar!",
"smod.command.vanish.toggle.off": "<primary>Du bist nicht mehr versteckt.",
"smod.command.vanish.toggle.on": "<primary>Du bist jetzt unsichtbar.",
"smod.confirm.no": "<red>Nein",
"smod.confirm.yes": "<green>Ja",
"smod.menu": "SMod Menü",
"smod.menu.filter": "Filter: <arg:0>",
"smod.menu.filter.active": "Aktive Strafen",
"smod.menu.filter.all": "Alle Strafen",
"smod.menu.filter.expired": "Abgelaufene Strafen",
"smod.menu.filter.switch": "\u00BB Klicke, um den Filter zu ändern",
"smod.menu.info.click": "\u00BB Klicke, um die Strafe aufzuheben",
"smod.menu.info.duration": "<secondary>Dauer: <primary><arg:0>",
"smod.menu.info.expiry.future": "<secondary>Läuft ab: <primary>In <arg:0>",
"smod.menu.info.expiry.past": "<secondary>Ist abgelaufen: <primary><arg:0> ago",
"smod.menu.info.player": "<secondary>Spieler: <primary><arg:0>",
"smod.menu.info.punishedBy": "<secondary>Bestraft von: <primary><arg:0>",
"smod.menu.info.reason": "<secondary>Grund: <primary><arg:0>",
"smod.menu.info.timestamp": "<secondary>Zeitpunkt: <primary><arg:0>",
"smod.menu.info.undone": "<red>Aufgehoben von: <gold><arg:0>",
"smod.menu.search": "Suchen",
"smod.menu.search.current": "Aktueller Suchbegriff: <arg:0>",
"smod.menu.search.new": "\u00BB Klicke, um einen neuen Suchbegriff einzugeben",
"smod.menu.search.none": "Keiner",
"smod.menu.search.query": "Gib deinen Suchbegriff in den Chat ein",
"smod.menu.search.remove": "\u00BB Rechtsklick, um die Suche aufzuheben",
"smod.menu.sort": "Sortieren nach: <arg:0>",
"smod.menu.sort.expiry": "Ablaufzeitpunkt",
"smod.menu.sort.moderatorName": "Moderatorname",
"smod.menu.sort.playerName": "Spielername",
"smod.menu.sort.switch": "\u00BB Klicke, um die Sortierung zu ändern",
"smod.menu.sort.time": "Ausstellungszeitpunkt",
"smod.menu.type": "Typ: <arg:0>",
"smod.menu.type.all": "Alle",
"smod.menu.type.switch": "\u00BB Klicke, um den Typ zu ändern",
"smod.menu.undoConfirmation": "Bist du sicher, dass du die Strafe aufheben willst?",
"smod.punishment.broadcast.ban": "<primary><secondary><arg:0></secondary> wurde von <secondary><arg:1></secondary> für <secondary><arg:2></secondary> gebannt.<newline>Grund: <secondary><arg:3>",
"smod.punishment.broadcast.kick": "<primary><secondary><arg:0></secondary> wurde von <secondary><arg:1></secondary> gekickt.<newline>Grund: <secondary><arg:3>",
"smod.punishment.broadcast.mute": "<primary><secondary><arg:0></secondary> wurde von <secondary><arg:1></secondary> für <secondary><arg:2></secondary> stummgeschaltet.<newline>Grund: <secondary><arg:3>",
"smod.punishment.name.ban": "Bann",
"smod.punishment.name.kick": "Kick",
"smod.punishment.name.mute": "Stummschaltung",
"smod.punishment.playerMessage.ban": "<primary>Du wurdest von <secondary><arg:0></secondary> vom Server gebannt.<newline>Grund: <secondary><arg:1></secondary><newline>Dein Bann läuft in <secondary><arg:2></secondary> ab.",
"smod.punishment.playerMessage.kick": "<primary>Du wurdest von <secondary><arg:0></secondary> vom Server gekickt.<newline>Grund: <secondary><arg:1>",
"smod.punishment.playerMessage.mute": "<primary>Du wurdest von <secondary><arg:0></secondary> stummgeschaltet.<newline>Grund: <secondary><arg:1></secondary><newline>Du kannst in <secondary><arg:2></secondary> wieder schreiben.",
"smod.punishment.playerMessage.mute.chat": "<primary>Du kannst diesen Befehl nicht ausführen, während du stummgeschaltet bist.",
"smod.punishment.undo.ban": "<primary><secondary><arg:0></secondary> wurde von <secondary><arg:1></secondary> entbannt.",
"smod.punishment.undo.mute": "<primary><secondary><arg:0></secondary>s Stummschaltung wurde von <secondary><arg:1></secondary> aufgehoben.",
"smod.socialspy.command": "<primary>[<secondary>SocialSpy</secondary>] <arg:0>: <secondary><arg:1>",
"smod.time.days": "<arg:0> Tage",
"smod.time.hours": "<arg:0> Stunden",
"smod.time.milliseconds": "<arg:0> Millisekunden",
"smod.time.minutes": "<arg:0> Minuten",
"smod.time.month.0": "Januar",
"smod.time.month.1": "Februar",
"smod.time.month.10": "November",
"smod.time.month.11": "Dezember",
"smod.time.month.2": "März",
"smod.time.month.3": "April",
"smod.time.month.4": "Mai",
"smod.time.month.5": "Juni",
"smod.time.month.6": "Juli",
"smod.time.month.7": "August",
"smod.time.month.8": "September",
"smod.time.month.9": "Oktober",
"smod.time.months": "<arg:0> Monate",
"smod.time.seconds": "<arg:0> Sekunden",
"smod.time.timestamp": "<arg:2>. <arg:1> <arg:0> <arg:3>:<arg:4>:<arg:5> <arg:6>",
"smod.time.weeks": "<arg:0> Wochen",
"smod.time.years": "<arg:0> Jahre",
"smod.inventory.next": "Nächste Seite (<arg:0>/<arg:1>)",
"smod.inventory.previous": "Vorherige Seite (<arg:0>/<arg:1>)"
}
@@ -8,17 +8,17 @@
"smod.command.ban.fail.protect": "This player can't be banned.",
"smod.command.ban.fail.self": "You can't ban yourself.",
"smod.command.ban.fail.tooShort": "You can't ban an offline player for less than 1ms.",
"smod.command.ecsee.opening": "<prefix>Opening ender chest of <secondary><arg:0></secondary>.",
"smod.command.ecsee.opening": "<primary>Opening ender chest of <secondary><arg:0></secondary>.",
"smod.command.fail.invalidPlayer": "Please provide a valid player.",
"smod.command.fail.players": "Only players can execute this command.",
"smod.command.fail.playersConsole": "Only players and the console can execute this command.",
"smod.command.invsee.fail.self": "You can't open your own inventory.",
"smod.command.invsee.opening": "<prefix>Opening inventory of <secondary><arg:0></secondary>.",
"smod.command.invsee.opening": "<primary>Opening inventory of <secondary><arg:0></secondary>.",
"smod.command.kick.fail.forceReason": "Please provide a reason.",
"smod.command.kick.fail.protect": "This player can't be kicked.",
"smod.command.kick.fail.self": "You can't kick yourself.",
"smod.command.modlogs.ban": "<primary>- is banned until <secondary><arg:0></secondary> <gray>(in <arg:1>)</gray>. Reason: <secondary><arg:2>",
"smod.command.modlogs.heading": "<prefix>Player <secondary><arg:0> <gray>(<arg:1>)",
"smod.command.modlogs.heading": "<primary>Player <secondary><arg:0> <gray>(<arg:1>)",
"smod.command.modlogs.mute": "<primary>- is muted until <secondary><arg:0></secondary> <gray>(in <arg:1>)</gray>. Reason: <secondary><arg:2>",
"smod.command.modlogs.none": "<primary>- is not currently muted or banned.",
"smod.command.mute.fail.forceReason": "Please provide a reason.",
@@ -26,20 +26,23 @@
"smod.command.mute.fail.self": "You can't mute yourself.",
"smod.command.mute.fail.tooShort": "You can't mute a player for less than 1ms.",
"smod.command.offlinetp.fail.unknown": "This player's location is unknown.",
"smod.command.offlinetp.teleporting": "<prefix>Teleporting you to <secondary><arg:0></secondary>.",
"smod.command.socialspy.disabled": "<prefix>SocialSpy <red>disabled</red>.",
"smod.command.socialspy.enabled": "<prefix>SocialSpy <green>enabled</green>.",
"smod.command.offlinetp.teleporting": "<primary>Teleporting you to <secondary><arg:0></secondary>.",
"smod.command.socialspy.disabled": "<primary>SocialSpy <red>disabled</red>.",
"smod.command.socialspy.enabled": "<primary>SocialSpy <green>enabled</green>.",
"smod.command.unban.fail.notBanned": "That player is not banned.",
"smod.command.unmute.fail.notMuted": "That player is not muted.",
"smod.command.vanish.broadcast.off": "<prefix><secondary><arg:0></secondary> re-appeared.",
"smod.command.vanish.broadcast.on": "<prefix><secondary><arg:0></secondary> vanished.",
"smod.command.vanish.broadcast.off": "<primary><secondary><arg:0></secondary> re-appeared.",
"smod.command.vanish.broadcast.on": "<primary><secondary><arg:0></secondary> vanished.",
"smod.command.vanish.fail.noPlayersFound": "No player was found.",
"smod.command.vanish.list": "<prefix>The following players are currently vanished: <arg:0>",
"smod.command.vanish.list.none": "<prefix>No players are currently vanished.",
"smod.command.vanish.toggle.off": "<prefix>You are no longer vanished.",
"smod.command.vanish.toggle.on": "<prefix>You are now vanished.",
"smod.command.vanish.list": "<primary>The following players are currently vanished: <arg:0>",
"smod.command.vanish.list.none": "<primary>No players are currently vanished.",
"smod.command.vanish.stillEnabled": "<bold><primary>You are still vanished!",
"smod.command.vanish.toggle.off": "<primary>You are no longer vanished.",
"smod.command.vanish.toggle.on": "<primary>You are now vanished.",
"smod.confirm.no": "<red>No",
"smod.confirm.yes": "<green>Yes",
"smod.inventory.next": "Next page (<arg:0>/<arg:1>)",
"smod.inventory.previous": "Previous page (<arg:0>/<arg:1>)",
"smod.menu": "SMod Menu",
"smod.menu.filter": "Filter: <arg:0>",
"smod.menu.filter.active": "Active punishments",
@@ -104,6 +107,5 @@
"smod.time.seconds": "<arg:0> seconds",
"smod.time.timestamp": "<arg:2> <arg:1> <arg:0> <arg:3>:<arg:4>:<arg:5> <arg:6>",
"smod.time.weeks": "<arg:0> weeks",
"smod.time.years": "<arg:0> years",
"smod.vanish.stillEnabled": "<bold><prefix>You are still vanished!"
"smod.time.years": "<arg:0> years"
}