From bd88b16e1440731c66a670bbc581d28d4e1a4d5b Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Wed, 15 May 2024 13:04:37 +0300 Subject: [PATCH 01/19] use double quotes --- src/main/resources/messages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml index 68756c1..3f45c02 100644 --- a/src/main/resources/messages.yml +++ b/src/main/resources/messages.yml @@ -3,7 +3,7 @@ usage: "(!) Usage: /" banned-player: (!) Banned singular non-member player . banned-member: (!) Banned member . No alts found. -banned-member-chain: '(!) Banned member and alts: .' +banned-member-chain: "(!) Banned member and alts: ." unbanned-player: (!) Unbanned singular non-member player . unbanned-member: (!) Unbanned member and alts. From b5aa7e06febdd815949206b1febe8f59430d9539 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Wed, 15 May 2024 13:06:06 +0300 Subject: [PATCH 02/19] command to list alts --- .../pro/cloudnode/smp/smpcore/Member.java | 7 ++ .../pro/cloudnode/smp/smpcore/Messages.java | 89 +++++++++++++++++++ .../pro/cloudnode/smp/smpcore/Permission.java | 19 ++++ .../smp/smpcore/command/MainCommand.java | 82 ++++++++++++++++- src/main/resources/messages.yml | 15 ++++ 5 files changed, 211 insertions(+), 1 deletion(-) diff --git a/src/main/java/pro/cloudnode/smp/smpcore/Member.java b/src/main/java/pro/cloudnode/smp/smpcore/Member.java index 95aa925..514584a 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/Member.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/Member.java @@ -11,9 +11,12 @@ import java.sql.SQLException; import java.util.Date; import java.util.HashSet; +import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.UUID; import java.util.logging.Level; +import java.util.stream.Collectors; public final class Member { public final @NotNull UUID uuid; @@ -184,4 +187,8 @@ private void delete() { } return members; } + + public static @NotNull Set<@NotNull String> getNames() { + return get().stream().map(m -> m.player().getName()).filter(Objects::nonNull).collect(Collectors.toSet()); + } } diff --git a/src/main/java/pro/cloudnode/smp/smpcore/Messages.java b/src/main/java/pro/cloudnode/smp/smpcore/Messages.java index fb9bb54..6bf9f53 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/Messages.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/Messages.java @@ -1,11 +1,14 @@ package pro.cloudnode.smp.smpcore; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import org.bukkit.OfflinePlayer; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -61,6 +64,60 @@ public Messages() { .toString())), Placeholder.unparsed("n-alts", String.valueOf(alts.size()))); } + public @NotNull Component subCommandHeader(final @NotNull String name, final @NotNull String usage) { + return MiniMessage.miniMessage() + .deserialize(Objects.requireNonNull(config.getString("subcommands.header")), Placeholder.unparsed("name", name), Placeholder.unparsed("usage", usage)); + } + + public @NotNull Component subCommandEntry(final @NotNull String command, final @NotNull String label, final @NotNull SubCommandArgument @NotNull [] args) { + return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("subcommands.entry")) + .replace("", command), Placeholder.unparsed("label", label), Placeholder.component("args", SubCommandArgument.join(args))); + } + + public @NotNull Component subCommandEntry(final @NotNull String command, final @NotNull String label) { + return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("subcommands.entry")) + .replace("", command), Placeholder.unparsed("label", label), Placeholder.component("args", SubCommandArgument.join(SubCommandArgument.of()))); + } + + public @NotNull Component subCommandEntry(final @NotNull String command, final @NotNull String label, final @NotNull SubCommandArgument @NotNull [] args, final @NotNull String description) { + return MiniMessage.miniMessage() + .deserialize(Objects.requireNonNull(config.getString("subcommands.entry-with-description")) + .replace("", command), Placeholder.unparsed("label", label), Placeholder.component("args", SubCommandArgument.join(args)), Placeholder.unparsed("description", description)); + } + + public @NotNull Component subCommandEntry(final @NotNull String command, final @NotNull String label, final @NotNull String description) { + return MiniMessage.miniMessage() + .deserialize(Objects.requireNonNull(config.getString("subcommands.entry-with-description")) + .replace("", command), Placeholder.unparsed("label", label), Placeholder.component("args", SubCommandArgument.join(SubCommandArgument.of())), Placeholder.unparsed("description", description)); + } + + public @NotNull Component altsHeader(final @NotNull Member member) { + return MiniMessage.miniMessage() + .deserialize(Objects.requireNonNull(config.getString("alts.header")), Placeholder.unparsed("player", Optional + .ofNullable(member.player().getName()).orElse(member.player().getUniqueId().toString()))); + } + + public @NotNull Component altsNone() { + return MiniMessage.miniMessage() + .deserialize(Objects.requireNonNull(config.getString("alts.none"))); + } + + public @NotNull Component altsEntry(final @NotNull Member alt) { + return MiniMessage.miniMessage() + .deserialize(Objects.requireNonNull(config.getString("alts.entry")), Placeholder.unparsed("alt", Optional + .ofNullable(alt.player().getName()).orElse(alt.player().getUniqueId().toString()))); + } + + public @NotNull Component subCommandArgumentRequired(final @NotNull String argument) { + return MiniMessage.miniMessage() + .deserialize(Objects.requireNonNull(config.getString("subcommands.argument.required")), Placeholder.unparsed("arg", argument)); + } + + public @NotNull Component subCommandArgumentOptional(final @NotNull String argument) { + return MiniMessage.miniMessage() + .deserialize(Objects.requireNonNull(config.getString("subcommands.argument.optional")), Placeholder.unparsed("arg", argument)); + } + public @NotNull Component errorNoPermission() { return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("error.no-permission"))); } @@ -70,4 +127,36 @@ public Messages() { .deserialize(Objects.requireNonNull(config.getString("error.player-not-banned")), Placeholder.unparsed("player", Optional .ofNullable(player.getName()).orElse(player.getUniqueId().toString()))); } + + public @NotNull Component errorNotMember(final @NotNull OfflinePlayer player) { + return MiniMessage.miniMessage() + .deserialize(Objects.requireNonNull(config.getString("error.not-member")), Placeholder.unparsed("player", Optional + .ofNullable(player.getName()).orElse(player.getUniqueId().toString()))); + } + + public @NotNull Component errorNotMember() { + return MiniMessage.miniMessage() + .deserialize(Objects.requireNonNull(config.getString("error.not-member-you"))); + } + + public record SubCommandArgument(@NotNull String name, boolean required) { + public @NotNull Component component() { + return required ? SMPCore.messages().subCommandArgumentRequired(name) : SMPCore.messages() + .subCommandArgumentOptional(name); + } + + public static @NotNull Component join(final @NotNull SubCommandArgument @NotNull [] args) { + final @NotNull TextComponent.Builder builder = Component.text().append(Component.text(" ")); + + for (int i = 0; i < args.length; ++i) { + builder.append(args[i].component()); + if (i < args.length - 1) builder.append(Component.text(" ")); + } + return builder.build(); + } + + public static @NotNull SubCommandArgument @NotNull [] of(final @Nullable SubCommandArgument @NotNull ... args) { + return Arrays.stream(args).filter(Objects::nonNull).toArray(SubCommandArgument[]::new); + } + } } diff --git a/src/main/java/pro/cloudnode/smp/smpcore/Permission.java b/src/main/java/pro/cloudnode/smp/smpcore/Permission.java index 2444e2a..ac3338c 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/Permission.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/Permission.java @@ -5,4 +5,23 @@ public final class Permission { public static @NotNull String RELOAD = "smpcore.reload"; public static @NotNull String BAN = "smpcore.ban"; + /** + * See your own alts + */ + public static @NotNull String ALT = "smpcore.alt"; + + /** + * See someone else's alts + */ + public static @NotNull String ALT_OTHER = "smpcore.alt.other"; + + /** + * Add an alt + */ + public static @NotNull String ALT_ADD = "smpcore.alt.add"; + + /** + * Add an alt for someone else + */ + public static @NotNull String ALT_ADD_OTHER = "smpcore.alt.add.other"; } diff --git a/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java b/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java index c0be72d..8cd3a8f 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java @@ -1,15 +1,24 @@ package pro.cloudnode.smp.smpcore.command; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import pro.cloudnode.smp.smpcore.Member; +import pro.cloudnode.smp.smpcore.Messages; import pro.cloudnode.smp.smpcore.Permission; import pro.cloudnode.smp.smpcore.SMPCore; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Optional; public final class MainCommand extends Command { @Override @@ -18,16 +27,34 @@ public boolean run(@NotNull CommandSender sender, @NotNull String label, @NotNul final @NotNull String @NotNull [] argsSubset = Arrays.copyOfRange(args, 1, args.length); return switch (args[0]) { case "reload" -> reload(sender); + case "alt" -> alt(sender, argsSubset, label + " " + args[0]); default -> sendMessage(sender, MiniMessage.miniMessage() .deserialize("(!) Unrecognised command ", Placeholder.unparsed("command", args[0]))); }; } @Override - public @NotNull List<@NotNull String> tab(@NotNull CommandSender sender, @NotNull String label, @NotNull String @NotNull [] args) { + public @Nullable List<@NotNull String> tab(@NotNull CommandSender sender, @NotNull String label, @NotNull String @NotNull [] args) { final @NotNull ArrayList<@NotNull String> suggestions = new ArrayList<>(); if (args.length == 1) { if (sender.hasPermission(Permission.RELOAD)) suggestions.add("reload"); + if (sender.hasPermission(Permission.ALT)) suggestions.add("alt"); + } + else if (args.length > 1) switch (args[0]) { + case "alt" -> { + if (args.length == 2) { + if (sender.hasPermission(Permission.ALT)) suggestions.add("list"); + if (sender.hasPermission(Permission.ALT_ADD)) suggestions.add("add"); + } + else switch (args[1]) { + case "list" -> { + if (args.length == 3 && sender.hasPermission(Permission.ALT_OTHER)) suggestions.addAll(Member.getNames()); + } + case "add" -> { + if (args.length == 4 && sender.hasPermission(Permission.ALT_ADD_OTHER)) suggestions.addAll(Member.getNames()); + } + } + } } return suggestions; } @@ -53,4 +80,57 @@ public static boolean reload(final @NotNull CommandSender sender) { SMPCore.getInstance().reload(); return sendMessage(sender, SMPCore.messages().reloaded()); } + + /** + *
  • {@code alt} - show list of subcommands + *
  • {@code alt list [player]} - show list of alts + *
  • {@code alt add [player]} - add an alt + */ + public static boolean alt(final @NotNull CommandSender sender, final @NotNull String @NotNull [] args, final @NotNull String label) { + if (!sender.hasPermission(Permission.ALT)) return sendMessage(sender, SMPCore.messages().errorNoPermission()); + + final @NotNull String command = "/" + label; + + final @NotNull TextComponent.Builder subCommandBuilder = Component.text() + .append(SMPCore.messages().subCommandHeader("Alt", command + " ...")).append(Component.newline()) + .append(SMPCore.messages() + .subCommandEntry(command + " list ", "list", Messages.SubCommandArgument.of(sender.hasPermission(Permission.ALT_OTHER) ? new Messages.SubCommandArgument("player", false) : null))); + if (sender.hasPermission(Permission.ALT_ADD)) subCommandBuilder.append(Component.newline()).append(SMPCore.messages() + .subCommandEntry(command + " add ", "add", Messages.SubCommandArgument.of(new Messages.SubCommandArgument("username", true), sender.hasPermission(Permission.ALT_ADD_OTHER) ? new Messages.SubCommandArgument("owner", false) : null))); + + if (args.length == 0) return sendMessage(sender, subCommandBuilder.build()); + else switch (args[0]) { + case "list" -> { + final @NotNull OfflinePlayer target; + if (!(sender instanceof final @NotNull Player player)) { + if (args.length == 1) return sendMessage(sender, SMPCore.messages().usage(label, "list ")); + target = sender.getServer().getOfflinePlayer(args[1]); + } + else { + if (args.length > 1 && player.hasPermission(Permission.ALT_OTHER)) target = player.getServer().getOfflinePlayer(args[1]); + else target = player; + } + final @NotNull Optional<@NotNull Member> targetMember = Member.get(target); + if (targetMember.isEmpty()) { + if (sender instanceof final @NotNull Player player && target.getUniqueId().equals(player.getUniqueId())) return sendMessage(sender, SMPCore.messages().errorNotMember()); + else return sendMessage(sender, SMPCore.messages().errorNotMember(target)); + } + + final @NotNull Member member = targetMember.get().altOwner().orElse(targetMember.get()); + final @NotNull HashSet<@NotNull Member> alts = member.getAlts(); + + sendMessage(sender, SMPCore.messages().altsHeader(member)); + if (alts.isEmpty()) return sendMessage(sender, SMPCore.messages().altsNone()); + for (final @NotNull Member alt : alts) + sendMessage(sender, SMPCore.messages().altsEntry(alt)); + return true; + } + case "add" -> { + return sendMessage(sender, Component.text("add")); + } + default -> { + return sendMessage(sender, subCommandBuilder.build()); + } + } + } } diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml index 3f45c02..4e72a46 100644 --- a/src/main/resources/messages.yml +++ b/src/main/resources/messages.yml @@ -8,6 +8,21 @@ banned-member-chain: "(!) Banned member and < unbanned-player: (!) Unbanned singular non-member player . unbanned-member: (!) Unbanned member and alts. +subcommands: + header: Sub Commands: () + entry: >> + entry-with-description: >> - + argument: + required: <> + optional: [] + +alts: + header: Alts of : + none: " (none)" + entry: > + error: no-permission: (!) You don't have permission to use this command. player-not-banned: (!) Player is not banned and is not a member. + not-member: (!) Player is not a member. + not-member-you: (!) You are not a member. From a36f5f92416fcc9b6ba0aae5f975c181e59b5f0a Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Wed, 15 May 2024 13:11:32 +0300 Subject: [PATCH 03/19] make subcommand args correctly show as required for console --- .../java/pro/cloudnode/smp/smpcore/command/MainCommand.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java b/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java index 8cd3a8f..8f6ffdf 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java @@ -94,9 +94,9 @@ public static boolean alt(final @NotNull CommandSender sender, final @NotNull St final @NotNull TextComponent.Builder subCommandBuilder = Component.text() .append(SMPCore.messages().subCommandHeader("Alt", command + " ...")).append(Component.newline()) .append(SMPCore.messages() - .subCommandEntry(command + " list ", "list", Messages.SubCommandArgument.of(sender.hasPermission(Permission.ALT_OTHER) ? new Messages.SubCommandArgument("player", false) : null))); + .subCommandEntry(command + " list ", "list", Messages.SubCommandArgument.of(sender.hasPermission(Permission.ALT_OTHER) ? new Messages.SubCommandArgument("player", !(sender instanceof Player)) : null))); if (sender.hasPermission(Permission.ALT_ADD)) subCommandBuilder.append(Component.newline()).append(SMPCore.messages() - .subCommandEntry(command + " add ", "add", Messages.SubCommandArgument.of(new Messages.SubCommandArgument("username", true), sender.hasPermission(Permission.ALT_ADD_OTHER) ? new Messages.SubCommandArgument("owner", false) : null))); + .subCommandEntry(command + " add ", "add", Messages.SubCommandArgument.of(new Messages.SubCommandArgument("username", true), sender.hasPermission(Permission.ALT_ADD_OTHER) ? new Messages.SubCommandArgument("owner", !(sender instanceof Player)) : null))); if (args.length == 0) return sendMessage(sender, subCommandBuilder.build()); else switch (args[0]) { From 6ae40a2465c8adbb895bb438e82e3986b58a9e6c Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Wed, 15 May 2024 13:28:23 +0300 Subject: [PATCH 04/19] add list namespace to alts messages --- .../java/pro/cloudnode/smp/smpcore/Messages.java | 12 ++++++------ src/main/resources/messages.yml | 7 ++++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/pro/cloudnode/smp/smpcore/Messages.java b/src/main/java/pro/cloudnode/smp/smpcore/Messages.java index 6bf9f53..967ed46 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/Messages.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/Messages.java @@ -91,20 +91,20 @@ public Messages() { .replace("", command), Placeholder.unparsed("label", label), Placeholder.component("args", SubCommandArgument.join(SubCommandArgument.of())), Placeholder.unparsed("description", description)); } - public @NotNull Component altsHeader(final @NotNull Member member) { + public @NotNull Component altsListHeader(final @NotNull Member member) { return MiniMessage.miniMessage() - .deserialize(Objects.requireNonNull(config.getString("alts.header")), Placeholder.unparsed("player", Optional + .deserialize(Objects.requireNonNull(config.getString("alts.list.header")), Placeholder.unparsed("player", Optional .ofNullable(member.player().getName()).orElse(member.player().getUniqueId().toString()))); } - public @NotNull Component altsNone() { + public @NotNull Component altsListNone() { return MiniMessage.miniMessage() - .deserialize(Objects.requireNonNull(config.getString("alts.none"))); + .deserialize(Objects.requireNonNull(config.getString("alts.list.none"))); } - public @NotNull Component altsEntry(final @NotNull Member alt) { + public @NotNull Component altsListEntry(final @NotNull Member alt) { return MiniMessage.miniMessage() - .deserialize(Objects.requireNonNull(config.getString("alts.entry")), Placeholder.unparsed("alt", Optional + .deserialize(Objects.requireNonNull(config.getString("alts.list.entry")), Placeholder.unparsed("alt", Optional .ofNullable(alt.player().getName()).orElse(alt.player().getUniqueId().toString()))); } diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml index 4e72a46..6396a24 100644 --- a/src/main/resources/messages.yml +++ b/src/main/resources/messages.yml @@ -17,9 +17,10 @@ subcommands: optional: [] alts: - header: Alts of : - none: " (none)" - entry: > + list: + header: Alts of : + none: " (none)" + entry: > error: no-permission: (!) You don't have permission to use this command. From 043224693f5d24ed0aaf43501608cdb6654f2abb Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Wed, 15 May 2024 14:02:13 +0300 Subject: [PATCH 05/19] move subcommand messages together --- .../pro/cloudnode/smp/smpcore/Messages.java | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/main/java/pro/cloudnode/smp/smpcore/Messages.java b/src/main/java/pro/cloudnode/smp/smpcore/Messages.java index 967ed46..70f74c3 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/Messages.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/Messages.java @@ -64,6 +64,8 @@ public Messages() { .toString())), Placeholder.unparsed("n-alts", String.valueOf(alts.size()))); } + // subcommands + public @NotNull Component subCommandHeader(final @NotNull String name, final @NotNull String usage) { return MiniMessage.miniMessage() .deserialize(Objects.requireNonNull(config.getString("subcommands.header")), Placeholder.unparsed("name", name), Placeholder.unparsed("usage", usage)); @@ -91,6 +93,18 @@ public Messages() { .replace("", command), Placeholder.unparsed("label", label), Placeholder.component("args", SubCommandArgument.join(SubCommandArgument.of())), Placeholder.unparsed("description", description)); } + public @NotNull Component subCommandArgumentRequired(final @NotNull String argument) { + return MiniMessage.miniMessage() + .deserialize(Objects.requireNonNull(config.getString("subcommands.argument.required")), Placeholder.unparsed("arg", argument)); + } + + public @NotNull Component subCommandArgumentOptional(final @NotNull String argument) { + return MiniMessage.miniMessage() + .deserialize(Objects.requireNonNull(config.getString("subcommands.argument.optional")), Placeholder.unparsed("arg", argument)); + } + + // end of subcommands + public @NotNull Component altsListHeader(final @NotNull Member member) { return MiniMessage.miniMessage() .deserialize(Objects.requireNonNull(config.getString("alts.list.header")), Placeholder.unparsed("player", Optional @@ -108,15 +122,7 @@ public Messages() { .ofNullable(alt.player().getName()).orElse(alt.player().getUniqueId().toString()))); } - public @NotNull Component subCommandArgumentRequired(final @NotNull String argument) { - return MiniMessage.miniMessage() - .deserialize(Objects.requireNonNull(config.getString("subcommands.argument.required")), Placeholder.unparsed("arg", argument)); - } - - public @NotNull Component subCommandArgumentOptional(final @NotNull String argument) { - return MiniMessage.miniMessage() - .deserialize(Objects.requireNonNull(config.getString("subcommands.argument.optional")), Placeholder.unparsed("arg", argument)); - } + // errors public @NotNull Component errorNoPermission() { return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("error.no-permission"))); From 072d5b7e2020af1135d5524e51c153ec39ff5426 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Thu, 16 May 2024 00:52:00 +0300 Subject: [PATCH 06/19] create Member instance from OfflinePlayer and nullable alt owner member --- src/main/java/pro/cloudnode/smp/smpcore/Member.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/pro/cloudnode/smp/smpcore/Member.java b/src/main/java/pro/cloudnode/smp/smpcore/Member.java index 514584a..95515de 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/Member.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/Member.java @@ -33,6 +33,10 @@ private Member(final @NotNull UUID uuid, final @Nullable String nationID, final this.added = added; } + public Member(final @NotNull OfflinePlayer player, final @Nullable Member altOwner) { + this(player.getUniqueId(), null, false, altOwner == null ? null : altOwner.uuid, new Date()); + } + private Member(final @NotNull ResultSet rs) throws @NotNull SQLException { this(UUID.fromString(rs.getString("uuid")), rs.getString("nation"), rs.getBoolean("staff"), rs.getString("alt_owner") == null ? null : UUID.fromString(rs.getString("alt_owner")), rs.getTimestamp("added")); } From e886d2f3a4a15e07f4c27faf241b9b701ba274a6 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Thu, 16 May 2024 00:52:13 +0300 Subject: [PATCH 07/19] get member's tokens --- src/main/java/pro/cloudnode/smp/smpcore/Member.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/pro/cloudnode/smp/smpcore/Member.java b/src/main/java/pro/cloudnode/smp/smpcore/Member.java index 95515de..02e531a 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/Member.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/Member.java @@ -65,6 +65,10 @@ public boolean isAlt() { return Token.create(this); } + public @NotNull HashSet<@NotNull Token> tokens() { + return Token.get(this); + } + public @NotNull HashSet<@NotNull Member> getAlts() { final @NotNull HashSet<@NotNull Member> alts = new HashSet<>(); try ( From b1d8e301dd4606d2312062559b4987ee13d2e4bc Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Thu, 16 May 2024 00:52:25 +0300 Subject: [PATCH 08/19] member deletion procedure --- .../pro/cloudnode/smp/smpcore/Member.java | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/main/java/pro/cloudnode/smp/smpcore/Member.java b/src/main/java/pro/cloudnode/smp/smpcore/Member.java index 02e531a..7001aee 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/Member.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/Member.java @@ -107,7 +107,10 @@ public void save() { } } - private void delete() { + /** + * Removes only from database + */ + private void remove() { try ( final @NotNull Connection conn = SMPCore.getInstance().db() .getConnection(); final @NotNull PreparedStatement stmt = conn.prepareStatement("DELETE FROM `members` WHERE `uuid` = ?") @@ -120,6 +123,36 @@ private void delete() { } } + /** + * Member deletion procedure, i.e. membership revocation + * + *

    Will not be deleted if:

    + *
      + *
    • has alts (delete alts first)
    • + *
    • is leader of a nation (change leader or delete nation)
    • + *
    + *

    If vice leader of a nation, will set nation's leader to both leader and vice leader

    + * + * @return whether the member was deleted + */ + public boolean delete() { + if (!getAlts().isEmpty()) return false; + final @NotNull OfflinePlayer player = player(); + player.setWhitelisted(false); + final @NotNull Optional<@NotNull Nation> nation = nation(); + if (nation.isPresent()) { + if (nation.get().leaderUUID.equals(player.getUniqueId())) return false; + if (nation.get().viceLeaderUUID.equals(player.getUniqueId())) { + nation.get().viceLeaderUUID = nation.get().leaderUUID; + nation.get().save(); + } + nation.get().getTeam().removePlayer(player); + } + tokens().forEach(Token::delete); + remove(); + return true; + } + public static @NotNull Member create(final @NotNull OfflinePlayer player, final @Nullable Member altOwner) { final @NotNull Member member = new Member(player.getUniqueId(), null, false, altOwner == null ? null : altOwner.uuid, new Date()); member.save(); From 74689d76e1a9eac7bbd679076d97041093ec0271 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Thu, 16 May 2024 00:54:40 +0300 Subject: [PATCH 09/19] alts messages config --- .../pro/cloudnode/smp/smpcore/Messages.java | 36 +++++++++++++++++++ src/main/resources/messages.yml | 14 ++++++++ 2 files changed, 50 insertions(+) diff --git a/src/main/java/pro/cloudnode/smp/smpcore/Messages.java b/src/main/java/pro/cloudnode/smp/smpcore/Messages.java index 70f74c3..95cb199 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/Messages.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/Messages.java @@ -9,6 +9,7 @@ import org.jetbrains.annotations.Nullable; import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -122,6 +123,18 @@ public Messages() { .ofNullable(alt.player().getName()).orElse(alt.player().getUniqueId().toString()))); } + public @NotNull Component altsConfirmAdd(final @NotNull OfflinePlayer alt, final @NotNull String confirmCommand) { + return MiniMessage.miniMessage() + .deserialize(Objects.requireNonNull(config.getString("alts.confirm-add")).replace("", confirmCommand), Placeholder.unparsed("alt", Optional + .ofNullable(alt.getName()).orElse(alt.getUniqueId().toString()))); + } + + public @NotNull Component altsCreated(final @NotNull Member alt) { + return MiniMessage.miniMessage() + .deserialize(Objects.requireNonNull(config.getString("alts.created")), Placeholder.unparsed("alt", Optional + .ofNullable(alt.player().getName()).orElse(alt.player().getUniqueId().toString()))); + } + // errors public @NotNull Component errorNoPermission() { @@ -145,6 +158,29 @@ public Messages() { .deserialize(Objects.requireNonNull(config.getString("error.not-member-you"))); } + public @NotNull Component errorAltAlreadyMember(final @NotNull Member player) { + return MiniMessage.miniMessage() + .deserialize(Objects.requireNonNull(config.getString("error.alt-already-member")), Placeholder.unparsed("player", Optional + .ofNullable(player.player().getName()).orElse(player.player().getUniqueId().toString()))); + } + + public @NotNull Component errorDisallowedCharacters(final @NotNull HashSet<@NotNull Character> chars) { + return MiniMessage.miniMessage() + .deserialize(Objects.requireNonNull(config.getString("error.disallowed-characters")), Placeholder.unparsed("chars", chars.stream().map(String::valueOf).collect(Collectors.joining()))); + } + + public @NotNull Component errorFailedDeleteMember(final @NotNull Member player) { + return MiniMessage.miniMessage() + .deserialize(Objects.requireNonNull(config.getString("error.failed-delete-member")), Placeholder.unparsed("player", Optional + .ofNullable(player.player().getName()).orElse(player.player().getUniqueId().toString()))); + } + + public @NotNull Component errorAlreadyYourAlt(final @NotNull Member alt) { + return MiniMessage.miniMessage() + .deserialize(Objects.requireNonNull(config.getString("error.already-your-alt")), Placeholder.unparsed("alt", Optional + .ofNullable(alt.player().getName()).orElse(alt.player().getUniqueId().toString()))); + } + public record SubCommandArgument(@NotNull String name, boolean required) { public @NotNull Component component() { return required ? SMPCore.messages().subCommandArgumentRequired(name) : SMPCore.messages() diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml index 6396a24..d5938ce 100644 --- a/src/main/resources/messages.yml +++ b/src/main/resources/messages.yml @@ -21,9 +21,23 @@ alts: header: Alts of : none: " (none)" entry: > + confirm-add: |- + (!) WARNING (!) + You are about to add as an alt. + + Adding an alt grants automatic membership, enabling the alt to join the server. + You bear full responsibility for all actions taken by the alt. + In the event of a ban on either your main account or any of your alts, all associated accounts will be banned automatically. + + >Click to confirm adding the alt
    '>[I CONFIRM] + created: (!) Created member profile for and added as an alt. error: no-permission: (!) You don't have permission to use this command. player-not-banned: (!) Player is not banned and is not a member. not-member: (!) Player is not a member. not-member-you: (!) You are not a member. + alt-already-member: (!) You cannot add as an alt because they are either already a member, or an alt that has already played on the server. + disallowed-characters: "(!) You used the following disallowed characters: " + failed-delete-member: (!) Failed to delete the member profile for . Please contact staff. + already-your-alt: (!) Player is already your alt. From 0b20d3ec915f1962eea2f8b6f885849f33b0cfd5 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Thu, 16 May 2024 00:54:59 +0300 Subject: [PATCH 10/19] method for getting disallowed characters --- .../pro/cloudnode/smp/smpcore/SMPCore.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/main/java/pro/cloudnode/smp/smpcore/SMPCore.java b/src/main/java/pro/cloudnode/smp/smpcore/SMPCore.java index 9c6f897..1b1f723 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/SMPCore.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/SMPCore.java @@ -14,8 +14,12 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.HashSet; import java.util.Objects; +import java.util.function.Consumer; import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public final class SMPCore extends JavaPlugin { public static @NotNull SMPCore getInstance() { @@ -128,4 +132,22 @@ public static void runAsync(final @NotNull Runnable runnable) { public static void runMain(final @NotNull Runnable runnable) { getInstance().getServer().getScheduler().runTask(getInstance(), runnable); } + + public static @NotNull HashSet<@NotNull Character> getDisallowedCharacters(final @NotNull String source, final @NotNull Pattern pattern) { + final @NotNull Matcher matcher = pattern.matcher(source); + final @NotNull HashSet<@NotNull Character> chars = new HashSet<>(); + while (matcher.find()) + for (char c : matcher.group().toCharArray()) + chars.add(c); + return chars; + } + + public static boolean ifDisallowedCharacters(final @NotNull String source, final @NotNull Pattern pattern, final @NotNull Consumer<@NotNull HashSet<@NotNull Character>> consumer) { + final @NotNull HashSet<@NotNull Character> chars = getDisallowedCharacters(source, pattern); + if (!chars.isEmpty()) { + consumer.accept(chars); + return true; + } + return false; + } } From 90ac81af404c68984a529a8c48f07e4c5bacf356 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Thu, 16 May 2024 00:55:26 +0300 Subject: [PATCH 11/19] get tokens for Member and change player -> member --- .../java/pro/cloudnode/smp/smpcore/Token.java | 32 ++++++++++++++++--- src/main/resources/init.sql | 2 +- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/main/java/pro/cloudnode/smp/smpcore/Token.java b/src/main/java/pro/cloudnode/smp/smpcore/Token.java index 111ae92..67cce84 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/Token.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/Token.java @@ -8,8 +8,10 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Date; +import java.util.HashSet; import java.util.Optional; import java.util.UUID; +import java.util.logging.Level; public final class Token { public final @NotNull UUID token; @@ -27,16 +29,16 @@ public Token(final @NotNull UUID token, final @NotNull UUID memberUUID, final @N public Token(final @NotNull ResultSet rs) throws @NotNull SQLException { this( UUID.fromString(rs.getString("token")), - UUID.fromString(rs.getString("player")), + UUID.fromString(rs.getString("member")), rs.getTimestamp("created"), rs.getTimestamp("last_used") ); } - public void save() throws @NotNull SQLException { + public void save() { try ( final @NotNull Connection conn = SMPCore.getInstance().db() - .getConnection(); final @NotNull PreparedStatement stmt = conn.prepareStatement("INSERT OR REPLACE INTO `tokens` (`token`, `player`, `created`, `last_used`) VALUES (?, ?, ?, ?)") + .getConnection(); final @NotNull PreparedStatement stmt = conn.prepareStatement("INSERT OR REPLACE INTO `tokens` (`token`, `member`, `created`, `last_used`) VALUES (?, ?, ?, ?)") ) { stmt.setString(1, token.toString()); stmt.setString(2, memberUUID.toString()); @@ -44,9 +46,12 @@ public void save() throws @NotNull SQLException { stmt.setString(4, lastUsed.toString()); stmt.executeUpdate(); } + catch (final @NotNull SQLException e) { + SMPCore.getInstance().getLogger().log(Level.SEVERE, "could not save token " + token, e); + } } - public void delete() throws @NotNull SQLException { + public void delete() { try ( final @NotNull Connection conn = SMPCore.getInstance().db() .getConnection(); final @NotNull PreparedStatement stmt = conn.prepareStatement("DELETE FROM `tokens` WHERE `token` = ?") @@ -54,6 +59,9 @@ public void delete() throws @NotNull SQLException { stmt.setString(1, token.toString()); stmt.executeUpdate(); } + catch (final @NotNull SQLException e) { + SMPCore.getInstance().getLogger().log(Level.SEVERE, "could not delete token " + token, e); + } } public static @NotNull Optional<@NotNull Token> get(final @NotNull UUID token) throws @NotNull SQLException, @NotNull MemberNotFoundException { @@ -68,6 +76,22 @@ public void delete() throws @NotNull SQLException { } } + public static @NotNull HashSet<@NotNull Token> get(final @NotNull Member member) { + final @NotNull HashSet<@NotNull Token> tokens = new HashSet<>(); + try ( + final @NotNull Connection conn = SMPCore.getInstance().db() + .getConnection(); final @NotNull PreparedStatement stmt = conn.prepareStatement("SELECT * FROM `tokens` WHERE `member` = ?") + ) { + stmt.setString(1, member.uuid.toString()); + final @NotNull ResultSet rs = stmt.executeQuery(); + while (rs.next()) tokens.add(new Token(rs)); + } + catch (final @NotNull SQLException e) { + SMPCore.getInstance().getLogger().log(Level.SEVERE, "could not get tokens for member " + member.uuid, e); + } + return tokens; + } + public static @NotNull Token create(final @NotNull Member member) throws @NotNull SQLException { final @NotNull Token token = new Token(UUID.randomUUID(), member.uuid, new Date(), new Date()); token.save(); diff --git a/src/main/resources/init.sql b/src/main/resources/init.sql index c885035..b261d4b 100644 --- a/src/main/resources/init.sql +++ b/src/main/resources/init.sql @@ -8,7 +8,7 @@ CREATE TABLE IF NOT EXISTS `members` ( CREATE TABLE IF NOT EXISTS `tokens` ( `token` CHAR(36) PRIMARY KEY NOT NULL COLLATE NOCASE, - `player` CHAR(36) NOT NULL COLLATE NOCASE, + `member` CHAR(36) NOT NULL COLLATE NOCASE, `created` CHAR(36) NOT NULL COLLATE NOCASE, `last_used` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); From dd7611c834e8db8ca60ccfe93e3279bd3530aa01 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Thu, 16 May 2024 00:56:12 +0300 Subject: [PATCH 12/19] add alt command --- .../smp/smpcore/command/MainCommand.java | 74 ++++++++++++++++--- 1 file changed, 64 insertions(+), 10 deletions(-) diff --git a/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java b/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java index 8f6ffdf..2f649ed 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java @@ -17,8 +17,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; +import java.util.Objects; import java.util.Optional; +import java.util.regex.Pattern; public final class MainCommand extends Command { @Override @@ -86,7 +89,7 @@ public static boolean reload(final @NotNull CommandSender sender) { *
  • {@code alt list [player]} - show list of alts *
  • {@code alt add [player]} - add an alt */ - public static boolean alt(final @NotNull CommandSender sender, final @NotNull String @NotNull [] args, final @NotNull String label) { + public static boolean alt(final @NotNull CommandSender sender, final @NotNull String @NotNull [] originalArgs, final @NotNull String label) { if (!sender.hasPermission(Permission.ALT)) return sendMessage(sender, SMPCore.messages().errorNoPermission()); final @NotNull String command = "/" + label; @@ -98,16 +101,16 @@ public static boolean alt(final @NotNull CommandSender sender, final @NotNull St if (sender.hasPermission(Permission.ALT_ADD)) subCommandBuilder.append(Component.newline()).append(SMPCore.messages() .subCommandEntry(command + " add ", "add", Messages.SubCommandArgument.of(new Messages.SubCommandArgument("username", true), sender.hasPermission(Permission.ALT_ADD_OTHER) ? new Messages.SubCommandArgument("owner", !(sender instanceof Player)) : null))); - if (args.length == 0) return sendMessage(sender, subCommandBuilder.build()); - else switch (args[0]) { + if (originalArgs.length == 0) return sendMessage(sender, subCommandBuilder.build()); + else switch (originalArgs[0]) { case "list" -> { final @NotNull OfflinePlayer target; if (!(sender instanceof final @NotNull Player player)) { - if (args.length == 1) return sendMessage(sender, SMPCore.messages().usage(label, "list ")); - target = sender.getServer().getOfflinePlayer(args[1]); + if (originalArgs.length == 1) return sendMessage(sender, SMPCore.messages().usage(label, "list ")); + target = sender.getServer().getOfflinePlayer(originalArgs[1]); } else { - if (args.length > 1 && player.hasPermission(Permission.ALT_OTHER)) target = player.getServer().getOfflinePlayer(args[1]); + if (originalArgs.length > 1 && player.hasPermission(Permission.ALT_OTHER)) target = player.getServer().getOfflinePlayer(originalArgs[1]); else target = player; } final @NotNull Optional<@NotNull Member> targetMember = Member.get(target); @@ -119,14 +122,65 @@ else switch (args[0]) { final @NotNull Member member = targetMember.get().altOwner().orElse(targetMember.get()); final @NotNull HashSet<@NotNull Member> alts = member.getAlts(); - sendMessage(sender, SMPCore.messages().altsHeader(member)); - if (alts.isEmpty()) return sendMessage(sender, SMPCore.messages().altsNone()); + sendMessage(sender, SMPCore.messages().altsListHeader(member)); + if (alts.isEmpty()) return sendMessage(sender, SMPCore.messages().altsListNone()); for (final @NotNull Member alt : alts) - sendMessage(sender, SMPCore.messages().altsEntry(alt)); + sendMessage(sender, SMPCore.messages().altsListEntry(alt)); return true; } case "add" -> { - return sendMessage(sender, Component.text("add")); + if (!sender.hasPermission(Permission.ALT_ADD)) return sendMessage(sender, SMPCore.messages().errorNoPermission()); + + final @NotNull LinkedList<@NotNull String> tempArgs = new LinkedList<>(Arrays.asList(originalArgs)); + final boolean confirm = tempArgs.contains("--confirm"); + final @NotNull String @NotNull [] args; + if (confirm) { + tempArgs.remove("--confirm"); + args = tempArgs.toArray(new String[0]); + } + else args = originalArgs; + + if (args.length == 1) { + if (!(sender instanceof Player)) return sendMessage(sender, SMPCore.messages().usage(label, "add ")); + if (sender.hasPermission(Permission.ALT_ADD_OTHER)) return sendMessage(sender, SMPCore.messages().usage(label, "add [owner]")); + return sendMessage(sender, SMPCore.messages().usage(label, "add ")); + } + + final @NotNull OfflinePlayer target; + if (!(sender instanceof final @NotNull Player player)) { + if (args.length == 2) return sendMessage(sender, SMPCore.messages().usage(label, "add ")); + target = sender.getServer().getOfflinePlayer(args[2]); + } + else { + if (args.length > 2 && player.hasPermission(Permission.ALT_ADD_OTHER)) target = player.getServer().getOfflinePlayer(args[2]); + else target = player; + } + final @NotNull Optional<@NotNull Member> targetMember = Member.get(target); + if (targetMember.isEmpty()) { + if (sender instanceof final @NotNull Player player && target.getUniqueId().equals(player.getUniqueId())) return sendMessage(sender, SMPCore.messages().errorNotMember()); + else return sendMessage(sender, SMPCore.messages().errorNotMember(target)); + } + + if (SMPCore.ifDisallowedCharacters(args[1], Pattern.compile("[^A-Za-z\\d._]+"), s -> sendMessage(sender, SMPCore.messages().errorDisallowedCharacters(s)))) + return true; + + final @NotNull OfflinePlayer altPlayer = sender.getServer().getOfflinePlayer(args[1]); + final @NotNull Optional<@NotNull Member> altMember = Member.get(altPlayer); + if (altMember.isPresent()) { + if (altMember.get().isAlt() && Objects.requireNonNull(altMember.get().altOwnerUUID).equals(target.getUniqueId())) + return sendMessage(sender, SMPCore.messages().errorAlreadyYourAlt(altMember.get())); + if (!altMember.get().isAlt() || altMember.get().player().hasPlayedBefore()) + return sendMessage(sender, SMPCore.messages().errorAltAlreadyMember(altMember.get())); + } + + if (!confirm) return sendMessage(sender, SMPCore.messages().altsConfirmAdd(altPlayer, command + " " + String.join(" ", args) + " --confirm")); + if (altMember.isPresent() && !altMember.get().delete()) return sendMessage(sender, SMPCore.messages().errorFailedDeleteMember(altMember.get())); + + final @NotNull Member alt = new Member(altPlayer, targetMember.get()); + alt.save(); + alt.player().setWhitelisted(true); + + return sendMessage(sender, SMPCore.messages().altsCreated(alt)); } default -> { return sendMessage(sender, subCommandBuilder.build()); From 83829ac6cf5ee71ec6373b1d81f9073e1d509728 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Thu, 16 May 2024 00:59:18 +0300 Subject: [PATCH 13/19] run whitelist and team modification on main thread --- src/main/java/pro/cloudnode/smp/smpcore/Member.java | 4 ++-- .../java/pro/cloudnode/smp/smpcore/command/MainCommand.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/pro/cloudnode/smp/smpcore/Member.java b/src/main/java/pro/cloudnode/smp/smpcore/Member.java index 7001aee..ecdd8d0 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/Member.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/Member.java @@ -138,7 +138,7 @@ private void remove() { public boolean delete() { if (!getAlts().isEmpty()) return false; final @NotNull OfflinePlayer player = player(); - player.setWhitelisted(false); + SMPCore.runMain(() -> player.setWhitelisted(false)); final @NotNull Optional<@NotNull Nation> nation = nation(); if (nation.isPresent()) { if (nation.get().leaderUUID.equals(player.getUniqueId())) return false; @@ -146,7 +146,7 @@ public boolean delete() { nation.get().viceLeaderUUID = nation.get().leaderUUID; nation.get().save(); } - nation.get().getTeam().removePlayer(player); + SMPCore.runMain(() -> nation.get().getTeam().removePlayer(player)); } tokens().forEach(Token::delete); remove(); diff --git a/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java b/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java index 2f649ed..406afcf 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java @@ -178,7 +178,7 @@ else switch (originalArgs[0]) { final @NotNull Member alt = new Member(altPlayer, targetMember.get()); alt.save(); - alt.player().setWhitelisted(true); + SMPCore.runMain(() -> alt.player().setWhitelisted(true)); return sendMessage(sender, SMPCore.messages().altsCreated(alt)); } From e433d8c26e427ace25c5167f0eaaa9167c8e755f Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Thu, 16 May 2024 01:08:18 +0300 Subject: [PATCH 14/19] max alts limit --- src/main/java/pro/cloudnode/smp/smpcore/Configuration.java | 7 +++++++ src/main/java/pro/cloudnode/smp/smpcore/Messages.java | 5 +++++ src/main/java/pro/cloudnode/smp/smpcore/Permission.java | 5 +++++ .../pro/cloudnode/smp/smpcore/command/MainCommand.java | 3 +++ src/main/resources/config.yml | 4 ++++ src/main/resources/messages.yml | 1 + 6 files changed, 25 insertions(+) diff --git a/src/main/java/pro/cloudnode/smp/smpcore/Configuration.java b/src/main/java/pro/cloudnode/smp/smpcore/Configuration.java index 46c34e4..27a7396 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/Configuration.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/Configuration.java @@ -18,4 +18,11 @@ public int apiPort() { public int membersInactiveDays() { return config.getInt("members.inactive-days"); } + + /** + * Maximum number of alts you can have + */ + public int altsMax() { + return config.getInt("alts.max"); + } } diff --git a/src/main/java/pro/cloudnode/smp/smpcore/Messages.java b/src/main/java/pro/cloudnode/smp/smpcore/Messages.java index 95cb199..b4caebf 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/Messages.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/Messages.java @@ -181,6 +181,11 @@ public Messages() { .ofNullable(alt.player().getName()).orElse(alt.player().getUniqueId().toString()))); } + public @NotNull Component errorMaxAltsReached(final int max) { + return MiniMessage.miniMessage() + .deserialize(Objects.requireNonNull(config.getString("error.max-alts-reached")), Placeholder.unparsed("max", String.valueOf(max))); + } + public record SubCommandArgument(@NotNull String name, boolean required) { public @NotNull Component component() { return required ? SMPCore.messages().subCommandArgumentRequired(name) : SMPCore.messages() diff --git a/src/main/java/pro/cloudnode/smp/smpcore/Permission.java b/src/main/java/pro/cloudnode/smp/smpcore/Permission.java index ac3338c..4b73664 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/Permission.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/Permission.java @@ -24,4 +24,9 @@ public final class Permission { * Add an alt for someone else */ public static @NotNull String ALT_ADD_OTHER = "smpcore.alt.add.other"; + + /** + * Bypass the maximum alts limit + */ + public static @NotNull String ALT_MAX_BYPASS = "smpcore.alt.bypass.max"; } diff --git a/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java b/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java index 406afcf..601dafb 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java @@ -164,6 +164,9 @@ else switch (originalArgs[0]) { if (SMPCore.ifDisallowedCharacters(args[1], Pattern.compile("[^A-Za-z\\d._]+"), s -> sendMessage(sender, SMPCore.messages().errorDisallowedCharacters(s)))) return true; + if (!sender.hasPermission(Permission.ALT_MAX_BYPASS) && targetMember.get().getAlts().size() >= SMPCore.config().altsMax()) + return sendMessage(sender, SMPCore.messages().errorMaxAltsReached(SMPCore.config().altsMax())); + final @NotNull OfflinePlayer altPlayer = sender.getServer().getOfflinePlayer(args[1]); final @NotNull Optional<@NotNull Member> altMember = Member.get(altPlayer); if (altMember.isPresent()) { diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 2d615d6..651b164 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -5,3 +5,7 @@ api: members: # Number of days since last seen after which player is considered inactive inactive-days: 30 + +alts: + # Maximum number of alts you can have + max: 10 diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml index d5938ce..a1305e9 100644 --- a/src/main/resources/messages.yml +++ b/src/main/resources/messages.yml @@ -41,3 +41,4 @@ error: disallowed-characters: "(!) You used the following disallowed characters: " failed-delete-member: (!) Failed to delete the member profile for . Please contact staff. already-your-alt: (!) Player is already your alt. + max-alts-reached: (!) You cannot have more than alts. From 301886fdfd200380f66eae4735036153c74a5d51 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Thu, 16 May 2024 01:10:55 +0300 Subject: [PATCH 15/19] ensure that alts cant have alts --- .../pro/cloudnode/smp/smpcore/command/MainCommand.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java b/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java index 601dafb..54f2d48 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java @@ -155,16 +155,18 @@ else switch (originalArgs[0]) { if (args.length > 2 && player.hasPermission(Permission.ALT_ADD_OTHER)) target = player.getServer().getOfflinePlayer(args[2]); else target = player; } - final @NotNull Optional<@NotNull Member> targetMember = Member.get(target); - if (targetMember.isEmpty()) { + final @NotNull Optional<@NotNull Member> tempTargetMember = Member.get(target); + if (tempTargetMember.isEmpty()) { if (sender instanceof final @NotNull Player player && target.getUniqueId().equals(player.getUniqueId())) return sendMessage(sender, SMPCore.messages().errorNotMember()); else return sendMessage(sender, SMPCore.messages().errorNotMember(target)); } + final @NotNull Member targetMember = tempTargetMember.get().altOwner().orElse(tempTargetMember.get()); + if (SMPCore.ifDisallowedCharacters(args[1], Pattern.compile("[^A-Za-z\\d._]+"), s -> sendMessage(sender, SMPCore.messages().errorDisallowedCharacters(s)))) return true; - if (!sender.hasPermission(Permission.ALT_MAX_BYPASS) && targetMember.get().getAlts().size() >= SMPCore.config().altsMax()) + if (!sender.hasPermission(Permission.ALT_MAX_BYPASS) && targetMember.getAlts().size() >= SMPCore.config().altsMax()) return sendMessage(sender, SMPCore.messages().errorMaxAltsReached(SMPCore.config().altsMax())); final @NotNull OfflinePlayer altPlayer = sender.getServer().getOfflinePlayer(args[1]); @@ -179,7 +181,7 @@ else switch (originalArgs[0]) { if (!confirm) return sendMessage(sender, SMPCore.messages().altsConfirmAdd(altPlayer, command + " " + String.join(" ", args) + " --confirm")); if (altMember.isPresent() && !altMember.get().delete()) return sendMessage(sender, SMPCore.messages().errorFailedDeleteMember(altMember.get())); - final @NotNull Member alt = new Member(altPlayer, targetMember.get()); + final @NotNull Member alt = new Member(altPlayer, targetMember); alt.save(); SMPCore.runMain(() -> alt.player().setWhitelisted(true)); From 22a5872b937d28ce49e7c11c391e15441894bbe2 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Thu, 16 May 2024 01:13:24 +0300 Subject: [PATCH 16/19] fix cmd permissions --- src/main/resources/plugin.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 99772cf..446345a 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -8,11 +8,11 @@ commands: description: SMPCore Main command usage: / ban: - permission: smpcore.command.ban + permission: smpcore.ban description: Ban a member and all of their alts usage: / [reason] unban: - permission: smpcore.command.unban + permission: smpcore.ban description: Unban a member and all of their alts usage: / aliases: [ pardon ] From d861b6e9963ed272a961b39f6a7fc79dd731b759 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Thu, 16 May 2024 01:14:51 +0300 Subject: [PATCH 17/19] add `/smp` as alias to `/smpcore` --- src/main/resources/plugin.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 446345a..caf69bc 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -7,6 +7,7 @@ commands: smpcore: description: SMPCore Main command usage: / + aliases: [ smp ] ban: permission: smpcore.ban description: Ban a member and all of their alts From 6990e3f2360f03de7457d6115792d27c53ca7711 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Thu, 16 May 2024 19:33:11 +0300 Subject: [PATCH 18/19] add `/alt` as alias to `/smpcore alt` --- .../pro/cloudnode/smp/smpcore/SMPCore.java | 17 +++++++++--- .../smp/smpcore/command/AltsCommand.java | 26 +++++++++++++++++++ src/main/resources/plugin.yml | 5 ++++ 3 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 src/main/java/pro/cloudnode/smp/smpcore/command/AltsCommand.java diff --git a/src/main/java/pro/cloudnode/smp/smpcore/SMPCore.java b/src/main/java/pro/cloudnode/smp/smpcore/SMPCore.java index 1b1f723..35fb2bc 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/SMPCore.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/SMPCore.java @@ -5,7 +5,9 @@ import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import pro.cloudnode.smp.smpcore.command.AltsCommand; import pro.cloudnode.smp.smpcore.command.BanCommand; +import pro.cloudnode.smp.smpcore.command.Command; import pro.cloudnode.smp.smpcore.command.MainCommand; import pro.cloudnode.smp.smpcore.command.UnbanCommand; @@ -14,7 +16,9 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Objects; import java.util.function.Consumer; import java.util.logging.Level; @@ -56,10 +60,15 @@ public void onEnable() { reload(); initDatabase(); - - Objects.requireNonNull(getServer().getPluginCommand("smpcore")).setExecutor(new MainCommand()); - Objects.requireNonNull(getServer().getPluginCommand("ban")).setExecutor(new BanCommand()); - Objects.requireNonNull(getServer().getPluginCommand("unban")).setExecutor(new UnbanCommand()); + + final @NotNull HashMap<@NotNull String, @NotNull Command> commands = new HashMap<>() {{ + put("smpcore", new MainCommand()); + put("ban", new BanCommand()); + put("unban", new UnbanCommand()); + }}; + commands.put("alts", new AltsCommand(commands.get("smpcore"))); + for (final @NotNull Map.Entry<@NotNull String, @NotNull Command> entry : commands.entrySet()) + Objects.requireNonNull(getServer().getPluginCommand(entry.getKey())).setExecutor(entry.getValue()); } @Override diff --git a/src/main/java/pro/cloudnode/smp/smpcore/command/AltsCommand.java b/src/main/java/pro/cloudnode/smp/smpcore/command/AltsCommand.java new file mode 100644 index 0000000..3c22c95 --- /dev/null +++ b/src/main/java/pro/cloudnode/smp/smpcore/command/AltsCommand.java @@ -0,0 +1,26 @@ +package pro.cloudnode.smp.smpcore.command; + +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.stream.Stream; + +public final class AltsCommand extends Command { + private final @NotNull Command mainCommand; + public AltsCommand(final @NotNull Command mainCommand) { + super(); + this.mainCommand = mainCommand; + } + + @Override + public boolean run(@NotNull CommandSender sender, @NotNull String label, @NotNull String @NotNull [] args) { + return MainCommand.alt(sender, args, label); + } + + @Override + public @Nullable List<@NotNull String> tab(@NotNull CommandSender sender, @NotNull String label, @NotNull String @NotNull [] args) { + return mainCommand.tab(sender, label, Stream.concat(Stream.of("alt"), Stream.of(args)).toArray(String[]::new)); + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index caf69bc..f5457cc 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -17,3 +17,8 @@ commands: description: Unban a member and all of their alts usage: / aliases: [ pardon ] + alts: + permission: smpcore.alt + description: Manage alternate accounts + usage: / + aliases: [ alt ] From 32ac1ac3a788c5fcca0ceea5477cb1b428192b01 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Thu, 16 May 2024 20:13:15 +0300 Subject: [PATCH 19/19] remove alt command --- .../pro/cloudnode/smp/smpcore/Member.java | 4 ++ .../pro/cloudnode/smp/smpcore/Messages.java | 18 ++++++++ .../pro/cloudnode/smp/smpcore/Permission.java | 15 +++++++ .../smp/smpcore/command/MainCommand.java | 42 +++++++++++++++++++ src/main/resources/messages.yml | 3 ++ 5 files changed, 82 insertions(+) diff --git a/src/main/java/pro/cloudnode/smp/smpcore/Member.java b/src/main/java/pro/cloudnode/smp/smpcore/Member.java index ecdd8d0..4cd2fbe 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/Member.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/Member.java @@ -232,4 +232,8 @@ public boolean delete() { public static @NotNull Set<@NotNull String> getNames() { return get().stream().map(m -> m.player().getName()).filter(Objects::nonNull).collect(Collectors.toSet()); } + + public static @NotNull Set<@NotNull String> getAltNames() { + return get().stream().filter(Member::isAlt).map(m -> m.player().getName()).filter(Objects::nonNull).collect(Collectors.toSet()); + } } diff --git a/src/main/java/pro/cloudnode/smp/smpcore/Messages.java b/src/main/java/pro/cloudnode/smp/smpcore/Messages.java index b4caebf..848d973 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/Messages.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/Messages.java @@ -135,6 +135,12 @@ public Messages() { .ofNullable(alt.player().getName()).orElse(alt.player().getUniqueId().toString()))); } + public @NotNull Component altsDeleted(final @NotNull Member alt) { + return MiniMessage.miniMessage() + .deserialize(Objects.requireNonNull(config.getString("alts.deleted")), Placeholder.unparsed("alt", Optional + .ofNullable(alt.player().getName()).orElse(alt.player().getUniqueId().toString()))); + } + // errors public @NotNull Component errorNoPermission() { @@ -186,6 +192,18 @@ public Messages() { .deserialize(Objects.requireNonNull(config.getString("error.max-alts-reached")), Placeholder.unparsed("max", String.valueOf(max))); } + public @NotNull Component errorMemberNotAlt(final @NotNull Member player) { + return MiniMessage.miniMessage() + .deserialize(Objects.requireNonNull(config.getString("error.member-not-alt")), Placeholder.unparsed("player", Optional + .ofNullable(player.player().getName()).orElse(player.player().getUniqueId().toString()))); + } + + public @NotNull Component errorRemoveJoinedAlt(final @NotNull Member player) { + return MiniMessage.miniMessage() + .deserialize(Objects.requireNonNull(config.getString("error.remove-joined-alt")), Placeholder.unparsed("player", Optional + .ofNullable(player.player().getName()).orElse(player.player().getUniqueId().toString()))); + } + public record SubCommandArgument(@NotNull String name, boolean required) { public @NotNull Component component() { return required ? SMPCore.messages().subCommandArgumentRequired(name) : SMPCore.messages() diff --git a/src/main/java/pro/cloudnode/smp/smpcore/Permission.java b/src/main/java/pro/cloudnode/smp/smpcore/Permission.java index 4b73664..d746014 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/Permission.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/Permission.java @@ -29,4 +29,19 @@ public final class Permission { * Bypass the maximum alts limit */ public static @NotNull String ALT_MAX_BYPASS = "smpcore.alt.bypass.max"; + + /** + * Remove an alt + */ + public static @NotNull String ALT_REMOVE = "smpcore.alt.remove"; + + /** + * Remove someone else's alt + */ + public static @NotNull String ALT_REMOVE_OTHER = "smpcore.alt.remove.other"; + + /** + * Remove an alt that has joined the server + */ + public static @NotNull String ALT_REMOVE_JOINED = "smpcore.alt.remove.joined"; } diff --git a/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java b/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java index 54f2d48..f0869c4 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java @@ -48,6 +48,7 @@ else if (args.length > 1) switch (args[0]) { if (args.length == 2) { if (sender.hasPermission(Permission.ALT)) suggestions.add("list"); if (sender.hasPermission(Permission.ALT_ADD)) suggestions.add("add"); + if (sender.hasPermission(Permission.ALT_REMOVE)) suggestions.add("remove"); } else switch (args[1]) { case "list" -> { @@ -56,6 +57,16 @@ else switch (args[1]) { case "add" -> { if (args.length == 4 && sender.hasPermission(Permission.ALT_ADD_OTHER)) suggestions.addAll(Member.getNames()); } + case "remove" -> { + if (args.length == 3) { + if (sender.hasPermission(Permission.ALT_REMOVE_OTHER)) suggestions.addAll(Member.getAltNames()); + if (sender instanceof final @NotNull Player player) { + final @NotNull Optional<@NotNull Member> member = Member.get(player); + member.ifPresent(value -> suggestions.addAll(value.getAlts().stream() + .map(m -> m.player().getName()).filter(Objects::nonNull).toList())); + } + } + } } } } @@ -100,6 +111,8 @@ public static boolean alt(final @NotNull CommandSender sender, final @NotNull St .subCommandEntry(command + " list ", "list", Messages.SubCommandArgument.of(sender.hasPermission(Permission.ALT_OTHER) ? new Messages.SubCommandArgument("player", !(sender instanceof Player)) : null))); if (sender.hasPermission(Permission.ALT_ADD)) subCommandBuilder.append(Component.newline()).append(SMPCore.messages() .subCommandEntry(command + " add ", "add", Messages.SubCommandArgument.of(new Messages.SubCommandArgument("username", true), sender.hasPermission(Permission.ALT_ADD_OTHER) ? new Messages.SubCommandArgument("owner", !(sender instanceof Player)) : null))); + if (sender.hasPermission(Permission.ALT_REMOVE)) subCommandBuilder.append(Component.newline()).append(SMPCore.messages( + ).subCommandEntry(command + " remove ", "remove", Messages.SubCommandArgument.of(new Messages.SubCommandArgument("alt", true)))); if (originalArgs.length == 0) return sendMessage(sender, subCommandBuilder.build()); else switch (originalArgs[0]) { @@ -187,6 +200,35 @@ else switch (originalArgs[0]) { return sendMessage(sender, SMPCore.messages().altsCreated(alt)); } + case "remove" -> { + if (!sender.hasPermission(Permission.ALT_REMOVE)) + return sendMessage(sender, SMPCore.messages().errorNoPermission()); + + if (originalArgs.length == 1) + return sendMessage(sender, SMPCore.messages().usage(label, "remove ")); + + final @NotNull OfflinePlayer altPlayer = sender.getServer().getOfflinePlayer(originalArgs[1]); + final @NotNull Optional<@NotNull Member> altMember = Member.get(altPlayer); + if (altMember.isEmpty()) return sendMessage(sender, SMPCore.messages().errorNotMember(altPlayer)); + + final @NotNull Optional<@NotNull Member> altOwner = altMember.get().altOwner(); + if (altOwner.isEmpty()) + return sendMessage(sender, SMPCore.messages().errorMemberNotAlt(altMember.get())); + + if (!sender.hasPermission(Permission.ALT_REMOVE_OTHER)) { + final @NotNull Optional<@NotNull Member> member = sender instanceof final @NotNull Player player ? Member.get(player) : Optional.empty(); + if (member.isEmpty()) return sendMessage(sender, SMPCore.messages().errorNotMember()); + if (!altOwner.get().uuid.equals(member.get().uuid)) return sendMessage(sender, SMPCore.messages().errorNoPermission()); + } + + if (!sender.hasPermission(Permission.ALT_REMOVE_JOINED) && altPlayer.hasPlayedBefore()) + return sendMessage(sender, SMPCore.messages().errorRemoveJoinedAlt(altMember.get())); + + if (!altMember.get().delete()) + return sendMessage(sender, SMPCore.messages().errorFailedDeleteMember(altMember.get())); + + return sendMessage(sender, SMPCore.messages().altsDeleted(altMember.get())); + } default -> { return sendMessage(sender, subCommandBuilder.build()); } diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml index a1305e9..f0ee155 100644 --- a/src/main/resources/messages.yml +++ b/src/main/resources/messages.yml @@ -31,6 +31,7 @@ alts: >Click to confirm adding the alt'>[I CONFIRM] created: (!) Created member profile for and added as an alt. + deleted: (!) Deleted alt member profile for . error: no-permission: (!) You don't have permission to use this command. @@ -42,3 +43,5 @@ error: failed-delete-member: (!) Failed to delete the member profile for . Please contact staff. already-your-alt: (!) Player is already your alt. max-alts-reached: (!) You cannot have more than alts. + member-not-alt: (!) Member is not an alt. + remove-joined-alt: (!) You cannot remove alt player because they have played the server.