From 82c057804582eb2e73f3edf30c91f82ac0b79df9 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Tue, 30 Dec 2025 14:41:50 +0200 Subject: [PATCH 01/10] Added commands to manage server members --- .../pro/cloudnode/smp/smpcore/Member.java | 4 + .../pro/cloudnode/smp/smpcore/Messages.java | 74 +++++++ .../pro/cloudnode/smp/smpcore/Permission.java | 25 +++ .../smp/smpcore/command/MainCommand.java | 187 ++++++++++++++++++ src/main/resources/messages.yml | 12 ++ 5 files changed, 302 insertions(+) diff --git a/src/main/java/pro/cloudnode/smp/smpcore/Member.java b/src/main/java/pro/cloudnode/smp/smpcore/Member.java index 1ef1955..4119198 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/Member.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/Member.java @@ -36,6 +36,10 @@ private Member(final @NotNull UUID uuid, final @Nullable String nationID, final this.added = added; } + public Member(final @NotNull OfflinePlayer player) { + this(player, null); + } + public Member(final @NotNull OfflinePlayer player, final @Nullable Member altOwner) { this(player.getUniqueId(), null, false, altOwner == null ? null : altOwner.uuid, new Date()); } diff --git a/src/main/java/pro/cloudnode/smp/smpcore/Messages.java b/src/main/java/pro/cloudnode/smp/smpcore/Messages.java index 0480c75..3f841dc 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/Messages.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/Messages.java @@ -199,6 +199,63 @@ public Messages() { .ofNullable(alt.player().getName()).orElse(alt.player().getUniqueId().toString()))); } + public @NotNull Component membersNationlessFallback() { + return MiniMessage.miniMessage().deserialize( + Objects.requireNonNull(config.getString("members.nationless-fallback")) + ); + } + + public @NotNull Component membersListHeader() { + return MiniMessage.miniMessage().deserialize( + Objects.requireNonNull(config.getString("members.list.header")) + ); + } + + public @NotNull Component membersListNone() { + return MiniMessage.miniMessage().deserialize( + Objects.requireNonNull(config.getString("members.list.none")) + ); + } + + public @NotNull Component membersListEntry(final @NotNull Member member) { + return MiniMessage.miniMessage().deserialize( + Objects.requireNonNull(config.getString("members.list.entry")), + Placeholder.unparsed("player", Optional.ofNullable(member.player().getName()) + .orElse(member.player().getUniqueId().toString())), + Formatter.choice("staff", member.staff ? 1 : 0), + Placeholder.component("nation", member.nation() + .map(n -> n.getTeam().displayName()) + .orElse(membersNationlessFallback()) + ), + Formatter.date("added", member.added.toInstant().atZone(ZoneOffset.systemDefault())) + ); + } + + public @NotNull Component membersAdded(final @NotNull Member member) { + return MiniMessage.miniMessage().deserialize( + Objects.requireNonNull(config.getString("members.added")), + Placeholder.unparsed("player", Optional.ofNullable(member.player().getName()) + .orElse(member.player().getUniqueId().toString())) + ); + } + + public @NotNull Component membersDeleted(final @NotNull OfflinePlayer player) { + return MiniMessage.miniMessage().deserialize( + Objects.requireNonNull(config.getString("members.deleted")), + Placeholder.unparsed("player", Optional.ofNullable(player.getName()) + .orElse(player.getUniqueId().toString())) + ); + } + + public @NotNull Component membersSetStaff(final @NotNull Member member) { + return MiniMessage.miniMessage().deserialize( + Objects.requireNonNull(config.getString("members.set-staff")), + Placeholder.unparsed("player", Optional.ofNullable(member.player().getName()) + .orElse(member.player().getUniqueId().toString())), + Formatter.choice("staff", member.staff ? 1 : 0) + ); + } + public @NotNull Component seen(final @NotNull Member member) { if (member.player().isOnline()) return MiniMessage.miniMessage() .deserialize(Objects.requireNonNull(config.getString("seen.online")), Placeholder.unparsed("player", Optional @@ -643,6 +700,23 @@ public Messages() { ); } + public @NotNull Component errorAlreadyMember(final @NotNull Member member) { + return MiniMessage.miniMessage().deserialize( + Objects.requireNonNull(config.getString("error.already-member")), + Placeholder.unparsed("player", Optional.ofNullable(member.player().getName()) + .orElse(member.player().getUniqueId().toString())) + ); + } + + public @NotNull Component errorAlreadyStaff(final @NotNull Member member) { + return MiniMessage.miniMessage().deserialize( + Objects.requireNonNull(config.getString("error.already-staff")), + Placeholder.unparsed("player", Optional.ofNullable(member.player().getName()) + .orElse(member.player().getUniqueId().toString())), + Formatter.choice("staff", member.staff ? 1 : 0) + ); + } + 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 e1e624c..c75a466 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/Permission.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/Permission.java @@ -161,4 +161,29 @@ public final class Permission { * Bypass death ban. */ public static final @NotNull String DEATHBAN_BYPASS = "smpcore.deathban.bypass"; + + /** + * Access to the {@code /smpcore member} command. + */ + public static final @NotNull String MEMBER = "smpcore.member"; + + /** + * Add member to server. + */ + public static final @NotNull String MEMBER_ADD = "smpcore.member.add"; + + /** + * List server members. + */ + public static final @NotNull String MEMBER_LIST = "smpcore.member.list"; + + /** + * Revoke server membership. + */ + public static final @NotNull String MEMBER_REMOVE = "smpcore.member.remove"; + + /** + * Set member staff status. + */ + public static final @NotNull String MEMBER_SET_STAFF = "smpcore.member.set.staff"; } 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 5e2bb28..9aed728 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java @@ -32,6 +32,7 @@ public boolean run(@NotNull CommandSender sender, @NotNull String label, @NotNul case "reload" -> reload(sender); case "alt" -> alt(sender, argsSubset, label + " " + args[0]); case "time", "date" -> time(sender); + case "member", "members" -> member(sender, argsSubset, label + " " + args[0]); default -> sendMessage(sender, MiniMessage.miniMessage() .deserialize("(!) Unrecognised command ", Placeholder.unparsed("command", args[0]))); }; @@ -45,6 +46,7 @@ public boolean run(@NotNull CommandSender sender, @NotNull String label, @NotNul if (sender.hasPermission(Permission.RELOAD)) suggestions.add("reload"); if (sender.hasPermission(Permission.ALT)) suggestions.add("alt"); if (sender.hasPermission(Permission.TIME)) suggestions.addAll(List.of("time", "date")); + if (sender.hasPermission(Permission.MEMBER)) suggestions.addAll(List.of("member", "members")); } else if (args.length > 1) switch (args[0]) { case "alt" -> { @@ -72,6 +74,36 @@ else switch (args[1]) { } } } + case "member", "members" -> { + if (args.length == 2) { + if (sender.hasPermission(Permission.MEMBER)) suggestions.add("add"); + if (sender.hasPermission(Permission.MEMBER_LIST)) suggestions.add("list"); + if (sender.hasPermission(Permission.MEMBER_REMOVE)) suggestions.add("remove"); + if (sender.hasPermission(Permission.MEMBER_SET_STAFF)) suggestions.add("staff"); + } + else switch (args[1]) { + case "add" -> { + if (args.length == 3 && sender.hasPermission(Permission.MEMBER_ADD)) + suggestions.addAll( + sender.getServer().getOnlinePlayers().stream() + .filter(p -> Member.get(p).isEmpty()) + .map(Player::getName).toList() + ); + } + case "remove" -> { + if (args.length == 3 && sender.hasPermission(Permission.MEMBER_REMOVE)) + suggestions.addAll(Member.getNames()); + } + case "staff" -> { + if (sender.hasPermission(Permission.MEMBER_SET_STAFF)) { + if (args.length == 3) + suggestions.addAll(Member.getNames()); + else if (args.length == 4) + suggestions.addAll(List.of("true", "false")); + } + } + } + } } return suggestions; } @@ -247,4 +279,159 @@ public static boolean time(final @NotNull CommandSender sender) { return sendMessage(sender, SMPCore.messages().errorNoPermission()); return sendMessage(sender, SMPCore.messages().time(SMPCore.gameTime())); } + + /** + *
  • {@code / member} - Member Sub Commands
  • + *
  • {@code / member add } - Add member to the server.
  • + *
  • {@code / member list} - List server members.
  • + *
  • {@code / member remove } - Revoke server membership.
  • + *
  • {@code / member staff } - Set member staff status.
  • + */ + public static boolean member( + final @NotNull CommandSender sender, + final @NotNull String @NotNull [] args, + final @NotNull String label + ) { + if (!sender.hasPermission(Permission.MEMBER)) + return sendMessage(sender, SMPCore.messages().errorNoPermission()); + + if (args.length > 0) switch (args[0]) { + case "add": { + if (!sender.hasPermission(Permission.MEMBER_ADD)) + return sendMessage(sender, SMPCore.messages().errorNoPermission()); + + if (args.length != 2) + return sendMessage(sender, SMPCore.messages().usage(label, "member add ")); + + final OfflinePlayer target = sender.getServer().getOfflinePlayer(args[1]); + + final Optional existing = Member.get(target); + if (existing.isPresent()) + return sendMessage(sender, SMPCore.messages().errorAlreadyMember(existing.get())); + + final Member member = new Member(target); + member.save(); + SMPCore.runMain(() -> member.player().setWhitelisted(true)); + + return sendMessage(sender, SMPCore.messages().membersAdded(member)); + } + + case "list": { + if (!sender.hasPermission(Permission.MEMBER_LIST)) + return sendMessage(sender, SMPCore.messages().errorNoPermission()); + + sendMessage(sender, SMPCore.messages().membersListHeader()); + + final Set members = Member.get(); + + if (members.isEmpty()) + return sendMessage(sender, SMPCore.messages().membersListNone()); + + for (final Member member : members) + sendMessage(sender, SMPCore.messages().membersListEntry(member)); + + return true; + } + + case "remove": { + if (!sender.hasPermission(Permission.MEMBER_REMOVE)) + return sendMessage(sender, SMPCore.messages().errorNoPermission()); + + if (args.length != 2) + return sendMessage(sender, SMPCore.messages().usage(label, "member remove ")); + + final OfflinePlayer target = sender.getServer().getOfflinePlayer(args[1]); + final Optional member = Member.get(target); + + if (member.isEmpty()) + return sendMessage(sender, SMPCore.messages().errorNotMember(target)); + + if (!member.get().delete()) + return sendMessage(sender, SMPCore.messages().errorFailedDeleteMember(member.get())); + + SMPCore.runMain(() -> member.get().player().setWhitelisted(false)); + + return sendMessage(sender, SMPCore.messages().membersDeleted(target)); + } + + case "staff": { + if (!sender.hasPermission(Permission.MEMBER_SET_STAFF)) + return sendMessage(sender, SMPCore.messages().errorNoPermission()); + + if (args.length != 3) + return sendMessage(sender, SMPCore.messages().usage(label, "member staff " + (args.length > 1 ? args[1] : "") + " ")); + + final OfflinePlayer target = sender.getServer().getOfflinePlayer(args[1]); + + final boolean requestedStatus; + switch (args[2].toLowerCase()) { + case "true", "yes" -> requestedStatus = true; + case "false", "no" -> requestedStatus = false; + default -> { + return sendMessage(sender, SMPCore.messages().usage(label, "member staff " + args[1] + " ")); + } + } + + final Optional member = Member.get(target); + + if (member.isEmpty()) + return sendMessage(sender, SMPCore.messages().errorNotMember(target)); + + if (member.get().staff == requestedStatus) + return sendMessage(sender, SMPCore.messages().errorAlreadyStaff(member.get())); + + member.get().staff = requestedStatus; + member.get().save(); + + if (member.get().staff) { + member.get().nation().ifPresent(nation -> nation.getTeam().removePlayer(member.get().player())); + Member.getStaffTeam().addPlayer(member.get().player()); + } + else { + Member.getStaffTeam().removePlayer(member.get().player()); + member.get().nation().ifPresent(nation -> nation.getTeam().addPlayer(member.get().player())); + } + + return sendMessage(sender, SMPCore.messages().membersSetStaff(member.get())); + } + } + + final TextComponent.Builder subCommandBuilder = Component.text() + .append(SMPCore.messages().subCommandHeader("Member", label + " ...")) + .append(Component.newline()); + + if (sender.hasPermission(Permission.MEMBER_ADD)) + subCommandBuilder.append(Component.newline()).append(SMPCore.messages().subCommandEntry( + label + " add ", + "add", + Messages.SubCommandArgument.of(new Messages.SubCommandArgument("username", true)), + "Add member to the server." + )); + + if (sender.hasPermission(Permission.MEMBER_LIST)) + subCommandBuilder.append(Component.newline()).append(SMPCore.messages().subCommandEntry( + label + " list ", "list", "List server members." + )); + + if (sender.hasPermission(Permission.MEMBER_REMOVE)) + subCommandBuilder.append(Component.newline()).append(SMPCore.messages().subCommandEntry( + label + " remove ", + "remove", + Messages.SubCommandArgument.of(new Messages.SubCommandArgument("member", true)), + "Remove member from the server." + )); + + if (sender.hasPermission(Permission.MEMBER_SET_STAFF)) + subCommandBuilder.append(Component.newline()).append(SMPCore.messages().subCommandEntry( + label + " staff ", + "staff", + Messages.SubCommandArgument.of( + new Messages.SubCommandArgument("member", true), + new Messages.SubCommandArgument("true|false", true) + ), + "Set member staff status." + )); + + return sendMessage(sender, subCommandBuilder.build()); + } } diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml index 3d4a72c..aae750b 100644 --- a/src/main/resources/messages.yml +++ b/src/main/resources/messages.yml @@ -33,6 +33,16 @@ alts: created: (!) Created member profile for and added as an alt. deleted: (!) Deleted alt member profile for . +members: + nationless-fallback: nationless + list: + header: Server members: + none: " (none)" + entry: > STAFF
    '> since + added: (!) Created member profile for . + deleted: (!) Revoked server membership of . + set-staff: (!) Removed staff status of .|1#(!) Given staff status to .'> + seen: online: (!) Player is online. active: (!) Member is active and last seen on UTC () @@ -130,3 +140,5 @@ error: demote-citizen: (!) Don't make this citizen unworthy of the nation! (To remove from nation, use /nation citizens kick.) duration-zero-or-less: (!) Duration must be greater than zero. invalid-duration: (!) Invalid duration format ’. + already-member: (!) Player is already a member. + already-staff: (!) Member staff status. From 3b2bfce552da5b498b6de68927067b0ab5fa8f06 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Tue, 30 Dec 2025 14:43:02 +0200 Subject: [PATCH 02/10] fix staff team member assignment bug --- src/main/java/pro/cloudnode/smp/smpcore/Member.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/pro/cloudnode/smp/smpcore/Member.java b/src/main/java/pro/cloudnode/smp/smpcore/Member.java index 4119198..3d79485 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/Member.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/Member.java @@ -274,8 +274,14 @@ public static int count() { team.displayName(SMPCore.config().staffTeamName()); team.prefix(SMPCore.config().staffTeamName().append(Component.text(" "))); - for (final Member staff : getStaff()) - team.addPlayer(staff.player()); + for (final Member staff : getStaff()) { + final OfflinePlayer player = staff.player(); + + if (player.getName() == null) + continue; + + team.addPlayer(player); + } return team; } From b8961ebf8909eaa797273014bea4ea2e99863b65 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Tue, 30 Dec 2025 14:43:31 +0200 Subject: [PATCH 03/10] fix typo --- src/main/java/pro/cloudnode/smp/smpcore/Messages.java | 2 +- src/main/java/pro/cloudnode/smp/smpcore/command/BanCommand.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/pro/cloudnode/smp/smpcore/Messages.java b/src/main/java/pro/cloudnode/smp/smpcore/Messages.java index 3f841dc..e6ecaee 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/Messages.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/Messages.java @@ -693,7 +693,7 @@ public Messages() { return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("error.duration-zero-or-less"))); } - public @NotNull Component invalidDuration(final @NotNull String duration) { + public @NotNull Component errorInvalidDuration(final @NotNull String duration) { return MiniMessage.miniMessage().deserialize( Objects.requireNonNull(config.getString("error.invalid-duration")), Placeholder.unparsed("duration", duration) diff --git a/src/main/java/pro/cloudnode/smp/smpcore/command/BanCommand.java b/src/main/java/pro/cloudnode/smp/smpcore/command/BanCommand.java index b05e3fd..d71ede5 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/command/BanCommand.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/command/BanCommand.java @@ -92,7 +92,7 @@ public boolean run(@NotNull CommandSender sender, @NotNull String label, @NotNul duration = Duration.parse(durationArg); } catch (DateTimeParseException ignored) { - return sendMessage(sender, SMPCore.messages().invalidDuration(durationArg)); + return sendMessage(sender, SMPCore.messages().errorInvalidDuration(durationArg)); } if (duration != null && (duration.isNegative() || duration.isZero())) From bc268fbc56e46340098670f3fd1849976a4bbd29 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Tue, 30 Dec 2025 14:55:04 +0200 Subject: [PATCH 04/10] use try/catch instead of checking if name null --- src/main/java/pro/cloudnode/smp/smpcore/Member.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/java/pro/cloudnode/smp/smpcore/Member.java b/src/main/java/pro/cloudnode/smp/smpcore/Member.java index 3d79485..e28b760 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/Member.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/Member.java @@ -274,13 +274,12 @@ public static int count() { team.displayName(SMPCore.config().staffTeamName()); team.prefix(SMPCore.config().staffTeamName().append(Component.text(" "))); - for (final Member staff : getStaff()) { - final OfflinePlayer player = staff.player(); - - if (player.getName() == null) - continue; - - team.addPlayer(player); + for (final Member staff : getStaff()) try { + team.addPlayer(staff.player()); + } + catch (final IllegalArgumentException e) { + SMPCore.getInstance().getLogger().log(Level.FINEST, "could not add staff member " + + staff.player().getUniqueId() + " to staff team", e); } return team; From a415a9a11de8d648910850d35de5e78f33f6fd24 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Tue, 30 Dec 2025 15:07:58 +0200 Subject: [PATCH 05/10] adjust LuckPerms group when adding/removing staff --- src/main/java/pro/cloudnode/smp/smpcore/Configuration.java | 4 ++++ .../pro/cloudnode/smp/smpcore/command/MainCommand.java | 7 +++++++ src/main/resources/config.yml | 3 +++ src/main/resources/plugin.yml | 2 ++ 4 files changed, 16 insertions(+) diff --git a/src/main/java/pro/cloudnode/smp/smpcore/Configuration.java b/src/main/java/pro/cloudnode/smp/smpcore/Configuration.java index 04f01c5..d716460 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/Configuration.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/Configuration.java @@ -109,4 +109,8 @@ public boolean deathBanEnabled() { public @NotNull Component staffTeamName() { return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("staff-team.name"))); } + + public @NotNull String staffGroup() { + return Objects.requireNonNull(config.getString("staff-group")); + } } 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 9aed728..728d332 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java @@ -6,6 +6,7 @@ import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -383,13 +384,19 @@ public static boolean member( member.get().staff = requestedStatus; member.get().save(); + final ConsoleCommandSender console = sender.getServer().getConsoleSender(); + if (member.get().staff) { member.get().nation().ifPresent(nation -> nation.getTeam().removePlayer(member.get().player())); Member.getStaffTeam().addPlayer(member.get().player()); + console.getServer().dispatchCommand(console, "luckperms:luckperms user " + + member.get().player().getName() + " parent add " + SMPCore.config().staffGroup()); } else { Member.getStaffTeam().removePlayer(member.get().player()); member.get().nation().ifPresent(nation -> nation.getTeam().addPlayer(member.get().player())); + console.getServer().dispatchCommand(console, "luckperms:luckperms user " + + member.get().player().getName() + " parent remove " + SMPCore.config().staffGroup()); } return sendMessage(sender, SMPCore.messages().membersSetStaff(member.get())); diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index fcfc296..5aac783 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -51,3 +51,6 @@ relative-time: staff-team: id: staff name: STAFF + +# LuckPerms group +staff-group: staff diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index c942f4f..172af7e 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -3,6 +3,8 @@ author: Cloudnode version: '${project.version}' main: pro.cloudnode.smp.smpcore.SMPCore api-version: '1.20' +depend: + - LuckPerms commands: smpcore: description: SMPCore Main command From 905b01b2d6e953a0361035379de9e6cb866ffaf7 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Wed, 31 Dec 2025 10:22:49 +0200 Subject: [PATCH 06/10] extra checks before removing members --- .../pro/cloudnode/smp/smpcore/Messages.java | 9 +++ .../smp/smpcore/command/MainCommand.java | 55 +++++++++++++++++-- src/main/resources/messages.yml | 1 + 3 files changed, 59 insertions(+), 6 deletions(-) diff --git a/src/main/java/pro/cloudnode/smp/smpcore/Messages.java b/src/main/java/pro/cloudnode/smp/smpcore/Messages.java index e6ecaee..c94aeec 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/Messages.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/Messages.java @@ -717,6 +717,15 @@ public Messages() { ); } + public @NotNull Component errorRemoveMemberLeader(final @NotNull Member member, final @NotNull Nation nation) { + return MiniMessage.miniMessage().deserialize( + Objects.requireNonNull(config.getString("error.remove-member-leader")), + Placeholder.unparsed("player", Optional.ofNullable(member.player().getName()) + .orElse(member.player().getUniqueId().toString())), + Placeholder.unparsed("nation", nation.name) + ); + } + 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/command/MainCommand.java b/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java index 728d332..78d1377 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java @@ -1,9 +1,11 @@ package pro.cloudnode.smp.smpcore.command; +import net.kyori.adventure.audience.Audience; 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.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; @@ -12,6 +14,7 @@ import org.jetbrains.annotations.Nullable; import pro.cloudnode.smp.smpcore.Member; import pro.cloudnode.smp.smpcore.Messages; +import pro.cloudnode.smp.smpcore.Nation; import pro.cloudnode.smp.smpcore.Permission; import pro.cloudnode.smp.smpcore.SMPCore; @@ -260,8 +263,8 @@ else switch (originalArgs[0]) { 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())); + if (removeMember(sender, altMember.get())) + return true; return sendMessage(sender, SMPCore.messages().altsDeleted(altMember.get())); } @@ -347,10 +350,8 @@ public static boolean member( if (member.isEmpty()) return sendMessage(sender, SMPCore.messages().errorNotMember(target)); - if (!member.get().delete()) - return sendMessage(sender, SMPCore.messages().errorFailedDeleteMember(member.get())); - - SMPCore.runMain(() -> member.get().player().setWhitelisted(false)); + if (removeMember(sender, member.get())) + return true; return sendMessage(sender, SMPCore.messages().membersDeleted(target)); } @@ -388,13 +389,17 @@ public static boolean member( if (member.get().staff) { member.get().nation().ifPresent(nation -> nation.getTeam().removePlayer(member.get().player())); + Member.getStaffTeam().addPlayer(member.get().player()); + console.getServer().dispatchCommand(console, "luckperms:luckperms user " + member.get().player().getName() + " parent add " + SMPCore.config().staffGroup()); } else { Member.getStaffTeam().removePlayer(member.get().player()); + member.get().nation().ifPresent(nation -> nation.getTeam().addPlayer(member.get().player())); + console.getServer().dispatchCommand(console, "luckperms:luckperms user " + member.get().player().getName() + " parent remove " + SMPCore.config().staffGroup()); } @@ -441,4 +446,42 @@ public static boolean member( return sendMessage(sender, subCommandBuilder.build()); } + + /** + * @return Whether the action was prevented. + */ + private static boolean removeMember(final @NotNull Audience audience, final @NotNull Member target) { + final Optional nation = target.nation(); + final OfflinePlayer player = target.player(); + + if (nation.isPresent()) { + if (nation.get().leaderUUID.equals(player.getUniqueId())) { + sendMessage(audience, SMPCore.messages().errorRemoveMemberLeader(target, nation.get())); + return true; + } + + if (nation.get().viceLeaderUUID.equals(player.getUniqueId())) { + nation.get().viceLeaderUUID = nation.get().leaderUUID; + nation.get().save(); + } + + nation.get().getTeam().removePlayer(player); + } + + if (target.staff) { + Member.getStaffTeam().removePlayer(player); + + final ConsoleCommandSender console = Bukkit.getConsoleSender(); + + Bukkit.dispatchCommand(console, "luckperms:luckperms user " + player.getName() + + " parent remove " + SMPCore.config().staffGroup()); + } + + if (!target.delete()) + return sendMessage(audience, SMPCore.messages().errorFailedDeleteMember(target)); + + SMPCore.runMain(() -> target.player().setWhitelisted(false)); + + return false; + } } diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml index aae750b..36ea0e8 100644 --- a/src/main/resources/messages.yml +++ b/src/main/resources/messages.yml @@ -142,3 +142,4 @@ error: invalid-duration: (!) Invalid duration format ’. already-member: (!) Player is already a member. already-staff: (!) Member staff status. + remove-member-leader: (!) You cannot remove because they are the leader of . From 97285c1f0fbb09325f932694635ded0bb8ed472d Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Wed, 31 Dec 2025 10:24:04 +0200 Subject: [PATCH 07/10] improve staff config structure --- .../java/pro/cloudnode/smp/smpcore/Configuration.java | 6 +++--- src/main/resources/config.yml | 11 +++++------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/main/java/pro/cloudnode/smp/smpcore/Configuration.java b/src/main/java/pro/cloudnode/smp/smpcore/Configuration.java index d716460..e429fe9 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/Configuration.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/Configuration.java @@ -103,14 +103,14 @@ public boolean deathBanEnabled() { } public @NotNull String staffTeamId() { - return Objects.requireNonNull(config.getString("staff-team.id")); + return Objects.requireNonNull(config.getString("staff.team.id")); } public @NotNull Component staffTeamName() { - return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("staff-team.name"))); + return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("staff.team.name"))); } public @NotNull String staffGroup() { - return Objects.requireNonNull(config.getString("staff-group")); + return Objects.requireNonNull(config.getString("staff.group")); } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 5aac783..f59a0b6 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -48,9 +48,8 @@ relative-time: duration: for duration-indefinite: forever -staff-team: - id: staff - name: STAFF - -# LuckPerms group -staff-group: staff +staff: + team: + id: staff + name: STAFF + group: staff From a376e58ff94ddb56f15739e1db36de292356696b Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Wed, 31 Dec 2025 10:36:18 +0200 Subject: [PATCH 08/10] remove reliance on LuckPerms group and run configurable commands --- .../cloudnode/smp/smpcore/Configuration.java | 17 +++++++++++++++-- .../smp/smpcore/command/MainCommand.java | 14 +++----------- src/main/resources/config.yml | 6 +++++- src/main/resources/plugin.yml | 2 -- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/main/java/pro/cloudnode/smp/smpcore/Configuration.java b/src/main/java/pro/cloudnode/smp/smpcore/Configuration.java index e429fe9..279cab3 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/Configuration.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/Configuration.java @@ -4,7 +4,10 @@ import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.tag.resolver.Formatter; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.time.Duration; import java.time.temporal.ChronoUnit; @@ -110,7 +113,17 @@ public boolean deathBanEnabled() { return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("staff.team.name"))); } - public @NotNull String staffGroup() { - return Objects.requireNonNull(config.getString("staff.group")); + public void staffCommands(final boolean addMode, final @NotNull OfflinePlayer player) { + final @Nullable String name = player.getName(); + if (name == null) + return; + + final List commands = config.getStringList("staff.commands." + (addMode ? "add" : "remove")); + + for (final String command : commands) + Bukkit.dispatchCommand( + Bukkit.getConsoleSender(), + command.replace("", name) + ); } } 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 78d1377..aceee3f 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/command/MainCommand.java @@ -5,7 +5,6 @@ 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.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; @@ -391,19 +390,15 @@ public static boolean member( member.get().nation().ifPresent(nation -> nation.getTeam().removePlayer(member.get().player())); Member.getStaffTeam().addPlayer(member.get().player()); - - console.getServer().dispatchCommand(console, "luckperms:luckperms user " - + member.get().player().getName() + " parent add " + SMPCore.config().staffGroup()); } else { Member.getStaffTeam().removePlayer(member.get().player()); member.get().nation().ifPresent(nation -> nation.getTeam().addPlayer(member.get().player())); - - console.getServer().dispatchCommand(console, "luckperms:luckperms user " - + member.get().player().getName() + " parent remove " + SMPCore.config().staffGroup()); } + SMPCore.config().staffCommands(member.get().staff, member.get().player()); + return sendMessage(sender, SMPCore.messages().membersSetStaff(member.get())); } } @@ -471,10 +466,7 @@ private static boolean removeMember(final @NotNull Audience audience, final @Not if (target.staff) { Member.getStaffTeam().removePlayer(player); - final ConsoleCommandSender console = Bukkit.getConsoleSender(); - - Bukkit.dispatchCommand(console, "luckperms:luckperms user " + player.getName() - + " parent remove " + SMPCore.config().staffGroup()); + SMPCore.config().staffCommands(false, player); } if (!target.delete()) diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index f59a0b6..c77f831 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -52,4 +52,8 @@ staff: team: id: staff name: STAFF - group: staff + commands: + add: + - luckperms:luckperms user parent remove staff + remove: + - luckperms:luckperms user parent add staff diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 172af7e..c942f4f 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -3,8 +3,6 @@ author: Cloudnode version: '${project.version}' main: pro.cloudnode.smp.smpcore.SMPCore api-version: '1.20' -depend: - - LuckPerms commands: smpcore: description: SMPCore Main command From 4c378a49a6b6063e8b01f0855619927fb9c4b389 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Wed, 31 Dec 2025 10:42:00 +0200 Subject: [PATCH 09/10] run commands on main thread --- .../pro/cloudnode/smp/smpcore/Configuration.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/pro/cloudnode/smp/smpcore/Configuration.java b/src/main/java/pro/cloudnode/smp/smpcore/Configuration.java index 279cab3..e85a0c8 100644 --- a/src/main/java/pro/cloudnode/smp/smpcore/Configuration.java +++ b/src/main/java/pro/cloudnode/smp/smpcore/Configuration.java @@ -120,10 +120,12 @@ public void staffCommands(final boolean addMode, final @NotNull OfflinePlayer pl final List commands = config.getStringList("staff.commands." + (addMode ? "add" : "remove")); - for (final String command : commands) - Bukkit.dispatchCommand( - Bukkit.getConsoleSender(), - command.replace("", name) - ); + SMPCore.runMain(() -> { + for (final String command : commands) + Bukkit.dispatchCommand( + Bukkit.getConsoleSender(), + command.replace("", name) + ); + }); } } From 057653dc4a2cc6194aeb6bcf45b2082b3dbede4e Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Wed, 31 Dec 2025 10:44:12 +0200 Subject: [PATCH 10/10] fix reverse logic in staff add/remove commands --- src/main/resources/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index c77f831..880f71a 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -54,6 +54,6 @@ staff: name: STAFF commands: add: - - luckperms:luckperms user parent remove staff - remove: - luckperms:luckperms user parent add staff + remove: + - luckperms:luckperms user parent remove staff