From ed85ab1bfd1673e10a313179936ad211c313910f Mon Sep 17 00:00:00 2001 From: Luck Date: Wed, 20 May 2020 14:51:03 +0100 Subject: [PATCH] Change ContextSet 'satisfy' behaviour (#2300) --- .../api/context/ContextSatisfyMode.java | 63 +++++++++++++++++++ .../net/luckperms/api/context/ContextSet.java | 48 +++++++++++--- .../net/luckperms/api/query/QueryOptions.java | 22 ++++++- .../vault/LuckPermsVaultPermission.java | 7 +-- bukkit/src/main/resources/config.yml | 9 +++ bungee/src/main/resources/config.yml | 9 +++ .../luckperms/common/config/ConfigKeys.java | 12 ++++ .../contextset/AbstractContextSet.java | 31 +++++++++ .../contextset/ImmutableContextSetImpl.java | 47 +++++++------- .../contextset/MutableContextSetImpl.java | 50 ++++++++------- .../lucko/luckperms/common/model/NodeMap.java | 14 +++-- .../common/query/QueryOptionsImpl.java | 12 +++- nukkit/src/main/resources/config.yml | 9 +++ .../calculated/CalculatedSubjectData.java | 12 +++- sponge/src/main/resources/luckperms.conf | 9 +++ velocity/src/main/resources/config.yml | 9 +++ 16 files changed, 294 insertions(+), 69 deletions(-) create mode 100644 api/src/main/java/net/luckperms/api/context/ContextSatisfyMode.java diff --git a/api/src/main/java/net/luckperms/api/context/ContextSatisfyMode.java b/api/src/main/java/net/luckperms/api/context/ContextSatisfyMode.java new file mode 100644 index 000000000..80baa54ef --- /dev/null +++ b/api/src/main/java/net/luckperms/api/context/ContextSatisfyMode.java @@ -0,0 +1,63 @@ +/* + * 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 net.luckperms.api.context; + +import net.luckperms.api.query.OptionKey; + +/** + * Mode for determining whether a {@link ContextSet} satisfies another. + * + * @since 5.2 + * @see ContextSet#isSatisfiedBy(ContextSet, ContextSatisfyMode) + */ +public enum ContextSatisfyMode { + + /** + * Mode where a {@link ContextSet} A will be satisfied by another set B, if at least one of the + * key-value entries per key in A are also in B. + * + *

For example, given A = {server=survival, world=overworld, world=nether}, + * another set {@code X} will satisfy {@code A} if {@code X} contains + * server=survival AND (world=overworld OR world=nether).

+ */ + AT_LEAST_ONE_VALUE_PER_KEY, + + /** + * Mode where a {@link ContextSet} A will be satisfied by another set B, if all key-value + * entries in A are also in B. + * + *

For example, given A = {server=survival, world=overworld, world=nether}, + * another set {@code X} will satisfy {@code A} if {@code X} contains + * server=survival AND world=overworld AND world=nether.

+ */ + ALL_VALUES_PER_KEY; + + /** + * The {@link OptionKey} for {@link ContextSatisfyMode}. + */ + public static final OptionKey KEY = OptionKey.of("contextsatisfymode", ContextSatisfyMode.class); + +} diff --git a/api/src/main/java/net/luckperms/api/context/ContextSet.java b/api/src/main/java/net/luckperms/api/context/ContextSet.java index e1c2c6cd1..c49942353 100644 --- a/api/src/main/java/net/luckperms/api/context/ContextSet.java +++ b/api/src/main/java/net/luckperms/api/context/ContextSet.java @@ -197,17 +197,47 @@ public interface ContextSet extends Iterable { } /** - * Returns if this {@link ContextSet} is fully "satisfied" by another set. + * Returns if the {@link ContextSet} contains any of the given context pairings. * - *

For a context set to "satisfy" another, it must itself contain all of - * the context pairings in the other set.

- * - *

Mathematically, this method returns true if this set is a subset of the other.

- * - * @param other the other set to check - * @return true if all entries in this set are also in the other set + * @param key the key to look for + * @param values the values to look for + * @return true if the set contains any of the pairs + * @since 5.2 */ - boolean isSatisfiedBy(@NonNull ContextSet other); + default boolean containsAny(@NonNull String key, @NonNull Iterable values) { + Objects.requireNonNull(key, "key"); + Objects.requireNonNull(values, "values"); + + for (String value : values) { + if (contains(key, value)) { + return true; + } + } + return false; + } + + /** + * Returns if this {@link ContextSet} is "satisfied" by another set. + * + *

{@link ContextSatisfyMode#AT_LEAST_ONE_VALUE_PER_KEY} is the mode used by this method.

+ * + * @param other the other set + * @return true if this context set is satisfied by the other + */ + default boolean isSatisfiedBy(@NonNull ContextSet other) { + return isSatisfiedBy(other, ContextSatisfyMode.AT_LEAST_ONE_VALUE_PER_KEY); + } + + /** + * Returns if this {@link ContextSet} is "satisfied" by another set, according to the given + * {@code mode}. + * + * @param other the other set + * @param mode the mode to use + * @return true if this context set is satisfied by the other + * @since 5.2 + */ + boolean isSatisfiedBy(@NonNull ContextSet other, @NonNull ContextSatisfyMode mode); /** * Returns if the {@link ContextSet} is empty. diff --git a/api/src/main/java/net/luckperms/api/query/QueryOptions.java b/api/src/main/java/net/luckperms/api/query/QueryOptions.java index 0f103ff0f..e3aa59fcc 100644 --- a/api/src/main/java/net/luckperms/api/query/QueryOptions.java +++ b/api/src/main/java/net/luckperms/api/query/QueryOptions.java @@ -26,6 +26,7 @@ package net.luckperms.api.query; import net.luckperms.api.LuckPermsProvider; +import net.luckperms.api.context.ContextSatisfyMode; import net.luckperms.api.context.ContextSet; import net.luckperms.api.context.ImmutableContextSet; @@ -166,10 +167,29 @@ public interface QueryOptions { * Gets whether this {@link QueryOptions} satisfies the given required * {@link ContextSet context}. * + *

{@link ContextSatisfyMode#AT_LEAST_ONE_VALUE_PER_KEY} is used if this {@link QueryOptions} + * instance doesn't have the {@link ContextSatisfyMode#KEY option key} set.

+ * * @param contextSet the contexts * @return the result */ - boolean satisfies(@NonNull ContextSet contextSet); + default boolean satisfies(@NonNull ContextSet contextSet) { + return satisfies(contextSet, ContextSatisfyMode.AT_LEAST_ONE_VALUE_PER_KEY); + } + + /** + * Gets whether this {@link QueryOptions} satisfies the given required + * {@link ContextSet context}. + * + *

The {@code defaultContextSatisfyMode} parameter is used if this {@link QueryOptions} + * instance doesn't have the {@link ContextSatisfyMode#KEY option key} set.

+ * + * @param contextSet the contexts + * @param defaultContextSatisfyMode the default context satisfy mode to use + * @return the result + * @since 5.2 + */ + boolean satisfies(@NonNull ContextSet contextSet, @NonNull ContextSatisfyMode defaultContextSatisfyMode); /** * Converts this {@link QueryOptions} to a mutable builder. diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/LuckPermsVaultPermission.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/LuckPermsVaultPermission.java index 70cfdadb5..c2dc804f1 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/LuckPermsVaultPermission.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/LuckPermsVaultPermission.java @@ -46,11 +46,11 @@ import me.lucko.luckperms.common.util.Uuids; import me.lucko.luckperms.common.verbose.event.MetaCheckEvent; import me.lucko.luckperms.common.verbose.event.PermissionCheckEvent; -import net.luckperms.api.context.ContextSet; import net.luckperms.api.context.DefaultContextKeys; import net.luckperms.api.context.MutableContextSet; import net.luckperms.api.model.data.DataType; import net.luckperms.api.node.Node; +import net.luckperms.api.node.NodeType; import net.luckperms.api.query.Flag; import net.luckperms.api.query.QueryOptions; import net.milkbowl.vault.permission.Permission; @@ -251,10 +251,9 @@ public class LuckPermsVaultPermission extends AbstractVaultPermission { Objects.requireNonNull(uuid, "uuid"); PermissionHolder user = lookupUser(uuid); - ContextSet contexts = getQueryOptions(uuid, world).context(); + QueryOptions queryOptions = getQueryOptions(uuid, world); - return user.normalData().immutableInheritance().values().stream() - .filter(n -> n.getContexts().isSatisfiedBy(contexts)) + return user.getOwnNodes(NodeType.INHERITANCE, queryOptions).stream() .map(n -> { Group group = this.plugin.getGroupManager().getIfLoaded(n.getGroupName()); if (group != null) { diff --git a/bukkit/src/main/resources/config.yml b/bukkit/src/main/resources/config.yml index db590a40d..84bd18bb6 100644 --- a/bukkit/src/main/resources/config.yml +++ b/bukkit/src/main/resources/config.yml @@ -400,6 +400,15 @@ inheritance-traversal-algorithm: depth-first-pre-order # and when this setting is 'false':, the rules are just applied during each step of the traversal. post-traversal-inheritance-sort: false +# Defines the mode used to determine whether a set of contexts are satisfied. +# +# - Possible options: +# => at-least-one-value-per-key Set A will be satisfied by another set B, if at least one of the +# key-value entries per key in A are also in B. +# => all-values-per-key Set A will be satisfied by another set B, if all key-value +# entries in A are also in B. +context-satisfy-mode: at-least-one-value-per-key + # +----------------------------------------------------------------------------------------------+ # # | Permission resolution settings | # # +----------------------------------------------------------------------------------------------+ # diff --git a/bungee/src/main/resources/config.yml b/bungee/src/main/resources/config.yml index 1588c0fa4..760aa01b9 100644 --- a/bungee/src/main/resources/config.yml +++ b/bungee/src/main/resources/config.yml @@ -408,6 +408,15 @@ inheritance-traversal-algorithm: depth-first-pre-order # and when this setting is 'false':, the rules are just applied during each step of the traversal. post-traversal-inheritance-sort: false +# Defines the mode used to determine whether a set of contexts are satisfied. +# +# - Possible options: +# => at-least-one-value-per-key Set A will be satisfied by another set B, if at least one of the +# key-value entries per key in A are also in B. +# => all-values-per-key Set A will be satisfied by another set B, if all key-value +# entries in A are also in B. +context-satisfy-mode: at-least-one-value-per-key + # +----------------------------------------------------------------------------------------------+ # # | Permission resolution settings | # # +----------------------------------------------------------------------------------------------+ # diff --git a/common/src/main/java/me/lucko/luckperms/common/config/ConfigKeys.java b/common/src/main/java/me/lucko/luckperms/common/config/ConfigKeys.java index e572dc126..98021b866 100644 --- a/common/src/main/java/me/lucko/luckperms/common/config/ConfigKeys.java +++ b/common/src/main/java/me/lucko/luckperms/common/config/ConfigKeys.java @@ -41,6 +41,7 @@ import me.lucko.luckperms.common.storage.implementation.split.SplitStorageType; import me.lucko.luckperms.common.storage.misc.StorageCredentials; import me.lucko.luckperms.common.util.ImmutableCollectors; +import net.luckperms.api.context.ContextSatisfyMode; import net.luckperms.api.metastacking.DuplicateRemovalFunction; import net.luckperms.api.metastacking.MetaStackDefinition; import net.luckperms.api.model.data.TemporaryNodeMergeStrategy; @@ -114,6 +115,17 @@ public final class ConfigKeys { return new QueryOptionsBuilderImpl(QueryMode.CONTEXTUAL).flags(flags).build(); }); + /** + * The default contexts satisfy mode + */ + public static final ConfigKey CONTEXT_SATISFY_MODE = customKey(c -> { + String value = c.getString("context-satisfy-mode", "at-least-one-value-per-key"); + if (value.toLowerCase().equals("all-values-per-key")) { + return ContextSatisfyMode.ALL_VALUES_PER_KEY; + } + return ContextSatisfyMode.AT_LEAST_ONE_VALUE_PER_KEY; + }); + /** * # If the servers own UUID cache/lookup facility should be used when there is no record for a player in the LuckPerms cache. */ diff --git a/common/src/main/java/me/lucko/luckperms/common/context/contextset/AbstractContextSet.java b/common/src/main/java/me/lucko/luckperms/common/context/contextset/AbstractContextSet.java index 2a73bfb01..5d5de1bef 100644 --- a/common/src/main/java/me/lucko/luckperms/common/context/contextset/AbstractContextSet.java +++ b/common/src/main/java/me/lucko/luckperms/common/context/contextset/AbstractContextSet.java @@ -29,6 +29,7 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.SetMultimap; import net.luckperms.api.context.Context; +import net.luckperms.api.context.ContextSatisfyMode; import net.luckperms.api.context.ContextSet; import net.luckperms.api.context.DefaultContextKeys; @@ -60,6 +61,36 @@ public abstract class AbstractContextSet implements ContextSet { return backing().containsEntry(sanitizeKey(key), sanitizeValue(value)); } + @Override + public boolean isSatisfiedBy(@NonNull ContextSet other, @NonNull ContextSatisfyMode mode) { + if (this == other) { + return true; + } + + Objects.requireNonNull(other, "other"); + Objects.requireNonNull(mode, "mode"); + + // this is empty, it is always satisfied. + if (this.isEmpty()) { + return true; + } + + // if this set isn't empty, but the other one is, then it can't be satisfied by it. + if (other.isEmpty()) { + return false; + } + + // if mode is ALL_VALUES & this set has more entries than the other one, then it can't be satisfied by it. + if (mode == ContextSatisfyMode.ALL_VALUES_PER_KEY && this.size() > other.size()) { + return false; + } + + // return true if 'other' contains all of 'this', according to the mode. + return otherContainsAll(other, mode); + } + + protected abstract boolean otherContainsAll(ContextSet other, ContextSatisfyMode mode); + @Override public boolean isEmpty() { return backing().isEmpty(); diff --git a/common/src/main/java/me/lucko/luckperms/common/context/contextset/ImmutableContextSetImpl.java b/common/src/main/java/me/lucko/luckperms/common/context/contextset/ImmutableContextSetImpl.java index 0283d16f9..ad3f7e37d 100644 --- a/common/src/main/java/me/lucko/luckperms/common/context/contextset/ImmutableContextSetImpl.java +++ b/common/src/main/java/me/lucko/luckperms/common/context/contextset/ImmutableContextSetImpl.java @@ -34,12 +34,14 @@ import com.google.common.collect.Multimaps; import com.google.common.collect.SetMultimap; import net.luckperms.api.context.Context; +import net.luckperms.api.context.ContextSatisfyMode; import net.luckperms.api.context.ContextSet; import net.luckperms.api.context.ImmutableContextSet; import net.luckperms.api.context.MutableContextSet; import org.checkerframework.checker.nullness.qual.NonNull; +import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.Objects; @@ -133,30 +135,31 @@ public final class ImmutableContextSetImpl extends AbstractContextSet implements } @Override - public boolean isSatisfiedBy(@NonNull ContextSet other) { - if (this == other) { - return true; - } - - Objects.requireNonNull(other, "other"); - if (this.isEmpty()) { - // this is empty, so is therefore always satisfied. - return true; - } else if (other.isEmpty()) { - // this set isn't empty, but the other one is - return false; - } else if (this.size() > other.size()) { - // this set has more unique entries than the other set, so there's no way this can be satisfied. - return false; - } else { - // neither are empty, we need to compare the individual entries - Set> entries = this.map.entries(); - for (Map.Entry e : entries) { - if (!other.contains(e.getKey(), e.getValue())) { - return false; + protected boolean otherContainsAll(ContextSet other, ContextSatisfyMode mode) { + switch (mode) { + // Use other.contains + case ALL_VALUES_PER_KEY: { + Set> entries = this.map.entries(); + for (Map.Entry e : entries) { + if (!other.contains(e.getKey(), e.getValue())) { + return false; + } } + return true; } - return true; + + // Use other.containsAny + case AT_LEAST_ONE_VALUE_PER_KEY: { + Set>> entries = this.map.asMap().entrySet(); + for (Map.Entry> e : entries) { + if (!other.containsAny(e.getKey(), e.getValue())) { + return false; + } + } + return true; + } + default: + throw new IllegalArgumentException("Unknown mode: " + mode); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/context/contextset/MutableContextSetImpl.java b/common/src/main/java/me/lucko/luckperms/common/context/contextset/MutableContextSetImpl.java index bb304603f..1c307b59f 100644 --- a/common/src/main/java/me/lucko/luckperms/common/context/contextset/MutableContextSetImpl.java +++ b/common/src/main/java/me/lucko/luckperms/common/context/contextset/MutableContextSetImpl.java @@ -35,6 +35,7 @@ import com.google.common.collect.Multimaps; import com.google.common.collect.SetMultimap; import net.luckperms.api.context.Context; +import net.luckperms.api.context.ContextSatisfyMode; import net.luckperms.api.context.ContextSet; import net.luckperms.api.context.ImmutableContextSet; import net.luckperms.api.context.MutableContextSet; @@ -186,32 +187,35 @@ public final class MutableContextSetImpl extends AbstractContextSet implements M } @Override - public boolean isSatisfiedBy(@NonNull ContextSet other) { - if (this == other) { - return true; - } - - Objects.requireNonNull(other, "other"); - if (this.isEmpty()) { - // this is empty, so is therefore always satisfied. - return true; - } else if (other.isEmpty()) { - // this set isn't empty, but the other one is - return false; - } else if (this.size() > other.size()) { - // this set has more unique entries than the other set, so there's no way this can be satisfied. - return false; - } else { - // neither are empty, we need to compare the individual entries - Set> entries = this.map.entries(); - synchronized (this.map) { - for (Map.Entry e : entries) { - if (!other.contains(e.getKey(), e.getValue())) { - return false; + protected boolean otherContainsAll(ContextSet other, ContextSatisfyMode mode) { + switch (mode) { + // Use other.contains + case ALL_VALUES_PER_KEY: { + Set> entries = this.map.entries(); + synchronized (this.map) { + for (Map.Entry e : entries) { + if (!other.contains(e.getKey(), e.getValue())) { + return false; + } } } + return true; } - return true; + + // Use other.containsAny + case AT_LEAST_ONE_VALUE_PER_KEY: { + Set>> entries = this.map.asMap().entrySet(); + synchronized (this.map) { + for (Map.Entry> e : entries) { + if (!other.containsAny(e.getKey(), e.getValue())) { + return false; + } + } + } + return true; + } + default: + throw new IllegalArgumentException("Unknown mode: " + mode); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/model/NodeMap.java b/common/src/main/java/me/lucko/luckperms/common/model/NodeMap.java index affdabb6c..ee69bb7af 100644 --- a/common/src/main/java/me/lucko/luckperms/common/model/NodeMap.java +++ b/common/src/main/java/me/lucko/luckperms/common/model/NodeMap.java @@ -31,11 +31,13 @@ import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.Multimap; import me.lucko.luckperms.common.cache.Cache; +import me.lucko.luckperms.common.config.ConfigKeys; import me.lucko.luckperms.common.context.ContextSetComparator; import me.lucko.luckperms.common.node.comparator.NodeComparator; import me.lucko.luckperms.common.node.comparator.NodeWithContextComparator; import me.lucko.luckperms.common.query.QueryOptionsImpl; +import net.luckperms.api.context.ContextSatisfyMode; import net.luckperms.api.context.ContextSet; import net.luckperms.api.context.DefaultContextKeys; import net.luckperms.api.context.ImmutableContextSet; @@ -154,9 +156,13 @@ public final class NodeMap { !flagExcludeTest(Flag.APPLY_INHERITANCE_NODES_WITHOUT_WORLD_CONTEXT, DefaultContextKeys.WORLD_KEY, filter, contextSet); } + private ContextSatisfyMode defaultSatisfyMode() { + return this.holder.getPlugin().getConfiguration().get(ConfigKeys.CONTEXT_SATISFY_MODE); + } + public void forEach(QueryOptions filter, Consumer consumer) { for (Map.Entry> e : this.map.entrySet()) { - if (!filter.satisfies(e.getKey())) { + if (!filter.satisfies(e.getKey(), defaultSatisfyMode())) { continue; } @@ -176,7 +182,7 @@ public final class NodeMap { public void copyTo(Collection collection, QueryOptions filter) { for (Map.Entry> e : this.map.entrySet()) { - if (!filter.satisfies(e.getKey())) { + if (!filter.satisfies(e.getKey(), defaultSatisfyMode())) { continue; } @@ -196,7 +202,7 @@ public final class NodeMap { public void copyTo(Collection collection, NodeType type, QueryOptions filter) { for (Map.Entry> e : this.map.entrySet()) { - if (!filter.satisfies(e.getKey())) { + if (!filter.satisfies(e.getKey(), defaultSatisfyMode())) { continue; } @@ -224,7 +230,7 @@ public final class NodeMap { public void copyInheritanceNodesTo(Collection collection, QueryOptions filter) { for (Map.Entry> e : this.inheritanceMap.entrySet()) { - if (!filter.satisfies(e.getKey())) { + if (!filter.satisfies(e.getKey(), defaultSatisfyMode())) { continue; } diff --git a/common/src/main/java/me/lucko/luckperms/common/query/QueryOptionsImpl.java b/common/src/main/java/me/lucko/luckperms/common/query/QueryOptionsImpl.java index efaef3203..46f3ef6aa 100644 --- a/common/src/main/java/me/lucko/luckperms/common/query/QueryOptionsImpl.java +++ b/common/src/main/java/me/lucko/luckperms/common/query/QueryOptionsImpl.java @@ -30,6 +30,7 @@ import com.google.common.collect.ImmutableSet; import me.lucko.luckperms.common.context.contextset.ImmutableContextSetImpl; +import net.luckperms.api.context.ContextSatisfyMode; import net.luckperms.api.context.ContextSet; import net.luckperms.api.context.ImmutableContextSet; import net.luckperms.api.query.Flag; @@ -49,20 +50,25 @@ public class QueryOptionsImpl implements QueryOptions { public static final QueryOptions DEFAULT_CONTEXTUAL = new QueryOptionsImpl(QueryMode.CONTEXTUAL, ImmutableContextSetImpl.EMPTY, FlagUtils.DEFAULT_FLAGS, null); public static final QueryOptions DEFAULT_NON_CONTEXTUAL = new QueryOptionsImpl(QueryMode.NON_CONTEXTUAL, null, FlagUtils.DEFAULT_FLAGS, null); + // state private final QueryMode mode; private final ImmutableContextSet context; private final byte flags; private final ImmutableMap, Object> options; - private final int hashCode; + // computed based on state above + private final int hashCode; private Set flagsSet = null; + private final ContextSatisfyMode contextSatisfyMode; QueryOptionsImpl(QueryMode mode, @Nullable ImmutableContextSet context, byte flags, @Nullable Map, Object> options) { this.mode = mode; this.context = context; this.flags = flags; this.options = options == null ? null : ImmutableMap.copyOf(options); + this.hashCode = calculateHashCode(); + this.contextSatisfyMode = options == null ? null : (ContextSatisfyMode) options.get(ContextSatisfyMode.KEY); } @Override @@ -113,10 +119,10 @@ public class QueryOptionsImpl implements QueryOptions { } @Override - public boolean satisfies(@NonNull ContextSet contextSet) { + public boolean satisfies(@NonNull ContextSet contextSet, @NonNull ContextSatisfyMode defaultContextSatisfyMode) { switch (this.mode) { case CONTEXTUAL: - return contextSet.isSatisfiedBy(this.context); + return contextSet.isSatisfiedBy(this.context, this.contextSatisfyMode == null ? defaultContextSatisfyMode : this.contextSatisfyMode); case NON_CONTEXTUAL: return true; default: diff --git a/nukkit/src/main/resources/config.yml b/nukkit/src/main/resources/config.yml index a6edea0f7..d6a7b126b 100644 --- a/nukkit/src/main/resources/config.yml +++ b/nukkit/src/main/resources/config.yml @@ -395,6 +395,15 @@ inheritance-traversal-algorithm: depth-first-pre-order # and when this setting is 'false':, the rules are just applied during each step of the traversal. post-traversal-inheritance-sort: false +# Defines the mode used to determine whether a set of contexts are satisfied. +# +# - Possible options: +# => at-least-one-value-per-key Set A will be satisfied by another set B, if at least one of the +# key-value entries per key in A are also in B. +# => all-values-per-key Set A will be satisfied by another set B, if all key-value +# entries in A are also in B. +context-satisfy-mode: at-least-one-value-per-key + # +----------------------------------------------------------------------------------------------+ # # | Permission resolution settings | # # +----------------------------------------------------------------------------------------------+ # diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/model/calculated/CalculatedSubjectData.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/model/calculated/CalculatedSubjectData.java index a3b584794..f98843789 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/service/model/calculated/CalculatedSubjectData.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/model/calculated/CalculatedSubjectData.java @@ -29,6 +29,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import me.lucko.luckperms.common.config.ConfigKeys; import me.lucko.luckperms.common.context.ContextSetComparator; import me.lucko.luckperms.sponge.service.ProxyFactory; import me.lucko.luckperms.sponge.service.model.LPPermissionService; @@ -36,6 +37,7 @@ import me.lucko.luckperms.sponge.service.model.LPSubject; import me.lucko.luckperms.sponge.service.model.LPSubjectData; import me.lucko.luckperms.sponge.service.model.LPSubjectReference; +import net.luckperms.api.context.ContextSatisfyMode; import net.luckperms.api.context.ImmutableContextSet; import net.luckperms.api.model.data.DataType; import net.luckperms.api.query.QueryOptions; @@ -87,6 +89,10 @@ public class CalculatedSubjectData implements LPSubjectData { return this.type; } + private ContextSatisfyMode defaultSatisfyMode() { + return this.service.getPlugin().getConfiguration().get(ConfigKeys.CONTEXT_SATISFY_MODE); + } + public void replacePermissions(Map> map) { this.permissions.clear(); for (Map.Entry> e : map.entrySet()) { @@ -131,7 +137,7 @@ public class CalculatedSubjectData implements LPSubjectData { // get relevant entries SortedMap> sorted = new TreeMap<>(ContextSetComparator.reverse()); for (Map.Entry> entry : this.permissions.entrySet()) { - if (!filter.satisfies(entry.getKey())) { + if (!filter.satisfies(entry.getKey(), defaultSatisfyMode())) { continue; } @@ -209,7 +215,7 @@ public class CalculatedSubjectData implements LPSubjectData { // get relevant entries SortedMap> sorted = new TreeMap<>(ContextSetComparator.reverse()); for (Map.Entry> entry : this.parents.entrySet()) { - if (!filter.satisfies(entry.getKey())) { + if (!filter.satisfies(entry.getKey(), defaultSatisfyMode())) { continue; } @@ -285,7 +291,7 @@ public class CalculatedSubjectData implements LPSubjectData { // get relevant entries SortedMap> sorted = new TreeMap<>(ContextSetComparator.reverse()); for (Map.Entry> entry : this.options.entrySet()) { - if (!filter.satisfies(entry.getKey())) { + if (!filter.satisfies(entry.getKey(), defaultSatisfyMode())) { continue; } diff --git a/sponge/src/main/resources/luckperms.conf b/sponge/src/main/resources/luckperms.conf index 8a693e269..a2cab6399 100644 --- a/sponge/src/main/resources/luckperms.conf +++ b/sponge/src/main/resources/luckperms.conf @@ -409,6 +409,15 @@ inheritance-traversal-algorithm = "depth-first-pre-order" # and when this setting is 'false':, the rules are just applied during each step of the traversal. post-traversal-inheritance-sort = false +# Defines the mode used to determine whether a set of contexts are satisfied. +# +# - Possible options: +# => at-least-one-value-per-key Set A will be satisfied by another set B, if at least one of the +# key-value entries per key in A are also in B. +# => all-values-per-key Set A will be satisfied by another set B, if all key-value +# entries in A are also in B. +context-satisfy-mode = "at-least-one-value-per-key" + # +----------------------------------------------------------------------------------------------+ # # | Permission resolution settings | # # +----------------------------------------------------------------------------------------------+ # diff --git a/velocity/src/main/resources/config.yml b/velocity/src/main/resources/config.yml index 1ef6ebd22..4289e400d 100644 --- a/velocity/src/main/resources/config.yml +++ b/velocity/src/main/resources/config.yml @@ -399,6 +399,15 @@ inheritance-traversal-algorithm: depth-first-pre-order # and when this setting is 'false':, the rules are just applied during each step of the traversal. post-traversal-inheritance-sort: false +# Defines the mode used to determine whether a set of contexts are satisfied. +# +# - Possible options: +# => at-least-one-value-per-key Set A will be satisfied by another set B, if at least one of the +# key-value entries per key in A are also in B. +# => all-values-per-key Set A will be satisfied by another set B, if all key-value +# entries in A are also in B. +context-satisfy-mode: at-least-one-value-per-key + # +----------------------------------------------------------------------------------------------+ # # | Permission resolution settings | # # +----------------------------------------------------------------------------------------------+ #