diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/BukkitCommandExecutor.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/BukkitCommandExecutor.java index 752a4f7ba..cfa091585 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/BukkitCommandExecutor.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/BukkitCommandExecutor.java @@ -27,18 +27,36 @@ package me.lucko.luckperms.bukkit; import me.lucko.luckperms.common.command.CommandManager; import me.lucko.luckperms.common.command.utils.ArgumentTokenizer; +import me.lucko.luckperms.common.config.ConfigKeys; import me.lucko.luckperms.common.sender.Sender; +import org.bukkit.Server; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.command.PluginCommand; import org.bukkit.command.TabExecutor; +import org.bukkit.entity.Player; import org.checkerframework.checker.nullness.qual.NonNull; import java.util.List; +import java.util.ListIterator; +import java.util.stream.Collectors; public class BukkitCommandExecutor extends CommandManager implements CommandExecutor, TabExecutor { + private static final boolean SELECT_ENTITIES_SUPPORTED; + + static { + boolean selectEntitiesSupported = false; + try { + Server.class.getMethod("selectEntities", CommandSender.class, String.class); + selectEntitiesSupported = true; + } catch (NoSuchMethodException e) { + // ignore + } + SELECT_ENTITIES_SUPPORTED = selectEntitiesSupported; + } + protected final LPBukkitPlugin plugin; protected final PluginCommand command; @@ -56,7 +74,7 @@ public class BukkitCommandExecutor extends CommandManager implements CommandExec @Override public boolean onCommand(@NonNull CommandSender sender, @NonNull Command command, @NonNull String label, @NonNull String[] args) { Sender wrapped = this.plugin.getSenderFactory().wrap(sender); - List arguments = ArgumentTokenizer.EXECUTE.tokenizeInput(args); + List arguments = processSelectors(sender, ArgumentTokenizer.EXECUTE.tokenizeInput(args)); executeCommand(wrapped, label, arguments); return true; } @@ -64,7 +82,51 @@ public class BukkitCommandExecutor extends CommandManager implements CommandExec @Override public List onTabComplete(@NonNull CommandSender sender, @NonNull Command command, @NonNull String label, @NonNull String[] args) { Sender wrapped = this.plugin.getSenderFactory().wrap(sender); - List arguments = ArgumentTokenizer.TAB_COMPLETE.tokenizeInput(args); + List arguments = processSelectors(sender, ArgumentTokenizer.TAB_COMPLETE.tokenizeInput(args)); return tabCompleteCommand(wrapped, arguments); } + + private List processSelectors(CommandSender sender, List args) { + if (!SELECT_ENTITIES_SUPPORTED) { + return args; + } + + if (!this.plugin.getConfiguration().get(ConfigKeys.RESOLVE_COMMAND_SELECTORS)) { + return args; + } + + ListIterator it = args.listIterator(); + while (it.hasNext()) { + String arg = it.next(); + if (arg.isEmpty() || arg.charAt(0) != '@') { + continue; + } + + List matchedPlayers; + try { + matchedPlayers = this.plugin.getBootstrap().getServer().selectEntities(sender, arg).stream() + .filter(e -> e instanceof Player) + .map(e -> ((Player) e)) + .collect(Collectors.toList()); + } catch (IllegalArgumentException e) { + this.plugin.getLogger().warn("Error parsing selector '" + arg + "' for " + sender + " executing " + args); + e.printStackTrace(); + continue; + } + + if (matchedPlayers.isEmpty()) { + continue; + } + + if (matchedPlayers.size() > 1) { + this.plugin.getLogger().warn("Error parsing selector '" + arg + "' for " + sender + " executing " + args + + ": ambiguous result (more than one player matched) - " + matchedPlayers); + continue; + } + + Player player = matchedPlayers.get(0); + it.set(player.getUniqueId().toString()); + } + return args; + } } diff --git a/bukkit/src/main/resources/config.yml b/bukkit/src/main/resources/config.yml index ccd4889f0..d6440e74e 100644 --- a/bukkit/src/main/resources/config.yml +++ b/bukkit/src/main/resources/config.yml @@ -563,3 +563,7 @@ allow-invalid-usernames: false # # - When this happens, the plugin will set their primary group back to default. prevent-primary-group-removal: false + +# If LuckPerms should attempt to resolve Vanilla command target selectors for LP commands. +# See here for more info: https://minecraft.gamepedia.com/Commands#Target_selectors +resolve-command-selectors: false 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 828257a7e..420702b57 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 @@ -128,6 +128,11 @@ public final class ConfigKeys { */ public static final ConfigKey CANCEL_FAILED_LOGINS = booleanKey("cancel-failed-logins", false); + /** + * If LuckPerms should attempt to resolve Vanilla command target selectors for LP commands. + */ + public static final ConfigKey RESOLVE_COMMAND_SELECTORS = booleanKey("resolve-command-selectors", false); + /** * Controls how temporary add commands should behave */ diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/SpongeCommandExecutor.java b/sponge/src/main/java/me/lucko/luckperms/sponge/SpongeCommandExecutor.java index a930f3366..311088a66 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/SpongeCommandExecutor.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/SpongeCommandExecutor.java @@ -27,6 +27,7 @@ package me.lucko.luckperms.sponge; import me.lucko.luckperms.common.command.CommandManager; import me.lucko.luckperms.common.command.utils.ArgumentTokenizer; +import me.lucko.luckperms.common.config.ConfigKeys; import me.lucko.luckperms.common.sender.Sender; import org.checkerframework.checker.nullness.qual.NonNull; @@ -43,6 +44,7 @@ import org.spongepowered.api.world.World; import java.util.List; import java.util.ListIterator; import java.util.Optional; +import java.util.stream.Collectors; public class SpongeCommandExecutor extends CommandManager implements CommandCallable { private final LPSpongePlugin plugin; @@ -88,20 +90,41 @@ public class SpongeCommandExecutor extends CommandManager implements CommandCall } private List processSelectors(CommandSource source, List args) { + if (!this.plugin.getConfiguration().get(ConfigKeys.RESOLVE_COMMAND_SELECTORS)) { + return args; + } + ListIterator it = args.listIterator(); while (it.hasNext()) { - String element = it.next(); - if (element.startsWith("@")) { - try { - Selector.parse(element).resolve(source).stream() - .filter(e -> e instanceof Player) - .map(e -> ((Player) e)) - .findFirst() - .ifPresent(player -> it.set(player.getUniqueId().toString())); - } catch (IllegalArgumentException e) { - // ignored - } + String arg = it.next(); + if (arg.isEmpty() || arg.charAt(0) != '@') { + continue; } + + List matchedPlayers; + try { + matchedPlayers = Selector.parse(arg).resolve(source).stream() + .filter(e -> e instanceof Player) + .map(e -> ((Player) e)) + .collect(Collectors.toList()); + } catch (IllegalArgumentException e) { + this.plugin.getLogger().warn("Error parsing selector '" + arg + "' for " + source + " executing " + args); + e.printStackTrace(); + continue; + } + + if (matchedPlayers.isEmpty()) { + continue; + } + + if (matchedPlayers.size() > 1) { + this.plugin.getLogger().warn("Error parsing selector '" + arg + "' for " + source + " executing " + args + + ": ambiguous result (more than one player matched) - " + matchedPlayers); + continue; + } + + Player player = matchedPlayers.get(0); + it.set(player.getUniqueId().toString()); } return args; } diff --git a/sponge/src/main/resources/luckperms.conf b/sponge/src/main/resources/luckperms.conf index f3ac4f5b8..d587865b8 100644 --- a/sponge/src/main/resources/luckperms.conf +++ b/sponge/src/main/resources/luckperms.conf @@ -496,3 +496,7 @@ allow-invalid-usernames = false # # - When this happens, the plugin will set their primary group back to default. prevent-primary-group-removal = false + +# If LuckPerms should attempt to resolve Vanilla command target selectors for LP commands. +# See here for more info: https://minecraft.gamepedia.com/Commands#Target_selectors +resolve-command-selectors = false