diff --git a/api/src/main/java/me/lucko/luckperms/api/DataMutateResult.java b/api/src/main/java/me/lucko/luckperms/api/DataMutateResult.java
index 0d9a3aa3..8f738d72 100644
--- a/api/src/main/java/me/lucko/luckperms/api/DataMutateResult.java
+++ b/api/src/main/java/me/lucko/luckperms/api/DataMutateResult.java
@@ -26,9 +26,11 @@
package me.lucko.luckperms.api;
/**
- * Represents the result of a mutation call.
+ * Represents the result of a data mutation call on a LuckPerms object.
+ *
+ *
Usually as the result to a call on a {@link PermissionHolder} or {@link Track}.
*/
-public enum DataMutateResult {
+public enum DataMutateResult implements MutateResult {
/**
* Indicates the mutation was a success
@@ -50,39 +52,14 @@ public enum DataMutateResult {
*/
FAIL(false);
- private final boolean value;
+ private final boolean success;
- DataMutateResult(boolean value) {
- this.value = value;
+ DataMutateResult(boolean success) {
+ this.success = success;
}
- /**
- * Gets a boolean representation of the result.
- *
- * @return a boolean representation
- */
- public boolean asBoolean() {
- return this.value;
- }
-
- /**
- * Gets if the result indicates a success
- *
- * @return if the result indicates a success
- * @since 3.4
- */
+ @Override
public boolean wasSuccess() {
- return this.value;
+ return this.success;
}
-
- /**
- * Gets if the result indicates a failure
- *
- * @return if the result indicates a failure
- * @since 3.4
- */
- public boolean wasFailure() {
- return !this.value;
- }
-
}
diff --git a/api/src/main/java/me/lucko/luckperms/api/DemotionResult.java b/api/src/main/java/me/lucko/luckperms/api/DemotionResult.java
new file mode 100644
index 00000000..02052925
--- /dev/null
+++ b/api/src/main/java/me/lucko/luckperms/api/DemotionResult.java
@@ -0,0 +1,130 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * 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.api;
+
+import java.util.Optional;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Encapsulates the result of {@link User}s demotion along a {@link Track}.
+ *
+ * @since 4.2
+ */
+public interface DemotionResult extends MutateResult {
+
+ /**
+ * Gets the status of the result.
+ *
+ * @return the status
+ */
+ @Nonnull
+ Status getStatus();
+
+ @Override
+ default boolean wasSuccess() {
+ return getStatus().wasSuccess();
+ }
+
+ /**
+ * Gets the name of the group the user was demoted from, if applicable.
+ *
+ * Will only be present for results with a {@link #getStatus() status} of
+ * {@link Status#SUCCESS} or {@link Status#REMOVED_FROM_FIRST_GROUP}.
+ *
+ * The value will also be set for results with the {@link Status#MALFORMED_TRACK} status,
+ * with this value marking the group which no longer exists.
+ *
+ * @return the group the user was demoted from.
+ */
+ @Nonnull
+ Optional getGroupFrom();
+
+ /**
+ * Gets the name of the group the user was demoted from, if applicable.
+ *
+ * Will only be present for results with a {@link #getStatus() status} of
+ * {@link Status#SUCCESS}.
+ *
+ * @return the group the user was demoted to.
+ */
+ @Nonnull
+ Optional getGroupTo();
+
+ /**
+ * The result status
+ */
+ enum Status implements MutateResult {
+
+ /**
+ * Indicates that the user was demoted normally.
+ */
+ SUCCESS(true),
+
+ /**
+ * Indicates that the user was removed from the first group in the track.
+ *
+ * This usually occurs when the user is currently on the first group, and was demoted
+ * "over the start" of the track.
+ */
+ REMOVED_FROM_FIRST_GROUP(true),
+
+ /**
+ * Indicates that the previous group in the track no longer exists.
+ */
+ MALFORMED_TRACK(false),
+
+ /**
+ * Indicates that the user isn't a member of any of the groups on this track.
+ */
+ NOT_ON_TRACK(false),
+
+ /**
+ * Indicates that the implementation was unable to determine the users current position on
+ * this track.
+ *
+ * This usually occurs when the user is on more than one group on the track.
+ */
+ AMBIGUOUS_CALL(false),
+
+ /**
+ * An undefined failure occurred.
+ */
+ UNDEFINED_FAILURE(false);
+
+ private final boolean success;
+
+ Status(boolean success) {
+ this.success = success;
+ }
+
+ @Override
+ public boolean wasSuccess() {
+ return this.success;
+ }
+ }
+
+}
diff --git a/api/src/main/java/me/lucko/luckperms/api/MutateResult.java b/api/src/main/java/me/lucko/luckperms/api/MutateResult.java
new file mode 100644
index 00000000..e2ca32e1
--- /dev/null
+++ b/api/src/main/java/me/lucko/luckperms/api/MutateResult.java
@@ -0,0 +1,74 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * 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.api;
+
+/**
+ * Represents the result to a "mutation" on an object.
+ *
+ * @since 4.2
+ */
+public interface MutateResult {
+
+ /**
+ * Instance of {@link MutateResult} which always reports success.
+ */
+ MutateResult GENERIC_SUCCESS = () -> true;
+
+ /**
+ * Instance of {@link MutateResult} which always reports failure.
+ */
+ MutateResult GENERIC_FAILURE = () -> false;
+
+ /**
+ * Gets if the operation which produced this result completed successfully.
+ *
+ * @return if the result indicates a success
+ */
+ boolean wasSuccess();
+
+ /**
+ * Gets if the operation which produced this result failed.
+ *
+ * @return if the result indicates a failure
+ */
+ default boolean wasFailure() {
+ return !wasSuccess();
+ }
+
+ /**
+ * Gets a boolean representation of the result.
+ *
+ * A value of true
marks that the operation {@link #wasSuccess() was a success}
+ * and a value of false
marks that the operation
+ * {@link #wasFailure() was a failure}.
+ *
+ * @return a boolean representation
+ */
+ default boolean asBoolean() {
+ return wasSuccess();
+ }
+
+}
diff --git a/api/src/main/java/me/lucko/luckperms/api/PromotionResult.java b/api/src/main/java/me/lucko/luckperms/api/PromotionResult.java
new file mode 100644
index 00000000..b1bcf819
--- /dev/null
+++ b/api/src/main/java/me/lucko/luckperms/api/PromotionResult.java
@@ -0,0 +1,130 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * 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.api;
+
+import java.util.Optional;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Encapsulates the result of {@link User}s promotion along a {@link Track}.
+ *
+ * @since 4.2
+ */
+public interface PromotionResult extends MutateResult {
+
+ /**
+ * Gets the status of the result.
+ *
+ * @return the status
+ */
+ @Nonnull
+ Status getStatus();
+
+ @Override
+ default boolean wasSuccess() {
+ return getStatus().wasSuccess();
+ }
+
+ /**
+ * Gets the name of the group the user was promoted from, if applicable.
+ *
+ * Will only be present for results with a {@link #getStatus() status} of
+ * {@link Status#SUCCESS}.
+ *
+ * @return the group the user was promoted from.
+ */
+ @Nonnull
+ Optional getGroupFrom();
+
+ /**
+ * Gets the name of the group the user was promoted from, if applicable.
+ *
+ * Will only be present for results with a {@link #getStatus() status} of
+ * {@link Status#SUCCESS} or {@link Status#ADDED_TO_FIRST_GROUP}.
+ *
+ * The value will also be set for results with the {@link Status#MALFORMED_TRACK} status,
+ * with this value marking the group which no longer exists.
+ *
+ * @return the group the user was promoted to.
+ */
+ @Nonnull
+ Optional getGroupTo();
+
+ /**
+ * The result status
+ */
+ enum Status implements MutateResult {
+
+ /**
+ * Indicates that the user was promoted normally.
+ */
+ SUCCESS(true),
+
+ /**
+ * Indicates that the user was added to the first group in the track.
+ *
+ * This usually occurs when the user isn't already on any of the groups in the track.
+ */
+ ADDED_TO_FIRST_GROUP(true),
+
+ /**
+ * Indicates that the next group in the track no longer exists.
+ */
+ MALFORMED_TRACK(false),
+
+ /**
+ * Indicates that the user is already a member of the group at the end of the track,
+ * and as such cannot be promoted any further.
+ */
+ END_OF_TRACK(false),
+
+ /**
+ * Indicates that the implementation was unable to determine the users current position on
+ * this track.
+ *
+ * This usually occurs when the user is on more than one group on the track.
+ */
+ AMBIGUOUS_CALL(false),
+
+ /**
+ * An undefined failure occurred.
+ */
+ UNDEFINED_FAILURE(false);
+
+ private final boolean success;
+
+ Status(boolean success) {
+ this.success = success;
+ }
+
+ @Override
+ public boolean wasSuccess() {
+ return this.success;
+ }
+ }
+
+}
diff --git a/api/src/main/java/me/lucko/luckperms/api/Track.java b/api/src/main/java/me/lucko/luckperms/api/Track.java
index 62f50643..be92aafc 100644
--- a/api/src/main/java/me/lucko/luckperms/api/Track.java
+++ b/api/src/main/java/me/lucko/luckperms/api/Track.java
@@ -25,6 +25,8 @@
package me.lucko.luckperms.api;
+import me.lucko.luckperms.api.context.ContextSet;
+
import java.util.List;
import javax.annotation.Nonnull;
@@ -88,6 +90,28 @@ public interface Track {
@Nullable
String getPrevious(@Nonnull Group current);
+ /**
+ * Promotes the given user along this track.
+ *
+ * @param user the user to promote
+ * @param contextSet the contexts to promote the user in
+ * @return the result of the action
+ * @since 4.2
+ */
+ @Nonnull
+ PromotionResult promote(@Nonnull User user, @Nonnull ContextSet contextSet);
+
+ /**
+ * Demotes the given user along this track.
+ *
+ * @param user the user to demote
+ * @param contextSet the contexts to demote the user in
+ * @return the result of the action
+ * @since 4.2
+ */
+ @Nonnull
+ DemotionResult demote(@Nonnull User user, @Nonnull ContextSet contextSet);
+
/**
* Appends a group to the end of this track
*
diff --git a/common/src/main/java/me/lucko/luckperms/common/api/DemotionResults.java b/common/src/main/java/me/lucko/luckperms/common/api/DemotionResults.java
new file mode 100644
index 00000000..95420718
--- /dev/null
+++ b/common/src/main/java/me/lucko/luckperms/common/api/DemotionResults.java
@@ -0,0 +1,124 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * 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.api;
+
+import me.lucko.luckperms.api.DemotionResult;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Utility class for creating instances of {@link DemotionResult}.
+ */
+public final class DemotionResults {
+
+ public static DemotionResult success(String groupFrom, String groupTo) {
+ return new Impl(DemotionResult.Status.SUCCESS, groupFrom, groupTo);
+ }
+
+ public static DemotionResult removedFromFirst(String groupFrom) {
+ return new Impl(DemotionResult.Status.REMOVED_FROM_FIRST_GROUP, groupFrom, null);
+ }
+
+ public static DemotionResult malformedTrack(String groupTo) {
+ return new Impl(DemotionResult.Status.MALFORMED_TRACK, null, groupTo);
+ }
+
+ public static DemotionResult notOnTrack() {
+ return new Impl(DemotionResult.Status.NOT_ON_TRACK);
+ }
+
+ public static DemotionResult ambiguousCall() {
+ return new Impl(DemotionResult.Status.AMBIGUOUS_CALL);
+ }
+
+ public static DemotionResult undefinedFailure() {
+ return new Impl(DemotionResult.Status.UNDEFINED_FAILURE);
+ }
+
+ private static final class Impl implements DemotionResult {
+ private final Status status;
+ private final String groupFrom;
+ private final String groupTo;
+
+ private Impl(Status status, String groupFrom, String groupTo) {
+ this.status = status;
+ this.groupFrom = groupFrom;
+ this.groupTo = groupTo;
+ }
+
+ private Impl(Status status) {
+ this(status, null, null);
+ }
+
+ @Nonnull
+ @Override
+ public Status getStatus() {
+ return this.status;
+ }
+
+ @Nonnull
+ @Override
+ public Optional getGroupFrom() {
+ return Optional.ofNullable(this.groupFrom);
+ }
+
+ @Nonnull
+ @Override
+ public Optional getGroupTo() {
+ return Optional.ofNullable(this.groupTo);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Impl that = (Impl) o;
+ return this.status == that.status &&
+ Objects.equals(this.groupFrom, that.groupFrom) &&
+ Objects.equals(this.groupTo, that.groupTo);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.status, this.groupFrom, this.groupTo);
+ }
+
+ @Override
+ public String toString() {
+ return "DemotionResult(" +
+ "status=" + this.status + ", " +
+ "groupFrom='" + this.groupFrom + "', " +
+ "groupTo='" + this.groupTo + "')";
+ }
+ }
+
+
+ private DemotionResults() {}
+
+}
diff --git a/common/src/main/java/me/lucko/luckperms/common/api/PromotionResults.java b/common/src/main/java/me/lucko/luckperms/common/api/PromotionResults.java
new file mode 100644
index 00000000..39474dda
--- /dev/null
+++ b/common/src/main/java/me/lucko/luckperms/common/api/PromotionResults.java
@@ -0,0 +1,124 @@
+/*
+ * This file is part of LuckPerms, licensed under the MIT License.
+ *
+ * Copyright (c) lucko (Luck)
+ * Copyright (c) contributors
+ *
+ * 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.api;
+
+import me.lucko.luckperms.api.PromotionResult;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Utility class for creating instances of {@link PromotionResult}.
+ */
+public final class PromotionResults {
+
+ public static PromotionResult success(String groupFrom, String groupTo) {
+ return new Impl(PromotionResult.Status.SUCCESS, groupFrom, groupTo);
+ }
+
+ public static PromotionResult addedToFirst(String groupTo) {
+ return new Impl(PromotionResult.Status.ADDED_TO_FIRST_GROUP, null, groupTo);
+ }
+
+ public static PromotionResult malformedTrack(String groupTo) {
+ return new Impl(PromotionResult.Status.MALFORMED_TRACK, null, groupTo);
+ }
+
+ public static PromotionResult endOfTrack() {
+ return new Impl(PromotionResult.Status.END_OF_TRACK);
+ }
+
+ public static PromotionResult ambiguousCall() {
+ return new Impl(PromotionResult.Status.AMBIGUOUS_CALL);
+ }
+
+ public static PromotionResult undefinedFailure() {
+ return new Impl(PromotionResult.Status.UNDEFINED_FAILURE);
+ }
+
+ private static final class Impl implements PromotionResult {
+ private final Status status;
+ private final String groupFrom;
+ private final String groupTo;
+
+ private Impl(Status status, String groupFrom, String groupTo) {
+ this.status = status;
+ this.groupFrom = groupFrom;
+ this.groupTo = groupTo;
+ }
+
+ private Impl(Status status) {
+ this(status, null, null);
+ }
+
+ @Nonnull
+ @Override
+ public Status getStatus() {
+ return this.status;
+ }
+
+ @Nonnull
+ @Override
+ public Optional getGroupFrom() {
+ return Optional.ofNullable(this.groupFrom);
+ }
+
+ @Nonnull
+ @Override
+ public Optional getGroupTo() {
+ return Optional.ofNullable(this.groupTo);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Impl that = (Impl) o;
+ return this.status == that.status &&
+ Objects.equals(this.groupFrom, that.groupFrom) &&
+ Objects.equals(this.groupTo, that.groupTo);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.status, this.groupFrom, this.groupTo);
+ }
+
+ @Override
+ public String toString() {
+ return "PromotionResult(" +
+ "status=" + this.status + ", " +
+ "groupFrom='" + this.groupFrom + "', " +
+ "groupTo='" + this.groupTo + "')";
+ }
+ }
+
+
+ private PromotionResults() {}
+
+}
diff --git a/common/src/main/java/me/lucko/luckperms/common/api/delegates/model/ApiTrack.java b/common/src/main/java/me/lucko/luckperms/common/api/delegates/model/ApiTrack.java
index 7d238e20..0e49a17c 100644
--- a/common/src/main/java/me/lucko/luckperms/common/api/delegates/model/ApiTrack.java
+++ b/common/src/main/java/me/lucko/luckperms/common/api/delegates/model/ApiTrack.java
@@ -28,8 +28,13 @@ package me.lucko.luckperms.common.api.delegates.model;
import com.google.common.base.Preconditions;
import me.lucko.luckperms.api.DataMutateResult;
+import me.lucko.luckperms.api.DemotionResult;
import me.lucko.luckperms.api.Group;
+import me.lucko.luckperms.api.PromotionResult;
+import me.lucko.luckperms.api.User;
+import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.common.model.Track;
+import me.lucko.luckperms.common.utils.Predicates;
import java.util.List;
import java.util.Objects;
@@ -90,6 +95,18 @@ public final class ApiTrack implements me.lucko.luckperms.api.Track {
}
}
+ @Nonnull
+ @Override
+ public PromotionResult promote(@Nonnull User user, @Nonnull ContextSet contextSet) {
+ return this.handle.promote(ApiUser.cast(user), contextSet, Predicates.alwaysTrue(), null);
+ }
+
+ @Nonnull
+ @Override
+ public DemotionResult demote(@Nonnull User user, @Nonnull ContextSet contextSet) {
+ return this.handle.demote(ApiUser.cast(user), contextSet, Predicates.alwaysTrue(), null);
+ }
+
@Override
public DataMutateResult appendGroup(@Nonnull Group group) {
Objects.requireNonNull(group, "group");
diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/user/UserDemote.java b/common/src/main/java/me/lucko/luckperms/common/commands/user/UserDemote.java
index f4c00b31..159ca876 100644
--- a/common/src/main/java/me/lucko/luckperms/common/commands/user/UserDemote.java
+++ b/common/src/main/java/me/lucko/luckperms/common/commands/user/UserDemote.java
@@ -25,9 +25,7 @@
package me.lucko.luckperms.common.commands.user;
-import com.google.common.collect.Iterables;
-
-import me.lucko.luckperms.api.Node;
+import me.lucko.luckperms.api.DemotionResult;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.common.actionlog.ExtendedLogEntry;
import me.lucko.luckperms.common.command.CommandResult;
@@ -42,18 +40,14 @@ import me.lucko.luckperms.common.command.utils.TabCompletions;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.command.CommandSpec;
import me.lucko.luckperms.common.locale.message.Message;
-import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.model.User;
-import me.lucko.luckperms.common.node.NodeFactory;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.storage.DataConstraints;
import me.lucko.luckperms.common.utils.Predicates;
import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
public class UserDemote extends SubCommand {
public UserDemote(LocaleManager locale) {
@@ -91,78 +85,52 @@ public class UserDemote extends SubCommand {
return CommandResult.NO_PERMISSION;
}
- // Load applicable groups
- Set nodes = user.getEnduringNodes().values().stream()
- .filter(Node::isGroupNode)
- .filter(Node::getValuePrimitive)
- .filter(node -> node.getFullContexts().makeImmutable().equals(context.makeImmutable()))
- .collect(Collectors.toSet());
+ DemotionResult result = track.demote(user, context, s -> !ArgumentPermissions.checkArguments(plugin, sender, getPermission().get(), track.getName(), s), sender);
+ switch (result.getStatus()) {
+ case NOT_ON_TRACK:
+ Message.USER_TRACK_ERROR_NOT_CONTAIN_GROUP.send(sender, user.getFriendlyName(), track.getName());
+ return CommandResult.FAILURE;
+ case AMBIGUOUS_CALL:
+ Message.TRACK_AMBIGUOUS_CALL.send(sender, user.getFriendlyName());
+ return CommandResult.FAILURE;
+ case UNDEFINED_FAILURE:
+ Message.COMMAND_NO_PERMISSION.send(sender);
+ return CommandResult.NO_PERMISSION;
+ case MALFORMED_TRACK:
+ Message.USER_DEMOTE_ERROR_MALFORMED.send(sender, result.getGroupTo().get());
+ return CommandResult.LOADING_ERROR;
- nodes.removeIf(g -> !track.containsGroup(g.getGroupName()));
+ case REMOVED_FROM_FIRST_GROUP: {
+ Message.USER_DEMOTE_ENDOFTRACK.send(sender, track.getName(), user.getFriendlyName(), result.getGroupFrom().get());
- if (nodes.isEmpty()) {
- Message.USER_TRACK_ERROR_NOT_CONTAIN_GROUP.send(sender, user.getFriendlyName(), track.getName());
- return CommandResult.FAILURE;
+ ExtendedLogEntry.build().actor(sender).acted(user)
+ .action("demote", track.getName(), context)
+ .build().submit(plugin, sender);
+
+ StorageAssistant.save(user, sender, plugin);
+ return CommandResult.SUCCESS;
+ }
+
+ case SUCCESS: {
+ String groupFrom = result.getGroupFrom().get();
+ String groupTo = result.getGroupTo().get();
+
+ Message.USER_DEMOTE_SUCCESS.send(sender, user.getFriendlyName(), track.getName(), groupFrom, groupTo, MessageUtils.contextSetToString(context));
+ if (!silent) {
+ Message.EMPTY.send(sender, MessageUtils.listToArrowSep(track.getGroups(), groupTo, groupFrom, true));
+ }
+
+ ExtendedLogEntry.build().actor(sender).acted(user)
+ .action("demote", track.getName(), context)
+ .build().submit(plugin, sender);
+
+ StorageAssistant.save(user, sender, plugin);
+ return CommandResult.SUCCESS;
+ }
+
+ default:
+ throw new AssertionError("Unknown status: " + result.getStatus());
}
-
- if (nodes.size() != 1) {
- Message.TRACK_AMBIGUOUS_CALL.send(sender, user.getFriendlyName());
- return CommandResult.FAILURE;
- }
-
- final Node oldNode = Iterables.getFirst(nodes, null);
- final String old = oldNode.getGroupName();
- final String previous;
- try {
- previous = track.getPrevious(old);
- } catch (IllegalArgumentException e) {
- Message.TRACK_DOES_NOT_CONTAIN.send(sender, track.getName(), old);
- return CommandResult.STATE_ERROR;
- }
-
- if (ArgumentPermissions.checkArguments(plugin, sender, getPermission().get(), track.getName(), oldNode.getGroupName())) {
- Message.COMMAND_NO_PERMISSION.send(sender);
- return CommandResult.NO_PERMISSION;
- }
-
- if (previous == null) {
- user.unsetPermission(oldNode);
- Message.USER_DEMOTE_ENDOFTRACK.send(sender, track.getName(), user.getFriendlyName(), old);
-
- ExtendedLogEntry.build().actor(sender).acted(user)
- .action("demote", track.getName(), context)
- .build().submit(plugin, sender);
-
- StorageAssistant.save(user, sender, plugin);
- plugin.getEventFactory().handleUserDemote(user, track, old, null, sender);
- return CommandResult.SUCCESS;
- }
-
- Group previousGroup = plugin.getStorage().loadGroup(previous).join().orElse(null);
- if (previousGroup == null) {
- Message.USER_DEMOTE_ERROR_MALFORMED.send(sender, previous);
- return CommandResult.LOADING_ERROR;
- }
-
- user.unsetPermission(oldNode);
- user.setPermission(NodeFactory.buildGroupNode(previousGroup.getName()).withExtraContext(context).build());
-
- if (context.isEmpty() && user.getPrimaryGroup().getStoredValue().orElse(NodeFactory.DEFAULT_GROUP_NAME).equalsIgnoreCase(old)) {
- user.getPrimaryGroup().setStoredValue(previousGroup.getName());
- }
-
- Message.USER_DEMOTE_SUCCESS.send(sender, user.getFriendlyName(), track.getName(), old, previousGroup.getFriendlyName(), MessageUtils.contextSetToString(context));
- if (!silent) {
- Message.EMPTY.send(sender, MessageUtils.listToArrowSep(track.getGroups(), previousGroup.getName(), old, true));
- }
-
- ExtendedLogEntry.build().actor(sender).acted(user)
- .action("demote", track.getName(), context)
- .build().submit(plugin, sender);
-
- StorageAssistant.save(user, sender, plugin);
- plugin.getEventFactory().handleUserDemote(user, track, old, previousGroup.getName(), sender);
- return CommandResult.SUCCESS;
}
@Override
diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/user/UserPromote.java b/common/src/main/java/me/lucko/luckperms/common/commands/user/UserPromote.java
index 541c9e77..76a86297 100644
--- a/common/src/main/java/me/lucko/luckperms/common/commands/user/UserPromote.java
+++ b/common/src/main/java/me/lucko/luckperms/common/commands/user/UserPromote.java
@@ -25,7 +25,7 @@
package me.lucko.luckperms.common.commands.user;
-import me.lucko.luckperms.api.Node;
+import me.lucko.luckperms.api.PromotionResult;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.common.actionlog.ExtendedLogEntry;
import me.lucko.luckperms.common.command.CommandResult;
@@ -40,18 +40,14 @@ import me.lucko.luckperms.common.command.utils.TabCompletions;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.command.CommandSpec;
import me.lucko.luckperms.common.locale.message.Message;
-import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.model.User;
-import me.lucko.luckperms.common.node.NodeFactory;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.storage.DataConstraints;
import me.lucko.luckperms.common.utils.Predicates;
import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
public class UserPromote extends SubCommand {
public UserPromote(LocaleManager locale) {
@@ -89,97 +85,52 @@ public class UserPromote extends SubCommand {
return CommandResult.NO_PERMISSION;
}
- // Load applicable groups
- Set nodes = user.getEnduringNodes().values().stream()
- .filter(Node::isGroupNode)
- .filter(Node::getValuePrimitive)
- .filter(node -> node.getFullContexts().makeImmutable().equals(context.makeImmutable()))
- .collect(Collectors.toSet());
-
- nodes.removeIf(g -> !track.containsGroup(g.getGroupName()));
-
- if (nodes.isEmpty()) {
- String first = track.getGroups().get(0);
-
- Group nextGroup = plugin.getGroupManager().getIfLoaded(first);
- if (nextGroup == null) {
- Message.USER_PROMOTE_ERROR_MALFORMED.send(sender, first);
+ PromotionResult result = track.promote(user, context, s -> !ArgumentPermissions.checkArguments(plugin, sender, getPermission().get(), track.getName(), s), sender);
+ switch (result.getStatus()) {
+ case MALFORMED_TRACK:
+ Message.USER_PROMOTE_ERROR_MALFORMED.send(sender, result.getGroupTo().get());
return CommandResult.LOADING_ERROR;
- }
-
- if (ArgumentPermissions.checkArguments(plugin, sender, getPermission().get(), track.getName(), nextGroup.getName())) {
+ case UNDEFINED_FAILURE:
Message.COMMAND_NO_PERMISSION.send(sender);
return CommandResult.NO_PERMISSION;
+ case AMBIGUOUS_CALL:
+ Message.TRACK_AMBIGUOUS_CALL.send(sender, user.getFriendlyName());
+ return CommandResult.FAILURE;
+ case END_OF_TRACK:
+ Message.USER_PROMOTE_ERROR_ENDOFTRACK.send(sender, track.getName(), user.getFriendlyName());
+ return CommandResult.STATE_ERROR;
+
+ case ADDED_TO_FIRST_GROUP: {
+ Message.USER_TRACK_ADDED_TO_FIRST.send(sender, user.getFriendlyName(), result.getGroupTo().get(), MessageUtils.contextSetToString(context));
+
+ ExtendedLogEntry.build().actor(sender).acted(user)
+ .action("promote", track.getName(), context)
+ .build().submit(plugin, sender);
+
+ StorageAssistant.save(user, sender, plugin);
+ return CommandResult.SUCCESS;
}
- user.setPermission(NodeFactory.buildGroupNode(nextGroup.getId()).withExtraContext(context).build());
+ case SUCCESS: {
+ String groupFrom = result.getGroupFrom().get();
+ String groupTo = result.getGroupTo().get();
- Message.USER_TRACK_ADDED_TO_FIRST.send(sender, user.getFriendlyName(), nextGroup.getFriendlyName(), MessageUtils.contextSetToString(context));
+ Message.USER_PROMOTE_SUCCESS.send(sender, user.getFriendlyName(), track.getName(), groupFrom, groupTo, MessageUtils.contextSetToString(context));
+ if (!silent) {
+ Message.EMPTY.send(sender, MessageUtils.listToArrowSep(track.getGroups(), groupFrom, groupTo, false));
+ }
- ExtendedLogEntry.build().actor(sender).acted(user)
- .action("promote", track.getName(), context)
- .build().submit(plugin, sender);
+ ExtendedLogEntry.build().actor(sender).acted(user)
+ .action("promote", track.getName(), context)
+ .build().submit(plugin, sender);
- StorageAssistant.save(user, sender, plugin);
- plugin.getEventFactory().handleUserPromote(user, track, null, first, sender);
- return CommandResult.SUCCESS;
+ StorageAssistant.save(user, sender, plugin);
+ return CommandResult.SUCCESS;
+ }
+
+ default:
+ throw new AssertionError("Unknown status: " + result.getStatus());
}
-
- if (nodes.size() != 1) {
- Message.TRACK_AMBIGUOUS_CALL.send(sender, user.getFriendlyName());
- return CommandResult.FAILURE;
- }
-
- final Node oldNode = nodes.stream().findAny().get();
- final String old = oldNode.getGroupName();
- final String next;
- try {
- next = track.getNext(old);
- } catch (IllegalArgumentException e) {
- Message.TRACK_DOES_NOT_CONTAIN.send(sender, track.getName(), old);
- return CommandResult.STATE_ERROR;
- }
-
- if (next == null) {
- Message.USER_PROMOTE_ERROR_ENDOFTRACK.send(sender, track.getName(), user.getFriendlyName());
- return CommandResult.STATE_ERROR;
- }
-
- if (!plugin.getStorage().loadGroup(next).join().isPresent()) {
- Message.USER_PROMOTE_ERROR_MALFORMED.send(sender, next);
- return CommandResult.STATE_ERROR;
- }
-
- Group nextGroup = plugin.getGroupManager().getIfLoaded(next);
- if (nextGroup == null) {
- Message.USER_PROMOTE_ERROR_MALFORMED.send(sender, next);
- return CommandResult.LOADING_ERROR;
- }
-
- if (ArgumentPermissions.checkArguments(plugin, sender, getPermission().get(), track.getName(), nextGroup.getName())) {
- Message.COMMAND_NO_PERMISSION.send(sender);
- return CommandResult.NO_PERMISSION;
- }
-
- user.unsetPermission(oldNode);
- user.setPermission(NodeFactory.buildGroupNode(nextGroup.getName()).withExtraContext(context).build());
-
- if (context.isEmpty() && user.getPrimaryGroup().getStoredValue().orElse(NodeFactory.DEFAULT_GROUP_NAME).equalsIgnoreCase(old)) {
- user.getPrimaryGroup().setStoredValue(nextGroup.getName());
- }
-
- Message.USER_PROMOTE_SUCCESS.send(sender, user.getFriendlyName(), track.getName(), old, nextGroup.getFriendlyName(), MessageUtils.contextSetToString(context));
- if (!silent) {
- Message.EMPTY.send(sender, MessageUtils.listToArrowSep(track.getGroups(), old, nextGroup.getName(), false));
- }
-
- ExtendedLogEntry.build().actor(sender).acted(user)
- .action("promote", track.getName(), context)
- .build().submit(plugin, sender);
-
- StorageAssistant.save(user, sender, plugin);
- plugin.getEventFactory().handleUserPromote(user, track, old, nextGroup.getName(), sender);
- return CommandResult.SUCCESS;
}
@Override
diff --git a/common/src/main/java/me/lucko/luckperms/common/event/EventFactory.java b/common/src/main/java/me/lucko/luckperms/common/event/EventFactory.java
index a76cb481..539dd95b 100644
--- a/common/src/main/java/me/lucko/luckperms/common/event/EventFactory.java
+++ b/common/src/main/java/me/lucko/luckperms/common/event/EventFactory.java
@@ -37,6 +37,7 @@ import me.lucko.luckperms.api.event.cause.CreationCause;
import me.lucko.luckperms.api.event.cause.DeletionCause;
import me.lucko.luckperms.api.event.log.LogBroadcastEvent;
import me.lucko.luckperms.api.event.log.LogNotifyEvent;
+import me.lucko.luckperms.api.event.source.Source;
import me.lucko.luckperms.common.api.delegates.model.ApiPermissionHolder;
import me.lucko.luckperms.common.api.delegates.model.ApiUser;
import me.lucko.luckperms.common.event.impl.EventConfigReload;
@@ -73,6 +74,7 @@ import me.lucko.luckperms.common.event.impl.EventUserLoginProcess;
import me.lucko.luckperms.common.event.impl.EventUserPromote;
import me.lucko.luckperms.common.event.model.EntitySourceImpl;
import me.lucko.luckperms.common.event.model.SenderEntity;
+import me.lucko.luckperms.common.event.model.UnknownSource;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.model.Track;
@@ -84,6 +86,8 @@ import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
+import javax.annotation.Nullable;
+
public final class EventFactory {
private final AbstractEventBus eventBus;
@@ -265,13 +269,15 @@ public final class EventFactory {
fireEvent(event);
}
- public void handleUserDemote(User user, Track track, String from, String to, Sender source) {
- EventUserDemote event = new EventUserDemote(track.getApiDelegate(), new ApiUser(user), from, to, new EntitySourceImpl(new SenderEntity(source)));
+ public void handleUserDemote(User user, Track track, String from, String to, @Nullable Sender source) {
+ Source s = source == null ? UnknownSource.INSTANCE : new EntitySourceImpl(new SenderEntity(source));
+ EventUserDemote event = new EventUserDemote(track.getApiDelegate(), new ApiUser(user), from, to, s);
fireEventAsync(event);
}
- public void handleUserPromote(User user, Track track, String from, String to, Sender source) {
- EventUserPromote event = new EventUserPromote(track.getApiDelegate(), new ApiUser(user), from, to, new EntitySourceImpl(new SenderEntity(source)));
+ public void handleUserPromote(User user, Track track, String from, String to, @Nullable Sender source) {
+ Source s = source == null ? UnknownSource.INSTANCE : new EntitySourceImpl(new SenderEntity(source));
+ EventUserPromote event = new EventUserPromote(track.getApiDelegate(), new ApiUser(user), from, to, s);
fireEventAsync(event);
}
diff --git a/common/src/main/java/me/lucko/luckperms/common/event/model/UnknownSource.java b/common/src/main/java/me/lucko/luckperms/common/event/model/UnknownSource.java
index d5440f7e..e6d84bfa 100644
--- a/common/src/main/java/me/lucko/luckperms/common/event/model/UnknownSource.java
+++ b/common/src/main/java/me/lucko/luckperms/common/event/model/UnknownSource.java
@@ -30,7 +30,7 @@ import me.lucko.luckperms.api.event.source.Source;
import javax.annotation.Nonnull;
public final class UnknownSource implements Source {
- private static final Source INSTANCE = new UnknownSource();
+ public static final Source INSTANCE = new UnknownSource();
private UnknownSource() {
diff --git a/common/src/main/java/me/lucko/luckperms/common/model/Track.java b/common/src/main/java/me/lucko/luckperms/common/model/Track.java
index 395cc5f2..3c93ab71 100644
--- a/common/src/main/java/me/lucko/luckperms/common/model/Track.java
+++ b/common/src/main/java/me/lucko/luckperms/common/model/Track.java
@@ -28,15 +28,27 @@ package me.lucko.luckperms.common.model;
import com.google.common.collect.ImmutableList;
import me.lucko.luckperms.api.DataMutateResult;
+import me.lucko.luckperms.api.DemotionResult;
+import me.lucko.luckperms.api.Node;
+import me.lucko.luckperms.api.PromotionResult;
+import me.lucko.luckperms.api.context.ContextSet;
+import me.lucko.luckperms.common.api.DemotionResults;
+import me.lucko.luckperms.common.api.PromotionResults;
import me.lucko.luckperms.common.api.delegates.model.ApiTrack;
+import me.lucko.luckperms.common.node.NodeFactory;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.references.Identifiable;
+import me.lucko.luckperms.common.sender.Sender;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nullable;
public final class Track implements Identifiable {
@@ -259,6 +271,119 @@ public final class Track implements Identifiable {
this.plugin.getEventFactory().handleTrackClear(this, before);
}
+ public PromotionResult promote(User user, ContextSet context, Predicate nextGroupPermissionChecker, @Nullable Sender sender) {
+ if (getSize() <= 1) {
+ throw new IllegalStateException("Track contains one or fewer groups, unable to promote");
+ }
+
+ // find all groups that are inherited by the user in the exact contexts given and applicable to this track
+ List nodes = user.getEnduringNodes().get(context.makeImmutable()).stream()
+ .filter(Node::isGroupNode)
+ .filter(Node::getValuePrimitive)
+ .filter(node -> containsGroup(node.getGroupName()))
+ .distinct()
+ .collect(Collectors.toList());
+
+ if (nodes.isEmpty()) {
+ String first = getGroups().get(0);
+
+ Group nextGroup = this.plugin.getGroupManager().getIfLoaded(first);
+ if (nextGroup == null) {
+ return PromotionResults.malformedTrack(first);
+ }
+
+ if (!nextGroupPermissionChecker.test(nextGroup.getName())) {
+ return PromotionResults.undefinedFailure();
+ }
+
+ user.setPermission(NodeFactory.buildGroupNode(nextGroup.getId()).withExtraContext(context).build());
+ this.plugin.getEventFactory().handleUserPromote(user, this, null, first, sender);
+ return PromotionResults.addedToFirst(first);
+ }
+
+ if (nodes.size() != 1) {
+ return PromotionResults.ambiguousCall();
+ }
+
+ Node oldNode = nodes.get(0);
+ String old = oldNode.getGroupName();
+ String next = getNext(old);
+
+ if (next == null) {
+ return PromotionResults.endOfTrack();
+ }
+
+ Group nextGroup = this.plugin.getGroupManager().getIfLoaded(next);
+ if (nextGroup == null) {
+ return PromotionResults.malformedTrack(next);
+ }
+
+ if (!nextGroupPermissionChecker.test(nextGroup.getName())) {
+ return PromotionResults.undefinedFailure();
+ }
+
+ user.unsetPermission(oldNode);
+ user.setPermission(NodeFactory.buildGroupNode(nextGroup.getName()).withExtraContext(context).build());
+
+ if (context.isEmpty() && user.getPrimaryGroup().getStoredValue().orElse(NodeFactory.DEFAULT_GROUP_NAME).equalsIgnoreCase(old)) {
+ user.getPrimaryGroup().setStoredValue(nextGroup.getName());
+ }
+
+ this.plugin.getEventFactory().handleUserPromote(user, this, old, nextGroup.getName(), sender);
+ return PromotionResults.success(old, nextGroup.getName());
+ }
+
+ public DemotionResult demote(User user, ContextSet context, Predicate previousGroupPermissionChecker, @Nullable Sender sender) {
+ if (getSize() <= 1) {
+ throw new IllegalStateException("Track contains one or fewer groups, unable to demote");
+ }
+
+ // find all groups that are inherited by the user in the exact contexts given and applicable to this track
+ List nodes = user.getEnduringNodes().get(context.makeImmutable()).stream()
+ .filter(Node::isGroupNode)
+ .filter(Node::getValuePrimitive)
+ .filter(node -> containsGroup(node.getGroupName()))
+ .distinct()
+ .collect(Collectors.toList());
+
+ if (nodes.isEmpty()) {
+ return DemotionResults.notOnTrack();
+ }
+
+ if (nodes.size() != 1) {
+ return DemotionResults.ambiguousCall();
+ }
+
+ Node oldNode = nodes.get(0);
+ String old = oldNode.getGroupName();
+ String previous = getPrevious(old);
+
+ if (!previousGroupPermissionChecker.test(oldNode.getGroupName())) {
+ return DemotionResults.undefinedFailure();
+ }
+
+ if (previous == null) {
+ user.unsetPermission(oldNode);
+ this.plugin.getEventFactory().handleUserDemote(user, this, old, null, sender);
+ return DemotionResults.removedFromFirst(old);
+ }
+
+ Group previousGroup = this.plugin.getGroupManager().getIfLoaded(previous);
+ if (previousGroup == null) {
+ return DemotionResults.malformedTrack(previous);
+ }
+
+ user.unsetPermission(oldNode);
+ user.setPermission(NodeFactory.buildGroupNode(previousGroup.getName()).withExtraContext(context).build());
+
+ if (context.isEmpty() && user.getPrimaryGroup().getStoredValue().orElse(NodeFactory.DEFAULT_GROUP_NAME).equalsIgnoreCase(old)) {
+ user.getPrimaryGroup().setStoredValue(previousGroup.getName());
+ }
+
+ this.plugin.getEventFactory().handleUserDemote(user, this, old, previousGroup.getName(), sender);
+ return DemotionResults.success(old, previousGroup.getName());
+ }
+
@Override
public boolean equals(Object o) {
if (o == this) return true;
diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/PlayerSaveResult.java b/common/src/main/java/me/lucko/luckperms/common/storage/PlayerSaveResult.java
index 7d883fcd..deee2e97 100644
--- a/common/src/main/java/me/lucko/luckperms/common/storage/PlayerSaveResult.java
+++ b/common/src/main/java/me/lucko/luckperms/common/storage/PlayerSaveResult.java
@@ -27,7 +27,6 @@ package me.lucko.luckperms.common.storage;
import com.google.common.collect.ImmutableSet;
-import java.util.Arrays;
import java.util.EnumSet;
import java.util.Objects;
import java.util.Set;
@@ -52,7 +51,7 @@ public final class PlayerSaveResult {
}
public static PlayerSaveResult usernameUpdated(String oldUsername) {
- return new PlayerSaveResult(oldUsername, null, Status.USERNAME_UPDATED);
+ return new PlayerSaveResult(EnumSet.of(Status.USERNAME_UPDATED), oldUsername, null);
}
public static PlayerSaveResult determineBaseResult(String username, String oldUsername) {
@@ -77,12 +76,8 @@ public final class PlayerSaveResult {
this.otherUuids = otherUuids;
}
- private PlayerSaveResult(@Nullable String oldUsername, @Nullable Set otherUuids, Status... status) {
- this(EnumSet.copyOf(Arrays.asList(status)), oldUsername, otherUuids);
- }
-
- private PlayerSaveResult(Status... status) {
- this(null, null, status);
+ private PlayerSaveResult(Status status) {
+ this(EnumSet.of(status), null, null);
}
/**