From b6fc3c2b611cc881895bcd1da42c8493002a92fa Mon Sep 17 00:00:00 2001 From: Marc Baloup Date: Wed, 10 Aug 2022 19:25:06 +0200 Subject: [PATCH] Even more javadoc --- .../lib/bungee/players/BungeeOffPlayer.java | 7 +- .../bungee/players/BungeeOnlinePlayer.java | 49 +- pandalib-paper-commands/pom.xml | 7 + .../paper/commands/PaperBrigadierCommand.java | 914 +++++++++++------- .../lib/paper/players/PaperOffPlayer.java | 9 +- .../lib/paper/players/PaperOnlinePlayer.java | 64 +- 6 files changed, 656 insertions(+), 394 deletions(-) diff --git a/pandalib-bungee-players/src/main/java/fr/pandacube/lib/bungee/players/BungeeOffPlayer.java b/pandalib-bungee-players/src/main/java/fr/pandacube/lib/bungee/players/BungeeOffPlayer.java index 717de5a..f2280d1 100644 --- a/pandalib-bungee-players/src/main/java/fr/pandacube/lib/bungee/players/BungeeOffPlayer.java +++ b/pandalib-bungee-players/src/main/java/fr/pandacube/lib/bungee/players/BungeeOffPlayer.java @@ -5,6 +5,9 @@ import net.md_5.bungee.api.connection.ProxiedPlayer; import fr.pandacube.lib.players.standalone.AbstractOffPlayer; +/** + * Represents any player on a Bungeecord proxy, either offline or online. + */ public interface BungeeOffPlayer extends AbstractOffPlayer { /* @@ -12,8 +15,8 @@ public interface BungeeOffPlayer extends AbstractOffPlayer { */ /** - * @return l'instance Bungee du joueur en ligne, ou null si il n'est pas en - * ligne + * Returns the {@link ProxiedPlayer} instance of this player, or null if not available (offline). + * @return the {@link ProxiedPlayer} instance of this player, or null if not available (offline). */ default ProxiedPlayer getBungeeProxiedPlayer() { return ProxyServer.getInstance().getPlayer(getUniqueId()); diff --git a/pandalib-bungee-players/src/main/java/fr/pandacube/lib/bungee/players/BungeeOnlinePlayer.java b/pandalib-bungee-players/src/main/java/fr/pandacube/lib/bungee/players/BungeeOnlinePlayer.java index edb66bd..f34f65e 100644 --- a/pandalib-bungee-players/src/main/java/fr/pandacube/lib/bungee/players/BungeeOnlinePlayer.java +++ b/pandalib-bungee-players/src/main/java/fr/pandacube/lib/bungee/players/BungeeOnlinePlayer.java @@ -23,6 +23,9 @@ import fr.pandacube.lib.players.standalone.AbstractOnlinePlayer; import fr.pandacube.lib.reflect.Reflect; import fr.pandacube.lib.util.MinecraftVersion; +/** + * Represents any online player on a Bungeecord proxy. + */ public interface BungeeOnlinePlayer extends BungeeOffPlayer, AbstractOnlinePlayer { /* @@ -41,24 +44,24 @@ public interface BungeeOnlinePlayer extends BungeeOffPlayer, AbstractOnlinePlaye return getServer().getInfo().getName(); } + /** + * Returns the server on which the player is. + * @return the server on which the player is. + */ default Server getServer() { return getBungeeProxiedPlayer().getServer(); } + /** + * Gets the minecraft version of this player’s client. + * @return the minecraft version of this player’s client. + */ default MinecraftVersion getMinecraftVersion() { return MinecraftVersion.getVersion(getBungeeProxiedPlayer().getPendingConnection().getVersion()); } - /* - * Related class instances - */ - - @Override - ProxiedPlayer getBungeeProxiedPlayer(); - - @@ -95,11 +98,17 @@ public interface BungeeOnlinePlayer extends BungeeOffPlayer, AbstractOnlinePlaye @Override default void sendMessage(Component message, Identified sender) { - getBungeeProxiedPlayer().sendMessage(sender.identity().uuid(), Chat.toBungee(message)); + getBungeeProxiedPlayer().sendMessage(sender == null ? null : sender.identity().uuid(), Chat.toBungee(message)); } + /** + * Display the provided message in the player’s chat, if they allows to display CHAT messages. + * @param message the message to send. + * @param sender the player causing the send of this message. Client side filtering may occur. May be null if we + * don’t want client filtering, but still consider the message as CHAT message. + */ default void sendMessage(ComponentLike message, ProxiedPlayer sender) { - getBungeeProxiedPlayer().sendMessage(sender.getUniqueId(), Chat.toBungee(message.asComponent())); + getBungeeProxiedPlayer().sendMessage(sender == null ? null : sender.getUniqueId(), Chat.toBungee(message.asComponent())); } @Override @@ -134,9 +143,17 @@ public interface BungeeOnlinePlayer extends BungeeOffPlayer, AbstractOnlinePlaye return new BungeeClientOptions(this); } + /** + * Provides various configuration values of the Minecraft client. + */ class BungeeClientOptions implements AbstractOnlinePlayer.ClientOptions { private final BungeeOnlinePlayer op; + + /** + * Create a new instance of {@link BungeeClientOptions}. + * @param op the {@link BungeeOnlinePlayer} instance. + */ public BungeeClientOptions(BungeeOnlinePlayer op) { this.op = op; } @@ -155,6 +172,10 @@ public interface BungeeOnlinePlayer extends BungeeOffPlayer, AbstractOnlinePlaye return op.getBungeeProxiedPlayer().hasChatColors(); } + /** + * Gets the chat visibility configuration. + * @return the chat visibility configuration. + */ public ChatMode getChatMode() { return op.getBungeeProxiedPlayer().getChatMode(); } @@ -185,6 +206,10 @@ public interface BungeeOnlinePlayer extends BungeeOffPlayer, AbstractOnlinePlaye return op.getBungeeProxiedPlayer().getViewDistance(); } + /** + * Gets the player’s main hand. + * @return the player’s main hand. + */ public MainHand getMainHand() { return op.getBungeeProxiedPlayer().getMainHand(); } @@ -211,6 +236,10 @@ public interface BungeeOnlinePlayer extends BungeeOffPlayer, AbstractOnlinePlaye return settings != null && settings.isAllowServerListing(); } + /** + * Gets the player’s skin configuration. + * @return the player’s skin configuration. + */ public SkinConfiguration getSkinParts() { return op.getBungeeProxiedPlayer().getSkinParts(); } diff --git a/pandalib-paper-commands/pom.xml b/pandalib-paper-commands/pom.xml index 4711358..d1fe212 100644 --- a/pandalib-paper-commands/pom.xml +++ b/pandalib-paper-commands/pom.xml @@ -54,6 +54,13 @@ ${project.version} + + fr.pandacube.lib + pandalib-paper-permissions + ${project.version} + provided + + io.papermc.paper diff --git a/pandalib-paper-commands/src/main/java/fr/pandacube/lib/paper/commands/PaperBrigadierCommand.java b/pandalib-paper-commands/src/main/java/fr/pandacube/lib/paper/commands/PaperBrigadierCommand.java index 63dcf03..6bd378a 100644 --- a/pandalib-paper-commands/src/main/java/fr/pandacube/lib/paper/commands/PaperBrigadierCommand.java +++ b/pandalib-paper-commands/src/main/java/fr/pandacube/lib/paper/commands/PaperBrigadierCommand.java @@ -2,12 +2,9 @@ package fr.pandacube.lib.paper.commands; import com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource; import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.LiteralMessage; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.context.CommandContext; -import com.mojang.brigadier.context.ParsedCommandNode; import com.mojang.brigadier.exceptions.CommandSyntaxException; -import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; @@ -15,7 +12,7 @@ import com.mojang.brigadier.tree.RootCommandNode; import fr.pandacube.lib.chat.Chat; import fr.pandacube.lib.commands.BrigadierCommand; import fr.pandacube.lib.commands.SuggestionsSupplier; -import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftNamespacedKey; +import fr.pandacube.lib.paper.permissions.PandalibPaperPermissions; import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftServer; import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftVector; import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.VanillaCommandWrapper; @@ -25,18 +22,14 @@ import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.ComponentArgume import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.Coordinates; import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.EntityArgument; import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.EntitySelector; -import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.GameProfileArgument; -import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.ResourceLocationArgument; import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.Vec3Argument; import fr.pandacube.lib.paper.reflect.wrapper.minecraft.core.BlockPos; -import fr.pandacube.lib.paper.reflect.wrapper.minecraft.resources.ResourceLocation; import fr.pandacube.lib.paper.reflect.wrapper.minecraft.server.ServerPlayer; import fr.pandacube.lib.paper.reflect.wrapper.paper.PaperAdventure; import fr.pandacube.lib.reflect.wrapper.ReflectWrapper; import fr.pandacube.lib.util.Log; import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; -import org.bukkit.NamespacedKey; import org.bukkit.World; import org.bukkit.command.Command; import org.bukkit.command.CommandMap; @@ -55,200 +48,283 @@ import org.bukkit.util.BlockVector; import org.bukkit.util.Vector; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.function.Function; import java.util.function.Predicate; +/** + * Abstract class to hold a command to be integrated into a Paper server vanilla command dispatcher. + */ public abstract class PaperBrigadierCommand extends BrigadierCommand implements Listener { - protected static final Commands vanillaCommandDispatcher; - private static final CommandDispatcher nmsDispatcher; - - static { - vanillaCommandDispatcher = ReflectWrapper.wrapTyped(Bukkit.getServer(), CraftServer.class) - .getServer() - .vanillaCommandDispatcher(); - nmsDispatcher = vanillaCommandDispatcher.dispatcher(); - } + private static final Commands vanillaCommandDispatcher; + private static final CommandDispatcher nmsDispatcher; + + static { + vanillaCommandDispatcher = ReflectWrapper.wrapTyped(Bukkit.getServer(), CraftServer.class) + .getServer() + .vanillaCommandDispatcher(); + nmsDispatcher = vanillaCommandDispatcher.dispatcher(); + } + + /** + * Removes a plugin command that overrides a vanilla command, so the vanilla command functionnalities are fully + * restored (so, not only the usage, but also the suggestions and the command structure sent to the client). + * @param name the name of the command to restore. + */ + public static void restoreVanillaCommand(String name) { + CommandMap bukkitCmdMap = Bukkit.getCommandMap(); + Command bukkitCommand = bukkitCmdMap.getCommand(name); + if (bukkitCommand != null) { + if (VanillaCommandWrapper.REFLECT.get().isInstance(bukkitCommand)) { + Log.info("Command /" + name + " is already a vanilla command."); + return; + } + Log.info("Removing Bukkit command /" + name + " (" + getCommandIdentity(bukkitCommand) + ")"); + bukkitCmdMap.getKnownCommands().remove(name.toLowerCase(java.util.Locale.ENGLISH)); + bukkitCommand.unregister(bukkitCmdMap); + + LiteralCommandNode node = (LiteralCommandNode) getRootNode().getChild(name); + Command newCommand = new VanillaCommandWrapper(vanillaCommandDispatcher, node).__getRuntimeInstance(); + bukkitCmdMap.getKnownCommands().put(name.toLowerCase(), newCommand); + newCommand.register(bukkitCmdMap); + } + } - public static CommandDispatcher getNMSDispatcher() { - return nmsDispatcher; - } + /** + * Returns the vanilla instance of the Brigadier dispatcher. + * @return the vanilla instance of the Brigadier dispatcher. + */ + public static CommandDispatcher getNMSDispatcher() { + return nmsDispatcher; + } - protected static RootCommandNode getRootNode() { - return nmsDispatcher.getRoot(); - } - - protected Plugin plugin; - - protected LiteralCommandNode commandNode; - - private Set allAliases; - - public PaperBrigadierCommand(Plugin pl) { - plugin = pl; - commandNode = buildCommand().build(); - postBuildCommand(commandNode); - register(); - Bukkit.getPluginManager().registerEvents(this, plugin); - } - + /** + * Returns the root command node of the Brigadier dispatcher. + * @return the root command node of the Brigadier dispatcher. + */ + protected static RootCommandNode getRootNode() { + return nmsDispatcher.getRoot(); + } - - - private void register() { - - String[] aliases = getAliases(); - if (aliases == null) - aliases = new String[0]; - - String pluginName = plugin.getName().toLowerCase(); - - allAliases = new HashSet<>(); - registerNode(commandNode, false); - registerAlias(pluginName + ":" + commandNode.getLiteral(), true); - - for (String alias : aliases) { - registerAlias(alias, false); - registerAlias(pluginName + ":" + alias, true); - } - } - - - private void registerAlias(String alias, boolean prefixed) { - LiteralCommandNode node = literal(alias) - .requires(commandNode.getRequirement()) - .executes(commandNode.getCommand()) - .redirect(commandNode) - .build(); - registerNode(node, prefixed); - } - - - private void registerNode(LiteralCommandNode node, boolean prefixed) { - RootCommandNode root = getRootNode(); - String name = node.getLiteral(); - //boolean isAlias = node.getRedirect() == commandNode; - boolean forceRegistration = true;//prefixed || !isAlias; - - // nmsDispatcher integration and conflit resolution - boolean nmsRegister = false, nmsRegistered = false; - CommandNode nmsConflited = root.getChild(name); - if (nmsConflited != null) { - - if (isFromThisCommand(nmsConflited)) { - // this command is already registered in NMS - // don’t need to register again - nmsRegistered = true; - } - else if (forceRegistration) { - nmsRegister = true; - Log.info("Overwriting Brigadier command /" + name); - } - else { - Log.severe("/" + name + " already in NMS Brigadier instance." - + " Wont replace it because registration is not forced."); - } - } - else { - nmsRegister = true; - } - - if (nmsRegister) { - @SuppressWarnings("unchecked") - var rCommandNode = ReflectWrapper.wrapTyped(root, fr.pandacube.lib.paper.reflect.wrapper.brigadier.CommandNode.class); - rCommandNode.removeCommand(name); - root.addChild(node); - nmsRegistered = true; - } - - if (!nmsRegistered) { - return; - } - - allAliases.add(name); - - // bukkit dispatcher conflict resolution - boolean bukkitRegister = false; - CommandMap bukkitCmdMap = Bukkit.getCommandMap(); - Command bukkitConflicted = bukkitCmdMap.getCommand(name); - if (bukkitConflicted != null) { - if (isFromThisCommand(bukkitConflicted)) { - // nothing to do, already good - } - else if (forceRegistration) { - bukkitRegister = true; - Log.info("Overwriting Bukkit command /" + name - + " (" + getCommandIdentity(bukkitConflicted) + ")"); - } - else { - Log.severe("/" + name + " already in Bukkit" - + " dispatcher (" + getCommandIdentity(bukkitConflicted) - + "). Wont replace it because registration is not forced."); - } - } - else { - bukkitRegister = true; - } - - if (bukkitRegister) { - bukkitCmdMap.getKnownCommands().remove(name.toLowerCase()); - if (bukkitConflicted != null) - bukkitConflicted.unregister(bukkitCmdMap); - Command newCommand = new VanillaCommandWrapper(vanillaCommandDispatcher, node).__getRuntimeInstance(); - bukkitCmdMap.getKnownCommands().put(name.toLowerCase(), newCommand); - newCommand.register(bukkitCmdMap); - } - - } - - private boolean isFromThisCommand(CommandNode node) { - return node == commandNode || node.getRedirect() == commandNode; - } - private boolean isFromThisCommand(Command bukkitCmd) { - if (VanillaCommandWrapper.REFLECT.get().isInstance(bukkitCmd)) { - return isFromThisCommand(ReflectWrapper.wrapTyped((BukkitCommand) bukkitCmd, VanillaCommandWrapper.class).vanillaCommand()); - } - return false; - } - - protected static String getCommandIdentity(Command bukkitCmd) { - if (bukkitCmd instanceof PluginCommand cmd) { - return "Bukkit command: /" + cmd.getName() + " from plugin " + cmd.getPlugin().getName(); - } - else if (VanillaCommandWrapper.REFLECT.get().isInstance(bukkitCmd)) { - return "Vanilla command: /" + bukkitCmd.getName(); - } - else - return bukkitCmd.getClass().getName() + ": /" + bukkitCmd.getName(); - } - - - - - @EventHandler - public void onPlayerCommandSend(PlayerCommandSendEvent event) { - event.getCommands().removeAll(allAliases.stream().map(s -> "minecraft:" + s).toList()); - } - - - @EventHandler - public void onServerLoad(ServerLoadEvent event) { - register(); - } - - - /** - * The permission that should be tested instead of "minecraft.command.cmdName". - */ - protected abstract String getTargetPermission(); + + + + + + private final Plugin plugin; + + /** + * The command node of this command. + */ + protected final LiteralCommandNode commandNode; + + private final RegistrationPolicy registrationPolicy; + + private Set registeredAliases; + + /** + * Instanciate this command isntance. + * + * @param pl the plugin instance. + * @param regPolicy the registration policy for this command. + */ + public PaperBrigadierCommand(Plugin pl, RegistrationPolicy regPolicy) { + plugin = pl; + registrationPolicy = regPolicy; + commandNode = buildCommand().build(); + postBuildCommand(commandNode); + register(); + Bukkit.getPluginManager().registerEvents(this, plugin); + try { + PandalibPaperPermissions.addPermissionMapping("minecraft.command." + commandNode.getLiteral().toLowerCase(), getTargetPermission().toLowerCase()); + } catch (NoClassDefFoundError ignored) { } + } + + /** + * Instanciate this command isntance with a registration policy of {@link RegistrationPolicy#ONLY_BASE_COMMAND}. + * @param pl the plugin instance. + */ + public PaperBrigadierCommand(Plugin pl) { + this(pl, RegistrationPolicy.ONLY_BASE_COMMAND); + } + + + + + private void register() { + + String[] aliases = getAliases(); + if (aliases == null) + aliases = new String[0]; + + String pluginName = plugin.getName().toLowerCase(); + + registeredAliases = new HashSet<>(); + registerNode(commandNode, false); + registerAlias(pluginName + ":" + commandNode.getLiteral(), true); + + for (String alias : aliases) { + registerAlias(alias, false); + registerAlias(pluginName + ":" + alias, true); + } + } + + + private void registerAlias(String alias, boolean prefixed) { + LiteralCommandNode node = literal(alias) + .requires(commandNode.getRequirement()) + .executes(commandNode.getCommand()) + .redirect(commandNode) + .build(); + registerNode(node, prefixed); + } + + + private void registerNode(LiteralCommandNode node, boolean prefixed) { + RootCommandNode root = getRootNode(); + String name = node.getLiteral(); + boolean isAlias = node.getRedirect() == commandNode; + boolean forceRegistration = switch (registrationPolicy) { + case NONE -> false; + case ONLY_BASE_COMMAND -> prefixed || !isAlias; + case ALL -> true; + }; + + // nmsDispatcher integration and conflit resolution + boolean nmsRegister = false, nmsRegistered = false; + CommandNode nmsConflited = root.getChild(name); + if (nmsConflited != null) { + + if (isFromThisCommand(nmsConflited)) { + // this command is already registered in NMS. Don’t need to register again + nmsRegistered = true; + } + else if (forceRegistration) { + nmsRegister = true; + Log.info("Overwriting Brigadier command /" + name); + } + else { + Log.severe("/" + name + " already in NMS Brigadier instance." + + " Wont replace it because registration is not forced."); + } + } + else { + nmsRegister = true; + } + + if (nmsRegister) { + @SuppressWarnings("unchecked") + var rCommandNode = ReflectWrapper.wrapTyped(root, fr.pandacube.lib.paper.reflect.wrapper.brigadier.CommandNode.class); + rCommandNode.removeCommand(name); + root.addChild(node); + nmsRegistered = true; + } + + if (!nmsRegistered) { + return; + } + + registeredAliases.add(name); + + // bukkit dispatcher conflict resolution + boolean bukkitRegister = false; + CommandMap bukkitCmdMap = Bukkit.getCommandMap(); + Command bukkitConflicted = bukkitCmdMap.getCommand(name); + if (bukkitConflicted != null) { + if (!isFromThisCommand(bukkitConflicted)) { + if (forceRegistration) { + bukkitRegister = true; + Log.info("Overwriting Bukkit command /" + name + + " (" + getCommandIdentity(bukkitConflicted) + ")"); + } + else { + Log.severe("/" + name + " already in Bukkit" + + " dispatcher (" + getCommandIdentity(bukkitConflicted) + + "). Wont replace it because registration is not forced."); + } + } + } + else { + bukkitRegister = true; + } + + if (bukkitRegister) { + bukkitCmdMap.getKnownCommands().remove(name.toLowerCase()); + if (bukkitConflicted != null) + bukkitConflicted.unregister(bukkitCmdMap); + + Command newCommand = new VanillaCommandWrapper(vanillaCommandDispatcher, node).__getRuntimeInstance(); + bukkitCmdMap.getKnownCommands().put(name.toLowerCase(), newCommand); + newCommand.register(bukkitCmdMap); + } + + } + + private boolean isFromThisCommand(CommandNode node) { + return node == commandNode || node.getRedirect() == commandNode; + } + + private boolean isFromThisCommand(Command bukkitCmd) { + if (VanillaCommandWrapper.REFLECT.get().isInstance(bukkitCmd)) { + return isFromThisCommand(ReflectWrapper.wrapTyped((BukkitCommand) bukkitCmd, VanillaCommandWrapper.class).vanillaCommand()); + } + return false; + } + + private static String getCommandIdentity(Command bukkitCmd) { + if (bukkitCmd instanceof PluginCommand cmd) { + return "Bukkit command: /" + cmd.getName() + " from plugin " + cmd.getPlugin().getName(); + } + else if (VanillaCommandWrapper.REFLECT.get().isInstance(bukkitCmd)) { + return "Vanilla command: /" + bukkitCmd.getName(); + } + else + return bukkitCmd.getClass().getName() + ": /" + bukkitCmd.getName(); + } + + + /** + * Player command sender event handler. + * @param event the event. + */ + @EventHandler + public void onPlayerCommandSend(PlayerCommandSendEvent event) { + event.getCommands().removeAll(registeredAliases.stream().map(s -> "minecraft:" + s).toList()); + } + + + /** + * Server load event handler. + * @param event the event. + */ + @EventHandler + public void onServerLoad(ServerLoadEvent event) { + register(); + } + + + + + + + + + + + + /** + * Returns the permission that should be tested instead of "minecraft.command.cmdName". The conversion from the + * minecraft prefixed permission node to the returned node is done by the {@code pandalib-paper-permissions} if it + * is present in the classpath during runtime. + * @return the permission that should be tested instead of "minecraft.command.cmdName". + */ + protected abstract String getTargetPermission(); @@ -263,198 +339,304 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand hasPermission(String permission) { - return wrapper -> getCommandSender(wrapper).hasPermission(permission); - } - - - public boolean isConsole(CommandSender sender) { - return sender instanceof ConsoleCommandSender; - } - public boolean isPlayer(CommandSender sender) { - return sender instanceof Player; - } - - - - - public static CommandSender getCommandSender(CommandContext context) { - return getCommandSender(context.getSource()); - } - public static CommandSender getCommandSender(BukkitBrigadierCommandSource wrapper) { - return wrapper.getBukkitSender(); - } - public static BukkitBrigadierCommandSource getBrigadierCommandSource(CommandSender sender) { - return VanillaCommandWrapper.getListener(sender); - } - - - - - - - - - - - + public boolean isConsole(BukkitBrigadierCommandSource wrapper) { + return isConsole(getCommandSender(wrapper)); + } + public boolean isPlayer(BukkitBrigadierCommandSource wrapper) { + return isPlayer(getCommandSender(wrapper)); + } + public Predicate hasPermission(String permission) { + return wrapper -> getCommandSender(wrapper).hasPermission(permission); + } - public static final SuggestionsSupplier TAB_WORLDS = (s, ti, token, a) -> SuggestionsSupplier.collectFilteredStream(Bukkit.getWorlds().stream().map(World::getName), token); - - - - protected SuggestionProvider wrapSuggestions(SuggestionsSupplier suggestions) { - return wrapSuggestions(suggestions, PaperBrigadierCommand::getCommandSender); - } - - - - - protected static com.mojang.brigadier.Command wrapCommand(com.mojang.brigadier.Command cmd) { - return context -> { - try { - return cmd.run(context); - } catch(CommandSyntaxException e) { - throw e; - } catch (Throwable t) { - Log.severe(t); - getCommandSender(context).sendMessage(Chat.failureText("Error while using the command: " + t)); - return 0; - } - }; - } - - - - - - - - - /* - * Minecraft argument type - */ - - - public static ArgumentType argumentMinecraftEntity(boolean singleTarget, boolean playersOnly) { - if (playersOnly) { - return singleTarget ? EntityArgument.player() : EntityArgument.players(); - } - else { - return singleTarget ? EntityArgument.entity() : EntityArgument.entities(); - } - } - - public List tryGetMinecraftEntityArgument(CommandContext context, String argument) { - EntitySelector es = ReflectWrapper.wrap(tryGetArgument(context, argument, EntitySelector.MAPPING.runtimeClass()), EntitySelector.class); - if (es == null) - return null; - List nmsEntityList = es.findEntities(context.getSource()); - List entityList = new ArrayList<>(nmsEntityList.size()); - for (fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.Entity nmsEntity : nmsEntityList) { - entityList.add(nmsEntity.getBukkitEntity()); - } - return entityList; - } - - public List tryGetMinecraftEntityArgumentPlayers(CommandContext context, String argument) { - EntitySelector es = ReflectWrapper.wrap(tryGetArgument(context, argument, EntitySelector.MAPPING.runtimeClass()), EntitySelector.class); - if (es == null) - return null; - List nmsPlayerList = es.findPlayers(context.getSource()); - List playerList = new ArrayList<>(nmsPlayerList.size()); - for (ServerPlayer nmsPlayer : nmsPlayerList) { - playerList.add(nmsPlayer.getBukkitEntity()); - } - return playerList; - } - - public Entity tryGetMinecraftEntityArgumentOneEntity(CommandContext context, String argument) { - EntitySelector es = ReflectWrapper.wrap(tryGetArgument(context, argument, EntitySelector.MAPPING.runtimeClass()), EntitySelector.class); - if (es == null) - return null; - fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.Entity nmsEntity = es.findSingleEntity(context.getSource()); - return nmsEntity == null ? null : nmsEntity.getBukkitEntity(); - } - - public Player tryGetMinecraftEntityArgumentOnePlayer(CommandContext context, String argument) { - EntitySelector es = ReflectWrapper.wrap(tryGetArgument(context, argument, EntitySelector.MAPPING.runtimeClass()), EntitySelector.class); - if (es == null) - return null; - ServerPlayer nmsPlayer = es.findSinglePlayer(context.getSource()); - return nmsPlayer == null ? null : nmsPlayer.getBukkitEntity(); - } - - - - - public static ArgumentType argumentMinecraftGameProfile() { - return GameProfileArgument.gameProfile(); - } - - - - public static ArgumentType argumentMinecraftResourceLocation() { - return ResourceLocationArgument.id(); - } - public NamespacedKey tryGetMinecraftResourceLocationArgument(CommandContext context, String argument, NamespacedKey deflt) { - return tryGetArgument(context, argument, ResourceLocation.MAPPING.runtimeClass(), - nmsKey -> CraftNamespacedKey.fromMinecraft(ReflectWrapper.wrap(nmsKey, ResourceLocation.class)), - deflt); - } - - public static ArgumentType argumentMinecraftBlockPosition() { - return BlockPosArgument.blockPos(); - } - public BlockVector tryGetMinecraftBlockPositionArgument(CommandContext context, String argument) { - Coordinates coord = ReflectWrapper.wrap(tryGetArgument(context, argument, Coordinates.MAPPING.runtimeClass()), Coordinates.class); - if (coord == null) - return null; - BlockPos bp = coord.getBlockPos(context.getSource()); - return new BlockVector(bp.getX(), bp.getY(), bp.getZ()); + /** + * Tells if the provided command sender is the console. + * @param sender the sender to test if it’s the console or not. + * @return true if the sender is the console, false otherwise. + */ + public boolean isConsole(CommandSender sender) { + return sender instanceof ConsoleCommandSender; + } - } - - - - - public static ArgumentType argumentMinecraftVec3() { - return Vec3Argument.vec3(true); - } - public Vector tryGetMinecraftVec3Argument(CommandContext context, String argument) { - Coordinates coord = ReflectWrapper.wrap(tryGetArgument(context, argument, Coordinates.MAPPING.runtimeClass()), Coordinates.class); - return coord == null ? null : CraftVector.toBukkit(coord.getPosition(context.getSource())); - - } - + /** + * Tells if the provided command sender is a player. + * @param sender the sender to test if it’s a player or not. + * @return true if the sender is a player, false otherwise. + */ + public boolean isPlayer(CommandSender sender) { + return sender instanceof Player; + } + /** + * Gets the Bukkit command sender from the provided context. + * @param context the command context from which to get the Bukkit command sender. + * @return the Bukkit command sender. + */ + public static CommandSender getCommandSender(CommandContext context) { + return getCommandSender(context.getSource()); + } - public static ArgumentType argumentMinecraftChatComponent() { - return ComponentArgument.textComponent(); - } - public Component tryGetMinecraftChatComponentArgument(CommandContext context, String argument) { - var nmsComponent = ReflectWrapper.wrap( - tryGetArgument(context, argument, fr.pandacube.lib.paper.reflect.wrapper.minecraft.network.chat.Component.MAPPING.runtimeClass()), - fr.pandacube.lib.paper.reflect.wrapper.minecraft.network.chat.Component.class - ); - return nmsComponent == null ? null : PaperAdventure.asAdventure(nmsComponent); - } + /** + * Gets the Bukkit command sender from the provided wrapper. + * @param wrapper the wrapper from which to get the Bukkit command sender. + * @return the Bukkit command sender. + */ + public static CommandSender getCommandSender(BukkitBrigadierCommandSource wrapper) { + return wrapper.getBukkitSender(); + } + + /** + * Gets a new instance of a command sender wrapper for the provided command sender. + * @param sender the command sender. + * @return a new instance of a command sender wrapper for the provided command sender. + */ + public static BukkitBrigadierCommandSource getBrigadierCommandSource(CommandSender sender) { + return VanillaCommandWrapper.getListener(sender); + } + + + /** + * A suggestions supplier that suggests the names of the worlds currently loaded on this server. + */ + public static final SuggestionsSupplier TAB_WORLDS = SuggestionsSupplier.fromStreamSupplier(() -> Bukkit.getWorlds().stream().map(World::getName)); + + + /** + * Wraps the provided {@link SuggestionsSupplier} into a Brigadier’s {@link SuggestionProvider}. + * @param suggestions the suggestions to wrap. + * @return a {@link SuggestionProvider} generating the suggestions from the provided {@link SuggestionsSupplier}. + */ + protected SuggestionProvider wrapSuggestions(SuggestionsSupplier suggestions) { + return wrapSuggestions(suggestions, PaperBrigadierCommand::getCommandSender); + } + + + /** + * Wraps the provided brigadier command executor in another one that logs eventual throw exceptions and informs the + * player. + * The default behaviour of the vanilla instance of the Brigadier dispatcher is to ignore any unchecked exception + * thrown by a command executor. + * @param cmd the command executor to wrap. + * @return a wrapper command executor. + */ + protected static com.mojang.brigadier.Command wrapCommand(com.mojang.brigadier.Command cmd) { + return context -> { + try { + return cmd.run(context); + } catch(CommandSyntaxException e) { + throw e; + } catch (Throwable t) { + Log.severe(t); + getCommandSender(context).sendMessage(Chat.failureText("Error while executing the command: " + t)); + return 0; + } + }; + } + + + + /* + * Minecraft argument type + */ + + + /** + * Creates a new instance of the Brigadier argument type {@code minecraft:entity}. + * @param singleTarget if this argument takes only a single target. + * @param playersOnly if this argument takes players only. + * @return the {@code minecraft:entity} argument type with the specified parameters. + */ + public static ArgumentType argumentMinecraftEntity(boolean singleTarget, boolean playersOnly) { + if (playersOnly) { + return singleTarget ? EntityArgument.player() : EntityArgument.players(); + } + else { + return singleTarget ? EntityArgument.entity() : EntityArgument.entities(); + } + } + + /** + * Gets the value of the provided argument of type {@code minecraft:entity} (list of entities), from the provided context. + * @param context the command execution context. + * @param argument the argument name. + * @return the value of the argument, or null if not found. + */ + public List tryGetMinecraftEntityArgument(CommandContext context, String argument) { + EntitySelector es = ReflectWrapper.wrap(tryGetArgument(context, argument, EntitySelector.MAPPING.runtimeClass()), EntitySelector.class); + if (es == null) + return null; + List nmsEntityList = es.findEntities(context.getSource()); + List entityList = new ArrayList<>(nmsEntityList.size()); + for (fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.Entity nmsEntity : nmsEntityList) { + entityList.add(nmsEntity.getBukkitEntity()); + } + return entityList; + } + + /** + * Gets the value of the provided argument of type {@code minecraft:entity} (list of players), from the provided context. + * @param context the command execution context. + * @param argument the argument name. + * @return the value of the argument, or null if not found. + */ + public List tryGetMinecraftEntityArgumentPlayers(CommandContext context, String argument) { + EntitySelector es = ReflectWrapper.wrap(tryGetArgument(context, argument, EntitySelector.MAPPING.runtimeClass()), EntitySelector.class); + if (es == null) + return null; + List nmsPlayerList = es.findPlayers(context.getSource()); + List playerList = new ArrayList<>(nmsPlayerList.size()); + for (ServerPlayer nmsPlayer : nmsPlayerList) { + playerList.add(nmsPlayer.getBukkitEntity()); + } + return playerList; + } + + /** + * Gets the value of the provided argument of type {@code minecraft:entity} (one entity), from the provided context. + * @param context the command execution context. + * @param argument the argument name. + * @return the value of the argument, or null if not found. + */ + public Entity tryGetMinecraftEntityArgumentOneEntity(CommandContext context, String argument) { + EntitySelector es = ReflectWrapper.wrap(tryGetArgument(context, argument, EntitySelector.MAPPING.runtimeClass()), EntitySelector.class); + if (es == null) + return null; + fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.Entity nmsEntity = es.findSingleEntity(context.getSource()); + return nmsEntity == null ? null : nmsEntity.getBukkitEntity(); + } + + /** + * Gets the value of the provided argument of type {@code minecraft:entity} (one player), from the provided context. + * @param context the command execution context. + * @param argument the argument name. + * @return the value of the argument, or null if not found. + */ + public Player tryGetMinecraftEntityArgumentOnePlayer(CommandContext context, String argument) { + EntitySelector es = ReflectWrapper.wrap(tryGetArgument(context, argument, EntitySelector.MAPPING.runtimeClass()), EntitySelector.class); + if (es == null) + return null; + ServerPlayer nmsPlayer = es.findSinglePlayer(context.getSource()); + return nmsPlayer == null ? null : nmsPlayer.getBukkitEntity(); + } + + + + + + + /** + * Creates a new instance of the Brigadier argument type {@code minecraft:block_pos}. + * @return the {@code minecraft:block_pos} argument type. + */ + public static ArgumentType argumentMinecraftBlockPosition() { + return BlockPosArgument.blockPos(); + } + + /** + * Gets the value of the provided argument of type {@code minecraft:block_pos}, from the provided context. + * @param context the command execution context. + * @param argument the argument name. + * @param deflt a defualt value if the argument is not found. + * @return the value of the argument. + */ + public BlockVector tryGetMinecraftBlockPositionArgument(CommandContext context, + String argument, BlockVector deflt) { + return tryGetArgument(context, argument, Coordinates.MAPPING.runtimeClass(), nmsCoord -> { + BlockPos bp = ReflectWrapper.wrap(nmsCoord, Coordinates.class).getBlockPos(context.getSource()); + return new BlockVector(bp.getX(), bp.getY(), bp.getZ()); + }, deflt); + } + + + + + /** + * Creates a new instance of the Brigadier argument type {@code minecraft:vec3}. + * @return the {@code minecraft:vec3} argument type. + */ + public static ArgumentType argumentMinecraftVec3() { + return Vec3Argument.vec3(true); + } + + /** + * Gets the value of the provided argument of type {@code minecraft:vec3}, from the provided context. + * @param context the command execution context. + * @param argument the argument name. + * @param deflt a defualt value if the argument is not found. + * @return the value of the argument. + */ + public Vector tryGetMinecraftVec3Argument(CommandContext context, String argument, + Vector deflt) { + return tryGetArgument(context, argument, Coordinates.MAPPING.runtimeClass(), + nmsCoord -> CraftVector.toBukkit( + ReflectWrapper.wrap(nmsCoord, Coordinates.class).getPosition(context.getSource()) + ), + deflt); + } + + + + + /** + * Creates a new instance of the Brigadier argument type {@code minecraft:component}. + * @return the {@code minecraft:component} argument type. + */ + public static ArgumentType argumentMinecraftChatComponent() { + return ComponentArgument.textComponent(); + } + + /** + * Gets the value of the provided argument of type {@code minecraft:component}, from the provided context. + * @param context the command execution context. + * @param argument the argument name. + * @param deflt a defualt value if the argument is not found. + * @return the value of the argument. + */ + public Component tryGetMinecraftChatComponentArgument(CommandContext context, + String argument, Component deflt) { + return tryGetArgument(context, argument, + fr.pandacube.lib.paper.reflect.wrapper.minecraft.network.chat.Component.MAPPING.runtimeClass(), + nmsComp -> PaperAdventure.asAdventure( + ReflectWrapper.wrap(nmsComp, + fr.pandacube.lib.paper.reflect.wrapper.minecraft.network.chat.Component.class) + ), + deflt); + + } + + + /** + * All possible choices on how to force the registration of a command, based on certain conditions. + */ + public enum RegistrationPolicy { + /** + * Do not force to register a command node or an alias if there is already a command with that name in the + * vanilla Brigadier dispatcher. + * Note that all plugin-name-prefixed aliases will be registered anyway. + */ + NONE, + /** + * Force only the base command (but not the aliases) to be registered, even if a command with that name already + * exists in the vanilla Brigadier dispatcher. + */ + ONLY_BASE_COMMAND, + /** + * Force the command and all of its aliases to be registered, even if a command with the same name or alias + * already exists in the vanilla Brigadier dispatcher. + */ + ALL + } + } diff --git a/pandalib-paper-players/src/main/java/fr/pandacub/lib/paper/players/PaperOffPlayer.java b/pandalib-paper-players/src/main/java/fr/pandacub/lib/paper/players/PaperOffPlayer.java index 90c910e..77c05e7 100644 --- a/pandalib-paper-players/src/main/java/fr/pandacub/lib/paper/players/PaperOffPlayer.java +++ b/pandalib-paper-players/src/main/java/fr/pandacub/lib/paper/players/PaperOffPlayer.java @@ -7,6 +7,9 @@ import org.bukkit.scoreboard.Team; import fr.pandacube.lib.players.standalone.AbstractOffPlayer; +/** + * Represents any player on a paper server, either offline or online. + */ public interface PaperOffPlayer extends AbstractOffPlayer { /* @@ -15,7 +18,7 @@ public interface PaperOffPlayer extends AbstractOffPlayer { @Override default boolean isOnline() { - return (getBukkitPlayer() != null); + return getBukkitPlayer() != null; } @@ -29,8 +32,8 @@ public interface PaperOffPlayer extends AbstractOffPlayer { PaperOnlinePlayer getOnlineInstance(); /** - * @return l'instance Bukkit du joueur en ligne, ou null si il n'est pas en - * ligne + * Returns the Bukkit online {@link Player} instance of this player, or null if not available (offline). + * @return the Bukkit online {@link Player} instance of this player, or null if not available (offline). */ default Player getBukkitPlayer() { return Bukkit.getPlayer(getUniqueId()); diff --git a/pandalib-paper-players/src/main/java/fr/pandacub/lib/paper/players/PaperOnlinePlayer.java b/pandalib-paper-players/src/main/java/fr/pandacub/lib/paper/players/PaperOnlinePlayer.java index ba720e7..3d8633d 100644 --- a/pandalib-paper-players/src/main/java/fr/pandacub/lib/paper/players/PaperOnlinePlayer.java +++ b/pandalib-paper-players/src/main/java/fr/pandacub/lib/paper/players/PaperOnlinePlayer.java @@ -1,11 +1,9 @@ package fr.pandacub.lib.paper.players; -import java.util.Locale; -import java.util.UUID; - import com.destroystokyo.paper.ClientOption; import com.destroystokyo.paper.ClientOption.ChatVisibility; import com.destroystokyo.paper.SkinParts; +import fr.pandacube.lib.players.standalone.AbstractOnlinePlayer; import net.kyori.adventure.audience.MessageType; import net.kyori.adventure.identity.Identified; import net.kyori.adventure.identity.Identity; @@ -18,11 +16,14 @@ import org.bukkit.Location; import org.bukkit.OfflinePlayer; import org.bukkit.Sound; import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; import org.bukkit.inventory.MainHand; -import fr.pandacube.lib.players.standalone.AbstractOnlinePlayer; +import java.util.Locale; +import java.util.UUID; +/** + * Represents any online player on a paper server. + */ public interface PaperOnlinePlayer extends PaperOffPlayer, AbstractOnlinePlayer { /* @@ -46,12 +47,6 @@ public interface PaperOnlinePlayer extends PaperOffPlayer, AbstractOnlinePlayer * Related class instances */ - /** - * @return l'instance Bukkit du joueur en ligne, ou null si il n'est pas en - * ligne - */ - Player getBukkitPlayer(); - @Override default OfflinePlayer getBukkitOfflinePlayer() { return getBukkitPlayer(); @@ -100,10 +95,23 @@ public interface PaperOnlinePlayer extends PaperOffPlayer, AbstractOnlinePlayer getBukkitPlayer().showTitle(Title.title(title, subtitle, Times.times(Ticks.duration(fadeIn), Ticks.duration(stay), Ticks.duration(fadeOut)))); } + /** + * Play a sound on this player’s client, sourced at this player’s location. + * @param sound the sound to play + * @param volume the volume of the sound. + * @param pitch the pich in which the sound is played. + */ default void playSound(Sound sound, float volume, float pitch) { playSound(sound, getBukkitPlayer().getLocation(), volume, pitch); } + /** + * Play a sound on this player’s client. + * @param sound the sound to play + * @param location the source location of the sound. + * @param volume the volume of the sound. + * @param pitch the pich in which the sound is played. + */ default void playSound(Sound sound, Location location, float volume, float pitch) { getBukkitPlayer().playSound(location, sound, volume, pitch); } @@ -119,11 +127,18 @@ public interface PaperOnlinePlayer extends PaperOffPlayer, AbstractOnlinePlayer @Override PaperClientOptions getClientOptions(); + /** + * Provides various configuration values of the Minecraft client. + */ abstract class PaperClientOptions implements AbstractOnlinePlayer.ClientOptions { private final PaperOnlinePlayer op; - public PaperClientOptions(PaperOnlinePlayer op) { + /** + * Create a new instance of {@link PaperClientOptions}. + * @param op the {@link PaperOnlinePlayer} instance. + */ + protected PaperClientOptions(PaperOnlinePlayer op) { this.op = op; } @@ -132,6 +147,10 @@ public interface PaperOnlinePlayer extends PaperOffPlayer, AbstractOnlinePlayer return op.getBukkitPlayer().getClientOption(ClientOption.CHAT_COLORS_ENABLED); } + /** + * Gets the chat visibility configuration. + * @return the chat visibility configuration. + */ public ChatVisibility getChatVisibility() { return op.getBukkitPlayer().getClientOption(ClientOption.CHAT_VISIBILITY); } @@ -162,6 +181,10 @@ public interface PaperOnlinePlayer extends PaperOffPlayer, AbstractOnlinePlayer return op.getBukkitPlayer().getClientViewDistance(); } + /** + * Gets the player’s main hand. + * @return the player’s main hand. + */ public MainHand getMainHand() { return op.getBukkitPlayer().getMainHand(); } @@ -177,13 +200,19 @@ public interface PaperOnlinePlayer extends PaperOffPlayer, AbstractOnlinePlayer } @Override - public abstract boolean isTextFilteringEnabled(); // needs reflection + public boolean isTextFilteringEnabled() { // needs reflection to get the actual value + return false; + } @Override public boolean allowsServerListing() { return op.getBukkitPlayer().isAllowingServerListings(); } + /** + * Gets the player’s skin configuration. + * @return the player’s skin configuration. + */ public SkinParts getSkinParts() { return op.getBukkitPlayer().getClientOption(ClientOption.SKIN_PARTS); } @@ -231,10 +260,19 @@ public interface PaperOnlinePlayer extends PaperOffPlayer, AbstractOnlinePlayer * Custom damage */ + /** + * Deals damages to this player. + * @param amount the amount of damage to deal. + */ default void damage(double amount) { getBukkitPlayer().damage(amount); // uses DamageSource.GENERIC } + /** + * Deals damages to this player, from the provided entity. + * @param amount the amount of damage to deal. + * @param source the entity from which the damage comes from. + */ default void damage(double amount, LivingEntity source) { getBukkitPlayer().damage(amount, source); // uses appropriate DamageSource according to provided player or entity }