diff --git a/bukkit/src/main/resources/luckperms.commodore b/bukkit/src/main/resources/luckperms.commodore index 146b96fc5..a6a202ee4 100644 --- a/bukkit/src/main/resources/luckperms.commodore +++ b/bukkit/src/main/resources/luckperms.commodore @@ -105,6 +105,9 @@ luckperms { unsettemp { node brigadier:string quotable_phrase { context brigadier:string greedy_phrase; + duration brigadier:string single_word { + context brigadier:string greedy_phrase; + } } } check { @@ -156,6 +159,9 @@ luckperms { removetemp { group brigadier:string single_word { context brigadier:string greedy_phrase; + duration brigadier:string single_word { + context brigadier:string greedy_phrase; + } } } clear { @@ -347,6 +353,9 @@ luckperms { unsettemp { node brigadier:string quotable_phrase { context brigadier:string greedy_phrase; + duration brigadier:string single_word { + context brigadier:string greedy_phrase; + } } } check { @@ -398,6 +407,9 @@ luckperms { removetemp { group brigadier:string single_word { context brigadier:string greedy_phrase; + duration brigadier:string single_word { + context brigadier:string greedy_phrase; + } } } clear { diff --git a/common/src/main/java/me/lucko/luckperms/common/command/utils/ArgumentParser.java b/common/src/main/java/me/lucko/luckperms/common/command/utils/ArgumentParser.java index 091dd787e..2a8d49e8e 100644 --- a/common/src/main/java/me/lucko/luckperms/common/command/utils/ArgumentParser.java +++ b/common/src/main/java/me/lucko/luckperms/common/command/utils/ArgumentParser.java @@ -108,6 +108,33 @@ public class ArgumentParser { return true; } + public static Duration parseDurationOrElse(int index, List args, Duration other) throws ArgumentException { + if (index < 0 || index >= args.size()) { + return other; + } + + String input = args.get(index); + Duration duration; + + try { + long number = Long.parseLong(input); + Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS); + duration = Duration.between(now, Instant.ofEpochSecond(number)); + } catch (NumberFormatException e) { + try { + duration = DurationParser.parseDuration(input); + } catch (IllegalArgumentException e1) { + return other; + } + } + + if (duration.isNegative()) { + throw new PastDateException(); + } + + return duration; + } + public static Duration parseDuration(int index, List args) throws ArgumentException { String input = args.get(index); Duration duration; diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/generic/parent/ParentRemoveTemp.java b/common/src/main/java/me/lucko/luckperms/common/commands/generic/parent/ParentRemoveTemp.java index 7a7cfbd4c..c9f46c58c 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/generic/parent/ParentRemoveTemp.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/generic/parent/ParentRemoveTemp.java @@ -43,12 +43,15 @@ import me.lucko.luckperms.common.model.PermissionHolder; import me.lucko.luckperms.common.node.types.Inheritance; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.sender.Sender; +import me.lucko.luckperms.common.util.DurationFormatter; import me.lucko.luckperms.common.util.Predicates; import net.luckperms.api.context.MutableContextSet; import net.luckperms.api.model.data.DataMutateResult; import net.luckperms.api.model.data.DataType; +import net.luckperms.api.node.Node; +import java.time.Duration; import java.util.List; public class ParentRemoveTemp extends GenericChildCommand { @@ -64,7 +67,8 @@ public class ParentRemoveTemp extends GenericChildCommand { } String groupName = ArgumentParser.parseNameWithSpace(0, args); - MutableContextSet context = ArgumentParser.parseContext(1, args, plugin); + Duration duration = ArgumentParser.parseDurationOrElse(1, args, null); + MutableContextSet context = ArgumentParser.parseContext(duration == null ? 1 : 2, args, plugin); if (ArgumentPermissions.checkContext(plugin, sender, permission, context) || ArgumentPermissions.checkGroup(plugin, sender, holder, context) || @@ -74,14 +78,29 @@ public class ParentRemoveTemp extends GenericChildCommand { return CommandResult.NO_PERMISSION; } - DataMutateResult result = holder.unsetNode(DataType.NORMAL, Inheritance.builder(groupName).expiry(10L).withContext(context).build()); + DataMutateResult.WithMergedNode result = holder.unsetNode(DataType.NORMAL, Inheritance.builder(groupName).expiry(10L).withContext(context).build(), duration); + if (result.getResult().wasSuccessful()) { + Node mergedNode = result.getMergedNode(); + //noinspection ConstantConditions + if (mergedNode != null) { + Message.UNSET_TEMP_INHERIT_SUBTRACT_SUCCESS.send(sender, + holder.getFormattedDisplayName(), + groupName, + DurationFormatter.LONG.format(mergedNode.getExpiryDuration()), + MessageUtils.contextSetToString(plugin.getLocaleManager(), context), + DurationFormatter.LONG.format(duration) + ); - if (result.wasSuccessful()) { - Message.UNSET_TEMP_INHERIT_SUCCESS.send(sender, holder.getFormattedDisplayName(), groupName, MessageUtils.contextSetToString(plugin.getLocaleManager(), context)); + LoggedAction.build().source(sender).target(holder) + .description("parent", "removetemp", groupName, duration, context) + .build().submit(plugin, sender); + } else { + Message.UNSET_TEMP_INHERIT_SUCCESS.send(sender, holder.getFormattedDisplayName(), groupName, MessageUtils.contextSetToString(plugin.getLocaleManager(), context)); - LoggedAction.build().source(sender).target(holder) - .description("parent", "removetemp", groupName, context) - .build().submit(plugin, sender); + LoggedAction.build().source(sender).target(holder) + .description("parent", "removetemp", groupName, context) + .build().submit(plugin, sender); + } StorageAssistant.save(holder, sender, plugin); return CommandResult.SUCCESS; diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/generic/permission/PermissionUnsetTemp.java b/common/src/main/java/me/lucko/luckperms/common/commands/generic/permission/PermissionUnsetTemp.java index 53179d4b0..965e4d80f 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/generic/permission/PermissionUnsetTemp.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/generic/permission/PermissionUnsetTemp.java @@ -43,6 +43,7 @@ import me.lucko.luckperms.common.model.PermissionHolder; import me.lucko.luckperms.common.node.factory.NodeBuilders; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.sender.Sender; +import me.lucko.luckperms.common.util.DurationFormatter; import me.lucko.luckperms.common.util.Predicates; import net.luckperms.api.context.MutableContextSet; @@ -51,6 +52,7 @@ import net.luckperms.api.model.data.DataType; import net.luckperms.api.node.Node; import net.luckperms.api.node.types.InheritanceNode; +import java.time.Duration; import java.util.List; public class PermissionUnsetTemp extends GenericChildCommand { @@ -66,7 +68,8 @@ public class PermissionUnsetTemp extends GenericChildCommand { } String node = ArgumentParser.parseString(0, args); - MutableContextSet context = ArgumentParser.parseContext(1, args, plugin); + Duration duration = ArgumentParser.parseDurationOrElse(1, args, null); + MutableContextSet context = ArgumentParser.parseContext(duration == null ? 1 : 2, args, plugin); if (ArgumentPermissions.checkContext(plugin, sender, permission, context) || ArgumentPermissions.checkGroup(plugin, sender, holder, context) || @@ -84,14 +87,30 @@ public class PermissionUnsetTemp extends GenericChildCommand { } } - DataMutateResult result = holder.unsetNode(DataType.NORMAL, builtNode); + DataMutateResult.WithMergedNode result = holder.unsetNode(DataType.NORMAL, builtNode, duration); + if (result.getResult().wasSuccessful()) { + Node mergedNode = result.getMergedNode(); + //noinspection ConstantConditions + if (mergedNode != null) { + Message.UNSET_TEMP_PERMISSION_SUBTRACT_SUCCESS.send(sender, + mergedNode.getKey(), + mergedNode.getValue(), + holder.getFormattedDisplayName(), + DurationFormatter.LONG.format(mergedNode.getExpiryDuration()), + MessageUtils.contextSetToString(plugin.getLocaleManager(), context), + DurationFormatter.LONG.format(duration) + ); - if (result.wasSuccessful()) { - Message.UNSET_TEMP_PERMISSION_SUCCESS.send(sender, node, holder.getFormattedDisplayName(), MessageUtils.contextSetToString(plugin.getLocaleManager(), context)); + LoggedAction.build().source(sender).target(holder) + .description("permission", "unsettemp", node, duration, context) + .build().submit(plugin, sender); + } else { + Message.UNSET_TEMP_PERMISSION_SUCCESS.send(sender, node, holder.getFormattedDisplayName(), MessageUtils.contextSetToString(plugin.getLocaleManager(), context)); - LoggedAction.build().source(sender).target(holder) - .description("permission", "unsettemp", node, context) - .build().submit(plugin, sender); + LoggedAction.build().source(sender).target(holder) + .description("permission", "unsettemp", node, context) + .build().submit(plugin, sender); + } StorageAssistant.save(holder, sender, plugin); return CommandResult.SUCCESS; diff --git a/common/src/main/java/me/lucko/luckperms/common/locale/command/CommandSpec.java b/common/src/main/java/me/lucko/luckperms/common/locale/command/CommandSpec.java index a024d0d56..69dee3007 100644 --- a/common/src/main/java/me/lucko/luckperms/common/locale/command/CommandSpec.java +++ b/common/src/main/java/me/lucko/luckperms/common/locale/command/CommandSpec.java @@ -240,6 +240,7 @@ public enum CommandSpec { PERMISSION_UNSETTEMP("Unsets a temporary permission for the object", Argument.list( Argument.create("node", true, "the permission node to unset"), + Argument.create("duration", false, "the duration to subtract"), Argument.create("context...", false, "the contexts to remove the permission in") ) ), @@ -303,6 +304,7 @@ public enum CommandSpec { PARENT_REMOVE_TEMP("Removes a previously set temporary inheritance rule", Argument.list( Argument.create("group", true, "the group to remove"), + Argument.create("duration", false, "the duration to subtract"), Argument.create("context...", false, "the contexts to remove the group in") ) ), diff --git a/common/src/main/java/me/lucko/luckperms/common/locale/message/Message.java b/common/src/main/java/me/lucko/luckperms/common/locale/message/Message.java index d9d41c328..96100bb1d 100644 --- a/common/src/main/java/me/lucko/luckperms/common/locale/message/Message.java +++ b/common/src/main/java/me/lucko/luckperms/common/locale/message/Message.java @@ -281,6 +281,7 @@ public enum Message { DOES_NOT_HAVE_PERMISSION("&b{}&a does not have &b{}&a set in context {}&a.", true), UNSET_TEMP_PERMISSION_SUCCESS("&aUnset temporary permission &b{}&a for &b{}&a in context {}&a.", true), + UNSET_TEMP_PERMISSION_SUBTRACT_SUCCESS("&aSet &b{}&a to &b{}&a for &b{}&a for a duration of &b{}&a in context {}&a, &b{}&a less than before.", true), DOES_NOT_HAVE_TEMP_PERMISSION("&b{}&a does not have &b{}&a set temporarily in context {}&a.", true), SET_INHERIT_SUCCESS("&b{}&a now inherits permissions from &b{}&a in context {}&a.", true), @@ -289,6 +290,7 @@ public enum Message { SET_TRACK_PARENT_SUCCESS("&b{}&a had their existing parent groups on track &b{}&a cleared, and now only inherits &b{}&a in context {}&a.", true), UNSET_INHERIT_SUCCESS("&b{}&a no longer inherits permissions from &b{}&a in context {}&a.", true), UNSET_TEMP_INHERIT_SUCCESS("&b{}&a no longer temporarily inherits permissions from &b{}&a in context {}&a.", true), + UNSET_TEMP_INHERIT_SUBTRACT_SUCCESS("&b{}&a will inherit permissions from &b{}&a for a duration of &b{}&a in context {}&a, &b{}&a less than before.", true), CLEAR_SUCCESS("&b{}&a's nodes were cleared in context {}&a. (&b{}&a nodes were removed.)", true), CLEAR_SUCCESS_SINGULAR("&b{}&a's nodes were cleared in context {}&a. (&b{}&a node was removed.)", true), diff --git a/common/src/main/java/me/lucko/luckperms/common/model/PermissionHolder.java b/common/src/main/java/me/lucko/luckperms/common/model/PermissionHolder.java index 3d4c1cabf..0f7e260c7 100644 --- a/common/src/main/java/me/lucko/luckperms/common/model/PermissionHolder.java +++ b/common/src/main/java/me/lucko/luckperms/common/model/PermissionHolder.java @@ -555,6 +555,39 @@ public abstract class PermissionHolder { return DataMutateResult.SUCCESS; } + public DataMutateResult.WithMergedNode unsetNode(DataType dataType, Node node, @Nullable Duration duration) { + if (node.getExpiry() != null && duration != null) { + Node otherMatch = getData(dataType).immutable().values().stream() + .filter(NodeEqualityPredicate.IGNORE_EXPIRY_TIME_AND_VALUE.equalTo(node)) + .findFirst().orElse(null); + + if (otherMatch != null && otherMatch.getExpiry() != null) { + NodeMap data = getData(dataType); + + Instant newExpiry = otherMatch.getExpiry().minus(duration); + + if (newExpiry.isAfter(Instant.now())) { + Node newNode = node.toBuilder().expiry(newExpiry).build(); + + // Remove the old Node & add the new one. + ImmutableCollection before = data.immutable().values(); + + data.replace(newNode, otherMatch); + invalidateCache(); + + ImmutableCollection after = data.immutable().values(); + this.plugin.getEventDispatcher().dispatchNodeRemove(otherMatch, this, dataType, before, after); + this.plugin.getEventDispatcher().dispatchNodeAdd(newNode, this, dataType, before, after); + + return new MergedNodeResult(DataMutateResult.SUCCESS, newNode); + } + } + } + + // Fallback to the normal handling. + return new MergedNodeResult(unsetNode(dataType, node), null); + } + public boolean removeIf(DataType dataType, @Nullable ContextSet contextSet, Predicate predicate, boolean giveDefault) { NodeMap data = getData(dataType); ImmutableCollection before = data.immutable().values();