diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/generic/parent/ParentSet.java b/common/src/main/java/me/lucko/luckperms/common/commands/generic/parent/ParentSet.java index 1f2cc39c..03210947 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/generic/parent/ParentSet.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/generic/parent/ParentSet.java @@ -83,7 +83,7 @@ public class ParentSet extends SecondarySubCommand { if (args.size() == 2) { - holder.clearParents(server); + holder.clearParents(server, null); holder.setInheritGroup(group, server); Message.SET_PARENT_SERVER_SUCCESS.send(sender, holder.getFriendlyName(), group.getDisplayName(), server); @@ -103,7 +103,7 @@ public class ParentSet extends SecondarySubCommand { } } else { - holder.clearParents(); + holder.clearParents(null, null); holder.setInheritGroup(group); if (holder instanceof User) { ((User) holder).setPrimaryGroup(group.getName()); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/user/UserMainCommand.java b/common/src/main/java/me/lucko/luckperms/common/commands/user/UserMainCommand.java index 53adbec8..b3cefd68 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/user/UserMainCommand.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/user/UserMainCommand.java @@ -48,7 +48,6 @@ public class UserMainCommand extends MainCommand { .add(new CommandMeta<>(true)) .add(new UserGetUUID()) .add(new UserSetPrimaryGroup()) - .add(new UserShowTracks()) .add(new UserPromote()) .add(new UserDemote()) .add(new UserShowPos()) diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/user/subcommands/UserDemote.java b/common/src/main/java/me/lucko/luckperms/common/commands/user/subcommands/UserDemote.java index 7d72a8dd..a76f28e6 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/user/subcommands/UserDemote.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/user/subcommands/UserDemote.java @@ -22,6 +22,8 @@ package me.lucko.luckperms.common.commands.user.subcommands; +import com.google.common.base.Objects; +import me.lucko.luckperms.api.Node; import me.lucko.luckperms.api.event.events.UserDemoteEvent; import me.lucko.luckperms.common.LuckPermsPlugin; import me.lucko.luckperms.common.api.internal.TrackLink; @@ -29,6 +31,7 @@ import me.lucko.luckperms.common.api.internal.UserLink; import me.lucko.luckperms.common.commands.*; import me.lucko.luckperms.common.constants.Message; import me.lucko.luckperms.common.constants.Permission; +import me.lucko.luckperms.common.core.NodeFactory; import me.lucko.luckperms.common.data.LogEntry; import me.lucko.luckperms.common.groups.Group; import me.lucko.luckperms.common.tracks.Track; @@ -37,12 +40,20 @@ import me.lucko.luckperms.common.utils.ArgumentChecker; import me.lucko.luckperms.exceptions.ObjectAlreadyHasException; import me.lucko.luckperms.exceptions.ObjectLacksException; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; public class UserDemote extends SubCommand { public UserDemote() { - super("demote", "Demotes the user down a track", Permission.USER_DEMOTE, Predicate.not(1), - Arg.list(Arg.create("track", true, "the track to demote the user down")) + super("demote", "Demotes the user down a track", Permission.USER_DEMOTE, Predicate.notInRange(1, 3), + Arg.list( + Arg.create("track", true, "the track to demote the user down"), + Arg.create("server", false, "the server to promote on"), + Arg.create("world", false, "the world to promote on") + ) ); } @@ -70,13 +81,68 @@ public class UserDemote extends SubCommand { return CommandResult.STATE_ERROR; } - final String old = user.getPrimaryGroup(); + String server = null; + String world = null; + + if (args.size() > 1) { + server = args.get(1); + if (ArgumentChecker.checkServer(server)) { + Message.SERVER_INVALID_ENTRY.send(sender); + return CommandResult.INVALID_ARGS; + } + if (args.size() > 2) { + world = args.get(2); + } + } + + // Load applicable groups + Set nodes = new HashSet<>(); + for (Node node : user.getNodes()) { + if (!node.isGroupNode()) { + continue; + } + + if (!node.getValue()) { + continue; + } + + String s = node.getServer().orElse(null); + if (!Objects.equal(s, server)) { + continue; + } + + String w = node.getWorld().orElse(null); + if (!Objects.equal(w, world)) { + continue; + } + + nodes.add(node); + } + + Iterator it = nodes.iterator(); + while (it.hasNext()) { + Node g = it.next(); + if (!track.containsGroup(g.getGroupName())) { + it.remove(); + } + } + + if (nodes.isEmpty()) { + Message.USER_TRACK_ERROR_NOT_CONTAIN_GROUP.send(sender); + return CommandResult.FAILURE; + } + + if (nodes.size() != 1) { + Message.TRACK_AMBIGUOUS_CALL.send(sender); + return CommandResult.FAILURE; + } + + final String old = nodes.stream().findAny().get().getGroupName(); final String previous; try { previous = track.getPrevious(old); } catch (ObjectLacksException e) { Message.TRACK_DOES_NOT_CONTAIN.send(sender, track.getName(), old); - Message.USER_DEMOTE_ERROR_NOT_CONTAIN_GROUP.send(sender); return CommandResult.STATE_ERROR; } @@ -96,19 +162,28 @@ public class UserDemote extends SubCommand { return CommandResult.LOADING_ERROR; } + user.clearParents(server, world); try { - user.unsetPermission("group." + old); - } catch (ObjectLacksException ignored) {} - try { - user.setInheritGroup(previousGroup); + user.setPermission(NodeFactory.newBuilder("group." + previousGroup.getName()).setServer(server).setWorld(world).build()); } catch (ObjectAlreadyHasException ignored) {} - user.setPrimaryGroup(previousGroup.getName()); - Message.USER_DEMOTE_SUCCESS_PROMOTE.send(sender, track.getName(), old, previousGroup.getDisplayName()); - Message.USER_DEMOTE_SUCCESS_REMOVE.send(sender, user.getName(), old, previousGroup.getDisplayName(), previousGroup.getDisplayName()); + if (server == null && world == null) { + user.setPrimaryGroup(previousGroup.getName()); + } + + if (server == null) { + Message.USER_DEMOTE_SUCCESS.send(sender, track.getName(), old, previousGroup.getDisplayName()); + } else { + if (world == null) { + Message.USER_DEMOTE_SUCCESS_SERVER.send(sender, track.getName(), old, previousGroup.getDisplayName(), server); + } else { + Message.USER_DEMOTE_SUCCESS_SERVER_WORLD.send(sender, track.getName(), old, previousGroup.getDisplayName(), server, world); + } + } + Message.EMPTY.send(sender, Util.listToArrowSep(track.getGroups(), previousGroup.getDisplayName(), old, true)); LogEntry.build().actor(sender).acted(user) - .action("demote " + track.getName() + "(from " + old + " to " + previousGroup.getName() + ")") + .action("demote " + args.stream().collect(Collectors.joining(" "))) .build().submit(plugin, sender); save(user, sender, plugin); plugin.getApiProvider().fireEventAsync(new UserDemoteEvent(new TrackLink(track), new UserLink(user), old, previousGroup.getName())); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/user/subcommands/UserPromote.java b/common/src/main/java/me/lucko/luckperms/common/commands/user/subcommands/UserPromote.java index 937d45ad..c0c26f03 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/user/subcommands/UserPromote.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/user/subcommands/UserPromote.java @@ -22,6 +22,8 @@ package me.lucko.luckperms.common.commands.user.subcommands; +import com.google.common.base.Objects; +import me.lucko.luckperms.api.Node; import me.lucko.luckperms.api.event.events.UserPromoteEvent; import me.lucko.luckperms.common.LuckPermsPlugin; import me.lucko.luckperms.common.api.internal.TrackLink; @@ -29,6 +31,7 @@ import me.lucko.luckperms.common.api.internal.UserLink; import me.lucko.luckperms.common.commands.*; import me.lucko.luckperms.common.constants.Message; import me.lucko.luckperms.common.constants.Permission; +import me.lucko.luckperms.common.core.NodeFactory; import me.lucko.luckperms.common.data.LogEntry; import me.lucko.luckperms.common.groups.Group; import me.lucko.luckperms.common.tracks.Track; @@ -37,12 +40,20 @@ import me.lucko.luckperms.common.utils.ArgumentChecker; import me.lucko.luckperms.exceptions.ObjectAlreadyHasException; import me.lucko.luckperms.exceptions.ObjectLacksException; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; public class UserPromote extends SubCommand { public UserPromote() { - super("promote", "Promotes the user up a track", Permission.USER_PROMOTE, Predicate.not(1), - Arg.list(Arg.create("track", true, "the track to promote the user up")) + super("promote", "Promotes the user up a track", Permission.USER_PROMOTE, Predicate.notInRange(1, 3), + Arg.list( + Arg.create("track", true, "the track to promote the user up"), + Arg.create("server", false, "the server to promote on"), + Arg.create("world", false, "the world to promote on") + ) ); } @@ -70,13 +81,68 @@ public class UserPromote extends SubCommand { return CommandResult.STATE_ERROR; } - final String old = user.getPrimaryGroup(); + String server = null; + String world = null; + + if (args.size() > 1) { + server = args.get(1); + if (ArgumentChecker.checkServer(server)) { + Message.SERVER_INVALID_ENTRY.send(sender); + return CommandResult.INVALID_ARGS; + } + if (args.size() > 2) { + world = args.get(2); + } + } + + // Load applicable groups + Set nodes = new HashSet<>(); + for (Node node : user.getNodes()) { + if (!node.isGroupNode()) { + continue; + } + + if (!node.getValue()) { + continue; + } + + String s = node.getServer().orElse(null); + if (!Objects.equal(s, server)) { + continue; + } + + String w = node.getWorld().orElse(null); + if (!Objects.equal(w, world)) { + continue; + } + + nodes.add(node); + } + + Iterator it = nodes.iterator(); + while (it.hasNext()) { + Node g = it.next(); + if (!track.containsGroup(g.getGroupName())) { + it.remove(); + } + } + + if (nodes.isEmpty()) { + Message.USER_TRACK_ERROR_NOT_CONTAIN_GROUP.send(sender); + return CommandResult.FAILURE; + } + + if (nodes.size() != 1) { + Message.TRACK_AMBIGUOUS_CALL.send(sender); + return CommandResult.FAILURE; + } + + final String old = nodes.stream().findAny().get().getGroupName(); final String next; try { next = track.getNext(old); } catch (ObjectLacksException e) { Message.TRACK_DOES_NOT_CONTAIN.send(sender, track.getName(), old); - Message.USER_PROMOTE_ERROR_NOT_CONTAIN_GROUP.send(sender); return CommandResult.STATE_ERROR; } @@ -96,19 +162,28 @@ public class UserPromote extends SubCommand { return CommandResult.LOADING_ERROR; } + user.clearParents(server, world); try { - user.unsetPermission("group." + old); - } catch (ObjectLacksException ignored) {} - try { - user.setInheritGroup(nextGroup); + user.setPermission(NodeFactory.newBuilder("group." + nextGroup.getName()).setServer(server).setWorld(world).build()); } catch (ObjectAlreadyHasException ignored) {} - user.setPrimaryGroup(nextGroup.getName()); - Message.USER_PROMOTE_SUCCESS_PROMOTE.send(sender, track.getName(), old, nextGroup.getDisplayName()); - Message.USER_PROMOTE_SUCCESS_REMOVE.send(sender, user.getName(), old, nextGroup.getDisplayName(), nextGroup.getDisplayName()); + if (server == null && world == null) { + user.setPrimaryGroup(nextGroup.getName()); + } + + if (server == null) { + Message.USER_PROMOTE_SUCCESS.send(sender, track.getName(), old, nextGroup.getDisplayName()); + } else { + if (world == null) { + Message.USER_PROMOTE_SUCCESS_SERVER.send(sender, track.getName(), old, nextGroup.getDisplayName(), server); + } else { + Message.USER_PROMOTE_SUCCESS_SERVER_WORLD.send(sender, track.getName(), old, nextGroup.getDisplayName(), server, world); + } + } + Message.EMPTY.send(sender, Util.listToArrowSep(track.getGroups(), old, nextGroup.getDisplayName(), false)); LogEntry.build().actor(sender).acted(user) - .action("promote " + track.getName() + "(from " + old + " to " + nextGroup.getName() + ")") + .action("promote " + args.stream().collect(Collectors.joining(" "))) .build().submit(plugin, sender); save(user, sender, plugin); plugin.getApiProvider().fireEventAsync(new UserPromoteEvent(new TrackLink(track), new UserLink(user), old, nextGroup.getName())); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/user/subcommands/UserShowTracks.java b/common/src/main/java/me/lucko/luckperms/common/commands/user/subcommands/UserShowTracks.java deleted file mode 100644 index f25afb84..00000000 --- a/common/src/main/java/me/lucko/luckperms/common/commands/user/subcommands/UserShowTracks.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2016 Lucko (Luck) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package me.lucko.luckperms.common.commands.user.subcommands; - -import me.lucko.luckperms.common.LuckPermsPlugin; -import me.lucko.luckperms.common.commands.*; -import me.lucko.luckperms.common.constants.Message; -import me.lucko.luckperms.common.constants.Permission; -import me.lucko.luckperms.common.tracks.Track; -import me.lucko.luckperms.common.users.User; - -import java.util.List; -import java.util.stream.Collectors; - -public class UserShowTracks extends SubCommand { - public UserShowTracks() { - super("showtracks", "Lists the tracks that the user's primary group features on", Permission.USER_SHOWTRACKS, - Predicate.alwaysFalse(), null); - } - - @Override - public CommandResult execute(LuckPermsPlugin plugin, Sender sender, User user, List args, String label) { - if (!plugin.getDatastore().loadAllTracks().getUnchecked()) { - Message.TRACKS_LOAD_ERROR.send(sender); - return CommandResult.LOADING_ERROR; - } - - Message.USER_SHOWTRACKS_INFO.send(sender, user.getPrimaryGroup(), user.getName()); - Message.TRACKS_LIST.send(sender, - Util.listToCommaSep(plugin.getTrackManager().getApplicableTracks(user.getPrimaryGroup()).stream() - .map(Track::getName) - .collect(Collectors.toList()) - ) - ); - - return CommandResult.SUCCESS; - } -} diff --git a/common/src/main/java/me/lucko/luckperms/common/constants/Message.java b/common/src/main/java/me/lucko/luckperms/common/constants/Message.java index 7d00cc0e..0e2ca8c8 100644 --- a/common/src/main/java/me/lucko/luckperms/common/constants/Message.java +++ b/common/src/main/java/me/lucko/luckperms/common/constants/Message.java @@ -100,6 +100,7 @@ public enum Message { TRACK_ALREADY_CONTAINS("Track {0} already contains the group '{1}'.", true), TRACK_DOES_NOT_CONTAIN("Track {0} does not contain the group '{1}'.", true), + TRACK_AMBIGUOUS_CALL("The user specified is a member of multiple groups on this track. Unable to determine their location.", true), GROUP_ALREADY_EXISTS("That group already exists!", true), GROUP_DOES_NOT_EXIST("That group does not exist!", true), @@ -268,25 +269,25 @@ public enum Message { USER_PRIMARYGROUP_SUCCESS("&b{0}&a's primary group was set to &b{1}&a.", true), USER_PRIMARYGROUP_ERROR_ALREADYHAS("The user already has this group set as their primary group.", true), USER_PRIMARYGROUP_ERROR_NOTMEMBER("&b{0}&a was not already a member of &b{1}&a, adding them now.", true), - USER_SHOWTRACKS_INFO("&aShowing tracks that contain the group '&b{0}&a' ({1}'s primary group)", true), - USER_PROMOTE_SUCCESS_PROMOTE("&aPromoting user along track &b{0}&a from &b{1}&a to &b{2}&a.", true), - USER_PROMOTE_SUCCESS_REMOVE("&b{0}&a was removed from &b{1}&a, added to &b{2}&a, and their primary group was set to &b{3}&a.", true), + USER_TRACK_ERROR_NOT_CONTAIN_GROUP("The user specified isn't already in any groups on this track.", true), + USER_PROMOTE_SUCCESS("&aPromoting user along track &b{0}&a from &b{1}&a to &b{2}&a.", true), + USER_PROMOTE_SUCCESS_SERVER("&aPromoting user along track &b{0}&a from &b{1}&a to &b{2}&a on server &b{3}&a.", true), + USER_PROMOTE_SUCCESS_SERVER_WORLD("&aPromoting user along track &b{0}&a from &b{1}&a to &b{2}&a on server &b{3}&a, world &b{4}&a.", true), USER_PROMOTE_ERROR_ENDOFTRACK("The end of track &4{0}&c was reached. Unable to promote user.", true), USER_PROMOTE_ERROR_MALFORMED( "{PREFIX}The next group on the track, {0}, no longer exists. Unable to promote user." + "\n" + "{PREFIX}Either create the group, or remove it from the track and try again.", false ), - USER_PROMOTE_ERROR_NOT_CONTAIN_GROUP("Promotions are done based on primary groups. The users primary group is not on the track specified.", true), - USER_DEMOTE_SUCCESS_PROMOTE("&aDemoting user along track &b{0}&a from &b{1}&a to &b{2}&a.", true), - USER_DEMOTE_SUCCESS_REMOVE("&b{0}&a was removed from &b{1}&a, added to &b{2}&a, and their primary group was set to &b{3}&a.", true), + USER_DEMOTE_SUCCESS("&aDemoting user along track &b{0}&a from &b{1}&a to &b{2}&a.", true), + USER_DEMOTE_SUCCESS_SERVER("&aDemoting user along track &b{0}&a from &b{1}&a to &b{2}&a on server &b{3}&a.", true), + USER_DEMOTE_SUCCESS_SERVER_WORLD("&aDemoting user along track &b{0}&a from &b{1}&a to &b{2}&a on server &b{3}&a, world &b{4}&a.", true), USER_DEMOTE_ERROR_ENDOFTRACK("The end of track &4{0}&c was reached. Unable to demote user.", true), USER_DEMOTE_ERROR_MALFORMED( "{PREFIX}The previous group on the track, {0}, no longer exists. Unable to demote user." + "\n" + "{PREFIX}Either create the group, or remove it from the track and try again.", false ), - USER_DEMOTE_ERROR_NOT_CONTAIN_GROUP("Demotions are done based on primary groups. The users primary group is not on the track specified.", true), USER_SHOWPOS("&aShowing &b{0}&a's position on track &b{1}&a.\n{2}", true), GROUP_INFO( @@ -298,7 +299,7 @@ public enum Message { ), TRACK_INFO( - "{PREFIX}&b&l> Showing Track: &f{0}" + "\n" + + "{PREFIX}&b&l> &bShowing Track: &f{0}" + "\n" + "{PREFIX}&f- &7Path: &f{1}", false ), diff --git a/common/src/main/java/me/lucko/luckperms/common/core/NodeFactory.java b/common/src/main/java/me/lucko/luckperms/common/core/NodeFactory.java index c0e9f8e4..7ff7479a 100644 --- a/common/src/main/java/me/lucko/luckperms/common/core/NodeFactory.java +++ b/common/src/main/java/me/lucko/luckperms/common/core/NodeFactory.java @@ -56,6 +56,10 @@ public class NodeFactory { return b ? CACHE.getUnchecked(s) : CACHE_NEGATED.getUnchecked(s); } + public static Node.Builder newBuilder(String s) { + return new NodeBuilder(s, false); + } + public static Node.Builder builderFromSerialisedNode(String s, Boolean b) { if (s.contains("/")) { List parts = Splitter.on('/').limit(2).splitToList(s); diff --git a/common/src/main/java/me/lucko/luckperms/common/core/PermissionHolder.java b/common/src/main/java/me/lucko/luckperms/common/core/PermissionHolder.java index 25ca663a..e6b6a3c8 100644 --- a/common/src/main/java/me/lucko/luckperms/common/core/PermissionHolder.java +++ b/common/src/main/java/me/lucko/luckperms/common/core/PermissionHolder.java @@ -748,26 +748,6 @@ public abstract class PermissionHolder { } } - public void clearParents() { - synchronized (nodes) { - boolean b = nodes.removeIf(Node::isGroupNode); - if (b) { - invalidateCache(true); - } - } - } - - public void clearParents(String server) { - String finalServer = Optional.ofNullable(server).orElse("global"); - - synchronized (nodes) { - boolean b = nodes.removeIf(n -> n.isGroupNode() && n.getServer().orElse("global").equalsIgnoreCase(finalServer)); - if (b) { - invalidateCache(true); - } - } - } - public void clearParents(String server, String world) { String finalServer = Optional.ofNullable(server).orElse("global"); String finalWorld = Optional.ofNullable(world).orElse("null"); @@ -824,30 +804,6 @@ public abstract class PermissionHolder { } } - public void clearMetaKeys(String key, boolean temp) { - synchronized (nodes) { - boolean b = nodes.removeIf(n -> n.isMeta() && (n.isTemporary() == temp) && n.getMeta().getKey().equalsIgnoreCase(key)); - if (b) { - invalidateCache(true); - } - } - } - - public void clearMetaKeys(String key, String server, boolean temp) { - String finalServer = Optional.ofNullable(server).orElse("global"); - - synchronized (nodes) { - boolean b = nodes.removeIf(n -> - n.isMeta() && (n.isTemporary() == temp) && - n.getMeta().getKey().equalsIgnoreCase(key) && - n.getServer().orElse("global").equalsIgnoreCase(finalServer) - ); - if (b) { - invalidateCache(true); - } - } - } - public void clearMetaKeys(String key, String server, String world, boolean temp) { String finalServer = Optional.ofNullable(server).orElse("global"); String finalWorld = Optional.ofNullable(world).orElse("null"); diff --git a/default-lang.yml b/default-lang.yml index ccebe443..ac492d5a 100644 --- a/default-lang.yml +++ b/default-lang.yml @@ -61,6 +61,7 @@ does-not-temp-inherit: "{0} does not temporarily inherit '{1}'." track-already-contains: "Track {0} already contains the group '{1}'." track-does-not-contain: "Track {0} does not contain the group '{1}'." +track-ambiguous-call: "The user specified is a member of multiple groups on this track. Unable to determine their location." group-already-exists: "That group already exists!" group-does-not-exist: "That group does not exist!" @@ -219,16 +220,17 @@ user-removegroup-error-primary: "You cannot remove a user from their primary gro user-primarygroup-success: "&b{0}&a's primary group was set to &b{1}&a." user-primarygroup-error-alreadyhas: "The user already has this group set as their primary group." user-primarygroup-error-notmember: "&b{0}&a was not already a member of &b{1}&a, adding them now." -user-showtracks-info: "&aShowing tracks that contain the group '&b{0}&a' ({1}'s primary group)" -user-promote-success-promote: "&aPromoting user along track &b{0}&a from &b{1}&a to &b{2}&a." -user-promote-success-remove: "&b{0}&a was removed from &b{1}&a, added to &b{2}&a, and their primary group was set to &b{3}&a." +user-track-error-not-contain-group: "The user specified isn't already in any groups on this track." +user-promote-success: "&aPromoting user along track &b{0}&a from &b{1}&a to &b{2}&a." +user-promote-success-server: "&aPromoting user along track &b{0}&a from &b{1}&a to &b{2}&a on server &b{3}&a." +user-promote-success-server-world: "&aPromoting user along track &b{0}&a from &b{1}&a to &b{2}&a on server &b{3}&a, world &b{4}&a." user-promote-error-endoftrack: "The end of track &4{0}&c was reached. Unable to promote user." user-promote-error-malformed: > {PREFIX}The next group on the track, {0}, no longer exists. Unable to promote user. {PREFIX}Either create the group, or remove it from the track and try again. -user-promote-error-not-contain-group: "Promotions are done based on primary groups. The users primary group is not on the track specified." -user-demote-success-promote: "&aDemoting user along track &b{0}&a from &b{1}&a to &b{2}&a." -user-demote-success-remove: "&b{0}&a was removed from &b{1}&a, added to &b{2}&a, and their primary group was set to &b{3}&a." +user-demote-success: "&aDemoting user along track &b{0}&a from &b{1}&a to &b{2}&a." +user-demote-success-server: "&aDemoting user along track &b{0}&a from &b{1}&a to &b{2}&a on server &b{3}&a." +user-demote-success-server-world: "&aDemoting user along track &b{0}&a from &b{1}&a to &b{2}&a on server &b{3}&a, world &b{4}&a." user-demote-error-endoftrack: "The end of track &4{0}&c was reached. Unable to demote user." user-demote-error-malformed: > {PREFIX}The previous group on the track, {0}, no longer exists. Unable to demote user.\n @@ -243,7 +245,7 @@ group-info: > {PREFIX}&f- &3Use &b/{3} group {4} permission info &3to see all permissions. track-info: > - {PREFIX}&b&l> Showing Track: &f{0}\n + {PREFIX}&b&l> &bShowing Track: &f{0}\n {PREFIX}&f- &7Path: &f{1} track-clear: "&b{0}&a's groups track was cleared." track-append-success: "&aGroup &b{0}&a was successfully appended to track &b{1}&a."