From 4d7a6cb6582e96676c1157f44b7df8912d92d68e Mon Sep 17 00:00:00 2001 From: Luck Date: Thu, 16 Apr 2020 14:44:38 +0100 Subject: [PATCH] Add meta-value-selection config setting (#2167) --- .../api/query/meta/MetaValueSelector.java | 59 ++++++++++ bukkit/src/main/resources/config.yml | 16 +++ bungee/src/main/resources/config.yml | 16 +++ .../common/cacheddata/type/MetaCache.java | 2 +- .../cacheddata/type/SimpleMetaCache.java | 19 +++- .../type/SimpleMetaValueSelector.java | 107 ++++++++++++++++++ .../luckperms/common/config/ConfigKeys.java | 20 ++++ nukkit/src/main/resources/config.yml | 16 +++ sponge/src/main/resources/luckperms.conf | 17 +++ velocity/src/main/resources/config.yml | 16 +++ 10 files changed, 284 insertions(+), 4 deletions(-) create mode 100644 api/src/main/java/net/luckperms/api/query/meta/MetaValueSelector.java create mode 100644 common/src/main/java/me/lucko/luckperms/common/cacheddata/type/SimpleMetaValueSelector.java diff --git a/api/src/main/java/net/luckperms/api/query/meta/MetaValueSelector.java b/api/src/main/java/net/luckperms/api/query/meta/MetaValueSelector.java new file mode 100644 index 000000000..ceb2b46a2 --- /dev/null +++ b/api/src/main/java/net/luckperms/api/query/meta/MetaValueSelector.java @@ -0,0 +1,59 @@ +/* + * 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.query.meta; + +import net.luckperms.api.node.types.MetaNode; +import net.luckperms.api.query.OptionKey; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.List; + +/** + * A function that selects the {@link MetaNode#getMetaValue() meta value} to be used in queries + * against a given {@link MetaNode#getMetaKey() meta key}. + * + * @since 5.1 + */ +public interface MetaValueSelector { + + /** + * The {@link OptionKey} for {@link MetaValueSelector}. + */ + OptionKey KEY = OptionKey.of("metavalueselector", MetaValueSelector.class); + + /** + * Selects the meta value to map to the given key. + * + *

The {@code values} list is guaranteed to contain at least 1 element.

+ * + * @param key the key + * @param values the values, in the order in which they were accumulated. + * @return the selected value + */ + @NonNull String selectValue(@NonNull String key, @NonNull List values); + +} diff --git a/bukkit/src/main/resources/config.yml b/bukkit/src/main/resources/config.yml index e43429533..a3b761c3f 100644 --- a/bukkit/src/main/resources/config.yml +++ b/bukkit/src/main/resources/config.yml @@ -407,6 +407,22 @@ apply-global-groups: true # If users on this server should have global (non-world specific) groups applied apply-global-world-groups: true +# +----------------------------------------------------------------------------------------------+ # +# | Meta lookup settings | # +# +----------------------------------------------------------------------------------------------+ # + +# Defines how meta values should be selected. +# +# - Possible options: +# => inheritance Selects the meta value that was inherited first +# => highest-number Selects the highest numerical meta value +# => lowest-number Selects the lowest numerical meta value +meta-value-selection-default: inheritance + +# Defines how meta values should be selected per key. +meta-value-selection: +# max-homes: highest-number + # +----------------------------------------------------------------------------------------------+ # # | Inheritance settings | # # +----------------------------------------------------------------------------------------------+ # diff --git a/bungee/src/main/resources/config.yml b/bungee/src/main/resources/config.yml index 9c5309a8d..ebd8b2a40 100644 --- a/bungee/src/main/resources/config.yml +++ b/bungee/src/main/resources/config.yml @@ -415,6 +415,22 @@ apply-global-groups: true # If users on this server should have global (non-world specific) groups applied apply-global-world-groups: true +# +----------------------------------------------------------------------------------------------+ # +# | Meta lookup settings | # +# +----------------------------------------------------------------------------------------------+ # + +# Defines how meta values should be selected. +# +# - Possible options: +# => inheritance Selects the meta value that was inherited first +# => highest-number Selects the highest numerical meta value +# => lowest-number Selects the lowest numerical meta value +meta-value-selection-default: inheritance + +# Defines how meta values should be selected per key. +meta-value-selection: +# max-homes: highest-number + # +----------------------------------------------------------------------------------------------+ # # | Inheritance settings | # # +----------------------------------------------------------------------------------------------+ # diff --git a/common/src/main/java/me/lucko/luckperms/common/cacheddata/type/MetaCache.java b/common/src/main/java/me/lucko/luckperms/common/cacheddata/type/MetaCache.java index 0e51cfc59..48df4f517 100644 --- a/common/src/main/java/me/lucko/luckperms/common/cacheddata/type/MetaCache.java +++ b/common/src/main/java/me/lucko/luckperms/common/cacheddata/type/MetaCache.java @@ -57,7 +57,7 @@ public class MetaCache extends SimpleMetaCache implements CachedMetaData { private final String verboseCheckTarget; public MetaCache(LuckPermsPlugin plugin, QueryOptions queryOptions, CacheMetadata metadata) { - super(queryOptions); + super(plugin, queryOptions); this.plugin = plugin; this.metadata = metadata; diff --git a/common/src/main/java/me/lucko/luckperms/common/cacheddata/type/SimpleMetaCache.java b/common/src/main/java/me/lucko/luckperms/common/cacheddata/type/SimpleMetaCache.java index 2f7745495..359f7e400 100644 --- a/common/src/main/java/me/lucko/luckperms/common/cacheddata/type/SimpleMetaCache.java +++ b/common/src/main/java/me/lucko/luckperms/common/cacheddata/type/SimpleMetaCache.java @@ -30,12 +30,15 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.Multimaps; +import me.lucko.luckperms.common.config.ConfigKeys; import me.lucko.luckperms.common.metastacking.MetaStack; +import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.verbose.event.MetaCheckEvent; import net.luckperms.api.cacheddata.CachedMetaData; import net.luckperms.api.metastacking.MetaStackDefinition; import net.luckperms.api.query.QueryOptions; +import net.luckperms.api.query.meta.MetaValueSelector; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -50,6 +53,8 @@ import java.util.SortedMap; */ public class SimpleMetaCache implements CachedMetaData { + private final LuckPermsPlugin plugin; + /** The query options this container is holding data for */ private final QueryOptions queryOptions; @@ -62,21 +67,29 @@ public class SimpleMetaCache implements CachedMetaData { protected MetaStack prefixStack = null; protected MetaStack suffixStack = null; - public SimpleMetaCache(QueryOptions queryOptions) { + public SimpleMetaCache(LuckPermsPlugin plugin, QueryOptions queryOptions) { + this.plugin = plugin; this.queryOptions = queryOptions; } public void loadMeta(MetaAccumulator meta) { this.meta = Multimaps.asMap(ImmutableListMultimap.copyOf(meta.getMeta())); + final MetaValueSelector metaValueSelector = this.queryOptions.option(MetaValueSelector.KEY) + .orElseGet(() -> this.plugin.getConfiguration().get(ConfigKeys.META_VALUE_SELECTOR)); + ImmutableMap.Builder builder = ImmutableMap.builder(); for (Map.Entry> e : this.meta.entrySet()) { if (e.getValue().isEmpty()) { continue; } - // take the value which was accumulated first - builder.put(e.getKey(), e.getValue().get(0)); + final String selected = metaValueSelector.selectValue(e.getKey(), e.getValue()); + if (selected == null) { + throw new NullPointerException(metaValueSelector + " returned null"); + } + + builder.put(e.getKey(), selected); } this.flattenedMeta = builder.build(); diff --git a/common/src/main/java/me/lucko/luckperms/common/cacheddata/type/SimpleMetaValueSelector.java b/common/src/main/java/me/lucko/luckperms/common/cacheddata/type/SimpleMetaValueSelector.java new file mode 100644 index 000000000..8f4585e8b --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/cacheddata/type/SimpleMetaValueSelector.java @@ -0,0 +1,107 @@ +/* + * 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.cacheddata.type; + +import net.luckperms.api.query.meta.MetaValueSelector; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.List; +import java.util.Map; + +public class SimpleMetaValueSelector implements MetaValueSelector { + private final Map strategies; + private final Strategy defaultStrategy; + + public SimpleMetaValueSelector(Map strategies, Strategy defaultStrategy) { + this.strategies = strategies; + this.defaultStrategy = defaultStrategy; + } + + @Override + public @NonNull String selectValue(@NonNull String key, @NonNull List values) { + return this.strategies.getOrDefault(key, this.defaultStrategy).select(values); + } + + public enum Strategy { + INHERITANCE { + @Override + public String select(List values) { + return values.get(0); + } + }, + HIGHEST_NUMBER { + private final DoubleSelectionPredicate selection = (value, current) -> value > current; + + @Override + public String select(List values) { + return selectNumber(values, this.selection); + } + }, + LOWEST_NUMBER { + private final DoubleSelectionPredicate selection = (value, current) -> value < current; + + @Override + public String select(List values) { + return selectNumber(values, this.selection); + } + }; + + public abstract String select(List values); + + public static Strategy parse(String s) { + try { + return Strategy.valueOf(s.replace('-', '_').toUpperCase()); + } catch (IllegalArgumentException e) { + return null; + } + } + } + + @FunctionalInterface + private interface DoubleSelectionPredicate { + boolean shouldSelect(double value, double current); + } + + private static String selectNumber(List values, DoubleSelectionPredicate selection) { + double current = 0; + String selected = null; + + for (String value : values) { + try { + double parse = Double.parseDouble(value); + if (selected == null || selection.shouldSelect(parse, current)) { + selected = value; + current = parse; + } + } catch (NumberFormatException e) { + // ignore + } + } + + return selected != null ? selected : Strategy.INHERITANCE.select(values); + } +} 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 e5b639e44..2d52dc663 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 @@ -26,7 +26,9 @@ package me.lucko.luckperms.common.config; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import me.lucko.luckperms.common.cacheddata.type.SimpleMetaValueSelector; import me.lucko.luckperms.common.command.utils.ArgumentParser; import me.lucko.luckperms.common.graph.TraversalAlgorithm; import me.lucko.luckperms.common.metastacking.SimpleMetaStackDefinition; @@ -45,6 +47,7 @@ import net.luckperms.api.model.data.TemporaryNodeMergeStrategy; import net.luckperms.api.query.Flag; import net.luckperms.api.query.QueryMode; import net.luckperms.api.query.QueryOptions; +import net.luckperms.api.query.meta.MetaValueSelector; import java.lang.reflect.Modifier; import java.util.ArrayList; @@ -53,6 +56,7 @@ import java.util.EnumMap; import java.util.EnumSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.function.Function; @@ -271,6 +275,22 @@ public final class ConfigKeys { */ public static final ConfigKey POST_TRAVERSAL_INHERITANCE_SORT = booleanKey("post-traversal-inheritance-sort", false); + /** + * The meta value selector + */ + public static final ConfigKey META_VALUE_SELECTOR = customKey(c -> { + SimpleMetaValueSelector.Strategy defaultStrategy = SimpleMetaValueSelector.Strategy.parse(c.getString("meta-value-selection-default", "inheritance")); + Map strategies = c.getStringMap("meta-value-selection", ImmutableMap.of()).entrySet().stream() + .map(e -> { + SimpleMetaValueSelector.Strategy parse = SimpleMetaValueSelector.Strategy.parse(e.getValue()); + return parse == null ? null : Maps.immutableEntry(e.getKey(), parse); + }) + .filter(Objects::nonNull) + .collect(ImmutableCollectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + return new SimpleMetaValueSelector(strategies, defaultStrategy); + }); + /** * The configured group weightings */ diff --git a/nukkit/src/main/resources/config.yml b/nukkit/src/main/resources/config.yml index 6ce07f708..3e671d2cd 100644 --- a/nukkit/src/main/resources/config.yml +++ b/nukkit/src/main/resources/config.yml @@ -402,6 +402,22 @@ apply-global-groups: true # If users on this server should have global (non-world specific) groups applied apply-global-world-groups: true +# +----------------------------------------------------------------------------------------------+ # +# | Meta lookup settings | # +# +----------------------------------------------------------------------------------------------+ # + +# Defines how meta values should be selected. +# +# - Possible options: +# => inheritance Selects the meta value that was inherited first +# => highest-number Selects the highest numerical meta value +# => lowest-number Selects the lowest numerical meta value +meta-value-selection-default: inheritance + +# Defines how meta values should be selected per key. +meta-value-selection: +# max-homes: highest-number + # +----------------------------------------------------------------------------------------------+ # # | Inheritance settings | # # +----------------------------------------------------------------------------------------------+ # diff --git a/sponge/src/main/resources/luckperms.conf b/sponge/src/main/resources/luckperms.conf index 41e51610d..7acee2c36 100644 --- a/sponge/src/main/resources/luckperms.conf +++ b/sponge/src/main/resources/luckperms.conf @@ -416,6 +416,23 @@ apply-global-groups = true # If users on this server should have global (non-world specific) groups applied apply-global-world-groups = true +# +----------------------------------------------------------------------------------------------+ # +# | Meta lookup settings | # +# +----------------------------------------------------------------------------------------------+ # + +# Defines how meta values should be selected. +# +# - Possible options: +# => inheritance Selects the meta value that was inherited first +# => highest-number Selects the highest numerical meta value +# => lowest-number Selects the lowest numerical meta value +meta-value-selection-default = "inheritance" + +# Defines how meta values should be selected per key. +meta-value-selection { + #max-homes = "highest-number" +} + # +----------------------------------------------------------------------------------------------+ # # | Inheritance settings | # # +----------------------------------------------------------------------------------------------+ # diff --git a/velocity/src/main/resources/config.yml b/velocity/src/main/resources/config.yml index 1f4de1abb..ecbd7090a 100644 --- a/velocity/src/main/resources/config.yml +++ b/velocity/src/main/resources/config.yml @@ -406,6 +406,22 @@ apply-global-groups: true # If users on this server should have global (non-world specific) groups applied apply-global-world-groups: true +# +----------------------------------------------------------------------------------------------+ # +# | Meta lookup settings | # +# +----------------------------------------------------------------------------------------------+ # + +# Defines how meta values should be selected. +# +# - Possible options: +# => inheritance Selects the meta value that was inherited first +# => highest-number Selects the highest numerical meta value +# => lowest-number Selects the lowest numerical meta value +meta-value-selection-default: inheritance + +# Defines how meta values should be selected per key. +meta-value-selection: +# max-homes: highest-number + # +----------------------------------------------------------------------------------------------+ # # | Inheritance settings | # # +----------------------------------------------------------------------------------------------+ #