diff --git a/necrify-api/src/main/java/de/jvstvshd/necrify/api/punishment/PunishmentType.java b/necrify-api/src/main/java/de/jvstvshd/necrify/api/punishment/PunishmentType.java index 0a0b19c..54b0487 100644 --- a/necrify-api/src/main/java/de/jvstvshd/necrify/api/punishment/PunishmentType.java +++ b/necrify-api/src/main/java/de/jvstvshd/necrify/api/punishment/PunishmentType.java @@ -28,6 +28,13 @@ public interface PunishmentType { String getName(); + /** + * Gets the ID of the punishment type. This ID is unique for each punishment type. + * @since 1.2.0 + * @return the ID of the punishment type. + */ + int getId(); + /** * Determines whether the punishment is a mute or not. * diff --git a/necrify-api/src/main/java/de/jvstvshd/necrify/api/punishment/StandardPunishmentType.java b/necrify-api/src/main/java/de/jvstvshd/necrify/api/punishment/StandardPunishmentType.java index ff18534..64f126b 100644 --- a/necrify-api/src/main/java/de/jvstvshd/necrify/api/punishment/StandardPunishmentType.java +++ b/necrify-api/src/main/java/de/jvstvshd/necrify/api/punishment/StandardPunishmentType.java @@ -66,6 +66,7 @@ public static StandardPunishmentType getById(int id) { return BY_ID.get(id); } + @Override public int getId() { return id; } diff --git a/necrify-common/src/main/java/de/jvstvshd/necrify/common/AbstractNecrifyPlugin.java b/necrify-common/src/main/java/de/jvstvshd/necrify/common/AbstractNecrifyPlugin.java index 749a6f2..158430f 100644 --- a/necrify-common/src/main/java/de/jvstvshd/necrify/common/AbstractNecrifyPlugin.java +++ b/necrify-common/src/main/java/de/jvstvshd/necrify/common/AbstractNecrifyPlugin.java @@ -47,6 +47,7 @@ import org.slf4j.Logger; import java.util.ArrayList; +import java.util.Arrays; import java.util.Set; import java.util.UUID; import java.util.concurrent.ExecutorService; @@ -89,12 +90,17 @@ public final void registerCommands(CommandManager manager, boolean manager.exceptionController() .registerHandler(ArgumentParseException.class, ExceptionHandler.unwrappingHandler(UserNotFoundParseException.class)) .registerHandler(UserNotFoundParseException.class, context -> { - context.context().sender().sendMessage("commands.general.not-found", Component.text(context.exception().playerName()).color(NamedTextColor.YELLOW)); + var component = getMessageProvider() + .provide("commands.general.not-found", Component.text(context.exception().playerName(), NamedTextColor.YELLOW)) + .color(NamedTextColor.RED); + context.context().sender().sendMessage(component); }); manager.exceptionController() .registerHandler(ArgumentParseException.class, ExceptionHandler.unwrappingHandler(PunishmentParser.PunishmentParseException.class)) .registerHandler(PunishmentParser.PunishmentParseException.class, context -> { - context.context().sender().sendMessage(context.exception().getMessage()); + var replacements = Arrays.stream(context.exception().getReplacements()).map(s -> Component.text(s, NamedTextColor.YELLOW)).toArray(Component[]::new); + var component = getMessageProvider().provide(context.exception().getMessage(), replacements).color(NamedTextColor.RED); + context.context().sender().sendMessage(component); }); /*manager.exceptionController()//.registerHandler(ArgumentParseException.class, ExceptionHandler.unwrappingHandler(ArgumentParseException.class)) .registerHandler(ArgumentParseException.class, context -> { diff --git a/necrify-common/src/main/java/de/jvstvshd/necrify/common/commands/NecrifyCommand.java b/necrify-common/src/main/java/de/jvstvshd/necrify/common/commands/NecrifyCommand.java index b6b0beb..cb73284 100644 --- a/necrify-common/src/main/java/de/jvstvshd/necrify/common/commands/NecrifyCommand.java +++ b/necrify-common/src/main/java/de/jvstvshd/necrify/common/commands/NecrifyCommand.java @@ -25,6 +25,7 @@ package de.jvstvshd.necrify.common.commands; import de.jvstvshd.necrify.api.duration.PunishmentDuration; +import de.jvstvshd.necrify.api.message.MessageProvider; import de.jvstvshd.necrify.api.punishment.Punishment; import de.jvstvshd.necrify.api.punishment.StandardPunishmentType; import de.jvstvshd.necrify.api.user.NecrifyUser; @@ -32,6 +33,7 @@ import de.jvstvshd.necrify.api.user.UserManager; import de.jvstvshd.necrify.common.AbstractNecrifyPlugin; import de.jvstvshd.necrify.common.util.PunishmentHelper; +import de.jvstvshd.necrify.common.util.Util; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.event.ClickEvent; @@ -56,6 +58,7 @@ import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; @@ -66,6 +69,7 @@ public class NecrifyCommand { private final UserManager userManager; private final Logger logger; private final ExecutorService executor; + private final MessageProvider provider; private static final List PUNISHMENT_COMMAND_OPTIONS = List.of("cancel", "remove", "info", "change"); private static final List USER_COMMAND_OPTIONS = List.of("info", "delete"); @@ -76,6 +80,7 @@ public NecrifyCommand(AbstractNecrifyPlugin plugin) { this.miniMessage = MiniMessage.miniMessage(); this.logger = plugin.getLogger(); this.executor = plugin.getExecutor(); + this.provider = plugin.getMessageProvider(); } //COMMANDS @@ -215,21 +220,22 @@ public void unmuteCommand( @Argument(value = "target", description = "Player to unmute", suggestions = "suggestOnlinePlayers") NecrifyUser target ) { var punishments = target.getPunishments(StandardPunishmentType.TEMPORARY_MUTE, StandardPunishmentType.PERMANENT_MUTE); + System.out.println(punishments); removePunishments(sender, "unmute", punishments); } - @Command("necrify punishment [option]") + @Command("necrify punishment [option]") @Permission(value = {"necrify.command.punishment", "necrify.admin"}, mode = Permission.Mode.ANY_OF) public void punishmentCommand( NecrifyUser sender, - @Argument(value = "punishment", description = "Punishment to manage") Punishment punishment, - @Argument(value = "option", description = "Option to manage the punishment", suggestions = "suggestPunishmentCommandOptions") @Default("info") String option + @Argument(value = "punishmentId", description = "Punishment to manage") Punishment punishmentParsed, + @Argument(value = "option", description = "Option to manage the punishment", suggestions = "suggestPunishmentCommandOptions") @Default(value = "info") String option ) { switch (option) { case "info" -> - sender.sendMessage(buildComponent(PunishmentHelper.buildPunishmentData(punishment, plugin.getMessageProvider()), punishment)); + sender.sendMessage(buildComponent(PunishmentHelper.buildPunishmentData(punishmentParsed, plugin.getMessageProvider()), punishmentParsed)); case "cancel", "remove" -> { - punishment.cancel().whenCompleteAsync((unused, th) -> { + punishmentParsed.cancel().whenCompleteAsync((unused, th) -> { if (th != null) { logException(sender, th); return; @@ -253,11 +259,12 @@ public void userCommand( switch (option) { case "info" -> { var punishments = target.getPunishments(); + sender.sendMessage(provider.provide("command.user.overview", + Util.copyComponent(Objects.requireNonNullElse(target.getUsername(), "null"), provider).color(NamedTextColor.YELLOW), + Util.copyComponent(target.getUuid().toString(), provider).color(NamedTextColor.YELLOW), + Component.text(punishments.size()))); for (Punishment punishment : punishments) { - Component component = PunishmentHelper.buildPunishmentData(punishment, plugin.getMessageProvider()) - .clickEvent(ClickEvent.suggestCommand(punishment.getPunishmentUuid().toString().toLowerCase(Locale.ROOT))) - .hoverEvent((HoverEventSource) op -> HoverEvent.showText(plugin.getMessageProvider().provide("commands.general.copy") - .color(NamedTextColor.GREEN))); + Component component = buildComponent(PunishmentHelper.buildPunishmentData(punishment, plugin.getMessageProvider()), punishment); sender.sendMessage(component); } } @@ -344,10 +351,12 @@ private void removePunishments(NecrifyUser source, String commandName, List) op -> HoverEvent.showText(Component - .text("Click to remove punishment").color(NamedTextColor.GREEN))); + var clickToRemove = provider.provide("command.punishment.click-to-remove"); + return dataComponent.append( + clickToRemove + .color(NamedTextColor.RED) + .clickEvent(ClickEvent.runCommand("/necrify punishment " + punishment.getPunishmentUuid().toString().toLowerCase(Locale.ROOT) + " remove")) + .hoverEvent((HoverEventSource) op -> HoverEvent.showText(clickToRemove.color(NamedTextColor.GREEN)))); } private Component reasonOrDefaultTo(String reason, StandardPunishmentType type) { diff --git a/necrify-common/src/main/java/de/jvstvshd/necrify/common/commands/PunishmentParser.java b/necrify-common/src/main/java/de/jvstvshd/necrify/common/commands/PunishmentParser.java index 8953a0e..5aefa02 100644 --- a/necrify-common/src/main/java/de/jvstvshd/necrify/common/commands/PunishmentParser.java +++ b/necrify-common/src/main/java/de/jvstvshd/necrify/common/commands/PunishmentParser.java @@ -49,14 +49,18 @@ public PunishmentParser(Necrify necrify) { var uuidString = commandInput.peekString(); var uuid = Util.fromString(uuidString); if (uuid.isEmpty()) { - return CompletableFuture.completedFuture(ArgumentParseResult.failure(new PunishmentParseException("command.parse.uuid.invalid", uuidString))); + return CompletableFuture.completedFuture(ArgumentParseResult.failure(new PunishmentParseException("command.punishment.uuid-parse-error", uuidString))); } return necrify.getPunishment(uuid.get()).handle((punishment, throwable) -> { if (throwable != null) { return ArgumentParseResult.failure(new PunishmentParseException("error.internal")); } - return punishment.map(ArgumentParseResult::success) - .orElseGet(() -> ArgumentParseResult.failure(new PunishmentParseException("command.parse.punishment.notfound", uuidString))); + if (punishment.isPresent()) { + commandInput.readString(); + return ArgumentParseResult.success(punishment.get()); + } else { + return ArgumentParseResult.failure(new PunishmentParseException("command.punishment.unknown-punishment-id", uuidString)); + } }); } diff --git a/necrify-common/src/main/java/de/jvstvshd/necrify/common/punishment/AbstractPunishment.java b/necrify-common/src/main/java/de/jvstvshd/necrify/common/punishment/AbstractPunishment.java index d77046f..5a22859 100644 --- a/necrify-common/src/main/java/de/jvstvshd/necrify/common/punishment/AbstractPunishment.java +++ b/necrify-common/src/main/java/de/jvstvshd/necrify/common/punishment/AbstractPunishment.java @@ -145,7 +145,8 @@ public String toString() { return "AbstractPunishment{" + "reason=" + reason + ", service=" + service + - ", user=" + user + + ", userUuid=" + user.getUuid() + + ", userName=" + user.getUsername() + ", punishmentUuid=" + punishmentUuid + ", messageProvider=" + messageProvider + ", validity=" + validity + diff --git a/necrify-common/src/main/java/de/jvstvshd/necrify/common/util/PunishmentHelper.java b/necrify-common/src/main/java/de/jvstvshd/necrify/common/util/PunishmentHelper.java index db8949b..75dd0dc 100644 --- a/necrify-common/src/main/java/de/jvstvshd/necrify/common/util/PunishmentHelper.java +++ b/necrify-common/src/main/java/de/jvstvshd/necrify/common/util/PunishmentHelper.java @@ -29,6 +29,7 @@ import de.jvstvshd.necrify.api.punishment.TemporalPunishment; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; public class PunishmentHelper { @@ -39,10 +40,13 @@ private PunishmentHelper() { public static Component buildPunishmentData(Punishment punishment, MessageProvider provider) { return Component.text() .append(provider.provide("helper.type").color(NamedTextColor.AQUA), - Component.text(punishment.getType().getName()).color(NamedTextColor.YELLOW), + copyable("%s (%d)".formatted(punishment.getType().getName(), punishment.getType().getId()), NamedTextColor.YELLOW, provider), Component.newline(), provider.provide("helper.reason").color(NamedTextColor.AQUA), - punishment.getReason(), + Util.copyComponent(punishment.getReason(), PlainTextComponentSerializer.plainText().serialize(punishment.getReason()), provider), + Component.newline(), + provider.prefixed(Component.text("ID: ")).color(NamedTextColor.AQUA), + copyable(punishment.getPunishmentUuid().toString(), NamedTextColor.YELLOW, provider), Component.newline(), punishment instanceof TemporalPunishment temporalPunishment ? buildPunishmentDataTemporal(temporalPunishment, provider) : Component.text(""), @@ -52,7 +56,13 @@ public static Component buildPunishmentData(Punishment punishment, MessageProvid } public static Component buildPunishmentDataTemporal(TemporalPunishment punishment, MessageProvider provider) { - return punishment.isPermanent() ? Component.text("permanent").color(NamedTextColor.RED) : Component.text() + if (punishment.isPermanent()) { + return Component.text() + .append(provider.provide("helper.temporal.duration").color(NamedTextColor.AQUA), + Component.text("PERMANENT").color(NamedTextColor.RED)) + .build(); + } + return Component.text() .append(provider.provide("helper.temporal.duration").color(NamedTextColor.AQUA), Component.text(punishment.getDuration().remainingDuration()).color(NamedTextColor.YELLOW), Component.newline(), @@ -60,4 +70,8 @@ public static Component buildPunishmentDataTemporal(TemporalPunishment punishmen Component.text(punishment.getDuration().expirationAsString()).color(NamedTextColor.YELLOW)) .build(); } + + private static Component copyable(String s, NamedTextColor color, MessageProvider provider) { + return Util.copyComponent(s, provider).color(color); + } } diff --git a/necrify-common/src/main/java/de/jvstvshd/necrify/common/util/Util.java b/necrify-common/src/main/java/de/jvstvshd/necrify/common/util/Util.java index 59055ac..855c417 100644 --- a/necrify-common/src/main/java/de/jvstvshd/necrify/common/util/Util.java +++ b/necrify-common/src/main/java/de/jvstvshd/necrify/common/util/Util.java @@ -31,6 +31,7 @@ import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.event.HoverEventSource; import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import java.util.Optional; import java.util.UUID; @@ -64,6 +65,11 @@ public static TextComponent copyComponent(String text, MessageProvider provider) .hoverEvent((HoverEventSource) op -> HoverEvent.showText(provider.provide("commands.general.copy").color(NamedTextColor.GREEN))); } + public static Component copyComponent(Component base, String copy, MessageProvider provider) { + return base.clickEvent(ClickEvent.suggestCommand(copy)) + .hoverEvent((HoverEventSource) op -> HoverEvent.showText(provider.provide("commands.general.copy").color(NamedTextColor.GREEN))); + } + public static Optional fromString(String uuidString) { UUID uuid; try { diff --git a/necrify-common/src/main/resources/translations/de.properties b/necrify-common/src/main/resources/translations/de.properties index 5254535..d0ca1ca 100644 --- a/necrify-common/src/main/resources/translations/de.properties +++ b/necrify-common/src/main/resources/translations/de.properties @@ -14,10 +14,11 @@ command.punishment.usage=Bitte benutze /punishment oder < command.punishment.not-banned=Dieser Spieler ist derzeit nicht gebannt. command.punishment.not-muted=Dieser Spieler ist derzeit nicht gemutet. command.punishment.cancel.success=Die Strafe wurde erfolgreich abgebrochen. +command.punishment.click-to-remove=Zum Entfernen klicken command.punishment.punishments=Dieser Spieler hat derzeit {0} laufende Bestrafungen. -command.punishment.uuid-parse-error='{0}' ist keine valide UUID. +command.punishment.uuid-parse-error={0} ist keine valide UUID. command.punishment.unknown-option=Unbekannte Option: {0} -command.punishment.unknown-punishment-id=Es konnte keine Strafe für die ID '{0}' gefunden werden. +command.punishment.unknown-punishment-id=Es konnte keine Strafe für die ID {0} gefunden werden. command.tempban.usage=Bitte benutze /tempban [Grund]. command.tempban.success=Du hast den Spieler {0}/{1} für {2} bis {3} gebannt. command.tempmute.usage=Bitte benutze /tempmute [Grund]. @@ -29,6 +30,7 @@ command.unmute.usage=Bitte benutze /unban . command.unmute.success=Der Spieler ist nun nicht mehr gemutet. command.unmute.multiple-mutes=Dieser Spieler wurde mehrfach gemutet. command.user.delete.success=Der User wurde inklusive all seiner Strafen erfolgreich gelöscht. +command.user.overview=Der User {0} mit der UUID {1} hat {2} Strafen. command.whitelist.usage=Bitte nutze /whitelist [add|remove] command.whitelist.status=Der Whitelist-Status des Spielers {0} ist folgender: {1}. command.whitelist.success=Der Eintrag wurde erfolgreich aktualisiert. diff --git a/necrify-common/src/main/resources/translations/en.properties b/necrify-common/src/main/resources/translations/en.properties index 6136665..f0482f3 100644 --- a/necrify-common/src/main/resources/translations/en.properties +++ b/necrify-common/src/main/resources/translations/en.properties @@ -13,10 +13,11 @@ command.punishment.usage=Please use /punishment or [reason]. command.tempban.success=You have banned the player {0}/{1} for {2} until {3}. command.tempmute.usage=Please use /tempmute [reason]. @@ -27,11 +28,13 @@ command.unban.multiple-bans=This player has been banned multiple times. command.unmute.usage=Please use /unban . command.unmute.success=The player is no longer muted. command.unmute.multiple-mutes=This player has been muted multiple times. +command.user.overview=User {0} with UUID {1} has {2} punishments. command.whitelist.usage=Please use /whitelist [add|remove] command.whitelist.status={0}'s whitelist status: {1}. command.whitelist.success=Updated successfully. command.whitelist.enabled=The whitelist is now enabled. command.whitelist.disabled=The whitelist is now disabled. +command.user.delete.success=The user has been successfully deleted including all of his punishments. error.internal=An internal error occurred. Please contact the network administration. helper.type=Type: helper.reason=reason: diff --git a/necrify-velocity/src/main/java/de/jvstvshd/necrify/velocity/NecrifyVelocityPlugin.java b/necrify-velocity/src/main/java/de/jvstvshd/necrify/velocity/NecrifyVelocityPlugin.java index 643877b..33b81ad 100644 --- a/necrify-velocity/src/main/java/de/jvstvshd/necrify/velocity/NecrifyVelocityPlugin.java +++ b/necrify-velocity/src/main/java/de/jvstvshd/necrify/velocity/NecrifyVelocityPlugin.java @@ -361,20 +361,19 @@ public void setUserManager(@NotNull UserManager userManager) { @SuppressWarnings("unchecked") @Override public CompletableFuture> getPunishment(@NotNull UUID punishmentId) { - System.out.println(punishmentId); return Util.executeAsync(() -> (Optional) Query .query("SELECT u.* FROM punishment.necrify_user u INNER JOIN punishment.necrify_punishment p ON u.uuid = p.uuid WHERE p.punishment_id = ?;") .single(Call.of().bind(punishmentId, Adapters.UUID_ADAPTER)) .map(row -> { var userId = row.getObject(1, UUID.class); - System.out.println("User " + userId); - return createUser(userId).getPunishment(punishmentId).orElse(null); + var user = createUser(userId, true); + return user.getPunishment(punishmentId).orElse(null); }).first(), getExecutor()); } public NecrifyUser createUser(CommandSource source) { if (source instanceof Player) { - return createUser(((Player) source).getUniqueId()); + return createUser(((Player) source).getUniqueId(), false); } else if (source instanceof ConsoleCommandSource) { return new VelocityConsoleUser(messageProvider, server.getConsoleCommandSource()); } else { @@ -387,19 +386,29 @@ public NecrifyUser createUser(CommandSource source) { *

Note: this user does not hold any valid data besides his uuid and maybe player instance (if online). After returning * the value, the missing user data will be loaded, whereafter the {@link UserLoadedEvent} will be fired.

* - * @param userId the UUID of the user to create. + * @param userId the UUID of the user to create. + * @param loadPunishmentsDirectly whether to load the punishments directly or not. This influences if punishments are + * loaded asynchronously or not. If set to true, punishments will be loaded blocking. * @return the created user. */ - public NecrifyUser createUser(UUID userId) { + public NecrifyUser createUser(UUID userId, boolean loadPunishmentsDirectly) { var cachedUser = getUserManager().getUser(userId); if (cachedUser.isPresent()) { return cachedUser.get(); } var user = new VelocityUser(userId, "unknown", false, this); - getExecutor().execute(() -> { - Query.query("SELECT type, expiration, reason, punishment_id FROM punishment.necrify_punishment WHERE uuid = ?;").single(Call.of().bind(userId, Adapters.UUID_ADAPTER)).map(user::addPunishment).all(); + Runnable loadPunishments = () -> { + Query.query("SELECT type, expiration, reason, punishment_id FROM punishment.necrify_punishment WHERE uuid = ?;") + .single(Call.of().bind(userId, Adapters.UUID_ADAPTER)) + .map(user::addPunishment) + .all(); getEventDispatcher().dispatch(new UserLoadedEvent(user).setOrigin(EventOrigin.ofClass(getClass()))); - }); + }; + if (loadPunishmentsDirectly) { + loadPunishments.run(); + } else { + getExecutor().execute(loadPunishments); + } return user; } diff --git a/necrify-velocity/src/main/java/de/jvstvshd/necrify/velocity/user/VelocityUser.java b/necrify-velocity/src/main/java/de/jvstvshd/necrify/velocity/user/VelocityUser.java index d8fc723..b2dfe95 100644 --- a/necrify-velocity/src/main/java/de/jvstvshd/necrify/velocity/user/VelocityUser.java +++ b/necrify-velocity/src/main/java/de/jvstvshd/necrify/velocity/user/VelocityUser.java @@ -268,6 +268,8 @@ public void setPlayer(Player player) { } public void addPunishment(Punishment punishment) { + if (punishments.contains(punishment)) + return; punishments.add(punishment); }