diff --git a/CHANGELOG.md b/CHANGELOG.md index ac46dc9..08a5b66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.3.0] - 2025-08-05 +### Added +- Fine-grained selector permission +- Selector weights +- Selector limits + ## [0.2.11] - 2025-07-10 ### Fixed - Gamemode switcher in 1.21.6+ @@ -53,6 +59,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Using wrong fallback command node requirement +### Changed +- `minecraft.command..*` are granted by default + ## [0.2.1] - 2023-07-01 ### Fixed - Checking permissions to early @@ -97,4 +106,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Use API for offline permissions ## [0.1.0] - 2022-10-07 -Init \ No newline at end of file +Init diff --git a/README.md b/README.md index 74dfd22..46645aa 100644 --- a/README.md +++ b/README.md @@ -8,40 +8,212 @@ This mod adds permission checks into vanilla, to allow for full permission custo ## Permissions -| Permission | Description | -|---------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------| -| `minecraft.adminbroadcast.receive` | Receive command feedback | -| `minecraft.bypass.spawn-protection` | Build inside spawn protection | -| `minecraft.bypass.force-gamemode` | Bypass forced gamemode | -| `minecraft.bypass.move-speed.player` | Bypass "Player moved too fast" | -| `minecraft.bypass.move-speed.vehicle.` | Bypass "Player moved too fast", while riding an `entity` (e.g `minecraft.boat`) | -| `minecraft.bypass.chat-speed` | Bypass chat kick, when sending messages / commands to quick | -| `minecraft.bypass.whitelist` | Bypass server whitelist | -| `minecraft.bypass.player-limit` | Bypass server player limit | -| `minecraft.command.` | Command permissions, see [commands](#commands) for more information | -| `minecraft.debug_stick.use.` | Use debug stick on `block` (e.g. `minecraft.oak_trapdoor`) | -| `minecraft.debug_chart` | View debug chart | -| `minecraft..` | Place blocks with nbt data and use debug commands | -| `minecraft.operator_block..` | Place, view, edit and break operator blocks. | -| `minecraft.selector` | Use entity selectors in commands | +| Permission | Description | +|-------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------| +| `minecraft.adminbroadcast.receive` | Receive command feedback | +| `minecraft.bypass.spawn-protection` | Build inside spawn protection | +| `minecraft.bypass.force-gamemode` | Bypass forced gamemode | +| `minecraft.bypass.move-speed.player` | Bypass "Player moved too fast" | +| `minecraft.bypass.move-speed.vehicle.` | Bypass "Player moved too fast", while riding an `entity` (e.g `minecraft.boat`) | +| `minecraft.bypass.chat-speed` | Bypass chat kick, when sending messages / commands to quick | +| `minecraft.bypass.whitelist` | Bypass server whitelist | +| `minecraft.bypass.player-limit` | Bypass server player limit | +| [`minecraft.command.`](#commands) | Command permissions, see [commands](#commands) for more information | +| `minecraft.debug_stick.use.` | Use debug stick on `block` (e.g. `minecraft.oak_trapdoor`) | +| `minecraft.debug_chart` | View debug chart | +| `minecraft..` | Place blocks with nbt data and use debug commands | +| `minecraft.operator_block..` | Place, view, edit and break operator blocks. | +| `minecraft.selector` | Use entity selectors in commands | +| [`minecraft.selector.entity.`](#selectors) | Allow selecting [non-player entities](#scope-control) using the `selector` | +| [`minecraft.selector.player.`](#selectors) | Allow selecting [nonself players](#scope-control) using the `selector` | +| [`minecraft.selector.self.`](#selectors) | Allow selecting [self](#scope-control) using the `selector` | + +## Meta + +Also sometimes referred to as "options" or "variables". + +Incorrect types are considered undefined values. + +| Meta | Type | Description | +|------------------------------------------------------|-----------|-------------------------------------------------------------------------------------------------| +| [`minecraft.selector.limit.`](#selectors) | `Integer` | [Limit the maximum number](#entity-limit) of entities that can be selected using the `selector` | +| [`minecraft.selector.weight.`](#selectors) | `Integer` | Selector weight, see [selection weight](#selection-weight) for more infomation | ## Commands -This mod uses [Brigadier's](https://github.com/Mojang/brigadier) node-based permission system. Each command is made up of multiple nodes, and each node has its own permission. +This mod uses [Brigadier's](https://github.com/Mojang/brigadier) node-based permission system. Each command is made up +of multiple nodes, and each node has its own permission. For example, the `/gamemode` command: - The root command node (`/gamemode`) requires minecraft.command.gamemode. -- Sub-nodes like `survival`, `creative`, etc., use `minecraft.command.gamemode.survival`, `minecraft.command.gamemode.creative`, and so on. +- Sub-nodes like `survival`, `creative`, etc., use `minecraft.command.gamemode.survival`, + `minecraft.command.gamemode.creative`, and so on. -In vanilla Minecraft, only the **root node** has a permission check (e.g. OP level 2). Once a player has access to that root node, **all sub-nodes are considered unlocked by default**. +In vanilla Minecraft, only the **root node** has a permission check (e.g. OP level 2). Once a player has access to that +root node, **all sub-nodes are considered unlocked by default**. If you want finer control, you can manually restrict sub-nodes by denying their specific permissions. -For example: -- Allow `minecraft.command.gamemode` -- Deny `minecraft.command.gamemode.creative` and `minecraft.command.gamemode.spectator` +#### Example -This allows players to use `/gamemode` but restricts them to only the allowed sub-options (e.g., survival and adventure). +```yml +Allow: + minecraft.command.gamemode +Deny: + minecraft.command.gamemode.creative + minecraft.command.gamemode.spectator +``` + +This allows players to use `/gamemode` but restricts them to only the allowed sub-options +(e.g., survival and adventure). + +For other commands, see *Syntax* section of each command's Minecraft Wiki page. + +## Selectors + +Command blocks and datapacks bypass all selector permission checks. + +By default, granting `minecraft.selector` allows players to use any selector in commands they have access to. + +Fine-grained permission control operates as follows. Note that this mod restricts based on **selection results**, not +raw selector syntax. Using player names, UUIDs, or selectors like `@e` are equivalent if they produce identical +results. + +### Value of `` + +The `` string follows the format: `..` where: +* `` is the root command name +* `` matches the argument name in the command's *Arguments* section on Minecraft Wiki +* `` traces the command's argument hierarchy after the selector + +For example, in the [`/teleport`](https://minecraft.wiki/w/Commands/teleport#Arguments) command: +- `` and `` are valid selector names + (they are [`entity`](https://minecraft.wiki/w/Argument_types#minecraft:entity) selectors) +- Subsequent arguments form the remainder of the path + +Most selectors are [`entity`](https://minecraft.wiki/w/Argument_types#minecraft:entity) selectors, which is supported. +For a complete support list, see [below](#status). + +#### Example + +| Command | Syntax (See Minecraft Wiki) | Selector to Control | `` Construction | +|----------------------| -----------------------------------|---------------------|--------------------------------------------| +| `/teleport @e @s` | `teleport ` | `@e` | `teleport.targets.targets.destination` | +| `/teleport @e @s` | `teleport ` | `@s` | `teleport.destination.targets.destination` | +| `/teleport @e ~ ~ ~` | `teleport ` | `@e` | `teleport.targets.targets.location` | + +#### Wildcard Support + +Selector permissions can be lengthy. Luckily you can use wildcards, like `teleport.*` to cover all teleport command +selectors. + +However, Luckperms doesn't support wildcard for metadata. You could install +[this mod](https://modrinth.com/mod/metadatawildcard4fabric-permissions-api +'Metadata Wildcard for fabric-permissions-api') to enable it. + +### Scope Control + +Use these permissions to define selector scope: + +* `minecraft.selector.entity.` +* `minecraft.selector.player.` +* `minecraft.selector.self.` + +Commands fail if a player attempts to select unauthorized entities. All three scopes (that is, `minecraft.selector.*`) +are allowed by default. + +#### Simple Example + +```yml +Allow: + minecraft.command.waypoint + minecraft.selector +Deny: + minecraft.selector.player.waypoint.* + minecraft.selector.entity.waypoint.* +``` + +Players modify only their own waypoints. + +#### Complex Example + +```yml +Allow: + minecraft.command.teleport # /teleport + minecraft.selector # All selectors + minecraft.selector.player.teleport.destination.destination #1 + minecraft.selector.entity.teleport.destination.destination #2 + minecraft.selector.entity.teleport.targets.targets.destination #3 + minecraft.selector.player.teleport.facingEntity.* #4 +Deny: + minecraft.selector.player.teleport.* #6 + minecraft.selector.entity.* #5 + minecraft.selector.self.teleport.facingEntity.* #7 +``` + +Command Behavior: + +| Command | Self | Nonself Players | Non-player Entities | Resulting Behavior | +|---------------------------------------------------------------|----------------------------------------------------------------------|------------------------------------------------------------|-----------------------------------------------------------|---------------------------------------------------------| +| `/teleport ` | `` allowed by default | `` allowed by #𝟏 | `` allowed by #𝟐 | Teleport to any entity | +| `/teleport ` | `` allowed by default
`` allowed by default | `` denied by #𝟓
`` denied by #𝟓 | `` allowed by #𝟑
`` denied by #𝟔 | Only teleport non-player entities to self | +| `/teleport ` | (No selectors) | - | - | Unrestricted position teleport | +| `/teleport ` | `` allowed by default | `` denied by #𝟓 | `` denied by #𝟔 | Only teleport self to positions | +| `/teleport facing entity ` | `` allowed by default
`` denied by #𝟕 | `` denied by #𝟓
`` allowed by #𝟒 | `` denied by #𝟔
`` denied by #𝟔 | Teleport self to positions while facing nonself players | + +### Entity Limit + +Set meta `minecraft.selector.limit.` to restrict the maximum number of entities selectable via a +given selector. + +No limit is applied if this meta is unset. + +### Selection Weight + +Controlled by meta `minecraft.selector.weight.`. + +Entities without weight settings can always select any target and be selected by any selector. When both entities have +weight values, a selector can only select targets whose weight is `less than or equal` to its own. + +#### Example + +You need to install [this mod](https://modrinth.com/mod/metadatawildcard4fabric-permissions-api +'Metadata Wildcard for fabric-permissions-api') first. + +```yml +# Global permissions +Allow: + minecraft.command.gamemode + minecraft.selector +# Player-specific metadata +Player1: minecraft.selector.weight.gamemode.* = 7 +Player2: minecraft.selector.weight.gamemode.* = -1 +Player3: minecraft.selector.weight.gamemode.* = 7 +Player4: (no weight set) +``` + +| Player | Can modify gamemode of | Reason | +|---------|--------------------------|----------------------------------------------------------------------------| +| Player1 | All players | Weight ($7$) ≥ all others' weights | +| Player2 | Only Player2 and Player4 | Weight ($-1$) < Player1/Player3 ($7$)
No weight restriction for Player4 | +| Player3 | All players | Weight ($7$) ≥ all others' weights | +| Player4 | All players | No weight restriction → unrestricted access | + +### Status + +The following list shows which selectors can use fine-grained permissions: + +* [`/ban-ip`](https://minecraft.wiki/w/Commands/ban#ban-ip): Not supported + +* [`entity`](https://minecraft.wiki/w/Argument_types#minecraft:entity 'Most cases'): Fully supported + +* [`game_profile`](https://minecraft.wiki/w/Argument_types#minecraft:game_profile 'e.g. /ban'): + [Selection Weight](#selection-weight) for offline players not supported in Minecraft < 1.21.6. Others fully supported + +* [`message`](https://minecraft.wiki/w/Argument_types#minecraft:message 'e.g. /say'): Not supported + +* [`score_holder`](https://minecraft.wiki/w/Argument_types#minecraft:score_holder '/scoreboard and /team'): + Only [Entity Limit](#entity-limit) supported ## Quality of Life @@ -66,4 +238,4 @@ permissions](#permissions) to place operator blocks and access the gamemode swit ### Client Side If the mod is installed on the client, the gamemode switcher can also be accessed, if the player has access to the -command, but isn't OP (useful for spigot-based servers)! \ No newline at end of file +command, but isn't OP (useful for spigot-based servers)! diff --git a/build.gradle.kts b/build.gradle.kts index 87cfb53..de53cf4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -48,7 +48,7 @@ dependencies { publishMods { file.set(tasks.remapJar.get().archiveFile) - type.set(STABLE) + type.set(BETA) changelog.set(fetchChangelog()) displayName = "VanillaPermissions ${version.get()}" diff --git a/gradle.properties b/gradle.properties index 1270786..1aafb33 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ modrinth_minecraft_versions=[VERSIONED] java_version=[VERSIONED] loader_version=0.16.14 # Mod Properties -mod_version=0.2.11 +mod_version=0.3.0 maven_group=me.drex archives_base_name=vanilla-permissions # Dependencies diff --git a/src/main/java/me/drex/vanillapermissions/mixin/selector/argument/EntityArgumentMixin.java b/src/main/java/me/drex/vanillapermissions/mixin/selector/argument/EntityArgumentMixin.java new file mode 100644 index 0000000..7504254 --- /dev/null +++ b/src/main/java/me/drex/vanillapermissions/mixin/selector/argument/EntityArgumentMixin.java @@ -0,0 +1,45 @@ +package me.drex.vanillapermissions.mixin.selector.argument; + +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.world.entity.Entity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Collection; + +import static me.drex.vanillapermissions.util.ArgumentPermission.validate; + +import static java.util.Collections.singleton; + +@Mixin(EntityArgument.class) +public abstract class EntityArgumentMixin { + + @Inject( + method = { + "getEntity", + "getPlayer", + }, + at = @At("RETURN") + ) + private static void addEntityPermission(CommandContext commandContext, String string, CallbackInfoReturnable cir) throws CommandSyntaxException { + validate(commandContext, string, singleton(cir.getReturnValue())); + } + + @Inject( + method = { + "getEntities", + "getOptionalEntities", + "getPlayers", + "getOptionalPlayers", + }, + at = @At("RETURN") + ) + private static void addEntitiesPermission(CommandContext commandContext, String string, CallbackInfoReturnable> cir) throws CommandSyntaxException { + validate(commandContext, string, cir.getReturnValue()); + } +} diff --git a/src/main/java/me/drex/vanillapermissions/mixin/selector/argument/GameProfileArgumentMixin.java b/src/main/java/me/drex/vanillapermissions/mixin/selector/argument/GameProfileArgumentMixin.java new file mode 100644 index 0000000..1371316 --- /dev/null +++ b/src/main/java/me/drex/vanillapermissions/mixin/selector/argument/GameProfileArgumentMixin.java @@ -0,0 +1,27 @@ +package me.drex.vanillapermissions.mixin.selector.argument; + +import com.mojang.authlib.GameProfile; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.arguments.GameProfileArgument; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Collection; + +import static me.drex.vanillapermissions.util.ArgumentPermission.validate; + +@Mixin(GameProfileArgument.class) +public abstract class GameProfileArgumentMixin { + + @Inject( + method = "getGameProfiles", + at = @At("RETURN") + ) + private static void addGameProfilesPermission(CommandContext commandContext, String string, CallbackInfoReturnable> cir) throws CommandSyntaxException { + validate(commandContext, string, cir.getReturnValue()); + } +} diff --git a/src/main/java/me/drex/vanillapermissions/mixin/selector/argument/ScoreHolderArgumentMixin.java b/src/main/java/me/drex/vanillapermissions/mixin/selector/argument/ScoreHolderArgumentMixin.java new file mode 100644 index 0000000..0768df0 --- /dev/null +++ b/src/main/java/me/drex/vanillapermissions/mixin/selector/argument/ScoreHolderArgumentMixin.java @@ -0,0 +1,27 @@ +package me.drex.vanillapermissions.mixin.selector.argument; + +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.arguments.ScoreHolderArgument; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Collection; +import java.util.function.Supplier; + +import static me.drex.vanillapermissions.util.ArgumentPermission.validate; + +@Mixin(ScoreHolderArgument.class) +public abstract class ScoreHolderArgumentMixin { + + @Inject( + method = "getNames(Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;Ljava/util/function/Supplier;)Ljava/util/Collection;", + at = @At("RETURN") + ) + private static void addScoreHoldersPermission(CommandContext commandContext, String string, Supplier> supplier, CallbackInfoReturnable> cir) throws CommandSyntaxException { + validate(commandContext, string, cir.getReturnValue()); + } +} diff --git a/src/main/java/me/drex/vanillapermissions/mixin/selector/argument/WaypointArgumentMixin.java b/src/main/java/me/drex/vanillapermissions/mixin/selector/argument/WaypointArgumentMixin.java new file mode 100644 index 0000000..95a771c --- /dev/null +++ b/src/main/java/me/drex/vanillapermissions/mixin/selector/argument/WaypointArgumentMixin.java @@ -0,0 +1,39 @@ +package me.drex.vanillapermissions.mixin.selector.argument; +//? if >= 1.21.6 { +import com.llamalad7.mixinextras.sugar.Local; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.arguments.WaypointArgument; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.waypoints.WaypointTransmitter; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import static me.drex.vanillapermissions.util.ArgumentPermission.validate; + +import static java.util.Collections.singleton; + +@Mixin(WaypointArgument.class) +public abstract class WaypointArgumentMixin { + + @Inject( + method = "getWaypoint", + at = @At("RETURN") + ) + private static void addWaypointPermission(CommandContext commandContext, String string, CallbackInfoReturnable cir, @Local Entity entity) throws CommandSyntaxException { + validate(commandContext, string, singleton(entity)); + } +} +//?} else { + +/*import net.minecraft.server.MinecraftServer; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(MinecraftServer.class) +public abstract class WaypointArgumentMixin { + +} +*///?} diff --git a/src/main/java/me/drex/vanillapermissions/util/ArgumentPermission.java b/src/main/java/me/drex/vanillapermissions/util/ArgumentPermission.java new file mode 100644 index 0000000..41d804d --- /dev/null +++ b/src/main/java/me/drex/vanillapermissions/util/ArgumentPermission.java @@ -0,0 +1,93 @@ +package me.drex.vanillapermissions.util; + +import com.google.common.collect.Iterables; +import com.mojang.authlib.GameProfile; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.tree.RootCommandNode; +import me.lucko.fabric.api.permissions.v0.Options; +import me.lucko.fabric.api.permissions.v0.Permissions; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; + +import java.util.Collection; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import static me.drex.vanillapermissions.VanillaPermissionsMod.LOGGER; +import static me.drex.vanillapermissions.util.Permission.*; +import static net.minecraft.commands.arguments.EntityArgument.ERROR_SELECTORS_NOT_ALLOWED; + +public class ArgumentPermission { + + public static void validate(CommandContext context, String selector, Collection selected) throws CommandSyntaxException { + var source = context.getSource(); + if (!source.isPlayer()) return; + + String[] parts; + if (context.getRootNode() instanceof RootCommandNode) { + parts = context.getNodes().stream().map(node -> node.getNode().getName()).toArray(String[]::new); + } else { + parts = source.getServer().getCommands().getDispatcher().getPath(Iterables.getLast(context.getNodes()).getNode()).toArray(String[]::new); + } + var name = Permission.build(parts[0], selector, Permission.build(1, parts.length, parts)); + + var limit = Options.get(source, SELECTOR_LIMIT.formatted(name), Integer::parseInt); + if (limit.isPresent() && limit.get() < selected.size()) throw ERROR_SELECTORS_NOT_ALLOWED.create(); + + var entity = Permissions.check(source, SELECTOR_ENTITY.formatted(name), true); + var player = Permissions.check(source, SELECTOR_PLAYER.formatted(name), true); + var self = Permissions.check(source, SELECTOR_SELF.formatted(name), true); + + var weight = SELECTOR_WEIGHT.formatted(name); + var sourceWeight = Options.get(source, weight, Integer::parseInt); + var sourceWeightPresent = sourceWeight.isPresent(); + if (entity && player && self && !sourceWeightPresent) return; + var sourceWeightValue = sourceWeight.orElse(0); + + var sourcePlayer = source.getPlayer().getGameProfile(); + try { + CompletableFuture.allOf(selected.stream().mapMulti((object, consumer) -> { + var selectedEntity = object; + if (selectedEntity instanceof Player selectedPlayer) { + selectedEntity = selectedPlayer.getGameProfile(); + } + + if (selectedEntity instanceof Entity) { + if (!entity) throwSelectorError(); + } else if (selectedEntity instanceof GameProfile selectedPlayer) { + if (selectedPlayer.equals(sourcePlayer)) { + if (!self) throwSelectorError(); + } else { + if (!player) throwSelectorError(); + if (!sourceWeightPresent) return; + + //? if >= 1.21.6 { + consumer.accept(Options.get(selectedPlayer, weight, Integer::parseInt).thenAcceptAsync(selectedWeight -> { + if (selectedWeight.isPresent() && selectedWeight.get() > sourceWeightValue) { + throw new CompletionException(ERROR_SELECTORS_NOT_ALLOWED.create()); + } + })); + //?} else { + /*if (object instanceof Player onlinePlayer) { + var selectedWeight = Options.get(onlinePlayer, weight, Integer::parseInt); + if (selectedWeight.isPresent() && selectedWeight.get() > sourceWeightValue) { + consumer.accept(CompletableFuture.failedFuture(new CompletionException(ERROR_SELECTORS_NOT_ALLOWED.create()))); + } + } + *///?} + } + } else throwSelectorError(); + }).unordered().toArray(CompletableFuture[]::new)).get(); + } catch (Exception e) { + if (e.getCause() instanceof CommandSyntaxException exception) throw exception; + LOGGER.warn("Bad selector in command {}", name, e); + throw ERROR_SELECTORS_NOT_ALLOWED.create(); + } + } + + private static void throwSelectorError() throws RuntimeException { + throw new RuntimeException(ERROR_SELECTORS_NOT_ALLOWED.create()); + } +} diff --git a/src/main/java/me/drex/vanillapermissions/util/Permission.java b/src/main/java/me/drex/vanillapermissions/util/Permission.java index 80d9c1b..bc4233d 100644 --- a/src/main/java/me/drex/vanillapermissions/util/Permission.java +++ b/src/main/java/me/drex/vanillapermissions/util/Permission.java @@ -1,6 +1,7 @@ package me.drex.vanillapermissions.util; import net.minecraft.resources.ResourceLocation; +import org.apache.commons.lang3.StringUtils; public class Permission { @@ -24,6 +25,11 @@ public class Permission { public static final String OPERATOR_BLOCK_EDIT = permission("operator_block.%s.edit"); public static final String OPERATOR_BLOCK_BREAK = permission("operator_block.%s.break"); public static final String SELECTOR = permission("selector"); + public static final String SELECTOR_ENTITY = permission("selector.entity.%s"); + public static final String SELECTOR_PLAYER = permission("selector.player.%s"); + public static final String SELECTOR_SELF = permission("selector.self.%s"); + public static final String SELECTOR_LIMIT = permission("selector.limit.%s"); + public static final String SELECTOR_WEIGHT = permission("selector.weight.%s"); protected static String permission(String permission) { return build(ResourceLocation.DEFAULT_NAMESPACE, permission); @@ -33,4 +39,8 @@ public static String build(String... parts) { return String.join(".", parts); } + public static String build(int startIndex, int endIndex, String... parts) { + return StringUtils.join(parts, '.', startIndex, endIndex); + } + } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 15cba2d..1b6d287 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -42,6 +42,7 @@ "fabric-permissions-api-v0": "*" }, "suggests": { - "luckperms": "*" + "luckperms": "*", + "metadatawildcard4fabric-permissions-api": "*" } } diff --git a/src/main/resources/vanilla-permissions.mixins.json b/src/main/resources/vanilla-permissions.mixins.json index e6be74c..9d16367 100644 --- a/src/main/resources/vanilla-permissions.mixins.json +++ b/src/main/resources/vanilla-permissions.mixins.json @@ -13,6 +13,10 @@ "PlayerListMixin", "ServerPlayerAccessor", "adminbroadcast.CommandSourceStackMixin", + "selector.argument.EntityArgumentMixin", + "selector.argument.GameProfileArgumentMixin", + "selector.argument.ScoreHolderArgumentMixin", + "selector.argument.WaypointArgumentMixin", "bypass.chat_speed.ServerGamePacketListenerImplMixin", "bypass.move_speed.ServerGamePacketListenerImplMixin", "bypass.spawn_protection.DedicatedServerMixin",