From 80b6d14ceefdfe5ad41116a040993b636ff98cb9 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 24 Mar 2022 17:39:35 -0400 Subject: [PATCH] Spigot: enable command completions for /geyser --- bootstrap/spigot/pom.xml | 22 +++++ .../platform/spigot/GeyserSpigotPlugin.java | 29 ++++++- .../command/GeyserPaperCommandListener.java | 87 +++++++++++++++++++ .../command/GeyserSpigotCommandManager.java | 21 +++-- 4 files changed, 152 insertions(+), 7 deletions(-) create mode 100644 bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserPaperCommandListener.java diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index da8b184e9..26f9c7083 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -19,6 +19,11 @@ viaversion-repo https://repo.viaversion.com + + + minecraft-repo + https://libraries.minecraft.net/ + @@ -34,6 +39,12 @@ 1.18.1-R0.1-SNAPSHOT provided + + io.papermc.paper + paper-mojangapi + 1.18.1-R0.1-SNAPSHOT + provided + com.viaversion viaversion @@ -45,6 +56,12 @@ spigot-all 1.4-SNAPSHOT + + me.lucko + commodore + 1.13 + compile + ${outputName}-Spigot @@ -95,6 +112,10 @@ org.objectweb.asm org.geysermc.geyser.platform.spigot.shaded.asm + + me.lucko.commodore + org.geysermc.geyser.platform.spigot.shaded.commodore + @@ -118,6 +139,7 @@ io.netty:netty-codec-dns:* io.netty:netty-resolver-dns:* io.netty:netty-resolver-dns-native-macos:* + com.mojang:* diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java index aae6c599a..b09aafd24 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java @@ -25,11 +25,15 @@ package org.geysermc.geyser.platform.spigot; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.data.MappingData; import com.viaversion.viaversion.api.protocol.ProtocolPathEntry; import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; +import me.lucko.commodore.Commodore; +import me.lucko.commodore.CommodoreProvider; import org.bukkit.Bukkit; +import org.bukkit.command.PluginCommand; import org.bukkit.plugin.java.JavaPlugin; import org.geysermc.common.PlatformType; import org.geysermc.geyser.Constants; @@ -43,6 +47,7 @@ import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.network.MinecraftProtocol; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; +import org.geysermc.geyser.platform.spigot.command.GeyserPaperCommandListener; import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandExecutor; import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandManager; import org.geysermc.geyser.platform.spigot.command.SpigotCommandSender; @@ -234,7 +239,29 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this); - this.getCommand("geyser").setExecutor(new GeyserSpigotCommandExecutor(geyser)); + PluginCommand pluginCommand = this.getCommand("geyser"); + pluginCommand.setExecutor(new GeyserSpigotCommandExecutor(geyser)); + + boolean brigadierSupported = CommodoreProvider.isSupported(); + geyserLogger.debug("Brigadier supported? " + brigadierSupported); + if (brigadierSupported) { + // Enable command completions if supported + // This is beneficial because this is sent over the network and Bedrock can see it + Commodore commodore = CommodoreProvider.getCommodore(this); + LiteralArgumentBuilder builder = LiteralArgumentBuilder.literal("geyser"); + for (String command : geyserCommandManager.getCommands().keySet()) { + builder.then(LiteralArgumentBuilder.literal(command)); + } + commodore.register(pluginCommand, builder); + + try { + Class.forName("com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent"); + Bukkit.getServer().getPluginManager().registerEvents(new GeyserPaperCommandListener(), this); + geyserLogger.debug("Successfully registered AsyncPlayerSendCommandsEvent listener."); + } catch (ClassNotFoundException e) { + geyserLogger.debug("Not registering AsyncPlayerSendCommandsEvent listener."); + } + } // Check to ensure the current setup can support the protocol version Geyser uses GeyserSpigotVersionChecker.checkForSupportedProtocol(geyserLogger, isViaVersion); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserPaperCommandListener.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserPaperCommandListener.java new file mode 100644 index 000000000..00c1ba58d --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserPaperCommandListener.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.platform.spigot.command; + +import com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent; +import com.mojang.brigadier.tree.CommandNode; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.GeyserCommand; + +import java.net.InetSocketAddress; +import java.util.Iterator; +import java.util.Map; + +public final class GeyserPaperCommandListener implements Listener { + + @EventHandler + @SuppressWarnings("deprecation") // Used to indicate an unstable event + public void onCommandSend(AsyncPlayerSendCommandsEvent event) { + // Documentation says to check (event.isAsynchronous() || !event.hasFiredAsync()), but as of Paper 1.18.2 + // event.hasFiredAsync is never true + if (event.isAsynchronous()) { + CommandNode geyserBrigadier = event.getCommandNode().getChild("geyser"); + if (geyserBrigadier != null) { + Player player = event.getPlayer(); + boolean isJavaPlayer = isProbablyJavaPlayer(player); + Map commands = GeyserImpl.getInstance().getCommandManager().getCommands(); + Iterator> it = geyserBrigadier.getChildren().iterator(); + + while (it.hasNext()) { + CommandNode subnode = it.next(); + GeyserCommand command = commands.get(subnode.getName()); + if (command != null) { + if ((command.isBedrockOnly() && isJavaPlayer) || !player.hasPermission(command.getPermission())) { + // Remove this from the node as we don't have permission to use it + it.remove(); + } + } + } + } + } + } + + /** + * This early on, there is a rare chance that Geyser has yet to process the connection. We'll try to minimize that + * chance, though. + */ + private boolean isProbablyJavaPlayer(Player player) { + if (GeyserImpl.getInstance().connectionByUuid(player.getUniqueId()) != null) { + // For sure this is a Bedrock player + return false; + } + + if (GeyserImpl.getInstance().getConfig().isUseDirectConnection()) { + InetSocketAddress address = player.getAddress(); + if (address != null) { + return address.getPort() != 0; + } + } + return true; + } +} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandManager.java index 103390ab8..6107d5b47 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandManager.java @@ -26,6 +26,7 @@ package org.geysermc.geyser.platform.spigot.command; import org.bukkit.Bukkit; +import org.bukkit.Server; import org.bukkit.command.Command; import org.bukkit.command.CommandMap; import org.geysermc.geyser.GeyserImpl; @@ -35,16 +36,24 @@ import java.lang.reflect.Field; public class GeyserSpigotCommandManager extends CommandManager { - private static CommandMap COMMAND_MAP; + private static final CommandMap COMMAND_MAP; static { + CommandMap commandMap = null; try { - Field cmdMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap"); - cmdMapField.setAccessible(true); - COMMAND_MAP = (CommandMap) cmdMapField.get(Bukkit.getServer()); - } catch (NoSuchFieldException | IllegalAccessException ex) { - ex.printStackTrace(); + // Paper-only + Server.class.getMethod("getCommandMap"); + commandMap = Bukkit.getServer().getCommandMap(); + } catch (NoSuchMethodException e) { + try { + Field cmdMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap"); + cmdMapField.setAccessible(true); + commandMap = (CommandMap) cmdMapField.get(Bukkit.getServer()); + } catch (NoSuchFieldException | IllegalAccessException ex) { + ex.printStackTrace(); + } } + COMMAND_MAP = commandMap; } public GeyserSpigotCommandManager(GeyserImpl geyser) {