From a5e15b8a29ce6da7e32a7f052fbe20e34b268d37 Mon Sep 17 00:00:00 2001 From: Luck Date: Mon, 18 Nov 2019 21:19:59 +0000 Subject: [PATCH] More work --- README.md | 2 +- api/build.gradle | 3 +- .../java/net/luckperms/api/LuckPerms.java | 2 +- .../api/extension/ExtensionManager.java | 1 + .../api/node/metadata/NodeMetadataKey.java | 6 +- .../node/metadata/SimpleNodeMetadataKey.java | 2 +- .../types/InheritanceOriginMetadata.java | 2 +- .../net/luckperms/api/query/OptionKey.java | 6 +- .../luckperms/api/query/SimpleOptionKey.java | 2 +- build.gradle | 3 +- bukkit/src/main/resources/config.yml | 2 +- bukkit/src/main/resources/plugin.yml | 2 +- bungee/src/main/resources/config.yml | 2 +- .../commands/generic/meta/MetaInfo.java | 6 +- .../permission/PermissionCheckInherits.java | 2 +- .../luckperms/common/config/ConfigKeys.java | 11 +- .../metastacking/StandardStackElements.java | 22 ++- .../lucko/luckperms/common/model/NodeMap.java | 4 +- .../common/model/PermissionHolder.java | 11 +- .../common/model/PrimaryGroupHolder.java | 182 ++++++++++++++++++ .../me/lucko/luckperms/common/model/User.java | 6 +- .../common/node/model/InheritanceOrigin.java | 7 +- .../AllParentsByWeightHolder.java | 60 ------ .../common/primarygroup/ContextualHolder.java | 73 ------- .../primarygroup/ParentsByWeightHolder.java | 68 ------- .../primarygroup/PrimaryGroupHolder.java | 56 ------ .../common/primarygroup/StoredHolder.java | 64 ------ nukkit/src/main/resources/config.yml | 2 +- nukkit/src/main/resources/plugin.yml | 2 +- .../luckperms/sponge/LPSpongeBootstrap.java | 2 +- sponge/src/main/resources/luckperms.conf | 2 +- .../velocity/LPVelocityBootstrap.java | 2 +- velocity/src/main/resources/config.yml | 2 +- 33 files changed, 246 insertions(+), 373 deletions(-) create mode 100644 common/src/main/java/me/lucko/luckperms/common/model/PrimaryGroupHolder.java delete mode 100644 common/src/main/java/me/lucko/luckperms/common/primarygroup/AllParentsByWeightHolder.java delete mode 100644 common/src/main/java/me/lucko/luckperms/common/primarygroup/ContextualHolder.java delete mode 100644 common/src/main/java/me/lucko/luckperms/common/primarygroup/ParentsByWeightHolder.java delete mode 100644 common/src/main/java/me/lucko/luckperms/common/primarygroup/PrimaryGroupHolder.java delete mode 100644 common/src/main/java/me/lucko/luckperms/common/primarygroup/StoredHolder.java diff --git a/README.md b/README.md index 3ad68c6a2..a34ebbc0f 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Still not convinced? More information can be found on the wiki, under: [Why Luck ## Useful Links -The latest **downloads** can be found on the [project's homepage](https://luckperms.github.io/). +The latest **downloads** can be found on the [project's homepage](https://luckperms.net/). More information about the project & how to use the plugin can be found in the [wiki](https://github.com/lucko/LuckPerms/wiki). diff --git a/api/build.gradle b/api/build.gradle index 579e61c1a..a36a833a8 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -11,6 +11,7 @@ if (project.hasProperty('sonatypeUsername') && project.hasProperty('sonatypePass apply plugin: 'signing' javadoc { + title = 'LuckPerms API (v' + project.ext.apiVersion + ')' options.encoding = 'UTF-8' options.charSet = 'UTF-8' options.links( @@ -56,7 +57,7 @@ if (project.hasProperty('sonatypeUsername') && project.hasProperty('sonatypePass project { name 'LuckPerms API' description 'An advanced permissions plugin for Bukkit/Spigot, BungeeCord, Sponge, Nukkit and Velocity.' - url 'https://luckperms.github.io' + url 'https://luckperms.net' licenses { license { diff --git a/api/src/main/java/net/luckperms/api/LuckPerms.java b/api/src/main/java/net/luckperms/api/LuckPerms.java index 7c9003b34..7855f4e88 100644 --- a/api/src/main/java/net/luckperms/api/LuckPerms.java +++ b/api/src/main/java/net/luckperms/api/LuckPerms.java @@ -160,7 +160,7 @@ public interface LuckPerms { /** * Gets the {@link ContextManager}, responsible for managing - * {@link ContextCalculator}s, and calculating applicable contexts.

+ * {@link ContextCalculator}s, and calculating applicable contexts. * * @return the context manager */ diff --git a/api/src/main/java/net/luckperms/api/extension/ExtensionManager.java b/api/src/main/java/net/luckperms/api/extension/ExtensionManager.java index 33780aa5a..352a4594f 100644 --- a/api/src/main/java/net/luckperms/api/extension/ExtensionManager.java +++ b/api/src/main/java/net/luckperms/api/extension/ExtensionManager.java @@ -47,6 +47,7 @@ public interface ExtensionManager { * Loads the extension at the given path. * * @param path the path to the extension + * @return the extension * @throws IOException if the extension could not be loaded */ @NonNull Extension loadExtension(Path path) throws IOException; diff --git a/api/src/main/java/net/luckperms/api/node/metadata/NodeMetadataKey.java b/api/src/main/java/net/luckperms/api/node/metadata/NodeMetadataKey.java index b87dcb9f1..9e14d320b 100644 --- a/api/src/main/java/net/luckperms/api/node/metadata/NodeMetadataKey.java +++ b/api/src/main/java/net/luckperms/api/node/metadata/NodeMetadataKey.java @@ -35,8 +35,8 @@ import java.util.Objects; *

It is intended that {@link NodeMetadataKey}s are obtained and stored as * follows.

*

- *     public static final NodeMetadataKey SPECIAL_METADATA_KEY = NodeMetadataKey.of("specialmetadata", SpecialMetadata.class);
- * 

+ * public static final NodeMetadataKey<SpecialMetadata> SPECIAL_METADATA_KEY = NodeMetadataKey.of("specialmetadata", SpecialMetadata.class); + * * * @param the metadata type */ @@ -70,6 +70,6 @@ public interface NodeMetadataKey { * * @return the type */ - @NonNull Class getType(); + @NonNull Class type(); } diff --git a/api/src/main/java/net/luckperms/api/node/metadata/SimpleNodeMetadataKey.java b/api/src/main/java/net/luckperms/api/node/metadata/SimpleNodeMetadataKey.java index ff1cbcace..44a00074f 100644 --- a/api/src/main/java/net/luckperms/api/node/metadata/SimpleNodeMetadataKey.java +++ b/api/src/main/java/net/luckperms/api/node/metadata/SimpleNodeMetadataKey.java @@ -44,7 +44,7 @@ final class SimpleNodeMetadataKey implements NodeMetadataKey { } @Override - public @NonNull Class getType() { + public @NonNull Class type() { return this.type; } diff --git a/api/src/main/java/net/luckperms/api/node/metadata/types/InheritanceOriginMetadata.java b/api/src/main/java/net/luckperms/api/node/metadata/types/InheritanceOriginMetadata.java index 5c1d7014c..89983cbca 100644 --- a/api/src/main/java/net/luckperms/api/node/metadata/types/InheritanceOriginMetadata.java +++ b/api/src/main/java/net/luckperms/api/node/metadata/types/InheritanceOriginMetadata.java @@ -52,6 +52,6 @@ public interface InheritanceOriginMetadata { * * @return where the node was inherited from. */ - @NonNull String getOrigin(); + PermissionHolder.@NonNull Identifier getOrigin(); } diff --git a/api/src/main/java/net/luckperms/api/query/OptionKey.java b/api/src/main/java/net/luckperms/api/query/OptionKey.java index 918275fcd..0ffd1ce89 100644 --- a/api/src/main/java/net/luckperms/api/query/OptionKey.java +++ b/api/src/main/java/net/luckperms/api/query/OptionKey.java @@ -36,8 +36,8 @@ import java.util.Objects; * *

It is intended that {@link OptionKey}s are created and defined as follows.

*

- *     public static final OptionKey SPECIAL_OPTION = OptionKey.of("special", String.class);
- * 

+ * public static final OptionKey<String> SPECIAL_OPTION = OptionKey.of("special", String.class); + * * * @param the option type */ @@ -71,6 +71,6 @@ public interface OptionKey { * * @return the type */ - @NonNull Class getType(); + @NonNull Class type(); } diff --git a/api/src/main/java/net/luckperms/api/query/SimpleOptionKey.java b/api/src/main/java/net/luckperms/api/query/SimpleOptionKey.java index d65ede9fb..097a43179 100644 --- a/api/src/main/java/net/luckperms/api/query/SimpleOptionKey.java +++ b/api/src/main/java/net/luckperms/api/query/SimpleOptionKey.java @@ -44,7 +44,7 @@ final class SimpleOptionKey implements OptionKey { } @Override - public @NonNull Class getType() { + public @NonNull Class type() { return this.type; } diff --git a/build.gradle b/build.gradle index 3797fa07f..dd56bab0f 100644 --- a/build.gradle +++ b/build.gradle @@ -47,7 +47,8 @@ subprojects { project.ext.majorVersion = '5' project.ext.minorVersion = '0' project.ext.patchVersion = determinePatchVersion() - project.ext.fullVersion = project.ext.majorVersion + '.' + project.ext.minorVersion + '.' + project.ext.patchVersion + project.ext.apiVersion = project.ext.majorVersion + '.' + project.ext.minorVersion + project.ext.fullVersion = project.ext.apiVersion + '.' + project.ext.patchVersion license { header = rootProject.file('HEADER.txt') diff --git a/bukkit/src/main/resources/config.yml b/bukkit/src/main/resources/config.yml index 36bf68183..d7fc9e747 100644 --- a/bukkit/src/main/resources/config.yml +++ b/bukkit/src/main/resources/config.yml @@ -4,7 +4,7 @@ # | | | | / ` |__/ |__) |__ |__) |\/| /__` | # # | |___ \__/ \__, | \ | |___ | \ | | .__/ | # # | | # -# | https://luckperms.github.io | # +# | https://luckperms.net | # # | | # # | SOURCE CODE: https://github.com/lucko/LuckPerms | # # | WIKI: https://github.com/lucko/LuckPerms/wiki | # diff --git a/bukkit/src/main/resources/plugin.yml b/bukkit/src/main/resources/plugin.yml index 7b6de4c12..7493dab1c 100644 --- a/bukkit/src/main/resources/plugin.yml +++ b/bukkit/src/main/resources/plugin.yml @@ -2,7 +2,7 @@ name: LuckPerms version: ${pluginVersion} description: A permissions plugin author: Luck -website: https://luckperms.github.io +website: https://luckperms.net main: me.lucko.luckperms.bukkit.LPBukkitBootstrap load: STARTUP diff --git a/bungee/src/main/resources/config.yml b/bungee/src/main/resources/config.yml index 6a8d9f56e..b09d583cb 100644 --- a/bungee/src/main/resources/config.yml +++ b/bungee/src/main/resources/config.yml @@ -4,7 +4,7 @@ # | | | | / ` |__/ |__) |__ |__) |\/| /__` | # # | |___ \__/ \__, | \ | |___ | \ | | .__/ | # # | | # -# | https://luckperms.github.io | # +# | https://luckperms.net | # # | | # # | SOURCE CODE: https://github.com/lucko/LuckPerms | # # | WIKI: https://github.com/lucko/LuckPerms/wiki | # diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/generic/meta/MetaInfo.java b/common/src/main/java/me/lucko/luckperms/common/commands/generic/meta/MetaInfo.java index 83c313ab8..3f74fbb2d 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/generic/meta/MetaInfo.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/generic/meta/MetaInfo.java @@ -69,7 +69,7 @@ import java.util.function.Consumer; public class MetaInfo extends SharedSubCommand { private static String processLocation(Node node, PermissionHolder holder) { - String location = node.metadata(InheritanceOriginMetadata.KEY).getOrigin(); + String location = node.metadata(InheritanceOriginMetadata.KEY).getOrigin().getName(); return location.equalsIgnoreCase(holder.getObjectName()) ? "self" : location; } @@ -162,7 +162,7 @@ public class MetaInfo extends SharedSubCommand { } private static Consumer> makeFancy(PermissionHolder holder, String label, ChatMetaNode node) { - String location = node.metadata(InheritanceOriginMetadata.KEY).getOrigin(); + String location = node.metadata(InheritanceOriginMetadata.KEY).getOrigin().getName(); if (!location.equals(holder.getObjectName())) { // inherited. Group group = holder.getPlugin().getGroupManager().getIfLoaded(location); @@ -189,7 +189,7 @@ public class MetaInfo extends SharedSubCommand { } private static Consumer> makeFancy(PermissionHolder holder, String label, MetaNode node) { - String location = node.metadata(InheritanceOriginMetadata.KEY).getOrigin(); + String location = node.metadata(InheritanceOriginMetadata.KEY).getOrigin().getName(); if (!location.equals(holder.getObjectName())) { // inherited. Group group = holder.getPlugin().getGroupManager().getIfLoaded(location); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/generic/permission/PermissionCheckInherits.java b/common/src/main/java/me/lucko/luckperms/common/commands/generic/permission/PermissionCheckInherits.java index 36476bb97..abbf3e383 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/generic/permission/PermissionCheckInherits.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/generic/permission/PermissionCheckInherits.java @@ -70,7 +70,7 @@ public class PermissionCheckInherits extends SharedSubCommand { .filter(n -> n.getKey().equalsIgnoreCase(node) && n.getContexts().equals(context)) .findFirst(); - String location = match.map(n -> n.metadata(InheritanceOriginMetadata.KEY).getOrigin()).orElse(null); + String location = match.map(n -> n.metadata(InheritanceOriginMetadata.KEY).getOrigin().getName()).orElse(null); if (location == null || location.equalsIgnoreCase(holder.getObjectName())) { location = "self"; 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 d75269230..e02cf786b 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,11 +32,8 @@ import me.lucko.luckperms.common.context.contextset.ImmutableContextSetImpl; import me.lucko.luckperms.common.graph.TraversalAlgorithm; import me.lucko.luckperms.common.metastacking.SimpleMetaStackDefinition; import me.lucko.luckperms.common.metastacking.StandardStackElements; +import me.lucko.luckperms.common.model.PrimaryGroupHolder; import me.lucko.luckperms.common.model.User; -import me.lucko.luckperms.common.primarygroup.AllParentsByWeightHolder; -import me.lucko.luckperms.common.primarygroup.ParentsByWeightHolder; -import me.lucko.luckperms.common.primarygroup.PrimaryGroupHolder; -import me.lucko.luckperms.common.primarygroup.StoredHolder; import me.lucko.luckperms.common.storage.StorageType; import me.lucko.luckperms.common.storage.implementation.split.SplitStorageType; import me.lucko.luckperms.common.storage.misc.StorageCredentials; @@ -162,11 +159,11 @@ public final class ConfigKeys { String option = PRIMARY_GROUP_CALCULATION_METHOD.get(c); switch (option) { case "stored": - return (Function) StoredHolder::new; + return (Function) PrimaryGroupHolder.Stored::new; case "parents-by-weight": - return (Function) ParentsByWeightHolder::new; + return (Function) PrimaryGroupHolder.ParentsByWeight::new; default: - return (Function) AllParentsByWeightHolder::new; + return (Function) PrimaryGroupHolder.AllParentsByWeight::new; } })); diff --git a/common/src/main/java/me/lucko/luckperms/common/metastacking/StandardStackElements.java b/common/src/main/java/me/lucko/luckperms/common/metastacking/StandardStackElements.java index 463b5b113..6023c3b01 100644 --- a/common/src/main/java/me/lucko/luckperms/common/metastacking/StandardStackElements.java +++ b/common/src/main/java/me/lucko/luckperms/common/metastacking/StandardStackElements.java @@ -28,9 +28,9 @@ package me.lucko.luckperms.common.metastacking; import me.lucko.luckperms.common.model.Track; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.util.ImmutableCollectors; -import me.lucko.luckperms.common.util.Uuids; import net.luckperms.api.metastacking.MetaStackElement; +import net.luckperms.api.model.PermissionHolder; import net.luckperms.api.node.ChatMetaType; import net.luckperms.api.node.metadata.types.InheritanceOriginMetadata; import net.luckperms.api.node.types.ChatMetaNode; @@ -97,8 +97,8 @@ public final class StandardStackElements { private static final MetaStackElement TYPE_CHECK = (type, node, current) -> type.nodeType().matches(node); private static final MetaStackElement HIGHEST_CHECK = (type, node, current) -> current == null || node.getPriority() > current.getPriority(); private static final MetaStackElement LOWEST_CHECK = (type, node, current) -> current == null || node.getPriority() < current.getPriority(); - private static final MetaStackElement OWN_CHECK = (type, node, current) -> Uuids.fromString(node.metadata(InheritanceOriginMetadata.KEY).getOrigin()) != null; - private static final MetaStackElement INHERITED_CHECK = (type, node, current) -> Uuids.fromString(node.metadata(InheritanceOriginMetadata.KEY).getOrigin()) == null; + private static final MetaStackElement OWN_CHECK = (type, node, current) -> node.metadata(InheritanceOriginMetadata.KEY).getOrigin().getType().equals(PermissionHolder.Identifier.USER_TYPE); + private static final MetaStackElement INHERITED_CHECK = (type, node, current) -> node.metadata(InheritanceOriginMetadata.KEY).getOrigin().getType().equals(PermissionHolder.Identifier.GROUP_TYPE); // implementations @@ -220,8 +220,9 @@ public final class StandardStackElements { @Override public boolean shouldAccumulate(@NonNull ChatMetaType type, @NonNull ChatMetaNode node, @Nullable ChatMetaNode current) { - Track t = this.plugin.getTrackManager().getIfLoaded(this.trackName); - return t != null && t.containsGroup(node.metadata(InheritanceOriginMetadata.KEY).getOrigin()); + Track track = this.plugin.getTrackManager().getIfLoaded(this.trackName); + PermissionHolder.Identifier origin = node.metadata(InheritanceOriginMetadata.KEY).getOrigin(); + return track != null && origin.getType().equals(PermissionHolder.Identifier.GROUP_TYPE) && track.containsGroup(origin.getName()); } @Override @@ -249,8 +250,9 @@ public final class StandardStackElements { @Override public boolean shouldAccumulate(@NonNull ChatMetaType type, @NonNull ChatMetaNode node, @Nullable ChatMetaNode current) { - Track t = this.plugin.getTrackManager().getIfLoaded(this.trackName); - return t != null && !t.containsGroup(node.metadata(InheritanceOriginMetadata.KEY).getOrigin()); + Track track = this.plugin.getTrackManager().getIfLoaded(this.trackName); + PermissionHolder.Identifier origin = node.metadata(InheritanceOriginMetadata.KEY).getOrigin(); + return track != null && !track.containsGroup(origin.getName()); } @Override @@ -276,7 +278,8 @@ public final class StandardStackElements { @Override public boolean shouldAccumulate(@NonNull ChatMetaType type, @NonNull ChatMetaNode node, @Nullable ChatMetaNode current) { - return this.groupName.equals(node.metadata(InheritanceOriginMetadata.KEY).getOrigin()); + PermissionHolder.Identifier origin = node.metadata(InheritanceOriginMetadata.KEY).getOrigin(); + return origin.getType().equals(PermissionHolder.Identifier.GROUP_TYPE) && this.groupName.equals(origin.getName()); } @Override @@ -302,7 +305,8 @@ public final class StandardStackElements { @Override public boolean shouldAccumulate(@NonNull ChatMetaType type, @NonNull ChatMetaNode node, @Nullable ChatMetaNode current) { - return !this.groupName.equals(node.metadata(InheritanceOriginMetadata.KEY).getOrigin()); + PermissionHolder.Identifier origin = node.metadata(InheritanceOriginMetadata.KEY).getOrigin(); + return !this.groupName.equals(origin.getName()); } @Override 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 ab914b6a3..87bc71029 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 @@ -198,11 +198,11 @@ public final class NodeMap { private Node localise(Node node) { Optional metadata = node.getMetadata(InheritanceOriginMetadata.KEY); - if (metadata.map(InheritanceOriginMetadata::getOrigin).equals(Optional.of(this.holder.getObjectName()))) { + if (metadata.isPresent() && metadata.get().getOrigin().equals(this.holder.getIdentifier())) { return node; } - return node.toBuilder().withMetadata(InheritanceOriginMetadata.KEY, new InheritanceOrigin(this.holder.getObjectName())).build(); + return node.toBuilder().withMetadata(InheritanceOriginMetadata.KEY, new InheritanceOrigin(this.holder.getIdentifier())).build(); } void add(Node node) { 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 a7e717386..ed5adcb23 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 @@ -52,6 +52,7 @@ import net.luckperms.api.query.QueryOptions; import net.luckperms.api.query.dataorder.DataQueryOrder; import net.luckperms.api.query.dataorder.DataQueryOrderFunction; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -100,6 +101,11 @@ public abstract class PermissionHolder { */ private final LuckPermsPlugin plugin; + /** + * The holders identifier + */ + private @MonotonicNonNull PermissionHolderIdentifier identifier; + /** * The holders persistent nodes. * @@ -175,7 +181,10 @@ public abstract class PermissionHolder { } public PermissionHolderIdentifier getIdentifier() { - return new PermissionHolderIdentifier(getType(), getObjectName()); + if (this.identifier == null) { + this.identifier = new PermissionHolderIdentifier(getType(), getObjectName()); + } + return this.identifier; } /** diff --git a/common/src/main/java/me/lucko/luckperms/common/model/PrimaryGroupHolder.java b/common/src/main/java/me/lucko/luckperms/common/model/PrimaryGroupHolder.java new file mode 100644 index 000000000..c66413660 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/model/PrimaryGroupHolder.java @@ -0,0 +1,182 @@ +/* + * 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.model; + +import me.lucko.luckperms.common.cache.LoadingMap; +import me.lucko.luckperms.common.config.ConfigKeys; +import me.lucko.luckperms.common.inheritance.InheritanceGraph; +import me.lucko.luckperms.common.model.manager.group.GroupManager; + +import net.luckperms.api.node.types.InheritanceNode; +import net.luckperms.api.query.QueryOptions; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +/** + * Calculates and caches a User's "primary group" + */ +public interface PrimaryGroupHolder { + + /** + * Gets the name of the primary group, or null. + * + * @return the name of the primary group, or null. + */ + String getValue(); + + /** + * Gets the primary group which is stored against the user's data. + * + * @return the stored value + */ + Optional getStoredValue(); + + /** + * Sets the primary group which is stored against the user's data. + * + * @param value the new stored value + */ + void setStoredValue(String value); + + + /** + * Simple implementation which just holds a stored value. + */ + class Stored implements PrimaryGroupHolder { + protected final User user; + private String value = null; + + public Stored(User user) { + this.user = Objects.requireNonNull(user, "user"); + } + + @Override + public String getValue() { + return this.value; + } + + @Override + public Optional getStoredValue() { + return Optional.ofNullable(this.value); + } + + @Override + public void setStoredValue(String value) { + if (value == null || value.isEmpty()) { + this.value = null; + } else { + this.value = value.toLowerCase(); + } + } + } + + /** + * Abstract implementation of {@link PrimaryGroupHolder} which caches all lookups by context. + */ + abstract class AbstractContextual extends Stored { + private final Map> cache = LoadingMap.of(this::calculateValue); + + AbstractContextual(User user) { + super(user); + } + + protected abstract @NonNull Optional calculateValue(QueryOptions queryOptions); + + public void invalidateCache() { + this.cache.clear(); + } + + @Override + public final String getValue() { + QueryOptions queryOptions = this.user.getPlugin().getQueryOptionsForUser(this.user).orElse(null); + if (queryOptions == null) { + queryOptions = this.user.getPlugin().getContextManager().getStaticQueryOptions(); + } + + return Objects.requireNonNull(this.cache.get(queryOptions)) + .orElseGet(() -> getStoredValue().orElse(GroupManager.DEFAULT_GROUP_NAME)); + } + + } + + class AllParentsByWeight extends AbstractContextual { + public AllParentsByWeight(User user) { + super(user); + } + + @Override + protected @NonNull Optional calculateValue(QueryOptions queryOptions) { + InheritanceGraph graph = this.user.getPlugin().getInheritanceHandler().getGraph(queryOptions); + + // fully traverse the graph, obtain a list of permission holders the user inherits from in weight order. + Iterable traversal = graph.traverse(this.user.getPlugin().getConfiguration().get(ConfigKeys.INHERITANCE_TRAVERSAL_ALGORITHM), true, this.user); + + // return the name of the first found group + for (PermissionHolder holder : traversal) { + if (holder instanceof Group) { + return Optional.of(((Group) holder).getName()); + } + } + return Optional.empty(); + } + } + + class ParentsByWeight extends AbstractContextual { + public ParentsByWeight(User user) { + super(user); + } + + @Override + protected @NonNull Optional calculateValue(QueryOptions queryOptions) { + Set groups = new LinkedHashSet<>(); + for (InheritanceNode node : this.user.getOwnInheritanceNodes(queryOptions)) { + Group group = this.user.getPlugin().getGroupManager().getIfLoaded(node.getGroupName()); + if (group != null) { + groups.add(group); + } + } + + Group bestGroup = null; + int best = 0; + + for (Group g : groups) { + int weight = g.getWeight().orElse(0); + if (bestGroup == null || weight > best) { + bestGroup = g; + best = weight; + } + } + + return bestGroup == null ? Optional.empty() : Optional.of(bestGroup.getName()); + } + } +} diff --git a/common/src/main/java/me/lucko/luckperms/common/model/User.java b/common/src/main/java/me/lucko/luckperms/common/model/User.java index c71f7ec6e..acefe3898 100644 --- a/common/src/main/java/me/lucko/luckperms/common/model/User.java +++ b/common/src/main/java/me/lucko/luckperms/common/model/User.java @@ -29,8 +29,6 @@ import me.lucko.luckperms.common.api.implementation.ApiUser; import me.lucko.luckperms.common.cacheddata.UserCachedDataManager; import me.lucko.luckperms.common.config.ConfigKeys; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; -import me.lucko.luckperms.common.primarygroup.ContextualHolder; -import me.lucko.luckperms.common.primarygroup.PrimaryGroupHolder; import org.checkerframework.checker.nullness.qual.Nullable; @@ -73,8 +71,8 @@ public class User extends PermissionHolder { super.invalidateCache(); // invalidate our caches - if (this.primaryGroup instanceof ContextualHolder) { - ((ContextualHolder) this.primaryGroup).invalidateCache(); + if (this.primaryGroup instanceof PrimaryGroupHolder.AbstractContextual) { + ((PrimaryGroupHolder.AbstractContextual) this.primaryGroup).invalidateCache(); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/node/model/InheritanceOrigin.java b/common/src/main/java/me/lucko/luckperms/common/node/model/InheritanceOrigin.java index 9a47214cf..1ca48c11d 100644 --- a/common/src/main/java/me/lucko/luckperms/common/node/model/InheritanceOrigin.java +++ b/common/src/main/java/me/lucko/luckperms/common/node/model/InheritanceOrigin.java @@ -25,19 +25,20 @@ package me.lucko.luckperms.common.node.model; +import net.luckperms.api.model.PermissionHolder; import net.luckperms.api.node.metadata.types.InheritanceOriginMetadata; import org.checkerframework.checker.nullness.qual.NonNull; public class InheritanceOrigin implements InheritanceOriginMetadata { - private final String location; + private final PermissionHolder.Identifier location; - public InheritanceOrigin(String location) { + public InheritanceOrigin(PermissionHolder.Identifier location) { this.location = location; } @Override - public @NonNull String getOrigin() { + public PermissionHolder.@NonNull Identifier getOrigin() { return this.location; } } diff --git a/common/src/main/java/me/lucko/luckperms/common/primarygroup/AllParentsByWeightHolder.java b/common/src/main/java/me/lucko/luckperms/common/primarygroup/AllParentsByWeightHolder.java deleted file mode 100644 index 229a7606b..000000000 --- a/common/src/main/java/me/lucko/luckperms/common/primarygroup/AllParentsByWeightHolder.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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.primarygroup; - -import me.lucko.luckperms.common.config.ConfigKeys; -import me.lucko.luckperms.common.inheritance.InheritanceGraph; -import me.lucko.luckperms.common.model.Group; -import me.lucko.luckperms.common.model.PermissionHolder; -import me.lucko.luckperms.common.model.User; - -import net.luckperms.api.query.QueryOptions; - -import org.checkerframework.checker.nullness.qual.NonNull; - -import java.util.Optional; - -public class AllParentsByWeightHolder extends ContextualHolder { - public AllParentsByWeightHolder(User user) { - super(user); - } - - @Override - protected @NonNull Optional calculateValue(QueryOptions queryOptions) { - InheritanceGraph graph = this.user.getPlugin().getInheritanceHandler().getGraph(queryOptions); - - // fully traverse the graph, obtain a list of permission holders the user inherits from in weight order. - Iterable traversal = graph.traverse(this.user.getPlugin().getConfiguration().get(ConfigKeys.INHERITANCE_TRAVERSAL_ALGORITHM), true, this.user); - - // return the name of the first found group - for (PermissionHolder holder : traversal) { - if (holder instanceof Group) { - return Optional.of(((Group) holder).getName()); - } - } - return Optional.empty(); - } -} diff --git a/common/src/main/java/me/lucko/luckperms/common/primarygroup/ContextualHolder.java b/common/src/main/java/me/lucko/luckperms/common/primarygroup/ContextualHolder.java deleted file mode 100644 index 118feb5f1..000000000 --- a/common/src/main/java/me/lucko/luckperms/common/primarygroup/ContextualHolder.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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.primarygroup; - -import com.github.benmanes.caffeine.cache.LoadingCache; - -import me.lucko.luckperms.common.model.User; -import me.lucko.luckperms.common.model.manager.group.GroupManager; -import me.lucko.luckperms.common.util.CaffeineFactory; - -import net.luckperms.api.query.QueryOptions; - -import org.checkerframework.checker.nullness.qual.NonNull; - -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.TimeUnit; - -/** - * Abstract implementation of {@link PrimaryGroupHolder} which caches all lookups. - */ -public abstract class ContextualHolder extends StoredHolder { - - // cache lookups - private final LoadingCache> cache = CaffeineFactory.newBuilder() - .expireAfterAccess(1, TimeUnit.MINUTES) - .build(this::calculateValue); - - public ContextualHolder(User user) { - super(user); - } - - protected abstract @NonNull Optional calculateValue(QueryOptions queryOptions); - - public void invalidateCache() { - this.cache.invalidateAll(); - } - - @Override - public final String getValue() { - QueryOptions queryOptions = this.user.getPlugin().getQueryOptionsForUser(this.user).orElse(null); - if (queryOptions == null) { - queryOptions = this.user.getPlugin().getContextManager().getStaticQueryOptions(); - } - - return Objects.requireNonNull(this.cache.get(queryOptions)) - .orElseGet(() -> getStoredValue().orElse(GroupManager.DEFAULT_GROUP_NAME)); - } - -} diff --git a/common/src/main/java/me/lucko/luckperms/common/primarygroup/ParentsByWeightHolder.java b/common/src/main/java/me/lucko/luckperms/common/primarygroup/ParentsByWeightHolder.java deleted file mode 100644 index e9f599d13..000000000 --- a/common/src/main/java/me/lucko/luckperms/common/primarygroup/ParentsByWeightHolder.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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.primarygroup; - -import me.lucko.luckperms.common.model.Group; -import me.lucko.luckperms.common.model.User; - -import net.luckperms.api.node.types.InheritanceNode; -import net.luckperms.api.query.QueryOptions; - -import org.checkerframework.checker.nullness.qual.NonNull; - -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.Set; - -public class ParentsByWeightHolder extends ContextualHolder { - public ParentsByWeightHolder(User user) { - super(user); - } - - @Override - protected @NonNull Optional calculateValue(QueryOptions queryOptions) { - Set groups = new LinkedHashSet<>(); - for (InheritanceNode node : this.user.getOwnInheritanceNodes(queryOptions)) { - Group group = this.user.getPlugin().getGroupManager().getIfLoaded(node.getGroupName()); - if (group != null) { - groups.add(group); - } - } - - Group bestGroup = null; - int best = 0; - - for (Group g : groups) { - int weight = g.getWeight().orElse(0); - if (bestGroup == null || weight > best) { - bestGroup = g; - best = weight; - } - } - - return bestGroup == null ? Optional.empty() : Optional.of(bestGroup.getName()); - } -} diff --git a/common/src/main/java/me/lucko/luckperms/common/primarygroup/PrimaryGroupHolder.java b/common/src/main/java/me/lucko/luckperms/common/primarygroup/PrimaryGroupHolder.java deleted file mode 100644 index dfff6809d..000000000 --- a/common/src/main/java/me/lucko/luckperms/common/primarygroup/PrimaryGroupHolder.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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.primarygroup; - -import java.util.Optional; - -/** - * Calculates and caches a User's "primary group" - */ -public interface PrimaryGroupHolder { - - /** - * Gets the name of the primary group, or null. - * - * @return the name of the primary group, or null. - */ - String getValue(); - - /** - * Gets the primary group which is stored against the user's data. - * - * @return the stored value - */ - Optional getStoredValue(); - - /** - * Sets the primary group which is stored against the user's data. - * - * @param storedValue the new stored value - */ - void setStoredValue(String storedValue); - -} diff --git a/common/src/main/java/me/lucko/luckperms/common/primarygroup/StoredHolder.java b/common/src/main/java/me/lucko/luckperms/common/primarygroup/StoredHolder.java deleted file mode 100644 index 367110578..000000000 --- a/common/src/main/java/me/lucko/luckperms/common/primarygroup/StoredHolder.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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.primarygroup; - -import me.lucko.luckperms.common.model.User; - -import java.util.Objects; -import java.util.Optional; - -/** - * Simple implementation of {@link PrimaryGroupHolder}, which just returns the stored value. - */ -public class StoredHolder implements PrimaryGroupHolder { - - protected final User user; - - private String storedValue = null; - - public StoredHolder(User user) { - this.user = Objects.requireNonNull(user, "user"); - } - - @Override - public String getValue() { - return this.storedValue; - } - - @Override - public Optional getStoredValue() { - return Optional.ofNullable(this.storedValue); - } - - @Override - public void setStoredValue(String storedValue) { - if (storedValue == null || storedValue.isEmpty()) { - this.storedValue = null; - } else { - this.storedValue = storedValue.toLowerCase(); - } - } -} diff --git a/nukkit/src/main/resources/config.yml b/nukkit/src/main/resources/config.yml index 286fabd77..9e8f8a7e2 100644 --- a/nukkit/src/main/resources/config.yml +++ b/nukkit/src/main/resources/config.yml @@ -4,7 +4,7 @@ # | | | | / ` |__/ |__) |__ |__) |\/| /__` | # # | |___ \__/ \__, | \ | |___ | \ | | .__/ | # # | | # -# | https://luckperms.github.io | # +# | https://luckperms.net | # # | | # # | SOURCE CODE: https://github.com/lucko/LuckPerms | # # | WIKI: https://github.com/lucko/LuckPerms/wiki | # diff --git a/nukkit/src/main/resources/plugin.yml b/nukkit/src/main/resources/plugin.yml index 697ad5141..2f40ed8c0 100644 --- a/nukkit/src/main/resources/plugin.yml +++ b/nukkit/src/main/resources/plugin.yml @@ -3,7 +3,7 @@ version: ${pluginVersion} api: ["1.0.5"] description: A permissions plugin author: Luck -website: https://luckperms.github.io +website: https://luckperms.net main: me.lucko.luckperms.nukkit.LPNukkitBootstrap load: STARTUP diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongeBootstrap.java b/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongeBootstrap.java index 7c869f12c..f5779db44 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongeBootstrap.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongeBootstrap.java @@ -71,7 +71,7 @@ import java.util.stream.Stream; version = "@version@", authors = "Luck", description = "A permissions plugin", - url = "https://luckperms.github.io", + url = "https://luckperms.net", dependencies = { // explicit dependency on spongeapi with no defined API version @Dependency(id = "spongeapi") diff --git a/sponge/src/main/resources/luckperms.conf b/sponge/src/main/resources/luckperms.conf index 475a78a8e..195bb893c 100644 --- a/sponge/src/main/resources/luckperms.conf +++ b/sponge/src/main/resources/luckperms.conf @@ -4,7 +4,7 @@ # | | | | / ` |__/ |__) |__ |__) |\/| /__` | # # | |___ \__/ \__, | \ | |___ | \ | | .__/ | # # | | # -# | https://luckperms.github.io | # +# | https://luckperms.net | # # | | # # | SOURCE CODE: https://github.com/lucko/LuckPerms | # # | WIKI: https://github.com/lucko/LuckPerms/wiki | # diff --git a/velocity/src/main/java/me/lucko/luckperms/velocity/LPVelocityBootstrap.java b/velocity/src/main/java/me/lucko/luckperms/velocity/LPVelocityBootstrap.java index 6573a1b3e..850b8b8fe 100644 --- a/velocity/src/main/java/me/lucko/luckperms/velocity/LPVelocityBootstrap.java +++ b/velocity/src/main/java/me/lucko/luckperms/velocity/LPVelocityBootstrap.java @@ -61,7 +61,7 @@ import java.util.stream.Stream; version = "@version@", authors = "Luck", description = "A permissions plugin", - url = "https://luckperms.github.io" + url = "https://luckperms.net" ) public class LPVelocityBootstrap implements LuckPermsBootstrap { diff --git a/velocity/src/main/resources/config.yml b/velocity/src/main/resources/config.yml index bf9db02ec..ba67c0739 100644 --- a/velocity/src/main/resources/config.yml +++ b/velocity/src/main/resources/config.yml @@ -4,7 +4,7 @@ # | | | | / ` |__/ |__) |__ |__) |\/| /__` | # # | |___ \__/ \__, | \ | |___ | \ | | .__/ | # # | | # -# | https://luckperms.github.io | # +# | https://luckperms.net | # # | | # # | SOURCE CODE: https://github.com/lucko/LuckPerms | # # | WIKI: https://github.com/lucko/LuckPerms/wiki | #