From 5dda522a629389a1a826ba1d87e6737f6e0c3848 Mon Sep 17 00:00:00 2001 From: Luck Date: Tue, 6 Nov 2018 14:29:15 +0000 Subject: [PATCH] Add option to deduplicate prefix/suffix stacks (#1285) --- .../DuplicateRemovalFunction.java | 111 ++++++++++++++++++ .../api/metastacking/MetaStackDefinition.java | 9 ++ .../api/metastacking/MetaStackFactory.java | 17 ++- bukkit/src/main/resources/config.yml | 4 + bungee/src/main/resources/config.yml | 4 + .../delegates/misc/ApiMetaStackFactory.java | 5 +- .../luckperms/common/config/ConfigKeys.java | 29 ++++- .../common/metastacking/SimpleMetaStack.java | 23 ++-- .../SimpleMetaStackDefinition.java | 28 +++-- .../metastacking/SimpleMetaStackEntry.java | 31 +---- nukkit/src/main/resources/config.yml | 4 + .../service/calculated/SubjectCachedData.java | 2 + sponge/src/main/resources/luckperms.conf | 4 + velocity/src/main/resources/config.yml | 4 + 14 files changed, 227 insertions(+), 48 deletions(-) create mode 100644 api/src/main/java/me/lucko/luckperms/api/metastacking/DuplicateRemovalFunction.java diff --git a/api/src/main/java/me/lucko/luckperms/api/metastacking/DuplicateRemovalFunction.java b/api/src/main/java/me/lucko/luckperms/api/metastacking/DuplicateRemovalFunction.java new file mode 100644 index 000000000..5e2149eff --- /dev/null +++ b/api/src/main/java/me/lucko/luckperms/api/metastacking/DuplicateRemovalFunction.java @@ -0,0 +1,111 @@ +/* + * 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.metastacking; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.HashSet; +import java.util.List; +import java.util.ListIterator; +import java.util.Set; + +/** + * Functional interface which removes duplicate entries from a list. + * + *

Used by LuckPerms to remove duplicate entries from a MetaStack.

+ * + * @since 4.4 + */ +public interface DuplicateRemovalFunction { + + /** + * Removes duplicates from the given list, according to the behaviour + * of the function. + * + * @param list the entries + * @param the type of entries + */ + void processDuplicates(@NonNull List list); + + /** + * A {@link DuplicateRemovalFunction} that does not remove duplicates. + */ + DuplicateRemovalFunction RETAIN_ALL = new DuplicateRemovalFunction() { + @Override + public void processDuplicates(@NonNull List list) { + + } + + @Override + public String toString() { + return "DuplicateRemovalFunction#RETAIN_ALL"; + } + }; + + /** + * A {@link DuplicateRemovalFunction} that retains only the first duplicate. + */ + DuplicateRemovalFunction FIRST_ONLY = new DuplicateRemovalFunction() { + @SuppressWarnings("Java8CollectionRemoveIf") + @Override + public void processDuplicates(@NonNull List list) { + Set seen = new HashSet<>(); + for (ListIterator it = list.listIterator(); it.hasNext(); ) { + T next = it.next(); + if (!seen.add(next)) { + it.remove(); + } + } + } + + @Override + public String toString() { + return "DuplicateRemovalFunction#FIRST_ONLY"; + } + }; + + /** + * A {@link DuplicateRemovalFunction} that retains only the first duplicate. + */ + DuplicateRemovalFunction LAST_ONLY = new DuplicateRemovalFunction() { + @Override + public void processDuplicates(@NonNull List list) { + Set seen = new HashSet<>(); + for (ListIterator it = list.listIterator(list.size()); it.hasPrevious(); ) { + T next = it.previous(); + if (!seen.add(next)) { + it.remove(); + } + } + } + + @Override + public String toString() { + return "DuplicateRemovalFunction#LAST_ONLY"; + } + }; + +} diff --git a/api/src/main/java/me/lucko/luckperms/api/metastacking/MetaStackDefinition.java b/api/src/main/java/me/lucko/luckperms/api/metastacking/MetaStackDefinition.java index 85303174e..01c0b473f 100644 --- a/api/src/main/java/me/lucko/luckperms/api/metastacking/MetaStackDefinition.java +++ b/api/src/main/java/me/lucko/luckperms/api/metastacking/MetaStackDefinition.java @@ -49,6 +49,15 @@ public interface MetaStackDefinition { */ @NonNull List getElements(); + /** + * Gets the duplicate removal function, applied to the entries before + * formatting takes place. + * + * @return the duplicate removal function + * @since 4.4 + */ + @NonNull DuplicateRemovalFunction getDuplicateRemovalFunction(); + /** * Gets the spacer string added before any stack elements * diff --git a/api/src/main/java/me/lucko/luckperms/api/metastacking/MetaStackFactory.java b/api/src/main/java/me/lucko/luckperms/api/metastacking/MetaStackFactory.java index 3dc63ba06..76fc0a51e 100644 --- a/api/src/main/java/me/lucko/luckperms/api/metastacking/MetaStackFactory.java +++ b/api/src/main/java/me/lucko/luckperms/api/metastacking/MetaStackFactory.java @@ -64,6 +64,21 @@ public interface MetaStackFactory { * @param endSpacer the spacer to be included at the end of the stacks output * @return the new stack definition instance */ - @NonNull MetaStackDefinition createDefinition(@NonNull List elements, @NonNull String startSpacer, @NonNull String middleSpacer, @NonNull String endSpacer); + default @NonNull MetaStackDefinition createDefinition(@NonNull List elements, @NonNull String startSpacer, @NonNull String middleSpacer, @NonNull String endSpacer) { + return createDefinition(elements, DuplicateRemovalFunction.RETAIN_ALL, startSpacer, middleSpacer, endSpacer); + } + + /** + * Creates a new {@link MetaStackDefinition} with the given properties. + * + * @param elements the elements to be included in the stack. + * @param duplicateRemovalFunction the duplicate removal function + * @param startSpacer the spacer to be included at the start of the stacks output + * @param middleSpacer the spacer to be included between stack elements + * @param endSpacer the spacer to be included at the end of the stacks output + * @return the new stack definition instance + */ + @NonNull MetaStackDefinition createDefinition(@NonNull List elements, @NonNull DuplicateRemovalFunction duplicateRemovalFunction, @NonNull String startSpacer, @NonNull String middleSpacer, @NonNull String endSpacer); + } diff --git a/bukkit/src/main/resources/config.yml b/bukkit/src/main/resources/config.yml index 878936f6d..c1ac48e69 100644 --- a/bukkit/src/main/resources/config.yml +++ b/bukkit/src/main/resources/config.yml @@ -300,6 +300,8 @@ log-notify: true # - It is explained and documented in more detail on the wiki under "Prefix & Suffix Stacking". # # - The options are divided into separate sections for prefixes and suffixes. +# - The 'duplicates' setting refers to how duplicate elements are handled. Can be 'retain-all', +# 'first-only' or 'last-only'. # - The value of 'start-spacer' is included at the start of the resultant prefix/suffix. # - The value of 'end-spacer' is included at the end of the resultant prefix/suffix. # - The value of 'middle-spacer' is included between each element in the resultant prefix/suffix. @@ -333,12 +335,14 @@ meta-formatting: prefix: format: - "highest" + duplicates: first-only start-spacer: "" middle-spacer: " " end-spacer: "" suffix: format: - "highest" + duplicates: first-only start-spacer: "" middle-spacer: " " end-spacer: "" diff --git a/bungee/src/main/resources/config.yml b/bungee/src/main/resources/config.yml index 3955fb53e..95da5a35e 100644 --- a/bungee/src/main/resources/config.yml +++ b/bungee/src/main/resources/config.yml @@ -308,6 +308,8 @@ log-notify: true # - It is explained and documented in more detail on the wiki under "Prefix & Suffix Stacking". # # - The options are divided into separate sections for prefixes and suffixes. +# - The 'duplicates' setting refers to how duplicate elements are handled. Can be 'retain-all', +# 'first-only' or 'last-only'. # - The value of 'start-spacer' is included at the start of the resultant prefix/suffix. # - The value of 'end-spacer' is included at the end of the resultant prefix/suffix. # - The value of 'middle-spacer' is included between each element in the resultant prefix/suffix. @@ -341,12 +343,14 @@ meta-formatting: prefix: format: - "highest" + duplicates: first-only start-spacer: "" middle-spacer: " " end-spacer: "" suffix: format: - "highest" + duplicates: first-only start-spacer: "" middle-spacer: " " end-spacer: "" diff --git a/common/src/main/java/me/lucko/luckperms/common/api/delegates/misc/ApiMetaStackFactory.java b/common/src/main/java/me/lucko/luckperms/common/api/delegates/misc/ApiMetaStackFactory.java index c72e47fda..a673c35a7 100644 --- a/common/src/main/java/me/lucko/luckperms/common/api/delegates/misc/ApiMetaStackFactory.java +++ b/common/src/main/java/me/lucko/luckperms/common/api/delegates/misc/ApiMetaStackFactory.java @@ -27,6 +27,7 @@ package me.lucko.luckperms.common.api.delegates.misc; import com.google.common.collect.ImmutableList; +import me.lucko.luckperms.api.metastacking.DuplicateRemovalFunction; import me.lucko.luckperms.api.metastacking.MetaStackDefinition; import me.lucko.luckperms.api.metastacking.MetaStackElement; import me.lucko.luckperms.api.metastacking.MetaStackFactory; @@ -63,7 +64,7 @@ public class ApiMetaStackFactory implements MetaStackFactory { } @Override - public @NonNull MetaStackDefinition createDefinition(@NonNull List elements, @NonNull String startSpacer, @NonNull String middleSpacer, @NonNull String endSpacer) { - return new SimpleMetaStackDefinition(elements, startSpacer, middleSpacer, endSpacer); + public @NonNull MetaStackDefinition createDefinition(@NonNull List elements, @NonNull DuplicateRemovalFunction duplicateRemovalFunction, @NonNull String startSpacer, @NonNull String middleSpacer, @NonNull String endSpacer) { + return new SimpleMetaStackDefinition(elements, duplicateRemovalFunction, startSpacer, middleSpacer, endSpacer); } } 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 d18f46dd2..a480df3b8 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 @@ -32,6 +32,7 @@ import me.lucko.luckperms.api.Contexts; import me.lucko.luckperms.api.LookupSetting; import me.lucko.luckperms.api.TemporaryMergeBehaviour; import me.lucko.luckperms.api.context.ContextSet; +import me.lucko.luckperms.api.metastacking.DuplicateRemovalFunction; import me.lucko.luckperms.api.metastacking.MetaStackDefinition; import me.lucko.luckperms.common.assignments.AssignmentRule; import me.lucko.luckperms.common.command.utils.ArgumentParser; @@ -291,8 +292,20 @@ public final class ConfigKeys { String startSpacer = l.getString("meta-formatting.prefix.start-spacer", ""); String middleSpacer = l.getString("meta-formatting.prefix.middle-spacer", " "); String endSpacer = l.getString("meta-formatting.prefix.end-spacer", ""); + DuplicateRemovalFunction duplicateRemovalFunction; + switch (l.getString("meta-formatting.prefix.duplicates", "").toLowerCase()) { + case "first-only": + duplicateRemovalFunction = DuplicateRemovalFunction.FIRST_ONLY; + break; + case "last-only": + duplicateRemovalFunction = DuplicateRemovalFunction.LAST_ONLY; + break; + default: + duplicateRemovalFunction = DuplicateRemovalFunction.RETAIN_ALL; + break; + } - return new SimpleMetaStackDefinition(StandardStackElements.parseList(l.getPlugin(), format), startSpacer, middleSpacer, endSpacer); + return new SimpleMetaStackDefinition(StandardStackElements.parseList(l.getPlugin(), format), duplicateRemovalFunction, startSpacer, middleSpacer, endSpacer); }); /** @@ -306,8 +319,20 @@ public final class ConfigKeys { String startSpacer = l.getString("meta-formatting.suffix.start-spacer", ""); String middleSpacer = l.getString("meta-formatting.suffix.middle-spacer", " "); String endSpacer = l.getString("meta-formatting.suffix.end-spacer", ""); + DuplicateRemovalFunction duplicateRemovalFunction; + switch (l.getString("meta-formatting.prefix.duplicates", "").toLowerCase()) { + case "first-only": + duplicateRemovalFunction = DuplicateRemovalFunction.FIRST_ONLY; + break; + case "last-only": + duplicateRemovalFunction = DuplicateRemovalFunction.LAST_ONLY; + break; + default: + duplicateRemovalFunction = DuplicateRemovalFunction.RETAIN_ALL; + break; + } - return new SimpleMetaStackDefinition(StandardStackElements.parseList(l.getPlugin(), format), startSpacer, middleSpacer, endSpacer); + return new SimpleMetaStackDefinition(StandardStackElements.parseList(l.getPlugin(), format), duplicateRemovalFunction, startSpacer, middleSpacer, endSpacer); }); /** diff --git a/common/src/main/java/me/lucko/luckperms/common/metastacking/SimpleMetaStack.java b/common/src/main/java/me/lucko/luckperms/common/metastacking/SimpleMetaStack.java index 237060498..02236355a 100644 --- a/common/src/main/java/me/lucko/luckperms/common/metastacking/SimpleMetaStack.java +++ b/common/src/main/java/me/lucko/luckperms/common/metastacking/SimpleMetaStack.java @@ -30,8 +30,11 @@ import me.lucko.luckperms.api.LocalizedNode; import me.lucko.luckperms.api.metastacking.MetaStackDefinition; import me.lucko.luckperms.common.utils.ImmutableCollectors; -import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; public final class SimpleMetaStack implements MetaStack { @@ -50,22 +53,26 @@ public final class SimpleMetaStack implements MetaStack { @Override public String toFormattedString() { - List ret = new ArrayList<>(this.entries); - ret.removeIf(m -> !m.getCurrentValue().isPresent()); + List elements = this.entries.stream() + .map(MetaStackEntry::getCurrentValue) + .filter(Optional::isPresent) + .map(Optional::get) + .map(Map.Entry::getValue) + .collect(Collectors.toCollection(LinkedList::new)); - if (ret.isEmpty()) { + if (elements.isEmpty()) { return null; } + this.definition.getDuplicateRemovalFunction().processDuplicates(elements); + StringBuilder sb = new StringBuilder(); sb.append(this.definition.getStartSpacer()); - for (int i = 0; i < ret.size(); i++) { + for (int i = 0; i < elements.size(); i++) { if (i != 0) { sb.append(this.definition.getMiddleSpacer()); } - - MetaStackEntry e = ret.get(i); - sb.append(e.getCurrentValue().get().getValue()); + sb.append(elements.get(i)); } sb.append(this.definition.getEndSpacer()); diff --git a/common/src/main/java/me/lucko/luckperms/common/metastacking/SimpleMetaStackDefinition.java b/common/src/main/java/me/lucko/luckperms/common/metastacking/SimpleMetaStackDefinition.java index c431e724d..08c6bfed2 100644 --- a/common/src/main/java/me/lucko/luckperms/common/metastacking/SimpleMetaStackDefinition.java +++ b/common/src/main/java/me/lucko/luckperms/common/metastacking/SimpleMetaStackDefinition.java @@ -27,6 +27,7 @@ package me.lucko.luckperms.common.metastacking; import com.google.common.collect.ImmutableList; +import me.lucko.luckperms.api.metastacking.DuplicateRemovalFunction; import me.lucko.luckperms.api.metastacking.MetaStackDefinition; import me.lucko.luckperms.api.metastacking.MetaStackElement; @@ -38,6 +39,7 @@ import java.util.Objects; public final class SimpleMetaStackDefinition implements MetaStackDefinition { private final List elements; + private final DuplicateRemovalFunction duplicateRemovalFunction; private final String startSpacer; private final String middleSpacer; private final String endSpacer; @@ -45,8 +47,9 @@ public final class SimpleMetaStackDefinition implements MetaStackDefinition { // cache hashcode - this class is immutable, and used an index in MetaContexts private final int hashCode; - public SimpleMetaStackDefinition(List elements, String startSpacer, String middleSpacer, String endSpacer) { + public SimpleMetaStackDefinition(List elements, DuplicateRemovalFunction duplicateRemovalFunction, String startSpacer, String middleSpacer, String endSpacer) { this.elements = ImmutableList.copyOf(Objects.requireNonNull(elements, "elements")); + this.duplicateRemovalFunction = Objects.requireNonNull(duplicateRemovalFunction, "duplicateRemovalFunction"); this.startSpacer = Objects.requireNonNull(startSpacer, "startSpacer"); this.middleSpacer = Objects.requireNonNull(middleSpacer, "middleSpacer"); this.endSpacer = Objects.requireNonNull(endSpacer, "endSpacer"); @@ -58,6 +61,11 @@ public final class SimpleMetaStackDefinition implements MetaStackDefinition { return this.elements; } + @Override + public @NonNull DuplicateRemovalFunction getDuplicateRemovalFunction() { + return this.duplicateRemovalFunction; + } + @Override public @NonNull String getStartSpacer() { return this.startSpacer; @@ -83,14 +91,15 @@ public final class SimpleMetaStackDefinition implements MetaStackDefinition { if (!(o instanceof SimpleMetaStackDefinition)) return false; final SimpleMetaStackDefinition that = (SimpleMetaStackDefinition) o; - return this.getElements().equals(that.getElements()) && - this.getStartSpacer().equals(that.getStartSpacer()) && - this.getMiddleSpacer().equals(that.getMiddleSpacer()) && - this.getEndSpacer().equals(that.getEndSpacer()); + return this.elements.equals(that.elements) && + this.duplicateRemovalFunction.equals(that.duplicateRemovalFunction) && + this.startSpacer.equals(that.startSpacer) && + this.middleSpacer.equals(that.middleSpacer) && + this.endSpacer.equals(that.endSpacer); } private int calculateHashCode() { - return Objects.hash(getElements(), getStartSpacer(), getMiddleSpacer(), getEndSpacer()); + return Objects.hash(this.elements, this.duplicateRemovalFunction, this.startSpacer, this.middleSpacer, this.elements); } @Override @@ -100,6 +109,11 @@ public final class SimpleMetaStackDefinition implements MetaStackDefinition { @Override public String toString() { - return "SimpleMetaStackDefinition(elements=" + this.getElements() + ", startSpacer=" + this.getStartSpacer() + ", middleSpacer=" + this.getMiddleSpacer() + ", endSpacer=" + this.getEndSpacer() + ", hashCode=" + this.getHashCode() + ")"; + return "SimpleMetaStackDefinition(" + + "elements=" + this.elements + ", " + + "duplicateRemovalFunction=" + this.duplicateRemovalFunction + ", " + + "startSpacer=" + this.startSpacer + ", " + + "middleSpacer=" + this.middleSpacer + ", " + + "endSpacer=" + this.endSpacer + ")"; } } diff --git a/common/src/main/java/me/lucko/luckperms/common/metastacking/SimpleMetaStackEntry.java b/common/src/main/java/me/lucko/luckperms/common/metastacking/SimpleMetaStackEntry.java index ddeeed15b..65e4d03c7 100644 --- a/common/src/main/java/me/lucko/luckperms/common/metastacking/SimpleMetaStackEntry.java +++ b/common/src/main/java/me/lucko/luckperms/common/metastacking/SimpleMetaStackEntry.java @@ -29,16 +29,17 @@ import me.lucko.luckperms.api.ChatMetaType; import me.lucko.luckperms.api.LocalizedNode; import me.lucko.luckperms.api.metastacking.MetaStackElement; +import org.checkerframework.checker.nullness.qual.Nullable; + import java.util.Map; import java.util.Optional; final class SimpleMetaStackEntry implements MetaStackEntry { - private final MetaStack parentStack; private final MetaStackElement element; private final ChatMetaType type; - private Map.Entry current = null; + private Map.@Nullable Entry current = null; public SimpleMetaStackEntry(MetaStack parentStack, MetaStackElement element, ChatMetaType type) { this.parentStack = parentStack; @@ -73,30 +74,4 @@ final class SimpleMetaStackEntry implements MetaStackEntry { public ChatMetaType getType() { return this.type; } - - @Override - public boolean equals(Object o) { - if (o == this) return true; - if (!(o instanceof SimpleMetaStackEntry)) return false; - final SimpleMetaStackEntry that = (SimpleMetaStackEntry) o; - - return this.getElement().equals(that.getElement()) && - this.getType() == that.getType() && - this.current.equals(that.current); - } - - @Override - public int hashCode() { - final int PRIME = 59; - int result = 1; - result = result * PRIME + this.getElement().hashCode(); - result = result * PRIME + this.getType().hashCode(); - result = result * PRIME + this.current.hashCode(); - return result; - } - - @Override - public String toString() { - return "SimpleMetaStackEntry(parentStack=" + this.getParentStack() + ", element=" + this.getElement() + ", type=" + this.getType() + ", current=" + this.current + ")"; - } } diff --git a/nukkit/src/main/resources/config.yml b/nukkit/src/main/resources/config.yml index 009db5c58..197bd6c2c 100644 --- a/nukkit/src/main/resources/config.yml +++ b/nukkit/src/main/resources/config.yml @@ -295,6 +295,8 @@ log-notify: true # - It is explained and documented in more detail on the wiki under "Prefix & Suffix Stacking". # # - The options are divided into separate sections for prefixes and suffixes. +# - The 'duplicates' setting refers to how duplicate elements are handled. Can be 'retain-all', +# 'first-only' or 'last-only'. # - The value of 'start-spacer' is included at the start of the resultant prefix/suffix. # - The value of 'end-spacer' is included at the end of the resultant prefix/suffix. # - The value of 'middle-spacer' is included between each element in the resultant prefix/suffix. @@ -328,12 +330,14 @@ meta-formatting: prefix: format: - "highest" + duplicates: first-only start-spacer: "" middle-spacer: " " end-spacer: "" suffix: format: - "highest" + duplicates: first-only start-spacer: "" middle-spacer: " " end-spacer: "" diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/calculated/SubjectCachedData.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/calculated/SubjectCachedData.java index c9393f6b8..b395539ea 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/service/calculated/SubjectCachedData.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/calculated/SubjectCachedData.java @@ -29,6 +29,7 @@ import com.google.common.collect.ImmutableList; import me.lucko.luckperms.api.Contexts; import me.lucko.luckperms.api.caching.MetaContexts; +import me.lucko.luckperms.api.metastacking.DuplicateRemovalFunction; import me.lucko.luckperms.api.metastacking.MetaStackDefinition; import me.lucko.luckperms.common.caching.AbstractCachedData; import me.lucko.luckperms.common.caching.CacheMetadata; @@ -49,6 +50,7 @@ import java.util.Map; public class SubjectCachedData extends AbstractCachedData implements CalculatorFactory { private static final MetaStackDefinition DEFAULT_META_STACK = new SimpleMetaStackDefinition( ImmutableList.of(StandardStackElements.HIGHEST), + DuplicateRemovalFunction.RETAIN_ALL, "", "", "" ); diff --git a/sponge/src/main/resources/luckperms.conf b/sponge/src/main/resources/luckperms.conf index 1cf1f3816..99043e0cc 100644 --- a/sponge/src/main/resources/luckperms.conf +++ b/sponge/src/main/resources/luckperms.conf @@ -304,6 +304,8 @@ log-notify = true # - It is explained and documented in more detail on the wiki under "Prefix & Suffix Stacking". # # - The options are divided into separate sections for prefixes and suffixes. +# - The 'duplicates' setting refers to how duplicate elements are handled. Can be 'retain-all', +# 'first-only' or 'last-only'. # - The value of 'start-spacer' is included at the start of the resultant prefix/suffix. # - The value of 'end-spacer' is included at the end of the resultant prefix/suffix. # - The value of 'middle-spacer' is included between each element in the resultant prefix/suffix. @@ -338,6 +340,7 @@ meta-formatting { format = [ "highest" ] + duplicates = "first-only" start-spacer = "" middle-spacer = " " end-spacer = "" @@ -346,6 +349,7 @@ meta-formatting { format = [ "highest" ] + duplicates = "first-only" start-spacer = "" middle-spacer = " " end-spacer = "" diff --git a/velocity/src/main/resources/config.yml b/velocity/src/main/resources/config.yml index ec27e49d5..aab721926 100644 --- a/velocity/src/main/resources/config.yml +++ b/velocity/src/main/resources/config.yml @@ -299,6 +299,8 @@ log-notify: true # - It is explained and documented in more detail on the wiki under "Prefix & Suffix Stacking". # # - The options are divided into separate sections for prefixes and suffixes. +# - The 'duplicates' setting refers to how duplicate elements are handled. Can be 'retain-all', +# 'first-only' or 'last-only'. # - The value of 'start-spacer' is included at the start of the resultant prefix/suffix. # - The value of 'end-spacer' is included at the end of the resultant prefix/suffix. # - The value of 'middle-spacer' is included between each element in the resultant prefix/suffix. @@ -332,12 +334,14 @@ meta-formatting: prefix: format: - "highest" + duplicates: first-only start-spacer: "" middle-spacer: " " end-spacer: "" suffix: format: - "highest" + duplicates: first-only start-spacer: "" middle-spacer: " " end-spacer: ""