diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/inject/PermissionModule.java b/Bukkit/src/main/java/com/plotsquared/bukkit/inject/PermissionModule.java
index a2a8c50f9..7c29f7713 100644
--- a/Bukkit/src/main/java/com/plotsquared/bukkit/inject/PermissionModule.java
+++ b/Bukkit/src/main/java/com/plotsquared/bukkit/inject/PermissionModule.java
@@ -22,12 +22,20 @@ import com.google.inject.AbstractModule;
 import com.google.inject.Provides;
 import com.google.inject.Singleton;
 import com.plotsquared.bukkit.permissions.BukkitPermissionHandler;
+import com.plotsquared.bukkit.permissions.BukkitRangedPermissionResolver;
+import com.plotsquared.bukkit.permissions.LuckPermsRangedPermissionResolver;
 import com.plotsquared.bukkit.permissions.VaultPermissionHandler;
+import com.plotsquared.core.configuration.Settings;
 import com.plotsquared.core.permissions.PermissionHandler;
+import com.plotsquared.core.permissions.RangedPermissionResolver;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
 import org.bukkit.Bukkit;
 
 public class PermissionModule extends AbstractModule {
 
+    private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + PermissionModule.class.getSimpleName());
+
     @Provides
     @Singleton
     PermissionHandler providePermissionHandler() {
@@ -40,4 +48,18 @@ public class PermissionModule extends AbstractModule {
         return new BukkitPermissionHandler();
     }
 
+    @Provides
+    @Singleton
+    RangedPermissionResolver provideRangedPermissionResolver() {
+        if (Settings.Permissions.USE_LUCKPERMS_RANGE_RESOLVER) {
+            if (Bukkit.getPluginManager().isPluginEnabled("LuckPerms")) {
+                LOGGER.info("Using experimental LuckPerms ranged permission resolver");
+                return new LuckPermsRangedPermissionResolver();
+            }
+            LOGGER.warn("Enabled LuckPerms ranged permission resolver, but LuckPerms is not installed. " +
+                    "Falling back to default Bukkit ranged permission resolver");
+        }
+        return new BukkitRangedPermissionResolver();
+    }
+
 }
diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/permissions/BukkitRangedPermissionResolver.java b/Bukkit/src/main/java/com/plotsquared/bukkit/permissions/BukkitRangedPermissionResolver.java
new file mode 100644
index 000000000..22349f952
--- /dev/null
+++ b/Bukkit/src/main/java/com/plotsquared/bukkit/permissions/BukkitRangedPermissionResolver.java
@@ -0,0 +1,107 @@
+/*
+ * PlotSquared, a land and world management plugin for Minecraft.
+ * Copyright (C) IntellectualSites <https://intellectualsites.com>
+ * Copyright (C) IntellectualSites team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package com.plotsquared.bukkit.permissions;
+
+import com.plotsquared.bukkit.player.BukkitPlayer;
+import com.plotsquared.core.permissions.RangedPermissionResolver;
+import com.plotsquared.core.player.PlotPlayer;
+import com.plotsquared.core.util.MathMan;
+import org.bukkit.permissions.PermissionAttachmentInfo;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.Set;
+import java.util.stream.IntStream;
+
+public class BukkitRangedPermissionResolver implements RangedPermissionResolver {
+
+    private static boolean CHECK_EFFECTIVE = true;
+
+    @Override
+    public @NonNegative int getPermissionRange(
+            final @NonNull PlotPlayer<?> generic,
+            final @NonNull String stub,
+            final @Nullable String worldContext,
+            @NonNegative final int range
+    ) {
+        if (!(generic instanceof BukkitPlayer player)) {
+            throw new IllegalArgumentException("PlotPlayer is not a BukkitPlayer");
+        }
+        if (hasWildcardRange(player, stub, worldContext)) {
+            return INFINITE_RANGE_VALUE;
+        }
+        int max = 0;
+        if (CHECK_EFFECTIVE) {
+            boolean hasAny = false;
+            String stubPlus = stub + ".";
+            final Set<PermissionAttachmentInfo> effective = player.getPlatformPlayer().getEffectivePermissions();
+            if (!effective.isEmpty()) {
+                for (PermissionAttachmentInfo attach : effective) {
+                    // Ignore all "false" permissions
+                    if (!attach.getValue()) {
+                        continue;
+                    }
+                    String permStr = attach.getPermission();
+                    if (permStr.startsWith(stubPlus)) {
+                        hasAny = true;
+                        String end = permStr.substring(stubPlus.length());
+                        if (MathMan.isInteger(end)) {
+                            int val = Integer.parseInt(end);
+                            if (val > range) {
+                                return val;
+                            }
+                            if (val > max) {
+                                max = val;
+                            }
+                        }
+                    }
+                }
+                if (hasAny) {
+                    return max;
+                }
+                // Workaround
+                for (PermissionAttachmentInfo attach : effective) {
+                    String permStr = attach.getPermission();
+                    if (permStr.startsWith("plots.") && !permStr.equals("plots.use")) {
+                        return max;
+                    }
+                }
+                CHECK_EFFECTIVE = false;
+            }
+        }
+        for (int i = range; i > 0; i--) {
+            if (player.hasPermission(worldContext, stub + "." + i)) {
+                return i;
+            }
+        }
+        return max;
+    }
+
+    @Override
+    public @NonNull IntStream streamFullPermissionRange(
+            final @NonNull PlotPlayer<?> player,
+            final @NonNull String stub,
+            final @Nullable String worldContext,
+            @NonNegative final int range
+    ) {
+        return IntStream.of(getPermissionRange(player, stub, worldContext, range));
+    }
+
+}
diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/permissions/LuckPermsRangedPermissionResolver.java b/Bukkit/src/main/java/com/plotsquared/bukkit/permissions/LuckPermsRangedPermissionResolver.java
new file mode 100644
index 000000000..d589ce251
--- /dev/null
+++ b/Bukkit/src/main/java/com/plotsquared/bukkit/permissions/LuckPermsRangedPermissionResolver.java
@@ -0,0 +1,95 @@
+/*
+ * PlotSquared, a land and world management plugin for Minecraft.
+ * Copyright (C) IntellectualSites <https://intellectualsites.com>
+ * Copyright (C) IntellectualSites team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package com.plotsquared.bukkit.permissions;
+
+import com.plotsquared.core.permissions.RangedPermissionResolver;
+import com.plotsquared.core.player.PlotPlayer;
+import com.plotsquared.core.util.MathMan;
+import net.luckperms.api.LuckPerms;
+import net.luckperms.api.context.ImmutableContextSet;
+import net.luckperms.api.model.user.User;
+import net.luckperms.api.node.NodeType;
+import net.luckperms.api.node.types.PermissionNode;
+import net.luckperms.api.query.QueryOptions;
+import org.bukkit.Bukkit;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.Objects;
+import java.util.stream.IntStream;
+
+public class LuckPermsRangedPermissionResolver implements RangedPermissionResolver {
+
+    private final LuckPerms luckPerms;
+
+    public LuckPermsRangedPermissionResolver() {
+        this.luckPerms = Objects.requireNonNull(
+                Bukkit.getServicesManager().getRegistration(LuckPerms.class),
+                "LuckPerms is not available"
+        ).getProvider();
+    }
+
+    @Override
+    public @NonNegative int getPermissionRange(
+            final @NonNull PlotPlayer<?> player,
+            final @NonNull String stub,
+            final @Nullable String worldContext,
+            final @NonNegative int range
+    ) {
+        // no need to use LuckPerms for basic checks
+        if (this.hasWildcardRange(player, stub, worldContext)) {
+            return INFINITE_RANGE_VALUE;
+        }
+        return this.streamFullPermissionRange(player, stub, worldContext, range)
+                .sorted()
+                .reduce((first, second) -> second)
+                .orElse(0);
+    }
+
+    @NonNull
+    public IntStream streamFullPermissionRange(
+            final @NonNull PlotPlayer<?> player,
+            final @NonNull String stub,
+            final @Nullable String worldContext,
+            @NonNegative final int range
+    ) {
+        final User user = this.luckPerms.getUserManager().getUser(player.getUUID());
+        if (user == null) {
+            throw new IllegalStateException("Luckperms User is null - is the Player online? (UUID: %s)".formatted(player.getUUID()));
+        }
+        final QueryOptions queryOptions = worldContext == null ?
+                QueryOptions.nonContextual() :
+                QueryOptions.contextual(ImmutableContextSet.of("world", worldContext));
+        return user.resolveInheritedNodes(queryOptions).stream()
+                // only support normal permission nodes (regex permission nodes would be a pita to support)
+                .filter(NodeType.PERMISSION::matches)
+                .map(node -> ((PermissionNode) node).getPermission())
+                // check that the node actually has additional data after the stub
+                .filter(permission -> permission.startsWith(stub + "."))
+                // extract the raw data after the stub
+                .map(permission -> permission.substring(stub.length() + 1))
+                // check if data is integer and parse
+                .filter(MathMan::isInteger)
+                .mapToInt(Integer::parseInt)
+                // only use values that are positive
+                .filter(value -> value > -1);
+    }
+
+}
diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/player/BukkitPlayer.java b/Bukkit/src/main/java/com/plotsquared/bukkit/player/BukkitPlayer.java
index bd9c99287..25dda34fb 100644
--- a/Bukkit/src/main/java/com/plotsquared/bukkit/player/BukkitPlayer.java
+++ b/Bukkit/src/main/java/com/plotsquared/bukkit/player/BukkitPlayer.java
@@ -24,14 +24,12 @@ import com.plotsquared.core.PlotSquared;
 import com.plotsquared.core.configuration.Settings;
 import com.plotsquared.core.events.TeleportCause;
 import com.plotsquared.core.location.Location;
-import com.plotsquared.core.permissions.Permission;
 import com.plotsquared.core.permissions.PermissionHandler;
 import com.plotsquared.core.player.ConsolePlayer;
 import com.plotsquared.core.player.PlotPlayer;
 import com.plotsquared.core.plot.PlotWeather;
 import com.plotsquared.core.plot.world.PlotAreaManager;
 import com.plotsquared.core.util.EventDispatcher;
-import com.plotsquared.core.util.MathMan;
 import com.plotsquared.core.util.WorldUtil;
 import com.sk89q.worldedit.bukkit.BukkitAdapter;
 import com.sk89q.worldedit.extension.platform.Actor;
@@ -47,13 +45,11 @@ import org.bukkit.entity.Player;
 import org.bukkit.event.Event;
 import org.bukkit.event.EventException;
 import org.bukkit.event.player.PlayerTeleportEvent;
-import org.bukkit.permissions.PermissionAttachmentInfo;
 import org.bukkit.plugin.RegisteredListener;
 import org.bukkit.potion.PotionEffectType;
 import org.checkerframework.checker.index.qual.NonNegative;
 import org.checkerframework.checker.nullness.qual.NonNull;
 
-import java.util.Set;
 import java.util.UUID;
 
 import static com.sk89q.worldedit.world.gamemode.GameModes.ADVENTURE;
@@ -150,77 +146,13 @@ public class BukkitPlayer extends PlotPlayer<Player> {
         }
     }
 
-    @SuppressWarnings("StringSplitter")
     @Override
     @NonNegative
     public int hasPermissionRange(
             final @NonNull String stub,
             @NonNegative final int range
     ) {
-        if (hasPermission(Permission.PERMISSION_ADMIN.toString())) {
-            return Integer.MAX_VALUE;
-        }
-        final String[] nodes = stub.split("\\.");
-        final StringBuilder n = new StringBuilder();
-        // Wildcard check from less specific permission to more specific permission
-        for (int i = 0; i < (nodes.length - 1); i++) {
-            n.append(nodes[i]).append(".");
-            if (!stub.equals(n + Permission.PERMISSION_STAR.toString())) {
-                if (hasPermission(n + Permission.PERMISSION_STAR.toString())) {
-                    return Integer.MAX_VALUE;
-                }
-            }
-        }
-        // Wildcard check for the full permission
-        if (hasPermission(stub + ".*")) {
-            return Integer.MAX_VALUE;
-        }
-        // Permission value cache for iterative check
-        int max = 0;
-        if (CHECK_EFFECTIVE) {
-            boolean hasAny = false;
-            String stubPlus = stub + ".";
-            final Set<PermissionAttachmentInfo> effective = player.getEffectivePermissions();
-            if (!effective.isEmpty()) {
-                for (PermissionAttachmentInfo attach : effective) {
-                    // Ignore all "false" permissions
-                    if (!attach.getValue()) {
-                        continue;
-                    }
-                    String permStr = attach.getPermission();
-                    if (permStr.startsWith(stubPlus)) {
-                        hasAny = true;
-                        String end = permStr.substring(stubPlus.length());
-                        if (MathMan.isInteger(end)) {
-                            int val = Integer.parseInt(end);
-                            if (val > range) {
-                                return val;
-                            }
-                            if (val > max) {
-                                max = val;
-                            }
-                        }
-                    }
-                }
-                if (hasAny) {
-                    return max;
-                }
-                // Workaround
-                for (PermissionAttachmentInfo attach : effective) {
-                    String permStr = attach.getPermission();
-                    if (permStr.startsWith("plots.") && !permStr.equals("plots.use")) {
-                        return max;
-                    }
-                }
-                CHECK_EFFECTIVE = false;
-            }
-        }
-        for (int i = range; i > 0; i--) {
-            if (hasPermission(stub + "." + i)) {
-                return i;
-            }
-        }
-        return max;
+        return PlotSquared.platform().rangedPermissionResolver().getPermissionRange(this, stub, null, range);
     }
 
     @Override
diff --git a/Core/src/main/java/com/plotsquared/core/PlotPlatform.java b/Core/src/main/java/com/plotsquared/core/PlotPlatform.java
index 844d553b6..c73a46623 100644
--- a/Core/src/main/java/com/plotsquared/core/PlotPlatform.java
+++ b/Core/src/main/java/com/plotsquared/core/PlotPlatform.java
@@ -31,6 +31,7 @@ import com.plotsquared.core.generator.IndependentPlotGenerator;
 import com.plotsquared.core.inject.annotations.DefaultGenerator;
 import com.plotsquared.core.location.World;
 import com.plotsquared.core.permissions.PermissionHandler;
+import com.plotsquared.core.permissions.RangedPermissionResolver;
 import com.plotsquared.core.player.PlotPlayer;
 import com.plotsquared.core.plot.expiration.ExpireManager;
 import com.plotsquared.core.plot.world.PlotAreaManager;
@@ -349,6 +350,10 @@ public interface PlotPlatform<P> extends LocaleHolder {
         return injector().getInstance(PermissionHandler.class);
     }
 
+    default @NonNull RangedPermissionResolver rangedPermissionResolver() {
+        return injector().getInstance(RangedPermissionResolver.class);
+    }
+
     /**
      * Get the {@link ServicePipeline} implementation
      *
diff --git a/Core/src/main/java/com/plotsquared/core/configuration/Settings.java b/Core/src/main/java/com/plotsquared/core/configuration/Settings.java
index 04e88a40c..190f7c1fb 100644
--- a/Core/src/main/java/com/plotsquared/core/configuration/Settings.java
+++ b/Core/src/main/java/com/plotsquared/core/configuration/Settings.java
@@ -525,8 +525,8 @@ public class Settings extends Config {
     public static final class Limit {
 
         @Comment("Should the limit be global (over multiple worlds)")
-        public static boolean GLOBAL =
-                false;
+        public static boolean GLOBAL = false;
+
         @Comment({"The max range of integer permissions to check for, e.g. 'plots.plot.127' or 'plots.set.flag.mob-cap.127'",
                 "The value covers the permission range to check, you need to assign the permission to players/groups still",
                 "Modifying the value does NOT change the amount of plots players can claim"})
@@ -737,6 +737,7 @@ public class Settings extends Config {
 
         @Comment("If \"instabreak\" should consider the used tool.")
         public static boolean INSTABREAK_CONSIDER_TOOL = false;
+
     }
 
     @Comment({"Enable or disable parts of the plugin",
@@ -830,4 +831,17 @@ public class Settings extends Config {
 
     }
 
+    @Comment({"Permission-Resolver specific settings"})
+    public static final class Permissions {
+
+        @Comment({
+                "use the new LuckPerms resolver for ranged permissions (e.g., plots.plot.<number>).",
+                "in contrary to the default resolver, this resolver can handle inheritance of permissions.",
+                "e.g. if the player has multiple matching permissions, for example, due to multiple group memberships " +
+                        "(plots.plot.5 & plots.plot.15), this resolver would use 15 as the limit as opposed to the previous 5."
+        })
+        public static boolean USE_LUCKPERMS_RANGE_RESOLVER = false;
+
+    }
+
 }
diff --git a/Core/src/main/java/com/plotsquared/core/permissions/RangedPermissionResolver.java b/Core/src/main/java/com/plotsquared/core/permissions/RangedPermissionResolver.java
new file mode 100644
index 000000000..91aa592f8
--- /dev/null
+++ b/Core/src/main/java/com/plotsquared/core/permissions/RangedPermissionResolver.java
@@ -0,0 +1,126 @@
+/*
+ * PlotSquared, a land and world management plugin for Minecraft.
+ * Copyright (C) IntellectualSites <https://intellectualsites.com>
+ * Copyright (C) IntellectualSites team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package com.plotsquared.core.permissions;
+
+import com.plotsquared.core.player.PlotPlayer;
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.stream.IntStream;
+
+/**
+ * Represents a resolver for ranged permissions. Return values depend on the actual implementation (see Bukkit module).
+ * <br>
+ * Even though this interface is not linked to platform implementations by design, implementation-specific details are added to
+ * the Javadocs.
+ *
+ * @since TODO
+ */
+public interface RangedPermissionResolver {
+
+    int INFINITE_RANGE_VALUE = Integer.MAX_VALUE;
+
+    /**
+     * Gets the applicable range value of a player for a specific permission stub
+     * ({@code plots.plot} would check for {@code plots.plot.<numeric>}).
+     * <br>
+     * The standard bukkit implementation would return the lowest numeric value, while the LuckPerms specific resolver would
+     * try returning the highest possible value.
+     *
+     * @param player       the permission holder
+     * @param stub         the permission stub to check against
+     * @param worldContext the optional world context of the action requiring the range
+     * @param range        the maximum permission range to check against (for the default bukkit resolver)
+     * @return the applicable range value of the player for the given permission stub
+     * @since TODO
+     */
+    @NonNegative
+    int getPermissionRange(
+            final @NonNull PlotPlayer<?> player,
+            final @NonNull String stub,
+            final @Nullable String worldContext,
+            final @NonNegative int range
+    );
+
+    /**
+     * Gets a stream of all applicable permission range values for the given stub. The stream is unordered by default. If a
+     * specific order is needed, use the stateful {@link IntStream#sorted()} operation.
+     * <br>
+     * The standard bukkit implementation will only return a stream containing a single value equal to
+     * {@link #getPermissionRange(PlotPlayer, String, String, int)}. For LuckPerms, all applicable node values will be in the
+     * stream.
+     *
+     * @param player       the permission holder
+     * @param stub         the permission stub to check against
+     * @param worldContext the optional world context of the action requiring the range
+     * @param range        the maximum permission range to check against (for the default bukkit resolver)
+     * @return a stream of all applicable permission range values for the given stub
+     * @since TODO
+     */
+    @NonNull
+    IntStream streamFullPermissionRange(
+            final @NonNull PlotPlayer<?> player,
+            final @NonNull String stub,
+            final @Nullable String worldContext,
+            final @NonNegative int range
+    );
+
+    /**
+     * Checks if the given player has a wildcard range for the given permission stub.
+     * <br>
+     * For example, if checking for the stub {@code plots.plot}, this method would check for:
+     * <ul>
+     *     <li>{@code *}</li>
+     *     <li>{@code plots.admin}</li>
+     *     <li>{@code plots.plot.*}</li>
+     *     <li>{@code plots.*}</li>
+     * </ul>
+     *
+     * @param player       the permission holder
+     * @param stub         the permission stub to check against
+     * @param worldContext the optional world context of the action requiring the range
+     * @return {@code true} if the player has a wildcard range for the given permission stub, else {@code false}
+     * @since TODO
+     */
+    default boolean hasWildcardRange(
+            final @NonNull PlotPlayer<?> player,
+            final @NonNull String stub,
+            final @Nullable String worldContext
+    ) {
+        if (player.hasPermission(Permission.PERMISSION_STAR) ||
+                player.hasPermission(Permission.PERMISSION_ADMIN) ||
+                player.hasPermission(worldContext, stub + ".*")) {
+            return true;
+        }
+        String node = stub;
+        while (true) {
+            int lastIndex = node.lastIndexOf('.');
+            if (lastIndex == -1) {
+                break;
+            }
+            node = node.substring(0, lastIndex);
+            if (player.hasPermission(worldContext, node + ".*")) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+}