From db5ccff9e546ab52f73bdf80640a3937aca5d559 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Thu, 1 Jun 2023 00:28:38 -0400 Subject: [PATCH 01/76] Initial start on cloud --- .../geyser/api/command/CommandSource.java | 11 ++ .../geyser/api/extension/Extension.java | 8 ++ bootstrap/bungeecord/build.gradle.kts | 2 + .../bungeecord/GeyserBungeePlugin.java | 29 +++-- .../command/BungeeCommandSource.java | 15 +++ .../command/GeyserBungeeCommandExecutor.java | 89 -------------- bootstrap/fabric/build.gradle.kts | 3 + .../platform/fabric/GeyserFabricMod.java | 40 +++---- .../fabric/GeyserFabricUpdateListener.java | 4 +- ...ndSender.java => FabricCommandSource.java} | 21 +++- .../command/GeyserFabricCommandExecutor.java | 74 ------------ bootstrap/spigot/build.gradle.kts | 2 + .../platform/spigot/GeyserSpigotPlugin.java | 83 +++++-------- .../command/GeyserBrigadierSupport.java | 61 ---------- .../command/GeyserPaperCommandListener.java | 87 -------------- .../command/GeyserSpigotCommandExecutor.java | 87 -------------- .../command/GeyserSpigotCommandManager.java | 72 ----------- .../spigot/command/SpigotCommandSource.java | 16 +++ .../platform/sponge/GeyserSpongePlugin.java | 24 +--- .../command/GeyserSpongeCommandExecutor.java | 112 ------------------ .../command/GeyserSpongeCommandManager.java | 61 ---------- .../sponge/command/SpongeCommandSource.java | 16 +++ .../standalone/GeyserStandaloneBootstrap.java | 2 +- .../GeyserStandaloneCommandManager.java | 51 ++++++++ .../standalone/GeyserStandaloneLogger.java | 2 +- .../standalone/gui/GeyserStandaloneGUI.java | 2 +- bootstrap/velocity/build.gradle.kts | 2 + .../velocity/GeyserVelocityPlugin.java | 33 +++--- .../GeyserVelocityCommandExecutor.java | 83 ------------- .../command/VelocityCommandSource.java | 15 +++ core/build.gradle.kts | 3 + .../java/org/geysermc/geyser/GeyserImpl.java | 2 +- .../org/geysermc/geyser/GeyserLogger.java | 7 ++ .../geyser/command/GeyserCommand.java | 29 +++++ .../geyser/command/GeyserCommandExecutor.java | 98 --------------- .../geyser/command/GeyserCommandManager.java | 79 ++++-------- .../geyser/command/GeyserCommandSource.java | 20 ++++ .../geyser/command/defaults/HelpCommand.java | 1 + .../geyser/session/GeyserSession.java | 5 + .../BedrockCommandRequestTranslator.java | 28 ++++- gradle/libs.versions.toml | 7 ++ 41 files changed, 366 insertions(+), 1020 deletions(-) delete mode 100644 bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandExecutor.java rename bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/{FabricCommandSender.java => FabricCommandSource.java} (83%) delete mode 100644 bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandExecutor.java delete mode 100644 bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserBrigadierSupport.java delete mode 100644 bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserPaperCommandListener.java delete mode 100644 bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandExecutor.java delete mode 100644 bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandManager.java delete mode 100644 bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/GeyserSpongeCommandExecutor.java delete mode 100644 bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/GeyserSpongeCommandManager.java create mode 100644 bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java delete mode 100644 bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/GeyserVelocityCommandExecutor.java delete mode 100644 core/src/main/java/org/geysermc/geyser/command/GeyserCommandExecutor.java diff --git a/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java b/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java index 45276e2c4..f20fd0f94 100644 --- a/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java +++ b/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java @@ -26,6 +26,10 @@ package org.geysermc.geyser.api.command; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.api.connection.Connection; + +import java.util.Optional; +import java.util.UUID; /** * Represents an instance capable of sending commands. @@ -64,6 +68,13 @@ public interface CommandSource { */ boolean isConsole(); + /** + * todo: commands + */ + Optional playerUuid(); + + Optional connection(); + /** * Returns the locale of the command source. * diff --git a/api/src/main/java/org/geysermc/geyser/api/extension/Extension.java b/api/src/main/java/org/geysermc/geyser/api/extension/Extension.java index 993bdee44..ee3ac640c 100644 --- a/api/src/main/java/org/geysermc/geyser/api/extension/Extension.java +++ b/api/src/main/java/org/geysermc/geyser/api/extension/Extension.java @@ -107,6 +107,14 @@ public interface Extension extends EventRegistrar { return this.extensionLoader().description(this); } + /** + * todo: commands + */ + @NonNull + default String rootCommand() { + return this.description().id(); + } + /** * Gets the extension's logger * diff --git a/bootstrap/bungeecord/build.gradle.kts b/bootstrap/bungeecord/build.gradle.kts index 3e0e9c147..9547c37ef 100644 --- a/bootstrap/bungeecord/build.gradle.kts +++ b/bootstrap/bungeecord/build.gradle.kts @@ -1,6 +1,7 @@ dependencies { api(projects.core) + implementation(libs.cloud.bungee) implementation(libs.adventure.text.serializer.bungeecord) } @@ -8,6 +9,7 @@ platformRelocate("net.md_5.bungee.jni") platformRelocate("com.fasterxml.jackson") platformRelocate("io.netty.channel.kqueue") // This is not used because relocating breaks natives, but we must include it or else we get ClassDefNotFound platformRelocate("net.kyori") +platformRelocate("cloud.commandframework") // These dependencies are already present on the platform provided(libs.bungeecord.proxy) diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java index 4141a8dbc..490c9e593 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java @@ -25,8 +25,12 @@ package org.geysermc.geyser.platform.bungeecord; +import cloud.commandframework.CommandManager; +import cloud.commandframework.bungee.BungeeCommandManager; +import cloud.commandframework.execution.CommandExecutionCoordinator; import io.netty.channel.Channel; import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.config.ListenerInfo; import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.protocol.ProtocolConstants; @@ -34,14 +38,13 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.command.Command; -import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; -import org.geysermc.geyser.platform.bungeecord.command.GeyserBungeeCommandExecutor; +import org.geysermc.geyser.platform.bungeecord.command.BungeeCommandSource; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; import org.jetbrains.annotations.NotNull; @@ -54,7 +57,6 @@ import java.net.SocketAddress; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collection; -import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -174,19 +176,16 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { this.geyserInjector = new GeyserBungeeInjector(this); this.geyserInjector.initializeLocalChannel(this); - this.geyserCommandManager = new GeyserCommandManager(geyser); + + CommandManager cloud = new BungeeCommandManager<>( + this, + CommandExecutionCoordinator.simpleCoordinator(), + BungeeCommandSource::new, + origin -> (CommandSender) origin.handle() + ); + this.geyserCommandManager = new GeyserCommandManager(geyser, cloud); this.geyserCommandManager.init(); - this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor("geyser", this.geyser, this.geyserCommandManager.getCommands())); - for (Map.Entry> entry : this.geyserCommandManager.extensionCommands().entrySet()) { - Map commands = entry.getValue(); - if (commands.isEmpty()) { - continue; - } - - this.getProxy().getPluginManager().registerCommand(this, new GeyserBungeeCommandExecutor(entry.getKey().description().id(), this.geyser, commands)); - } - if (geyserConfig.isLegacyPingPassthrough()) { this.geyserBungeePingPassthrough = GeyserLegacyPingPassthrough.init(geyser); } else { diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java index f65377643..65703c105 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java @@ -33,6 +33,8 @@ import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.text.GeyserLocale; import java.util.Locale; +import java.util.Optional; +import java.util.UUID; public class BungeeCommandSource implements GeyserCommandSource { @@ -71,6 +73,14 @@ public class BungeeCommandSource implements GeyserCommandSource { return !(handle instanceof ProxiedPlayer); } + @Override + public Optional playerUuid() { + if (handle instanceof ProxiedPlayer player) { + return Optional.of(player.getUniqueId()); + } + return Optional.empty(); + } + @Override public String locale() { if (handle instanceof ProxiedPlayer player) { @@ -87,4 +97,9 @@ public class BungeeCommandSource implements GeyserCommandSource { public boolean hasPermission(String permission) { return handle.hasPermission(permission); } + + @Override + public Object handle() { + return handle; + } } diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandExecutor.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandExecutor.java deleted file mode 100644 index 2d02c9950..000000000 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/GeyserBungeeCommandExecutor.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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.bungeecord.command; - -import net.md_5.bungee.api.ChatColor; -import net.md_5.bungee.api.CommandSender; -import net.md_5.bungee.api.plugin.Command; -import net.md_5.bungee.api.plugin.TabExecutor; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.GeyserCommand; -import org.geysermc.geyser.command.GeyserCommandExecutor; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.text.GeyserLocale; - -import java.util.Arrays; -import java.util.Collections; -import java.util.Map; - -public class GeyserBungeeCommandExecutor extends Command implements TabExecutor { - private final GeyserCommandExecutor commandExecutor; - - public GeyserBungeeCommandExecutor(String name, GeyserImpl geyser, Map commands) { - super(name); - - this.commandExecutor = new GeyserCommandExecutor(geyser, commands); - } - - @Override - public void execute(CommandSender sender, String[] args) { - BungeeCommandSource commandSender = new BungeeCommandSource(sender); - GeyserSession session = this.commandExecutor.getGeyserSession(commandSender); - - if (args.length > 0) { - GeyserCommand command = this.commandExecutor.getCommand(args[0]); - if (command != null) { - if (!sender.hasPermission(command.permission())) { - String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.locale()); - - commandSender.sendMessage(ChatColor.RED + message); - return; - } - if (command.isBedrockOnly() && session == null) { - String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", commandSender.locale()); - - commandSender.sendMessage(ChatColor.RED + message); - return; - } - command.execute(session, commandSender, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); - } else { - String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.not_found", commandSender.locale()); - commandSender.sendMessage(ChatColor.RED + message); - } - } else { - this.commandExecutor.getCommand("help").execute(session, commandSender, new String[0]); - } - } - - @Override - public Iterable onTabComplete(CommandSender sender, String[] args) { - if (args.length == 1) { - return commandExecutor.tabComplete(new BungeeCommandSource(sender)); - } else { - return Collections.emptyList(); - } - } -} diff --git a/bootstrap/fabric/build.gradle.kts b/bootstrap/fabric/build.gradle.kts index e85c2f809..ca986c618 100644 --- a/bootstrap/fabric/build.gradle.kts +++ b/bootstrap/fabric/build.gradle.kts @@ -17,6 +17,9 @@ dependencies { // Fabric API. This is technically optional, but you probably want it anyway. modImplementation(libs.fabric.api) + modImplementation(libs.cloud.fabric) + include(libs.cloud.fabric) + // This should be in the libs TOML, but something about modImplementation AND include just doesn't work include(modImplementation("me.lucko", "fabric-permissions-api", "0.2-SNAPSHOT")) diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java index fdc820b19..d1a2c315c 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java @@ -25,7 +25,11 @@ package org.geysermc.geyser.platform.fabric; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import cloud.commandframework.CommandManager; +import cloud.commandframework.execution.CommandExecutionCoordinator; +import cloud.commandframework.fabric.FabricClientCommandManager; +import cloud.commandframework.fabric.FabricCommandManager; +import cloud.commandframework.fabric.FabricServerCommandManager; import net.fabricmc.api.EnvType; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; @@ -33,22 +37,21 @@ import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.ModContainer; import net.minecraft.commands.CommandSourceStack; -import net.minecraft.commands.Commands; +import net.minecraft.commands.SharedSuggestionProvider; import net.minecraft.server.MinecraftServer; import org.apache.logging.log4j.LogManager; import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; -import org.geysermc.geyser.api.command.Command; -import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; -import org.geysermc.geyser.platform.fabric.command.GeyserFabricCommandExecutor; +import org.geysermc.geyser.platform.fabric.command.FabricCommandSource; import org.geysermc.geyser.platform.fabric.world.GeyserFabricWorldManager; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; @@ -59,7 +62,6 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; -import java.util.Map; import java.util.Optional; import java.util.UUID; @@ -144,26 +146,16 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); - this.geyserCommandManager = new GeyserCommandManager(geyser); + + CommandManager cloud = new FabricServerCommandManager<>( + CommandExecutionCoordinator.simpleCoordinator(), + FabricCommandSource::new, + source -> (CommandSourceStack) source.handle() + ); + this.geyserCommandManager = new GeyserCommandManager(geyser, cloud); this.geyserCommandManager.init(); this.geyserWorldManager = new GeyserFabricWorldManager(server); - - // Start command building - // Set just "geyser" as the help command - GeyserFabricCommandExecutor helpExecutor = new GeyserFabricCommandExecutor(geyser, - (GeyserCommand) geyser.commandManager().getCommands().get("help")); - LiteralArgumentBuilder builder = Commands.literal("geyser").executes(helpExecutor); - - // Register all subcommands as valid - for (Map.Entry command : geyser.commandManager().getCommands().entrySet()) { - GeyserFabricCommandExecutor executor = new GeyserFabricCommandExecutor(geyser, (GeyserCommand) command.getValue()); - builder.then(Commands.literal(command.getKey()) - .executes(executor) - // Could also test for Bedrock but depending on when this is called it may backfire - .requires(executor::testPermission)); - } - server.getCommands().getDispatcher().register(builder); } @Override @@ -257,7 +249,7 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { } public void setReloading(boolean reloading) { - this.reloading = reloading; + this.reloading = reloading; // todo: commands } public static GeyserFabricMod getInstance() { diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricUpdateListener.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricUpdateListener.java index 1ea69cbe2..bf3541152 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricUpdateListener.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricUpdateListener.java @@ -28,13 +28,13 @@ package org.geysermc.geyser.platform.fabric; import me.lucko.fabric.api.permissions.v0.Permissions; import net.minecraft.server.network.ServerGamePacketListenerImpl; import org.geysermc.geyser.Constants; -import org.geysermc.geyser.platform.fabric.command.FabricCommandSender; +import org.geysermc.geyser.platform.fabric.command.FabricCommandSource; import org.geysermc.geyser.util.VersionCheckUtils; public final class GeyserFabricUpdateListener { public static void onPlayReady(ServerGamePacketListenerImpl handler) { if (Permissions.check(handler.player, Constants.UPDATE_PERMISSION, 2)) { - VersionCheckUtils.checkForGeyserUpdate(() -> new FabricCommandSender(handler.player.createCommandSourceStack())); + VersionCheckUtils.checkForGeyserUpdate(() -> new FabricCommandSource(handler.player.createCommandSourceStack())); } } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSender.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSource.java similarity index 83% rename from bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSender.java rename to bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSource.java index 5973e04f1..e4a0c5611 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSender.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSource.java @@ -35,12 +35,14 @@ import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.text.ChatColor; import javax.annotation.Nonnull; +import java.util.Optional; +import java.util.UUID; -public class FabricCommandSender implements GeyserCommandSource { +public class FabricCommandSource implements GeyserCommandSource { private final CommandSourceStack source; - public FabricCommandSender(CommandSourceStack source) { + public FabricCommandSource(CommandSourceStack source) { this.source = source; } @@ -73,8 +75,21 @@ public class FabricCommandSender implements GeyserCommandSource { return !(source.getEntity() instanceof ServerPlayer); } + @Override + public Optional playerUuid() { + if (source.getEntity() instanceof ServerPlayer player) { + return Optional.of(player.getUUID()); + } + return Optional.empty(); + } + @Override public boolean hasPermission(String permission) { - return Permissions.check(source, permission); + return Permissions.check(source, permission, source.getServer().getOperatorUserPermissionLevel()); + } + + @Override + public Object handle() { + return source; } } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandExecutor.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandExecutor.java deleted file mode 100644 index 7600e4136..000000000 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandExecutor.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.fabric.command; - -import com.mojang.brigadier.Command; -import com.mojang.brigadier.context.CommandContext; -import me.lucko.fabric.api.permissions.v0.Permissions; -import net.minecraft.commands.CommandSourceStack; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.GeyserCommand; -import org.geysermc.geyser.command.GeyserCommandExecutor; -import org.geysermc.geyser.platform.fabric.GeyserFabricMod; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.text.ChatColor; -import org.geysermc.geyser.text.GeyserLocale; - -import java.util.Collections; - -public class GeyserFabricCommandExecutor extends GeyserCommandExecutor implements Command { - private final GeyserCommand command; - - public GeyserFabricCommandExecutor(GeyserImpl connector, GeyserCommand command) { - super(connector, Collections.singletonMap(command.name(), command)); - this.command = command; - } - - public boolean testPermission(CommandSourceStack source) { - return Permissions.check(source, command.permission(), command.isSuggestedOpOnly() ? 2 : 0); - } - - @Override - public int run(CommandContext context) { - CommandSourceStack source = (CommandSourceStack) context.getSource(); - FabricCommandSender sender = new FabricCommandSender(source); - GeyserSession session = getGeyserSession(sender); - if (!testPermission(source)) { - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale())); - return 0; - } - if (this.command.name().equals("reload")) { - GeyserFabricMod.getInstance().setReloading(true); - } - - if (command.isBedrockOnly() && session == null) { - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.locale())); - return 0; - } - command.execute(session, sender, new String[0]); - return 0; - } -} diff --git a/bootstrap/spigot/build.gradle.kts b/bootstrap/spigot/build.gradle.kts index 58ea763eb..e9977c037 100644 --- a/bootstrap/spigot/build.gradle.kts +++ b/bootstrap/spigot/build.gradle.kts @@ -6,6 +6,7 @@ dependencies { implementation(libs.adapters.spigot) + implementation(libs.cloud.paper) implementation(libs.commodore) implementation(libs.adventure.text.serializer.bungeecord) @@ -29,6 +30,7 @@ platformRelocate("com.fasterxml.jackson") platformRelocate("net.kyori", "net.kyori.adventure.text.logger.slf4j.ComponentLogger") platformRelocate("org.objectweb.asm") platformRelocate("me.lucko.commodore") +platformRelocate("cloud.commandframework") // These dependencies are already present on the platform provided(libs.viaversion) 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 a660d735b..afd90f921 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,22 +25,22 @@ package org.geysermc.geyser.platform.spigot; +import cloud.commandframework.bukkit.BukkitCommandManager; +import cloud.commandframework.execution.CommandExecutionCoordinator; +import cloud.commandframework.paper.PaperCommandManager; 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 io.netty.buffer.ByteBuf; -import me.lucko.commodore.CommodoreProvider; import org.bukkit.Bukkit; import org.bukkit.block.data.BlockData; -import org.bukkit.command.CommandMap; -import org.bukkit.command.PluginCommand; +import org.bukkit.command.CommandSender; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.server.ServerLoadEvent; import org.bukkit.permissions.Permission; import org.bukkit.permissions.PermissionDefault; -import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; import org.geysermc.common.PlatformType; import org.geysermc.geyser.Constants; @@ -50,15 +50,14 @@ import org.geysermc.geyser.adapters.spigot.SpigotAdapters; import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; -import org.geysermc.geyser.platform.spigot.command.GeyserBrigadierSupport; -import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandExecutor; -import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandManager; +import org.geysermc.geyser.platform.spigot.command.SpigotCommandSource; import org.geysermc.geyser.platform.spigot.world.GeyserPistonListener; import org.geysermc.geyser.platform.spigot.world.GeyserSpigotBlockPlaceListener; import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotLegacyNativeWorldManager; @@ -70,8 +69,6 @@ import org.jetbrains.annotations.NotNull; import java.io.File; import java.io.IOException; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.net.SocketAddress; import java.nio.file.Path; import java.util.List; @@ -85,7 +82,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { */ private static boolean INITIALIZED = false; - private GeyserSpigotCommandManager geyserCommandManager; + private GeyserCommandManager geyserCommandManager; private GeyserSpigotConfiguration geyserConfig; private GeyserSpigotInjector geyserInjector; private GeyserSpigotLogger geyserLogger; @@ -172,7 +169,30 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { return; } - this.geyserCommandManager = new GeyserSpigotCommandManager(geyser); + PaperCommandManager cloud; + try { + cloud = new PaperCommandManager<>( + this, + CommandExecutionCoordinator.simpleCoordinator(), + SpigotCommandSource::new, + source -> (CommandSender) source.handle() + ); + } catch (Exception e) { + throw new RuntimeException(e); + } + + try { + // Should always be available on 1.13 and up + cloud.registerBrigadier(); + } catch (BukkitCommandManager.BrigadierFailureException e) { + geyserLogger.error("Failed to initialize Brigadier support: " + e.getMessage()); + if (e.getReason() == BukkitCommandManager.BrigadierFailureReason.VERSION_TOO_HIGH) { + // Commodore brig only supports Spigot 1.13 - 1.18.2 + geyserLogger.error("Using Paper instead of Spigot will likely fix this."); + } + } + + this.geyserCommandManager = new GeyserCommandManager(geyser, cloud); this.geyserCommandManager.init(); if (!INITIALIZED) { @@ -185,25 +205,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { postStartup(); } }, this); - - // Because Bukkit locks its command map upon startup, we need to - // add our plugin commands in onEnable, but populating the executor - // can happen at any time - CommandMap commandMap = GeyserSpigotCommandManager.getCommandMap(); - for (Extension extension : this.geyserCommandManager.extensionCommands().keySet()) { - // Thanks again, Bukkit - try { - Constructor constructor = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class); - constructor.setAccessible(true); - - PluginCommand pluginCommand = constructor.newInstance(extension.description().id(), this); - pluginCommand.setDescription("The main command for the " + extension.name() + " Geyser extension!"); - - commandMap.register(extension.description().id(), "geyserext", pluginCommand); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) { - this.geyserLogger.error("Failed to construct PluginCommand for extension " + extension.description().name(), ex); - } - } } if (INITIALIZED) { @@ -278,22 +279,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { geyserLogger.debug("Using default world manager."); } - PluginCommand geyserCommand = this.getCommand("geyser"); - geyserCommand.setExecutor(new GeyserSpigotCommandExecutor(geyser, geyserCommandManager.getCommands())); - - for (Map.Entry> entry : this.geyserCommandManager.extensionCommands().entrySet()) { - Map commands = entry.getValue(); - if (commands.isEmpty()) { - continue; - } - - PluginCommand command = this.getCommand(entry.getKey().description().id()); - if (command == null) { - continue; - } - - command.setExecutor(new GeyserSpigotCommandExecutor(this.geyser, commands)); - } if (!INITIALIZED) { // Register permissions so they appear in, for example, LuckPerms' UI @@ -341,12 +326,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { Bukkit.getServer().getPluginManager().registerEvents(new GeyserSpigotUpdateListener(), this); } - boolean brigadierSupported = CommodoreProvider.isSupported(); - geyserLogger.debug("Brigadier supported? " + brigadierSupported); - if (brigadierSupported) { - GeyserBrigadierSupport.loadBrigadier(this, geyserCommand); - } - // 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/GeyserBrigadierSupport.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserBrigadierSupport.java deleted file mode 100644 index 61900174c..000000000 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserBrigadierSupport.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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.mojang.brigadier.builder.LiteralArgumentBuilder; -import me.lucko.commodore.Commodore; -import me.lucko.commodore.CommodoreProvider; -import org.bukkit.Bukkit; -import org.bukkit.command.PluginCommand; -import org.geysermc.geyser.platform.spigot.GeyserSpigotPlugin; - -/** - * Needs to be a separate class so pre-1.13 loads correctly. - */ -public final class GeyserBrigadierSupport { - - public static void loadBrigadier(GeyserSpigotPlugin plugin, PluginCommand pluginCommand) { - // Enable command completions if supported - // This is beneficial because this is sent over the network and Bedrock can see it - Commodore commodore = CommodoreProvider.getCommodore(plugin); - LiteralArgumentBuilder builder = LiteralArgumentBuilder.literal("geyser"); - for (String command : plugin.getGeyserCommandManager().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(), plugin); - plugin.getGeyserLogger().debug("Successfully registered AsyncPlayerSendCommandsEvent listener."); - } catch (ClassNotFoundException e) { - plugin.getGeyserLogger().debug("Not registering AsyncPlayerSendCommandsEvent listener."); - } - } - - private GeyserBrigadierSupport() { - } -} 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 deleted file mode 100644 index 9375e3a62..000000000 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserPaperCommandListener.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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.api.command.Command; - -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().commandManager().getCommands(); - Iterator> it = geyserBrigadier.getChildren().iterator(); - - while (it.hasNext()) { - CommandNode subnode = it.next(); - Command command = commands.get(subnode.getName()); - if (command != null) { - if ((command.isBedrockOnly() && isJavaPlayer) || !player.hasPermission(command.permission())) { - // 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/GeyserSpigotCommandExecutor.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandExecutor.java deleted file mode 100644 index 61d394214..000000000 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandExecutor.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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 org.bukkit.ChatColor; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.command.TabExecutor; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.GeyserCommand; -import org.geysermc.geyser.command.GeyserCommandExecutor; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.text.GeyserLocale; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -public class GeyserSpigotCommandExecutor extends GeyserCommandExecutor implements TabExecutor { - - public GeyserSpigotCommandExecutor(GeyserImpl geyser, Map commands) { - super(geyser, commands); - } - - @Override - public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - SpigotCommandSource commandSender = new SpigotCommandSource(sender); - GeyserSession session = getGeyserSession(commandSender); - - if (args.length > 0) { - GeyserCommand geyserCommand = getCommand(args[0]); - if (geyserCommand != null) { - if (!sender.hasPermission(geyserCommand.permission())) { - String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.locale()); - - commandSender.sendMessage(ChatColor.RED + message); - return true; - } - if (geyserCommand.isBedrockOnly() && session == null) { - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", commandSender.locale())); - return true; - } - geyserCommand.execute(session, commandSender, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); - return true; - } else { - String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.not_found", commandSender.locale()); - commandSender.sendMessage(ChatColor.RED + message); - } - } else { - getCommand("help").execute(session, commandSender, new String[0]); - return true; - } - return true; - } - - @Override - public List onTabComplete(CommandSender sender, Command command, String label, String[] args) { - if (args.length == 1) { - return tabComplete(new SpigotCommandSource(sender)); - } - return Collections.emptyList(); - } -} 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 deleted file mode 100644 index 655d3be23..000000000 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandManager.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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 org.bukkit.Bukkit; -import org.bukkit.Server; -import org.bukkit.command.Command; -import org.bukkit.command.CommandMap; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.GeyserCommandManager; - -import java.lang.reflect.Field; - -public class GeyserSpigotCommandManager extends GeyserCommandManager { - - private static final CommandMap COMMAND_MAP; - - static { - CommandMap commandMap = null; - try { - // 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) { - super(geyser); - } - - @Override - public String description(String command) { - Command cmd = COMMAND_MAP.getCommand(command.replace("/", "")); - return cmd != null ? cmd.getDescription() : ""; - } - - public static CommandMap getCommandMap() { - return COMMAND_MAP; - } -} diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSource.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSource.java index 95fba707f..c80db4df7 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSource.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSource.java @@ -33,6 +33,9 @@ import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.platform.spigot.PaperAdventure; import org.geysermc.geyser.text.GeyserLocale; +import java.util.Optional; +import java.util.UUID; + public class SpigotCommandSource implements GeyserCommandSource { private final org.bukkit.command.CommandSender handle; @@ -63,11 +66,24 @@ public class SpigotCommandSource implements GeyserCommandSource { handle.spigot().sendMessage(BungeeComponentSerializer.get().serialize(message)); } + @Override + public Object handle() { + return handle; + } + @Override public boolean isConsole() { return handle instanceof ConsoleCommandSender; } + @Override + public Optional playerUuid() { + if (handle instanceof Player player) { + return Optional.of(player.getUniqueId()); + } + return Optional.empty(); + } + @Override public String locale() { if (this.handle instanceof Player player) { diff --git a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java index dd84bf31c..8bce03590 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java @@ -30,17 +30,13 @@ import org.apache.logging.log4j.Logger; import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.command.Command; -import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; -import org.geysermc.geyser.platform.sponge.command.GeyserSpongeCommandManager; import org.geysermc.geyser.util.FileUtils; import org.geysermc.geyser.text.GeyserLocale; -import org.geysermc.geyser.platform.sponge.command.GeyserSpongeCommandExecutor; import org.jetbrains.annotations.NotNull; import org.spongepowered.api.Server; import org.spongepowered.api.Sponge; @@ -59,7 +55,6 @@ import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.file.Path; -import java.util.Map; import java.util.UUID; @Plugin(value = "geyser") @@ -85,7 +80,7 @@ public class GeyserSpongePlugin implements GeyserBootstrap { private GeyserSpongeConfiguration geyserConfig; private GeyserSpongeLogger geyserLogger; private GeyserImpl geyser; - private GeyserSpongeCommandManager geyserCommandManager; // Commands are only registered after command registration lifecycle + private GeyserCommandManager geyserCommandManager; // Available after StartedEngine lifecycle private IGeyserPingPassthrough geyserSpongePingPassthrough; @@ -144,29 +139,18 @@ public class GeyserSpongePlugin implements GeyserBootstrap { this.geyser = GeyserImpl.load(PlatformType.SPONGE, this); - this.geyserCommandManager = new GeyserSpongeCommandManager(geyser); + this.geyserCommandManager = new GeyserCommandManager(geyser); // todo: commands this.geyserCommandManager.init(); } /** - * Construct the {@link GeyserSpongeCommandManager} and register the commands + * Construct the {@link GeyserCommandManager} and register the commands * * @param event required to register the commands */ @Listener public void onRegisterCommands(@Nonnull RegisterCommandEvent event) { - if (enabled) { - event.register(this.pluginContainer, new GeyserSpongeCommandExecutor(geyser, geyserCommandManager.getCommands()), "geyser"); - - for (Map.Entry> entry : this.geyserCommandManager.extensionCommands().entrySet()) { - Map commands = entry.getValue(); - if (commands.isEmpty()) { - continue; - } - - event.register(this.pluginContainer, new GeyserSpongeCommandExecutor(this.geyser, commands), entry.getKey().description().id()); - } - } + // todo: commands. sponge-cloud probably hooks into this without us needing to } /** diff --git a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/GeyserSpongeCommandExecutor.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/GeyserSpongeCommandExecutor.java deleted file mode 100644 index a1a0d99ad..000000000 --- a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/GeyserSpongeCommandExecutor.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * 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.sponge.command; - -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.command.Command; -import org.geysermc.geyser.command.GeyserCommand; -import org.geysermc.geyser.command.GeyserCommandExecutor; -import org.geysermc.geyser.command.GeyserCommandSource; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.text.GeyserLocale; -import org.jetbrains.annotations.NotNull; -import org.spongepowered.api.command.CommandCause; -import org.spongepowered.api.command.CommandCompletion; -import org.spongepowered.api.command.CommandResult; -import org.spongepowered.api.command.parameter.ArgumentReader; - -import java.util.*; -import java.util.stream.Collectors; - -public class GeyserSpongeCommandExecutor extends GeyserCommandExecutor implements org.spongepowered.api.command.Command.Raw { - - public GeyserSpongeCommandExecutor(GeyserImpl geyser, Map commands) { - super(geyser, commands); - } - - @Override - public CommandResult process(CommandCause cause, ArgumentReader.Mutable arguments) { - GeyserCommandSource commandSource = new SpongeCommandSource(cause); - GeyserSession session = getGeyserSession(commandSource); - - String[] args = arguments.input().split(" "); - // This split operation results in an array of length 1, containing a zero length string, if the input string is empty - if (args.length > 0 && !args[0].isEmpty()) { - GeyserCommand command = getCommand(args[0]); - if (command != null) { - if (!cause.hasPermission(command.permission())) { - cause.audience().sendMessage(Component.text(GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.permission_fail")).color(NamedTextColor.RED)); - return CommandResult.success(); - } - if (command.isBedrockOnly() && session == null) { - cause.audience().sendMessage(Component.text(GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.bedrock_only")).color(NamedTextColor.RED)); - return CommandResult.success(); - } - command.execute(session, commandSource, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]); - } else { - cause.audience().sendMessage(Component.text(GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.not_found")).color(NamedTextColor.RED)); - } - } else { - getCommand("help").execute(session, commandSource, new String[0]); - } - return CommandResult.success(); - } - - @Override - public List complete(CommandCause cause, ArgumentReader.Mutable arguments) { - if (arguments.input().split(" ").length == 1) { - return tabComplete(new SpongeCommandSource(cause)).stream().map(CommandCompletion::of).collect(Collectors.toList()); - } - return Collections.emptyList(); - } - - @Override - public boolean canExecute(CommandCause cause) { - return true; - } - - @Override - public Optional shortDescription(CommandCause cause) { - return Optional.of(Component.text("The main command for Geyser.")); - } - - @Override - public Optional extendedDescription(CommandCause cause) { - return shortDescription(cause); - } - - @Override - public Optional help(@NotNull CommandCause cause) { - return Optional.of(Component.text("/geyser help")); - } - - @Override - public Component usage(CommandCause cause) { - return Component.text("/geyser help"); - } -} diff --git a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/GeyserSpongeCommandManager.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/GeyserSpongeCommandManager.java deleted file mode 100644 index d83e3a723..000000000 --- a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/GeyserSpongeCommandManager.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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.sponge.command; - -import net.kyori.adventure.text.Component; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.GeyserCommandManager; -import org.geysermc.geyser.translator.text.MessageTranslator; -import org.spongepowered.api.Sponge; -import org.spongepowered.api.command.CommandCause; -import org.spongepowered.api.command.manager.CommandMapping; - -import java.util.Optional; - -public class GeyserSpongeCommandManager extends GeyserCommandManager { - - public GeyserSpongeCommandManager(GeyserImpl geyser) { - super(geyser); - } - - @Override - public String description(String command) { - if (!Sponge.isServerAvailable()) { - return ""; - } - - // Note: The command manager may be replaced at any point during the game lifecycle - return Sponge.server().commandManager().commandMapping(command) - .map(this::description) - .map(Optional::get) - .map(MessageTranslator::convertMessage) - .orElse(""); - } - - public Optional description(CommandMapping mapping) { - return mapping.registrar().shortDescription(CommandCause.create(), mapping); - } -} diff --git a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/SpongeCommandSource.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/SpongeCommandSource.java index 31dccc1fb..d578c73d0 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/SpongeCommandSource.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/command/SpongeCommandSource.java @@ -32,6 +32,9 @@ import org.geysermc.geyser.command.GeyserCommandSource; import org.spongepowered.api.command.CommandCause; import org.spongepowered.api.entity.living.player.server.ServerPlayer; +import java.util.Optional; +import java.util.UUID; + @AllArgsConstructor public class SpongeCommandSource implements GeyserCommandSource { @@ -52,8 +55,21 @@ public class SpongeCommandSource implements GeyserCommandSource { return !(handle.cause().root() instanceof ServerPlayer); } + @Override + public Optional playerUuid() { + if (handle.cause().root() instanceof ServerPlayer player) { + return Optional.of(player.uniqueId()); + } + return Optional.empty(); + } + @Override public boolean hasPermission(String permission) { return handle.hasPermission(permission); } + + @Override + public Object handle() { + return handle; + } } diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java index c4271be4c..4d7836f84 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java @@ -220,7 +220,7 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { geyser = GeyserImpl.load(PlatformType.STANDALONE, this); GeyserImpl.start(); - geyserCommandManager = new GeyserCommandManager(geyser); + geyserCommandManager = new GeyserCommandManager(geyser, new GeyserStandaloneCommandManager()); geyserCommandManager.init(); if (gui != null) { diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java new file mode 100644 index 000000000..cfd47f7a1 --- /dev/null +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019-2023 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.standalone; + +import cloud.commandframework.CommandManager; +import cloud.commandframework.execution.CommandExecutionCoordinator; +import cloud.commandframework.internal.CommandRegistrationHandler; +import cloud.commandframework.meta.CommandMeta; +import cloud.commandframework.meta.SimpleCommandMeta; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.command.GeyserCommandSource; + +public class GeyserStandaloneCommandManager extends CommandManager { + + protected GeyserStandaloneCommandManager() { + super(CommandExecutionCoordinator.simpleCoordinator(), CommandRegistrationHandler.nullCommandRegistrationHandler()); + } + + @Override + public boolean hasPermission(@NonNull GeyserCommandSource sender, @NonNull String permission) { + return false; // todo: commands + } + + @Override + public @NonNull CommandMeta createDefaultCommandMeta() { + return SimpleCommandMeta.empty(); + } +} diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java index e7e24a465..029fbd8ec 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java @@ -44,7 +44,7 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements Gey @Override protected void runCommand(String line) { - GeyserImpl.getInstance().commandManager().runCommand(this, line); + GeyserImpl.getInstance().commandManager().cloud().executeCommand(this, line); } @Override diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java index 41cbafb25..64e33089e 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java @@ -260,7 +260,7 @@ public class GeyserStandaloneGUI { commandsMenu.removeAll(); optionsMenu.removeAll(); - for (Map.Entry entry : geyserCommandManager.getCommands().entrySet()) { + for (Map.Entry entry : geyserCommandManager.commands().entrySet()) { // Remove the offhand command and any alias commands to prevent duplicates in the list if (!entry.getValue().isExecutableOnConsole() || entry.getValue().aliases().contains(entry.getKey())) { continue; diff --git a/bootstrap/velocity/build.gradle.kts b/bootstrap/velocity/build.gradle.kts index 8908b2afd..fdd0edc46 100644 --- a/bootstrap/velocity/build.gradle.kts +++ b/bootstrap/velocity/build.gradle.kts @@ -1,11 +1,13 @@ dependencies { annotationProcessor(libs.velocity.api) api(projects.core) + api(libs.cloud.velocity) } platformRelocate("com.fasterxml.jackson") platformRelocate("it.unimi.dsi.fastutil") platformRelocate("net.kyori.adventure.text.serializer.gson.legacyimpl") +platformRelocate("cloud.commandframework") exclude("com.google.*:*") diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java index 92b3a71a7..7b0706217 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java @@ -25,28 +25,31 @@ package org.geysermc.geyser.platform.velocity; +import cloud.commandframework.CommandManager; +import cloud.commandframework.execution.CommandExecutionCoordinator; +import cloud.commandframework.velocity.VelocityCommandManager; import com.google.inject.Inject; -import com.velocitypowered.api.command.CommandManager; +import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.proxy.ListenerBoundEvent; import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; import com.velocitypowered.api.network.ListenerType; import com.velocitypowered.api.plugin.Plugin; +import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.proxy.ProxyServer; import lombok.Getter; import net.kyori.adventure.util.Codec; import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.command.Command; -import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; -import org.geysermc.geyser.platform.velocity.command.GeyserVelocityCommandExecutor; +import org.geysermc.geyser.platform.velocity.command.VelocityCommandSource; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; import org.jetbrains.annotations.NotNull; @@ -58,7 +61,6 @@ import java.io.IOException; import java.net.SocketAddress; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Map; import java.util.UUID; @Plugin(id = "geyser", name = GeyserImpl.NAME + "-Velocity", version = GeyserImpl.VERSION, url = "https://geysermc.org", authors = "GeyserMC") @@ -71,7 +73,7 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { private ProxyServer proxyServer; @Inject - private CommandManager commandManager; + private PluginContainer container; private GeyserCommandManager geyserCommandManager; private GeyserVelocityConfiguration geyserConfig; @@ -132,19 +134,16 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { this.geyserInjector = new GeyserVelocityInjector(proxyServer); // Will be initialized after the proxy has been bound - this.geyserCommandManager = new GeyserCommandManager(geyser); + CommandManager cloud = new VelocityCommandManager<>( + container, + proxyServer, + CommandExecutionCoordinator.simpleCoordinator(), + VelocityCommandSource::new, + origin -> (CommandSource) origin.handle() + ); + this.geyserCommandManager = new GeyserCommandManager(geyser, cloud); this.geyserCommandManager.init(); - this.commandManager.register("geyser", new GeyserVelocityCommandExecutor(geyser, geyserCommandManager.getCommands())); - for (Map.Entry> entry : this.geyserCommandManager.extensionCommands().entrySet()) { - Map commands = entry.getValue(); - if (commands.isEmpty()) { - continue; - } - - this.commandManager.register(entry.getKey().description().id(), new GeyserVelocityCommandExecutor(this.geyser, commands)); - } - if (geyserConfig.isLegacyPingPassthrough()) { this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); } else { diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/GeyserVelocityCommandExecutor.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/GeyserVelocityCommandExecutor.java deleted file mode 100644 index c89c35b06..000000000 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/GeyserVelocityCommandExecutor.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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.velocity.command; - -import com.velocitypowered.api.command.SimpleCommand; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.command.Command; -import org.geysermc.geyser.command.GeyserCommand; -import org.geysermc.geyser.command.GeyserCommandExecutor; -import org.geysermc.geyser.command.GeyserCommandSource; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.text.ChatColor; -import org.geysermc.geyser.text.GeyserLocale; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -public class GeyserVelocityCommandExecutor extends GeyserCommandExecutor implements SimpleCommand { - - public GeyserVelocityCommandExecutor(GeyserImpl geyser, Map commands) { - super(geyser, commands); - } - - @Override - public void execute(Invocation invocation) { - GeyserCommandSource sender = new VelocityCommandSource(invocation.source()); - GeyserSession session = getGeyserSession(sender); - - if (invocation.arguments().length > 0) { - GeyserCommand command = getCommand(invocation.arguments()[0]); - if (command != null) { - if (!invocation.source().hasPermission(getCommand(invocation.arguments()[0]).permission())) { - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale())); - return; - } - if (command.isBedrockOnly() && session == null) { - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.locale())); - return; - } - command.execute(session, sender, invocation.arguments().length > 1 ? Arrays.copyOfRange(invocation.arguments(), 1, invocation.arguments().length) : new String[0]); - } else { - String message = GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.not_found", sender.locale()); - sender.sendMessage(ChatColor.RED + message); - } - } else { - getCommand("help").execute(session, sender, new String[0]); - } - } - - @Override - public List suggest(Invocation invocation) { - // Velocity seems to do the splitting a bit differently. This results in the same behaviour in bungeecord/spigot. - if (invocation.arguments().length == 0 || invocation.arguments().length == 1) { - return tabComplete(new VelocityCommandSource(invocation.source())); - } - return Collections.emptyList(); - } -} diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSource.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSource.java index 00c99e92b..a0c0e3ab0 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSource.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSource.java @@ -34,6 +34,8 @@ import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.text.GeyserLocale; import java.util.Locale; +import java.util.Optional; +import java.util.UUID; public class VelocityCommandSource implements GeyserCommandSource { @@ -71,6 +73,14 @@ public class VelocityCommandSource implements GeyserCommandSource { return handle instanceof ConsoleCommandSource; } + @Override + public Optional playerUuid() { + if (handle instanceof Player player) { + return Optional.of(player.getUniqueId()); + } + return Optional.empty(); + } + @Override public String locale() { if (handle instanceof Player) { @@ -84,4 +94,9 @@ public class VelocityCommandSource implements GeyserCommandSource { public boolean hasPermission(String permission) { return handle.hasPermission(permission); } + + @Override + public Object handle() { + return handle; + } } diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 97540f96e..eb4af3479 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -47,6 +47,9 @@ dependencies { // Adventure text serialization api(libs.bundles.adventure) + // command library + api(libs.cloud.core) + api(libs.erosion.common) { isTransitive = false } diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 8204cfd3b..31b177e8e 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -616,7 +616,7 @@ public class GeyserImpl implements GeyserApi { skinUploader.close(); } newsHandler.shutdown(); - this.commandManager().getCommands().clear(); + this.commandManager().clear(); if (this.erosionUnixListener != null) { this.erosionUnixListener.close(); diff --git a/core/src/main/java/org/geysermc/geyser/GeyserLogger.java b/core/src/main/java/org/geysermc/geyser/GeyserLogger.java index 88220eec9..44ca25504 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserLogger.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserLogger.java @@ -29,6 +29,8 @@ import net.kyori.adventure.text.Component; import org.geysermc.geyser.command.GeyserCommandSource; import javax.annotation.Nullable; +import java.util.Optional; +import java.util.UUID; public interface GeyserLogger extends GeyserCommandSource { @@ -128,6 +130,11 @@ public interface GeyserLogger extends GeyserCommandSource { return true; } + @Override + default Optional playerUuid() { + return Optional.empty(); + } + @Override default boolean hasPermission(String permission) { return true; diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java index 5808dbc2c..5d78079f8 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java @@ -25,6 +25,8 @@ package org.geysermc.geyser.command; +import cloud.commandframework.CommandManager; +import cloud.commandframework.arguments.standard.StringArgument; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.experimental.Accessors; @@ -95,4 +97,31 @@ public abstract class GeyserCommand implements Command { public boolean isSuggestedOpOnly() { return false; } + + public String rootCommand() { + return "geyser"; + } + + public void register(CommandManager manager) { + // todo: commands all builtin commands should directly use cloud + // also, there needs to be customized messages for isExecutableOnConsole and isBedrockOnly, etc. Review the old CommandExecutors. + manager.command(manager.commandBuilder(rootCommand()) + .literal(name, aliases.toArray(new String[0])) + .permission(source -> { + if (source.isConsole()) { + return isExecutableOnConsole(); + } + if (isBedrockOnly() && source.connection().isEmpty()) { + return false; // bedrock only but not a bedrock player + } + + return source.hasPermission(permission); + }) + .argument(StringArgument.optional("args", StringArgument.StringMode.GREEDY)) + .handler(context -> { + GeyserCommandSource source = context.getSender(); + execute(source.connection().orElse(null), source, context.getOrDefault("args", "").split(" ")); + }) + ); + } } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandExecutor.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandExecutor.java deleted file mode 100644 index a9b1c734f..000000000 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandExecutor.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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.command; - -import lombok.AllArgsConstructor; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.command.Command; -import org.geysermc.geyser.session.GeyserSession; - -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -/** - * Represents helper functions for listening to {@code /geyser} or {@code /geyserext} commands. - */ -@AllArgsConstructor -public class GeyserCommandExecutor { - - protected final GeyserImpl geyser; - private final Map commands; - - public GeyserCommand getCommand(String label) { - return (GeyserCommand) commands.get(label); - } - - @Nullable - public GeyserSession getGeyserSession(GeyserCommandSource sender) { - if (sender.isConsole()) { - return null; - } - - for (GeyserSession session : geyser.getSessionManager().getSessions().values()) { - if (sender.name().equals(session.getPlayerEntity().getUsername())) { - return session; - } - } - return null; - } - - /** - * Determine which subcommands to suggest in the tab complete for the main /geyser command by a given command sender. - * - * @param sender The command sender to receive the tab complete suggestions. - * If the command sender is a bedrock player, an empty list will be returned as bedrock players do not get command argument suggestions. - * If the command sender is not a bedrock player, bedrock commands will not be shown. - * If the command sender does not have the permission for a given command, the command will not be shown. - * @return A list of command names to include in the tab complete - */ - public List tabComplete(GeyserCommandSource sender) { - if (getGeyserSession(sender) != null) { - // Bedrock doesn't get tab completions or argument suggestions - return Collections.emptyList(); - } - - List availableCommands = new ArrayList<>(); - - // Only show commands they have permission to use - for (Map.Entry entry : commands.entrySet()) { - Command geyserCommand = entry.getValue(); - if (sender.hasPermission(geyserCommand.permission())) { - if (geyserCommand.isBedrockOnly()) { - // Don't show commands the JE player can't run - continue; - } - - availableCommands.add(entry.getKey()); - } - } - - return availableCommands; - } -} diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java index d28f9d24e..7c87e8a2d 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java @@ -25,10 +25,11 @@ package org.geysermc.geyser.command; +import cloud.commandframework.CommandManager; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import lombok.Getter; import lombok.RequiredArgsConstructor; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.command.Command; @@ -54,22 +55,24 @@ import org.geysermc.geyser.extension.command.GeyserExtensionCommand; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.Collections; import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.Map; -@RequiredArgsConstructor public class GeyserCommandManager { - @Getter - private final Map commands = new Object2ObjectOpenHashMap<>(12); + private final Map commands = new Object2ObjectOpenHashMap<>(13); private final Map> extensionCommands = new Object2ObjectOpenHashMap<>(0); private final GeyserImpl geyser; + private final CommandManager cloud; + + public GeyserCommandManager(GeyserImpl geyser, CommandManager cloud) { + this.geyser = geyser; + this.cloud = cloud; + } public void init() { registerBuiltInCommand(new HelpCommand(geyser, "help", "geyser.commands.help.desc", "geyser.command.help", "geyser", this.commands)); @@ -118,11 +121,13 @@ public class GeyserCommandManager { register(command, this.commands); } - public void registerExtensionCommand(@NonNull Extension extension, @NonNull Command command) { + public void registerExtensionCommand(@NonNull Extension extension, @NonNull GeyserCommand command) { register(command, this.extensionCommands.computeIfAbsent(extension, e -> new HashMap<>())); } - private void register(Command command, Map commands) { + private void register(GeyserCommand command, Map commands) { + command.register(cloud); + commands.put(command.name(), command); geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.commands.registered", command.name())); @@ -135,6 +140,11 @@ public class GeyserCommandManager { } } + public void clear() { + this.commands.clear(); + this.extensionCommands.clear(); + } + @NotNull public Map commands() { return Collections.unmodifiableMap(this.commands); @@ -145,51 +155,9 @@ public class GeyserCommandManager { return Collections.unmodifiableMap(this.extensionCommands); } - public boolean runCommand(GeyserCommandSource sender, String command) { - Extension extension = null; - for (Extension loopedExtension : this.extensionCommands.keySet()) { - if (command.startsWith(loopedExtension.description().id() + " ")) { - extension = loopedExtension; - break; - } - } - - if (!command.startsWith("geyser ") && extension == null) { - return false; - } - - command = command.trim().replace(extension != null ? extension.description().id() + " " : "geyser ", ""); - String label; - String[] args; - - if (!command.contains(" ")) { - label = command.toLowerCase(Locale.ROOT); - args = new String[0]; - } else { - label = command.substring(0, command.indexOf(" ")).toLowerCase(Locale.ROOT); - String argLine = command.substring(command.indexOf(" ") + 1); - args = argLine.contains(" ") ? argLine.split(" ") : new String[] { argLine }; - } - - Command cmd = (extension != null ? this.extensionCommands.getOrDefault(extension, Collections.emptyMap()) : this.commands).get(label); - if (cmd == null) { - sender.sendMessage(GeyserLocale.getLocaleStringLog("geyser.commands.invalid")); - return false; - } - - if (cmd instanceof GeyserCommand) { - if (sender instanceof GeyserSession) { - ((GeyserCommand) cmd).execute((GeyserSession) sender, sender, args); - } else { - if (!cmd.isBedrockOnly()) { - ((GeyserCommand) cmd).execute(null, sender, args); - } else { - geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.bedrock_only")); - } - } - } - - return true; + @NotNull + public CommandManager cloud() { + return cloud; } /** @@ -324,6 +292,11 @@ public class GeyserCommandManager { public boolean isExecutableOnConsole() { return CommandBuilder.this.executableOnConsole; } + + @Override + public String rootCommand() { + return extension().rootCommand(); + } }; } } diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java index 88d148b11..1381e5b88 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java @@ -25,11 +25,15 @@ package org.geysermc.geyser.command; +import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.command.CommandSource; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import java.util.Optional; + /** * Implemented on top of any class that can send a command. * For example, it wraps around Spigot's CommandSender class. @@ -46,4 +50,20 @@ public interface GeyserCommandSource extends CommandSource { default void sendMessage(Component message) { sendMessage(LegacyComponentSerializer.legacySection().serialize(message)); } + + default void sendLocaleString(String key, Object... values) { + sendMessage(GeyserLocale.getPlayerLocaleString(key, locale(), values)); + } + + @Override + default Optional connection() { + return playerUuid().map(id -> GeyserImpl.getInstance().connectionByUuid(id)); + } + + /** + * todo: commands + */ + default Object handle() { + return this; + } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java index 6e7ad2f04..cad851694 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java @@ -66,6 +66,7 @@ public class HelpCommand extends GeyserCommand { String header = GeyserLocale.getPlayerLocaleString("geyser.commands.help.header", sender.locale(), page, maxPage); sender.sendMessage(header); + // todo: commands looks like this doesn't guard against alias keys this.commands.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(entry -> { Command cmd = entry.getValue(); diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 052222be1..ff52aec90 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -1407,6 +1407,11 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { return false; } + @Override + public Optional playerUuid() { + return Optional.of(playerEntity.getUuid()); + } + @Override public String locale() { return clientData.getLanguageCode(); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java index bc04bf50e..1d2417301 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java @@ -25,9 +25,11 @@ package org.geysermc.geyser.translator.protocol.bedrock; +import cloud.commandframework.CommandManager; import org.cloudburstmc.protocol.bedrock.packet.CommandRequestPacket; import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.translator.protocol.PacketTranslator; @@ -40,13 +42,27 @@ public class BedrockCommandRequestTranslator extends PacketTranslator 0) { + String root = args[0]; + + CommandManager manager = GeyserImpl.getInstance().commandManager().cloud(); + if (manager.rootCommands().contains(root)) { + manager.executeCommand(session, strippedCommand); + return; + } + } } + + if (MessageTranslator.isTooLong(command, session)) { + return; + } + + session.sendCommand(strippedCommand); } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d674c5c29..8da4ec21c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,6 +24,7 @@ terminalconsoleappender = "1.2.0" folia = "1.19.4-R0.1-SNAPSHOT" viaversion = "4.0.0" adapters = "1.8-SNAPSHOT" +cloud = "1.8.3" commodore = "2.2" bungeecord = "a7c6ede" velocity = "3.0.0" @@ -71,6 +72,12 @@ jline-terminal = { group = "org.jline", name = "jline-terminal", version.ref = " jline-terminal-jna = { group = "org.jline", name = "jline-terminal-jna", version.ref = "jline" } jline-reader = { group = "org.jline", name = "jline-reader", version.ref = "jline" } +cloud-core = { group = "cloud.commandframework", name = "cloud-core", version.ref = "cloud" } +cloud-paper = { group = "cloud.commandframework", name = "cloud-paper", version.ref = "cloud" } +cloud-velocity = { group = "cloud.commandframework", name = "cloud-velocity", version.ref = "cloud" } +cloud-bungee = { group = "cloud.commandframework", name = "cloud-bungee", version.ref = "cloud" } +cloud-fabric = { group = "cloud.commandframework", name = "cloud-fabric", version.ref = "cloud" } + folia-api = { group = "dev.folia", name = "folia-api", version.ref = "folia" } paper-mojangapi = { group = "io.papermc.paper", name = "paper-mojangapi", version.ref = "folia" } From 8d7e789338480b508f6ce496151b9bd831f2998a Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Thu, 1 Jun 2023 21:14:49 -0400 Subject: [PATCH 02/76] Compiling and working on spigot --- bootstrap/spigot/src/main/resources/plugin.yml | 5 ----- .../geysermc/geyser/platform/sponge/GeyserSpongePlugin.java | 3 ++- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/bootstrap/spigot/src/main/resources/plugin.yml b/bootstrap/spigot/src/main/resources/plugin.yml index 6e81ccdb6..085ce13af 100644 --- a/bootstrap/spigot/src/main/resources/plugin.yml +++ b/bootstrap/spigot/src/main/resources/plugin.yml @@ -6,11 +6,6 @@ version: ${version} softdepend: ["ViaVersion", "floodgate"] api-version: 1.13 folia-supported: true -commands: - geyser: - description: The main command for Geyser. - usage: /geyser - permission: geyser.command permissions: geyser.command: default: true diff --git a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java index 8bce03590..f66c9b88b 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java @@ -55,6 +55,7 @@ import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.file.Path; +import java.util.Objects; import java.util.UUID; @Plugin(value = "geyser") @@ -139,7 +140,7 @@ public class GeyserSpongePlugin implements GeyserBootstrap { this.geyser = GeyserImpl.load(PlatformType.SPONGE, this); - this.geyserCommandManager = new GeyserCommandManager(geyser); // todo: commands + this.geyserCommandManager = new GeyserCommandManager(geyser, Objects.requireNonNull(null)); // todo: commands this.geyserCommandManager.init(); } From 9da33459828f02e9eaf33d50d9cae6a9590a41d4 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Thu, 1 Jun 2023 22:56:14 -0400 Subject: [PATCH 03/76] paper-mojangapi is no longer required at least right now lol --- bootstrap/spigot/build.gradle.kts | 5 ----- gradle/libs.versions.toml | 1 - 2 files changed, 6 deletions(-) diff --git a/bootstrap/spigot/build.gradle.kts b/bootstrap/spigot/build.gradle.kts index e9977c037..aabae0fd3 100644 --- a/bootstrap/spigot/build.gradle.kts +++ b/bootstrap/spigot/build.gradle.kts @@ -17,11 +17,6 @@ dependencies { attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17) } } - compileOnly(libs.paper.mojangapi) { - attributes { - attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17) - } - } } platformRelocate("it.unimi.dsi.fastutil") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8da4ec21c..7defead25 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -79,7 +79,6 @@ cloud-bungee = { group = "cloud.commandframework", name = "cloud-bungee", versio cloud-fabric = { group = "cloud.commandframework", name = "cloud-fabric", version.ref = "cloud" } folia-api = { group = "dev.folia", name = "folia-api", version.ref = "folia" } -paper-mojangapi = { group = "io.papermc.paper", name = "paper-mojangapi", version.ref = "folia" } # check these on https://modmuss50.me/fabric.html fabric-minecraft = { group = "com.mojang", name = "minecraft", version.ref = "fabric-minecraft" } From 37797d7ae3cc79552b89d5927fcf193f0e3e465c Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Fri, 2 Jun 2023 18:52:37 -0400 Subject: [PATCH 04/76] Be more lenient with backwardsCommandSenderMappers any GeyserCommandSource should be valid to use in any CommandManager as long as one of the following is satisfied 1. it is a platform implementation 2. isConsole() returns true 2. playerUuid() returns a valid uuid and the player lookup succeeds --- .../bungeecord/GeyserBungeePlugin.java | 25 ++++++++++++++-- .../command/BungeeCommandSource.java | 8 +++-- .../platform/fabric/GeyserFabricMod.java | 29 ++++++++++++++---- .../fabric/command/FabricCommandSource.java | 4 +-- .../platform/spigot/GeyserSpigotPlugin.java | 30 ++++++++++++++++--- .../spigot/command/SpigotCommandSource.java | 8 +++-- .../platform/sponge/GeyserSpongePlugin.java | 5 ---- .../standalone/GeyserStandaloneBootstrap.java | 1 - .../velocity/GeyserVelocityPlugin.java | 22 ++++++++++++-- .../command/VelocityCommandSource.java | 3 +- .../org/geysermc/geyser/GeyserLogger.java | 3 +- .../geyser/command/GeyserCommandManager.java | 2 -- .../geyser/session/GeyserSession.java | 2 +- core/src/main/resources/languages | 2 +- 14 files changed, 111 insertions(+), 33 deletions(-) diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java index 490c9e593..9473db0b7 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java @@ -181,10 +181,9 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { this, CommandExecutionCoordinator.simpleCoordinator(), BungeeCommandSource::new, - origin -> (CommandSender) origin.handle() + this::convertCommandSource ); this.geyserCommandManager = new GeyserCommandManager(geyser, cloud); - this.geyserCommandManager.init(); if (geyserConfig.isLegacyPingPassthrough()) { this.geyserBungeePingPassthrough = GeyserLegacyPingPassthrough.init(geyser); @@ -203,6 +202,28 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { } } + private CommandSender convertCommandSource(GeyserCommandSource source) { + if (source.handle() instanceof CommandSender sender) { + return sender; + } + + if (source.isConsole()) { + return getProxy().getConsole(); + } + + Optional optionalUuid = source.playerUuid(); + if (optionalUuid.isPresent()) { + UUID uuid = optionalUuid.get(); + CommandSender sender = getProxy().getPlayer(uuid); + if (sender == null) { + throw new IllegalArgumentException("failed to find player for " + uuid); + } + return sender; + } + + throw new IllegalArgumentException("failed to convert source for " + source); + } + @Override public GeyserBungeeConfiguration getGeyserConfig() { return geyserConfig; diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java index 65703c105..24676ec00 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java @@ -27,8 +27,10 @@ package org.geysermc.geyser.platform.bungeecord.command; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; +import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.connection.ProxiedPlayer; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.text.GeyserLocale; @@ -38,9 +40,9 @@ import java.util.UUID; public class BungeeCommandSource implements GeyserCommandSource { - private final net.md_5.bungee.api.CommandSender handle; + private final CommandSender handle; - public BungeeCommandSource(net.md_5.bungee.api.CommandSender handle) { + public BungeeCommandSource(CommandSender handle) { this.handle = handle; // Ensure even Java players' languages are loaded GeyserLocale.loadGeyserLocale(this.locale()); @@ -52,7 +54,7 @@ public class BungeeCommandSource implements GeyserCommandSource { } @Override - public void sendMessage(String message) { + public void sendMessage(@NonNull String message) { handle.sendMessage(TextComponent.fromLegacyText(message)); } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java index d1a2c315c..0318a55d8 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java @@ -27,8 +27,6 @@ package org.geysermc.geyser.platform.fabric; import cloud.commandframework.CommandManager; import cloud.commandframework.execution.CommandExecutionCoordinator; -import cloud.commandframework.fabric.FabricClientCommandManager; -import cloud.commandframework.fabric.FabricCommandManager; import cloud.commandframework.fabric.FabricServerCommandManager; import net.fabricmc.api.EnvType; import net.fabricmc.api.ModInitializer; @@ -37,8 +35,8 @@ import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.ModContainer; import net.minecraft.commands.CommandSourceStack; -import net.minecraft.commands.SharedSuggestionProvider; import net.minecraft.server.MinecraftServer; +import net.minecraft.world.entity.player.Player; import org.apache.logging.log4j.LogManager; import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserBootstrap; @@ -150,10 +148,9 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { CommandManager cloud = new FabricServerCommandManager<>( CommandExecutionCoordinator.simpleCoordinator(), FabricCommandSource::new, - source -> (CommandSourceStack) source.handle() + this::convertCommandSource ); this.geyserCommandManager = new GeyserCommandManager(geyser, cloud); - this.geyserCommandManager.init(); this.geyserWorldManager = new GeyserFabricWorldManager(server); } @@ -169,6 +166,28 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { } } + private CommandSourceStack convertCommandSource(GeyserCommandSource source) { + if (source.handle() instanceof CommandSourceStack stack) { + return stack; + } + + if (source.isConsole()) { + return server.createCommandSourceStack(); // todo: commands something better? + } + + Optional optionalUUID = source.playerUuid(); + if (optionalUUID.isPresent()) { + UUID uuid = optionalUUID.get(); + Player player = server.getPlayerList().getPlayer(uuid); + if (player == null) { + throw new IllegalArgumentException("failed to find player for " + uuid); + } + return player.createCommandSourceStack(); + } + + throw new IllegalArgumentException("failed to convert source for " + source); + } + @Override public GeyserConfiguration getGeyserConfig() { return geyserConfig; diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSource.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSource.java index e4a0c5611..9a7543efb 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSource.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSource.java @@ -30,11 +30,11 @@ import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.minecraft.commands.CommandSourceStack; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.text.ChatColor; -import javax.annotation.Nonnull; import java.util.Optional; import java.util.UUID; @@ -52,7 +52,7 @@ public class FabricCommandSource implements GeyserCommandSource { } @Override - public void sendMessage(@Nonnull String message) { + public void sendMessage(@NonNull String message) { if (source.getEntity() instanceof ServerPlayer) { ((ServerPlayer) source.getEntity()).displayClientMessage(Component.literal(message), false); } else { 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 afd90f921..f6784dd73 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 @@ -73,6 +73,7 @@ import java.net.SocketAddress; import java.nio.file.Path; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.UUID; import java.util.logging.Level; @@ -175,7 +176,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { this, CommandExecutionCoordinator.simpleCoordinator(), SpigotCommandSource::new, - source -> (CommandSender) source.handle() + this::convertCommandSource ); } catch (Exception e) { throw new RuntimeException(e); @@ -185,15 +186,14 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { // Should always be available on 1.13 and up cloud.registerBrigadier(); } catch (BukkitCommandManager.BrigadierFailureException e) { - geyserLogger.error("Failed to initialize Brigadier support: " + e.getMessage()); + geyserLogger.debug("Failed to initialize Brigadier support: " + e.getMessage()); if (e.getReason() == BukkitCommandManager.BrigadierFailureReason.VERSION_TOO_HIGH) { // Commodore brig only supports Spigot 1.13 - 1.18.2 - geyserLogger.error("Using Paper instead of Spigot will likely fix this."); + geyserLogger.debug("Using Paper instead of Spigot will likely fix this."); } } this.geyserCommandManager = new GeyserCommandManager(geyser, cloud); - this.geyserCommandManager.init(); if (!INITIALIZED) { // Needs to be an anonymous inner class otherwise Bukkit complains about missing classes @@ -342,6 +342,28 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { } } + private CommandSender convertCommandSource(GeyserCommandSource source) { + if (source.handle() instanceof CommandSender sender) { + return sender; + } + + if (source.isConsole()) { + return getServer().getConsoleSender(); + } + + Optional optionalUuid = source.playerUuid(); + if (optionalUuid.isPresent()) { + UUID uuid = optionalUuid.get(); + CommandSender sender = getServer().getPlayer(uuid); + if (sender == null) { + throw new IllegalArgumentException("failed to find player for " + uuid); + } + return sender; + } + + throw new IllegalArgumentException("failed to convert source for " + source); + } + @Override public GeyserSpigotConfiguration getGeyserConfig() { return geyserConfig; diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSource.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSource.java index c80db4df7..28fe06038 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSource.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSource.java @@ -27,8 +27,10 @@ package org.geysermc.geyser.platform.spigot.command; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; +import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; import org.bukkit.entity.Player; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.platform.spigot.PaperAdventure; import org.geysermc.geyser.text.GeyserLocale; @@ -37,9 +39,9 @@ import java.util.Optional; import java.util.UUID; public class SpigotCommandSource implements GeyserCommandSource { - private final org.bukkit.command.CommandSender handle; + private final CommandSender handle; - public SpigotCommandSource(org.bukkit.command.CommandSender handle) { + public SpigotCommandSource(CommandSender handle) { this.handle = handle; // Ensure even Java players' languages are loaded GeyserLocale.loadGeyserLocale(locale()); @@ -51,7 +53,7 @@ public class SpigotCommandSource implements GeyserCommandSource { } @Override - public void sendMessage(String message) { + public void sendMessage(@NonNull String message) { handle.sendMessage(message); } diff --git a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java index f66c9b88b..1ced0f37f 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java @@ -46,7 +46,6 @@ import org.spongepowered.api.event.lifecycle.ConstructPluginEvent; import org.spongepowered.api.event.lifecycle.RegisterCommandEvent; import org.spongepowered.api.event.lifecycle.StartedEngineEvent; import org.spongepowered.api.event.lifecycle.StoppingEngineEvent; -import org.spongepowered.plugin.PluginContainer; import org.spongepowered.plugin.builtin.jvm.Plugin; import javax.annotation.Nonnull; @@ -67,9 +66,6 @@ public class GeyserSpongePlugin implements GeyserBootstrap { */ private boolean enabled = true; - @Inject - private PluginContainer pluginContainer; - @Inject private Logger logger; @@ -141,7 +137,6 @@ public class GeyserSpongePlugin implements GeyserBootstrap { this.geyser = GeyserImpl.load(PlatformType.SPONGE, this); this.geyserCommandManager = new GeyserCommandManager(geyser, Objects.requireNonNull(null)); // todo: commands - this.geyserCommandManager.init(); } /** diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java index 4d7836f84..72f693325 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java @@ -221,7 +221,6 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { GeyserImpl.start(); geyserCommandManager = new GeyserCommandManager(geyser, new GeyserStandaloneCommandManager()); - geyserCommandManager.init(); if (gui != null) { gui.setupInterface(geyserLogger, geyserCommandManager); diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java index 7b0706217..b057ed755 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java @@ -61,6 +61,7 @@ import java.io.IOException; import java.net.SocketAddress; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Optional; import java.util.UUID; @Plugin(id = "geyser", name = GeyserImpl.NAME + "-Velocity", version = GeyserImpl.VERSION, url = "https://geysermc.org", authors = "GeyserMC") @@ -139,10 +140,9 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { proxyServer, CommandExecutionCoordinator.simpleCoordinator(), VelocityCommandSource::new, - origin -> (CommandSource) origin.handle() + this::convertCommandSource ); this.geyserCommandManager = new GeyserCommandManager(geyser, cloud); - this.geyserCommandManager.init(); if (geyserConfig.isLegacyPingPassthrough()) { this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); @@ -163,6 +163,24 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { } } + private CommandSource convertCommandSource(GeyserCommandSource source) { + if (source.handle() instanceof CommandSource velocitySource) { + return velocitySource; + } + + if (source.isConsole()) { + return proxyServer.getConsoleCommandSource(); + } + + Optional optionalUUID = source.playerUuid(); + if (optionalUUID.isPresent()) { + UUID uuid = optionalUUID.get(); + return proxyServer.getPlayer(uuid).orElseThrow(() -> new IllegalArgumentException("failed to find player for " + uuid)); + } + + throw new IllegalArgumentException("failed to convert source for " + source); + } + @Override public GeyserVelocityConfiguration getGeyserConfig() { return geyserConfig; diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSource.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSource.java index a0c0e3ab0..041d31abd 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSource.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSource.java @@ -30,6 +30,7 @@ import com.velocitypowered.api.proxy.ConsoleCommandSource; import com.velocitypowered.api.proxy.Player; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.text.GeyserLocale; @@ -58,7 +59,7 @@ public class VelocityCommandSource implements GeyserCommandSource { } @Override - public void sendMessage(String message) { + public void sendMessage(@NonNull String message) { handle.sendMessage(LegacyComponentSerializer.legacy('§').deserialize(message)); } diff --git a/core/src/main/java/org/geysermc/geyser/GeyserLogger.java b/core/src/main/java/org/geysermc/geyser/GeyserLogger.java index 44ca25504..f0336d93e 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserLogger.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserLogger.java @@ -26,6 +26,7 @@ package org.geysermc.geyser; import net.kyori.adventure.text.Component; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.command.GeyserCommandSource; import javax.annotation.Nullable; @@ -121,7 +122,7 @@ public interface GeyserLogger extends GeyserCommandSource { } @Override - default void sendMessage(String message) { + default void sendMessage(@NonNull String message) { info(message); } diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java index 7c87e8a2d..e3cd98b5c 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java @@ -72,9 +72,7 @@ public class GeyserCommandManager { public GeyserCommandManager(GeyserImpl geyser, CommandManager cloud) { this.geyser = geyser; this.cloud = cloud; - } - public void init() { registerBuiltInCommand(new HelpCommand(geyser, "help", "geyser.commands.help.desc", "geyser.command.help", "geyser", this.commands)); registerBuiltInCommand(new ListCommand(geyser, "list", "geyser.commands.list.desc", "geyser.command.list")); registerBuiltInCommand(new ReloadCommand(geyser, "reload", "geyser.commands.reload.desc", "geyser.command.reload")); diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index ff52aec90..269cc0e65 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -1390,7 +1390,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { } @Override - public void sendMessage(String message) { + public void sendMessage(@NonNull String message) { TextPacket textPacket = new TextPacket(); textPacket.setPlatformChatId(""); textPacket.setSourceName(""); diff --git a/core/src/main/resources/languages b/core/src/main/resources/languages index f6685c4cc..24be9ef7f 160000 --- a/core/src/main/resources/languages +++ b/core/src/main/resources/languages @@ -1 +1 @@ -Subproject commit f6685c4ccc6e77b07402d45cb41213559004b7d6 +Subproject commit 24be9ef7f850f7d180650a65792c266c709cadf5 From 8e6d7d3cc015965d5abf68dfec52eb3b3e144680 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Fri, 2 Jun 2023 20:27:29 -0400 Subject: [PATCH 05/76] Cleanup the last commit --- .../bungeecord/GeyserBungeePlugin.java | 27 +------ .../platform/fabric/GeyserFabricMod.java | 32 +++------ .../platform/spigot/GeyserSpigotPlugin.java | 27 +------ .../velocity/GeyserVelocityPlugin.java | 23 +----- .../command/CommandSourceConverter.java | 70 +++++++++++++++++++ 5 files changed, 87 insertions(+), 92 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java index 9473db0b7..4c565bed2 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java @@ -38,6 +38,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandSourceConverter; import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.configuration.GeyserConfiguration; @@ -176,12 +177,12 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { this.geyserInjector = new GeyserBungeeInjector(this); this.geyserInjector.initializeLocalChannel(this); - + var sourceConverter = CommandSourceConverter.simple(CommandSender.class, id -> getProxy().getPlayer(id), () -> getProxy().getConsole()); CommandManager cloud = new BungeeCommandManager<>( this, CommandExecutionCoordinator.simpleCoordinator(), BungeeCommandSource::new, - this::convertCommandSource + sourceConverter::convert ); this.geyserCommandManager = new GeyserCommandManager(geyser, cloud); @@ -202,28 +203,6 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { } } - private CommandSender convertCommandSource(GeyserCommandSource source) { - if (source.handle() instanceof CommandSender sender) { - return sender; - } - - if (source.isConsole()) { - return getProxy().getConsole(); - } - - Optional optionalUuid = source.playerUuid(); - if (optionalUuid.isPresent()) { - UUID uuid = optionalUuid.get(); - CommandSender sender = getProxy().getPlayer(uuid); - if (sender == null) { - throw new IllegalArgumentException("failed to find player for " + uuid); - } - return sender; - } - - throw new IllegalArgumentException("failed to convert source for " + source); - } - @Override public GeyserBungeeConfiguration getGeyserConfig() { return geyserConfig; diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java index 0318a55d8..d5c9e8609 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java @@ -42,6 +42,7 @@ import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; +import org.geysermc.geyser.command.CommandSourceConverter; import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.configuration.GeyserConfiguration; @@ -144,11 +145,16 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); - + var sourceConverter = new CommandSourceConverter<>( + CommandSourceStack.class, + id -> server.getPlayerList().getPlayer(id), + Player::createCommandSourceStack, + server::createCommandSourceStack + ); CommandManager cloud = new FabricServerCommandManager<>( CommandExecutionCoordinator.simpleCoordinator(), FabricCommandSource::new, - this::convertCommandSource + sourceConverter::convert ); this.geyserCommandManager = new GeyserCommandManager(geyser, cloud); @@ -166,28 +172,6 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { } } - private CommandSourceStack convertCommandSource(GeyserCommandSource source) { - if (source.handle() instanceof CommandSourceStack stack) { - return stack; - } - - if (source.isConsole()) { - return server.createCommandSourceStack(); // todo: commands something better? - } - - Optional optionalUUID = source.playerUuid(); - if (optionalUUID.isPresent()) { - UUID uuid = optionalUUID.get(); - Player player = server.getPlayerList().getPlayer(uuid); - if (player == null) { - throw new IllegalArgumentException("failed to find player for " + uuid); - } - return player.createCommandSourceStack(); - } - - throw new IllegalArgumentException("failed to convert source for " + source); - } - @Override public GeyserConfiguration getGeyserConfig() { return geyserConfig; 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 f6784dd73..89e30bef5 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 @@ -49,6 +49,7 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.adapters.spigot.SpigotAdapters; import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.command.CommandSourceConverter; import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.configuration.GeyserConfiguration; @@ -73,7 +74,6 @@ import java.net.SocketAddress; import java.nio.file.Path; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.UUID; import java.util.logging.Level; @@ -170,13 +170,14 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { return; } + var sourceConverter = CommandSourceConverter.simple(CommandSender.class, Bukkit::getPlayer, Bukkit::getConsoleSender); PaperCommandManager cloud; try { cloud = new PaperCommandManager<>( this, CommandExecutionCoordinator.simpleCoordinator(), SpigotCommandSource::new, - this::convertCommandSource + sourceConverter::convert ); } catch (Exception e) { throw new RuntimeException(e); @@ -342,28 +343,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { } } - private CommandSender convertCommandSource(GeyserCommandSource source) { - if (source.handle() instanceof CommandSender sender) { - return sender; - } - - if (source.isConsole()) { - return getServer().getConsoleSender(); - } - - Optional optionalUuid = source.playerUuid(); - if (optionalUuid.isPresent()) { - UUID uuid = optionalUuid.get(); - CommandSender sender = getServer().getPlayer(uuid); - if (sender == null) { - throw new IllegalArgumentException("failed to find player for " + uuid); - } - return sender; - } - - throw new IllegalArgumentException("failed to convert source for " + source); - } - @Override public GeyserSpigotConfiguration getGeyserConfig() { return geyserConfig; diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java index b057ed755..b065e8301 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java @@ -43,6 +43,7 @@ import net.kyori.adventure.util.Codec; import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandSourceConverter; import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.configuration.GeyserConfiguration; @@ -61,7 +62,6 @@ import java.io.IOException; import java.net.SocketAddress; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Optional; import java.util.UUID; @Plugin(id = "geyser", name = GeyserImpl.NAME + "-Velocity", version = GeyserImpl.VERSION, url = "https://geysermc.org", authors = "GeyserMC") @@ -135,12 +135,13 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { this.geyserInjector = new GeyserVelocityInjector(proxyServer); // Will be initialized after the proxy has been bound + var sourceConverter = CommandSourceConverter.simple(CommandSource.class, id -> proxyServer.getPlayer(id).get(), proxyServer::getConsoleCommandSource); CommandManager cloud = new VelocityCommandManager<>( container, proxyServer, CommandExecutionCoordinator.simpleCoordinator(), VelocityCommandSource::new, - this::convertCommandSource + sourceConverter::convert ); this.geyserCommandManager = new GeyserCommandManager(geyser, cloud); @@ -163,24 +164,6 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { } } - private CommandSource convertCommandSource(GeyserCommandSource source) { - if (source.handle() instanceof CommandSource velocitySource) { - return velocitySource; - } - - if (source.isConsole()) { - return proxyServer.getConsoleCommandSource(); - } - - Optional optionalUUID = source.playerUuid(); - if (optionalUUID.isPresent()) { - UUID uuid = optionalUUID.get(); - return proxyServer.getPlayer(uuid).orElseThrow(() -> new IllegalArgumentException("failed to find player for " + uuid)); - } - - throw new IllegalArgumentException("failed to convert source for " + source); - } - @Override public GeyserVelocityConfiguration getGeyserConfig() { return geyserConfig; diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java b/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java new file mode 100644 index 000000000..f42d77f1c --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2019-2023 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.command; + +import java.util.Optional; +import java.util.UUID; +import java.util.function.Function; +import java.util.function.Supplier; + +public record CommandSourceConverter(Class senderType, + Function playerLookup, Function senderLookup, + Supplier consoleProvider) { + + + public S convert(GeyserCommandSource source) throws IllegalArgumentException { + Object handle = source.handle(); + if (senderType.isAssignableFrom(source.handle().getClass())) { + return (S) handle; // one of the server platform implementations + } + + if (source.isConsole()) { + return consoleProvider.get(); // one of the loggers + } + + // GeyserSession + Optional optionalUUID = source.playerUuid(); + if (optionalUUID.isPresent()) { + UUID uuid = optionalUUID.get(); + + P player = playerLookup.apply(uuid); + if (player == null) { + throw new IllegalArgumentException("failed to find player for " + uuid); + } + + return senderLookup.apply(player); + } + + throw new IllegalArgumentException("failed to convert source for " + source); + } + + public static CommandSourceConverter simple(Class senderType, + Function playerLookup, + Supplier consoleProvider) { + + return new CommandSourceConverter<>(senderType, playerLookup, s -> s, consoleProvider); + } +} From e2f71af6e74d23ad93f6f8b3e88075967c69cded Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Fri, 2 Jun 2023 20:39:15 -0400 Subject: [PATCH 06/76] Fix small mistakes --- .../org/geysermc/geyser/command/CommandSourceConverter.java | 1 - core/src/main/resources/languages | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java b/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java index f42d77f1c..e7d0d08df 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java @@ -34,7 +34,6 @@ public record CommandSourceConverter(Class senderType, Function playerLookup, Function senderLookup, Supplier consoleProvider) { - public S convert(GeyserCommandSource source) throws IllegalArgumentException { Object handle = source.handle(); if (senderType.isAssignableFrom(source.handle().getClass())) { diff --git a/core/src/main/resources/languages b/core/src/main/resources/languages index 24be9ef7f..f6685c4cc 160000 --- a/core/src/main/resources/languages +++ b/core/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 24be9ef7f850f7d180650a65792c266c709cadf5 +Subproject commit f6685c4ccc6e77b07402d45cb41213559004b7d6 From 411c289fa700964b706dd65395f690c4c13ac344 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Tue, 20 Jun 2023 01:26:16 -0400 Subject: [PATCH 07/76] Refactor CommandSourceConverter --- .../bungeecord/GeyserBungeePlugin.java | 2 +- .../platform/fabric/GeyserFabricMod.java | 2 +- .../platform/spigot/GeyserSpigotPlugin.java | 2 +- .../velocity/GeyserVelocityPlugin.java | 2 +- .../command/CommandSourceConverter.java | 36 +++++++++---------- 5 files changed, 20 insertions(+), 24 deletions(-) diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java index 4c565bed2..0dfc553e9 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java @@ -177,7 +177,7 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { this.geyserInjector = new GeyserBungeeInjector(this); this.geyserInjector.initializeLocalChannel(this); - var sourceConverter = CommandSourceConverter.simple(CommandSender.class, id -> getProxy().getPlayer(id), () -> getProxy().getConsole()); + var sourceConverter = new CommandSourceConverter<>(CommandSender.class, id -> getProxy().getPlayer(id), () -> getProxy().getConsole()); CommandManager cloud = new BungeeCommandManager<>( this, CommandExecutionCoordinator.simpleCoordinator(), diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java index d5c9e8609..796d13edb 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java @@ -145,7 +145,7 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); - var sourceConverter = new CommandSourceConverter<>( + var sourceConverter = CommandSourceConverter.layered( CommandSourceStack.class, id -> server.getPlayerList().getPlayer(id), Player::createCommandSourceStack, 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 89e30bef5..4147df64e 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 @@ -170,7 +170,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { return; } - var sourceConverter = CommandSourceConverter.simple(CommandSender.class, Bukkit::getPlayer, Bukkit::getConsoleSender); + var sourceConverter = new CommandSourceConverter<>(CommandSender.class, Bukkit::getPlayer, Bukkit::getConsoleSender); PaperCommandManager cloud; try { cloud = new PaperCommandManager<>( diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java index b065e8301..878b2f644 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java @@ -135,7 +135,7 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { this.geyserInjector = new GeyserVelocityInjector(proxyServer); // Will be initialized after the proxy has been bound - var sourceConverter = CommandSourceConverter.simple(CommandSource.class, id -> proxyServer.getPlayer(id).get(), proxyServer::getConsoleCommandSource); + var sourceConverter = new CommandSourceConverter<>(CommandSource.class, id -> proxyServer.getPlayer(id).orElse(null), proxyServer::getConsoleCommandSource); CommandManager cloud = new VelocityCommandManager<>( container, proxyServer, diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java b/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java index e7d0d08df..304f56b41 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java @@ -25,18 +25,17 @@ package org.geysermc.geyser.command; -import java.util.Optional; import java.util.UUID; import java.util.function.Function; import java.util.function.Supplier; -public record CommandSourceConverter(Class senderType, - Function playerLookup, Function senderLookup, +public record CommandSourceConverter(Class senderType, + Function playerLookup, Supplier consoleProvider) { public S convert(GeyserCommandSource source) throws IllegalArgumentException { Object handle = source.handle(); - if (senderType.isAssignableFrom(source.handle().getClass())) { + if (senderType.isInstance(handle)) { return (S) handle; // one of the server platform implementations } @@ -44,26 +43,23 @@ public record CommandSourceConverter(Class senderType, return consoleProvider.get(); // one of the loggers } - // GeyserSession - Optional optionalUUID = source.playerUuid(); - if (optionalUUID.isPresent()) { - UUID uuid = optionalUUID.get(); + // Handles GeyserSession + return source.playerUuid() + .map(playerLookup) + .orElseThrow(() -> new IllegalArgumentException("failed to find sender for name=%s, uuid=%s".formatted(source.name(), source.playerUuid()))); + } + public static CommandSourceConverter layered(Class senderType, + Function playerLookup, + Function senderLookup, + Supplier consoleProvider) { + Function lookup = uuid -> { P player = playerLookup.apply(uuid); if (player == null) { - throw new IllegalArgumentException("failed to find player for " + uuid); + return null; } - return senderLookup.apply(player); - } - - throw new IllegalArgumentException("failed to convert source for " + source); - } - - public static CommandSourceConverter simple(Class senderType, - Function playerLookup, - Supplier consoleProvider) { - - return new CommandSourceConverter<>(senderType, playerLookup, s -> s, consoleProvider); + }; + return new CommandSourceConverter<>(senderType, lookup, consoleProvider); } } From e3532fa4f9b1f9245b2863d1901d22a9483ed7f1 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Tue, 20 Jun 2023 15:35:01 -0400 Subject: [PATCH 08/76] Initial start on permissions --- ...GeyserRegisterPermissionCheckersEvent.java | 42 +++++++++++++++ .../GeyserRegisterPermissionsEvent.java | 43 ++++++++++++++++ .../api/permission/PermissionChecker.java | 35 +++++++++++++ .../standalone/GeyserStandaloneBootstrap.java | 4 +- .../GeyserStandaloneCommandManager.java | 51 ++++++++++++++++++- .../standalone/PermissionConfiguration.java | 42 +++++++++++++++ .../org/geysermc/geyser/util/FileUtils.java | 12 +++++ 7 files changed, 226 insertions(+), 3 deletions(-) create mode 100644 api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionCheckersEvent.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/permission/PermissionChecker.java create mode 100644 bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/PermissionConfiguration.java diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionCheckersEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionCheckersEvent.java new file mode 100644 index 000000000..264344d98 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionCheckersEvent.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019-2023 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.api.event.lifecycle; + +import org.geysermc.event.Event; +import org.geysermc.event.PostOrder; +import org.geysermc.geyser.api.permission.PermissionChecker; + +/** + * Fired by any permission manager implementations that wish to add support for custom permission checking. + * This event is not guaranteed to be fired. + *

+ * Subscribing to this event with an earlier {@link PostOrder} and registering a {@link PermissionChecker} + * will result in that checker having a higher priority than others. + */ +public interface GeyserRegisterPermissionCheckersEvent extends Event { + + void register(PermissionChecker checker); +} diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java new file mode 100644 index 000000000..059ca50b7 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019-2023 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.api.event.lifecycle; + +import org.geysermc.event.Event; +import org.geysermc.geyser.api.util.TriState; + +/** + * Fired by any implementations that want to gather permission nodes and defaults + */ +public interface GeyserRegisterPermissionsEvent extends Event { + + /** + * Registers a permission node with the permission system being used by Geyser's command manager. + * + * @param permission the permission node to register + * @param def the default value of the node + */ + void register(String permission, TriState def); +} diff --git a/api/src/main/java/org/geysermc/geyser/api/permission/PermissionChecker.java b/api/src/main/java/org/geysermc/geyser/api/permission/PermissionChecker.java new file mode 100644 index 000000000..fee8f7f41 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/permission/PermissionChecker.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2019-2023 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.api.permission; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.command.CommandSource; +import org.geysermc.geyser.api.util.TriState; + +public interface PermissionChecker { + + TriState hasPermission(@NonNull CommandSource source, @NonNull String permission); +} diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java index 8771a1093..bdcf10ab6 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java @@ -220,7 +220,9 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { geyser = GeyserImpl.load(PlatformType.STANDALONE, this); GeyserImpl.start(); - geyserCommandManager = new GeyserCommandManager(geyser, new GeyserStandaloneCommandManager()); + GeyserStandaloneCommandManager cloud = new GeyserStandaloneCommandManager(geyser); + geyserCommandManager = new GeyserCommandManager(geyser, cloud); + cloud.gatherPermissions(); if (gui != null) { gui.setupInterface(geyserLogger, geyserCommandManager); diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java index cfd47f7a1..0ab0c2bac 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java @@ -30,18 +30,65 @@ import cloud.commandframework.execution.CommandExecutionCoordinator; import cloud.commandframework.internal.CommandRegistrationHandler; import cloud.commandframework.meta.CommandMeta; import cloud.commandframework.meta.SimpleCommandMeta; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionCheckersEvent; +import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent; +import org.geysermc.geyser.api.permission.PermissionChecker; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommandSource; +import org.geysermc.geyser.util.FileUtils; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; public class GeyserStandaloneCommandManager extends CommandManager { - protected GeyserStandaloneCommandManager() { + private final GeyserImpl geyser; + private final List permissionCheckers = new ArrayList<>(); + private final Set basePermissions = new ObjectOpenHashSet<>(); + + public GeyserStandaloneCommandManager(GeyserImpl geyser) { super(CommandExecutionCoordinator.simpleCoordinator(), CommandRegistrationHandler.nullCommandRegistrationHandler()); + this.geyser = geyser; + + // allow any extensions to customize permissions + geyser.getEventBus().fire((GeyserRegisterPermissionCheckersEvent) permissionCheckers::add); + + // must still implement a basic permission system + try { + File permissionsFile = geyser.getBootstrap().getConfigFolder().resolve("permissions.yml").toFile(); + FileUtils.fileOrCopiedFromResource(permissionsFile, "permissions.yml", geyser.getBootstrap()); + PermissionConfiguration config = FileUtils.loadConfig(permissionsFile, PermissionConfiguration.class); + basePermissions.addAll(config.getDefaultPermissions()); + } catch (Exception e) { + geyser.getLogger().warning("Failed to load permissions.yml"); + e.printStackTrace(); + } + } + + public void gatherPermissions() { + geyser.getEventBus().fire((GeyserRegisterPermissionsEvent) (permission, def) -> { + if (def == TriState.TRUE) { + basePermissions.add(permission); + } else if (def == TriState.FALSE) { + basePermissions.remove(permission); + } + }); } @Override public boolean hasPermission(@NonNull GeyserCommandSource sender, @NonNull String permission) { - return false; // todo: commands + for (PermissionChecker checker : permissionCheckers) { + Boolean result = checker.hasPermission(sender, permission).toBoolean(); + if (result != null) { + return result; + } + } + return basePermissions.contains(permission); } @Override diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/PermissionConfiguration.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/PermissionConfiguration.java new file mode 100644 index 000000000..28edddaaa --- /dev/null +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/PermissionConfiguration.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019-2023 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.standalone; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; + +import java.util.Collections; +import java.util.Set; + +@Getter +@JsonIgnoreProperties(ignoreUnknown = true) +@SuppressWarnings("FieldMayBeFinal") // Jackson requires that the fields are not final +public class PermissionConfiguration { + + @JsonProperty("default-permissions") + private Set defaultPermissions = Collections.emptySet(); +} diff --git a/core/src/main/java/org/geysermc/geyser/util/FileUtils.java b/core/src/main/java/org/geysermc/geyser/util/FileUtils.java index 2fde1547d..9251ee17a 100644 --- a/core/src/main/java/org/geysermc/geyser/util/FileUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/FileUtils.java @@ -94,6 +94,18 @@ public class FileUtils { return file; } + /** + * Open the specified file or copy if from resources + * + * @param file File to open + * @param name Name of the resource get if needed + * @return File handle of the specified file + * @throws IOException if the file failed to copy from resource + */ + public static File fileOrCopiedFromResource(File file, String name, GeyserBootstrap bootstrap) throws IOException { + return fileOrCopiedFromResource(file, name, Function.identity(), bootstrap); + } + /** * Writes the given data to the specified file on disk * From 5b3226b0bf3e061e098f4c547c64711f0ee65a59 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Sun, 25 Jun 2023 03:12:03 -0400 Subject: [PATCH 09/76] Fix GeyserSession#hasPermission, remove WorldManager#hasPermission, docs --- .../api/permission/PermissionChecker.java | 1 + .../world/GeyserFabricWorldManager.java | 6 ------ .../manager/GeyserSpigotWorldManager.java | 5 ----- .../GeyserStandaloneCommandManager.java | 14 +++++++++++++ .../command/CommandSourceConverter.java | 20 +++++++++++++++++++ .../geyser/command/GeyserCommandManager.java | 2 +- .../geyser/level/GeyserWorldManager.java | 5 ----- .../geysermc/geyser/level/WorldManager.java | 9 --------- .../geyser/session/GeyserSession.java | 3 ++- 9 files changed, 38 insertions(+), 27 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/permission/PermissionChecker.java b/api/src/main/java/org/geysermc/geyser/api/permission/PermissionChecker.java index fee8f7f41..52f19eb5c 100644 --- a/api/src/main/java/org/geysermc/geyser/api/permission/PermissionChecker.java +++ b/api/src/main/java/org/geysermc/geyser/api/permission/PermissionChecker.java @@ -31,5 +31,6 @@ import org.geysermc.geyser.api.util.TriState; public interface PermissionChecker { + @NonNull TriState hasPermission(@NonNull CommandSource source, @NonNull String permission); } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java index 9cd01f993..719fa55da 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java @@ -147,12 +147,6 @@ public class GeyserFabricWorldManager extends GeyserWorldManager { BlockEntityUtils.updateBlockEntity(session, blockEntityTag, Vector3i.from(x, y, z)); } - @Override - public boolean hasPermission(GeyserSession session, String permission) { - ServerPlayer player = getPlayer(session); - return Permissions.check(player, permission); - } - @Nonnull @Override public CompletableFuture getPickItemNbt(GeyserSession session, int x, int y, int z, boolean addNbtData) { diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java index 056747d6a..5373ba938 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java @@ -171,11 +171,6 @@ public class GeyserSpigotWorldManager extends WorldManager { return gameRule.getDefaultIntValue(); } - @Override - public boolean hasPermission(GeyserSession session, String permission) { - return Bukkit.getPlayer(session.getPlayerEntity().getUsername()).hasPermission(permission); - } - @Nonnull @Override public CompletableFuture<@Nullable CompoundTag> getPickItemNbt(GeyserSession session, int x, int y, int z, boolean addNbtData) { diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java index 0ab0c2bac..8e70ea3a2 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java @@ -37,6 +37,7 @@ import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionCheckersE import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent; import org.geysermc.geyser.api.permission.PermissionChecker; import org.geysermc.geyser.api.util.TriState; +import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.util.FileUtils; @@ -70,6 +71,10 @@ public class GeyserStandaloneCommandManager extends CommandManager { if (def == TriState.TRUE) { @@ -82,6 +87,15 @@ public class GeyserStandaloneCommandManager extends CommandManager server command sender type + */ public record CommandSourceConverter(Class senderType, Function playerLookup, Supplier consoleProvider) { @@ -49,6 +57,18 @@ public record CommandSourceConverter(Class senderType, .orElseThrow(() -> new IllegalArgumentException("failed to find sender for name=%s, uuid=%s".formatted(source.name(), source.playerUuid()))); } + /** + * Creates a new CommandSourceConverter for a server platform + * in which the player type is not the command sender type, and must be mapped. + * + * @param senderType class of the command sender type + * @param playerLookup function for looking up a player by UUID + * @param senderLookup function for converting a player to a command sender + * @param consoleProvider supplier of the console command sender + * @return a new CommandSourceConverter + * @param

server player type + * @param server command sender type + */ public static CommandSourceConverter layered(Class senderType, Function playerLookup, Function senderLookup, diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java index 95c6bb312..bcb7294a9 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java @@ -165,7 +165,7 @@ public class GeyserCommandManager { * @return Command description */ public String description(String command) { - return ""; + return ""; // todo: reimplement } @RequiredArgsConstructor diff --git a/core/src/main/java/org/geysermc/geyser/level/GeyserWorldManager.java b/core/src/main/java/org/geysermc/geyser/level/GeyserWorldManager.java index 8d4b3f2e6..eea486bdc 100644 --- a/core/src/main/java/org/geysermc/geyser/level/GeyserWorldManager.java +++ b/core/src/main/java/org/geysermc/geyser/level/GeyserWorldManager.java @@ -160,11 +160,6 @@ public class GeyserWorldManager extends WorldManager { return gameRule.getDefaultIntValue(); } - @Override - public boolean hasPermission(GeyserSession session, String permission) { - return false; - } - @Nonnull @Override public CompletableFuture<@Nullable CompoundTag> getPickItemNbt(GeyserSession session, int x, int y, int z, boolean addNbtData) { diff --git a/core/src/main/java/org/geysermc/geyser/level/WorldManager.java b/core/src/main/java/org/geysermc/geyser/level/WorldManager.java index 006caff55..9e20e8da1 100644 --- a/core/src/main/java/org/geysermc/geyser/level/WorldManager.java +++ b/core/src/main/java/org/geysermc/geyser/level/WorldManager.java @@ -180,15 +180,6 @@ public abstract class WorldManager { session.sendCommand("difficulty " + difficulty.name().toLowerCase(Locale.ROOT)); } - /** - * Checks if the given session's player has a permission - * - * @param session The session of the player to check the permission of - * @param permission The permission node to check - * @return True if the player has the requested permission, false if not - */ - public abstract boolean hasPermission(GeyserSession session, String permission); - /** * Returns a list of biome identifiers available on the server. */ diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 1dcb40b53..82a78cb2f 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -1693,7 +1693,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { */ @Override public boolean hasPermission(String permission) { - return geyser.getWorldManager().hasPermission(this, permission); + // let the cloud implementation handle permission checking + return geyser.commandManager().cloud().hasPermission(this, permission); } private static final Ability[] USED_ABILITIES = Ability.values(); From a830fb76fb5fc22007dc7ff56cde5f77bc8c7d10 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Mon, 26 Jun 2023 16:38:11 -0400 Subject: [PATCH 10/76] permissions.yml and compiling --- .../platform/standalone/gui/GeyserStandaloneGUI.java | 2 +- bootstrap/standalone/src/main/resources/permissions.yml | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 bootstrap/standalone/src/main/resources/permissions.yml diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java index af3e1069f..86627e01f 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java @@ -278,7 +278,7 @@ public class GeyserStandaloneGUI { public void enableCommands(ScheduledExecutorService executor, GeyserCommandManager commandManager) { // we don't want to block the GUI thread with the command execution // todo: once cloud is used, an AsynchronousCommandExecutionCoordinator can be used to avoid this scheduler - commandListener.handler = cmd -> executor.schedule(() -> commandManager.runCommand(logger, cmd), 0, TimeUnit.SECONDS); + commandListener.handler = cmd -> executor.schedule(() -> commandManager.cloud().executeCommand(logger, cmd), 0, TimeUnit.SECONDS); commandInput.setEnabled(true); commandInput.requestFocusInWindow(); } diff --git a/bootstrap/standalone/src/main/resources/permissions.yml b/bootstrap/standalone/src/main/resources/permissions.yml new file mode 100644 index 000000000..c7a135140 --- /dev/null +++ b/bootstrap/standalone/src/main/resources/permissions.yml @@ -0,0 +1,9 @@ + +# Add any permissions here that all players should have. +# Permissions for builtin geyser commands do not have to be listed here. + +# If an extension/plugin hooks into the permission system of Geyser-Standalone, entries here may be ignored/overridden. +# If extensions don't register their permissions, they must be added here manually. + +default-permissions: + - geyser.command.help # this is unnecessary From 049e29ed42eb182cb8f115ab321720f9f1a4a4b6 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Tue, 27 Jun 2023 04:39:00 -0400 Subject: [PATCH 11/76] Make commands fully use cloud, command api deprecations --- .../geysermc/geyser/api/command/Command.java | 51 ++++++---- .../geyser/command/GeyserCommand.java | 96 +++++-------------- .../geyser/command/GeyserCommandManager.java | 53 +++++----- .../geyser/command/SenderTypeProcessor.java | 58 +++++++++++ .../defaults/AdvancedTooltipsCommand.java | 18 ++-- .../command/defaults/AdvancementsCommand.java | 12 ++- .../defaults/ConnectionTestCommand.java | 72 +++++++------- .../geyser/command/defaults/DumpCommand.java | 57 ++++++----- .../command/defaults/ExtensionsCommand.java | 20 ++-- .../geyser/command/defaults/HelpCommand.java | 58 +++++------ .../geyser/command/defaults/ListCommand.java | 17 +++- .../command/defaults/OffhandCommand.java | 11 +-- .../command/defaults/ReloadCommand.java | 22 ++--- .../command/defaults/SettingsCommand.java | 12 ++- .../command/defaults/StatisticsCommand.java | 16 ++-- .../geyser/command/defaults/StopCommand.java | 17 ++-- .../command/defaults/VersionCommand.java | 27 ++++-- .../geyser/session/GeyserSession.java | 26 ++--- core/src/main/resources/languages | 2 +- 19 files changed, 343 insertions(+), 302 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/command/SenderTypeProcessor.java diff --git a/api/src/main/java/org/geysermc/geyser/api/command/Command.java b/api/src/main/java/org/geysermc/geyser/api/command/Command.java index 2f1f2b24d..1472c37cb 100644 --- a/api/src/main/java/org/geysermc/geyser/api/command/Command.java +++ b/api/src/main/java/org/geysermc/geyser/api/command/Command.java @@ -76,25 +76,18 @@ public interface Command { * * @return if this command is designated to be used only by server operators. */ - boolean isSuggestedOpOnly(); + @Deprecated(forRemoval = true) + default boolean isSuggestedOpOnly() { + return false; + } /** * Gets if this command is executable on console. * * @return if this command is executable on console */ - boolean isExecutableOnConsole(); - - /** - * Gets the subcommands associated with this - * command. Mainly used within the Geyser Standalone - * GUI to know what subcommands are supported. - * - * @return the subcommands associated with this command - */ - @NonNull - default List subCommands() { - return Collections.emptyList(); + default boolean isExecutableOnConsole() { + return true; } /** @@ -106,6 +99,19 @@ public interface Command { return false; } + /** + * Gets the subcommands associated with this + * command. Mainly used within the Geyser Standalone + * GUI to know what subcommands are supported. + * + * @return the subcommands associated with this command + */ + @Deprecated(forRemoval = true) + @NonNull + default List subCommands() { + return Collections.emptyList(); + } + /** * Creates a new {@link Command.Builder} used to construct commands. * @@ -180,14 +186,6 @@ public interface Command { */ Builder executableOnConsole(boolean executableOnConsole); - /** - * Sets the subcommands. - * - * @param subCommands the subcommands - * @return the builder - */ - Builder subCommands(@NonNull List subCommands); - /** * Sets if this command is bedrock only. * @@ -196,6 +194,17 @@ public interface Command { */ Builder bedrockOnly(boolean bedrockOnly); + /** + * Sets the subcommands. + * + * @param subCommands the subcommands + * @return the builder + */ + @Deprecated(forRemoval = true) + default Builder subCommands(@NonNull List subCommands) { + return this; + } + /** * Sets the {@link CommandExecutor} for this command. * diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java index 5d78079f8..e753e568b 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java @@ -25,23 +25,32 @@ package org.geysermc.geyser.command; +import cloud.commandframework.Command; import cloud.commandframework.CommandManager; -import cloud.commandframework.arguments.standard.StringArgument; +import cloud.commandframework.meta.CommandMeta; import lombok.Getter; import lombok.RequiredArgsConstructor; +import lombok.Setter; import lombok.experimental.Accessors; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.command.Command; -import org.geysermc.geyser.session.GeyserSession; +import org.jetbrains.annotations.Contract; -import javax.annotation.Nullable; import java.util.Collections; import java.util.List; @Accessors(fluent = true) @Getter @RequiredArgsConstructor -public abstract class GeyserCommand implements Command { +public abstract class GeyserCommand implements org.geysermc.geyser.api.command.Command { + + /** + * CommandMeta to indicate that the command is only available to bedrock players. default of false. + */ + public static final CommandMeta.Key BEDROCK_ONLY = CommandMeta.Key.of(Boolean.class, "bedrock-only", meta -> false); + + /** + * CommandMeta to indicate that the command is only available to players. default of false. + */ + public static final CommandMeta.Key PLAYER_ONLY = CommandMeta.Key.of(Boolean.class, "player-only", meta -> false); protected final String name; /** @@ -50,78 +59,23 @@ public abstract class GeyserCommand implements Command { protected final String description; protected final String permission; + @Setter private List aliases = Collections.emptyList(); - public abstract void execute(@Nullable GeyserSession session, GeyserCommandSource sender, String[] args); - - /** - * If false, hides the command from being shown on the Geyser Standalone GUI. - * - * @return true if the command can be run on the server console - */ - @Override - public boolean isExecutableOnConsole() { - return true; - } - - /** - * Used in the GUI to know what subcommands can be run - * - * @return a list of all possible subcommands, or empty if none. - */ - @NonNull - @Override - public List subCommands() { - return Collections.emptyList(); - } - - /** - * Shortcut to {@link #subCommands()} ()}{@code .isEmpty()}. - * - * @return true if there are subcommand present for this command. - */ - public boolean hasSubCommands() { - return !this.subCommands().isEmpty(); - } - - public void setAliases(List aliases) { - this.aliases = aliases; - } - - /** - * Used for permission defaults on server implementations. - * - * @return if this command is designated to be used only by server operators. - */ - @Override - public boolean isSuggestedOpOnly() { - return false; - } - public String rootCommand() { return "geyser"; } - public void register(CommandManager manager) { - // todo: commands all builtin commands should directly use cloud - // also, there needs to be customized messages for isExecutableOnConsole and isBedrockOnly, etc. Review the old CommandExecutors. - manager.command(manager.commandBuilder(rootCommand()) + @Contract(value = "_ -> new", pure = true) + public Command.Builder builder(CommandManager manager) { + return manager.commandBuilder(rootCommand()) .literal(name, aliases.toArray(new String[0])) - .permission(source -> { - if (source.isConsole()) { - return isExecutableOnConsole(); - } - if (isBedrockOnly() && source.connection().isEmpty()) { - return false; // bedrock only but not a bedrock player - } + .meta(BEDROCK_ONLY, isBedrockOnly()) + .meta(PLAYER_ONLY, !isExecutableOnConsole()) + .permission(permission); + } - return source.hasPermission(permission); - }) - .argument(StringArgument.optional("args", StringArgument.StringMode.GREEDY)) - .handler(context -> { - GeyserCommandSource source = context.getSender(); - execute(source.connection().orElse(null), source, context.getOrDefault("args", "").split(" ")); - }) - ); + public void register(CommandManager manager) { + manager.command(builder(manager)); } } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java index bcb7294a9..8d62a2079 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java @@ -26,10 +26,10 @@ package org.geysermc.geyser.command; import cloud.commandframework.CommandManager; +import cloud.commandframework.arguments.standard.StringArgument; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import lombok.RequiredArgsConstructor; import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.command.Command; @@ -73,6 +73,8 @@ public class GeyserCommandManager { this.geyser = geyser; this.cloud = cloud; + cloud.registerCommandPostProcessor(new SenderTypeProcessor()); + registerBuiltInCommand(new HelpCommand(geyser, "help", "geyser.commands.help.desc", "geyser.command.help", "geyser", this.commands)); registerBuiltInCommand(new ListCommand(geyser, "list", "geyser.commands.list.desc", "geyser.command.list")); registerBuiltInCommand(new ReloadCommand(geyser, "reload", "geyser.commands.reload.desc", "geyser.command.reload")); @@ -178,7 +180,6 @@ public class GeyserCommandManager { private List aliases; private boolean suggestedOpOnly = false; private boolean executableOnConsole = true; - private List subCommands; private boolean bedrockOnly; private CommandExecutor executor; @@ -219,11 +220,6 @@ public class GeyserCommandManager { return this; } - public CommandBuilder subCommands(@NonNull List subCommands) { - this.subCommands = subCommands; - return this; - } - public CommandBuilder bedrockOnly(boolean bedrockOnly) { this.bedrockOnly = bedrockOnly; return this; @@ -248,20 +244,29 @@ public class GeyserCommandManager { @SuppressWarnings("unchecked") @Override - public void execute(@Nullable GeyserSession session, GeyserCommandSource sender, String[] args) { - Class sourceType = CommandBuilder.this.sourceType; - CommandExecutor executor = CommandBuilder.this.executor; - if (sourceType.isInstance(session)) { - executor.execute((T) session, this, args); - return; - } + public cloud.commandframework.Command.Builder builder(CommandManager manager) { + return super.builder(manager) + .argument(StringArgument.optional("args", StringArgument.StringMode.GREEDY)) + .handler(context -> { + GeyserCommandSource source = context.getSender(); + String[] args = context.getOrDefault("args", "").split(" "); - if (sourceType.isInstance(sender)) { - executor.execute((T) sender, this, args); - return; - } + Class sourceType = CommandBuilder.this.sourceType; + CommandExecutor executor = CommandBuilder.this.executor; - GeyserImpl.getInstance().getLogger().debug("Ignoring command " + this.name + " due to no suitable sender."); + if (sourceType.isInstance(source)) { + executor.execute((T) source, this, args); + return; + } + + GeyserSession session = source.connection().orElse(null); + if (sourceType.isInstance(session)) { + executor.execute((T) session, this, args); + return; + } + + GeyserImpl.getInstance().getLogger().debug("Ignoring command " + this.name + " due to no suitable sender."); + }); } @NonNull @@ -275,10 +280,9 @@ public class GeyserCommandManager { return CommandBuilder.this.suggestedOpOnly; } - @NonNull @Override - public List subCommands() { - return CommandBuilder.this.subCommands == null ? Collections.emptyList() : CommandBuilder.this.subCommands; + public boolean isExecutableOnConsole() { + return CommandBuilder.this.executableOnConsole; } @Override @@ -286,11 +290,6 @@ public class GeyserCommandManager { return CommandBuilder.this.bedrockOnly; } - @Override - public boolean isExecutableOnConsole() { - return CommandBuilder.this.executableOnConsole; - } - @Override public String rootCommand() { return extension().rootCommand(); diff --git a/core/src/main/java/org/geysermc/geyser/command/SenderTypeProcessor.java b/core/src/main/java/org/geysermc/geyser/command/SenderTypeProcessor.java new file mode 100644 index 000000000..40aeac728 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/command/SenderTypeProcessor.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019-2023 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.command; + +import cloud.commandframework.execution.postprocessor.CommandPostprocessingContext; +import cloud.commandframework.execution.postprocessor.CommandPostprocessor; +import cloud.commandframework.meta.CommandMeta; +import cloud.commandframework.services.types.ConsumerService; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.text.ChatColor; +import org.geysermc.geyser.text.GeyserLocale; + +public class SenderTypeProcessor implements CommandPostprocessor { + + @Override + public void accept(@NonNull CommandPostprocessingContext processContext) { + CommandMeta meta = processContext.getCommand().getCommandMeta(); + GeyserCommandSource source = processContext.getCommandContext().getSender(); + + if (meta.getOrDefault(GeyserCommand.BEDROCK_ONLY, false)) { + if (source.connection().isEmpty()) { + source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", source.locale())); + ConsumerService.interrupt(); + } + } else if (meta.getOrDefault(GeyserCommand.PLAYER_ONLY, false)) { + // it should be fine to use else-if here, because if the command is bedrock-only, + // performing the bedrock player check is also inherently a player check + + if (source.playerUuid().isEmpty()) { + source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.player_only", source.locale())); + ConsumerService.interrupt(); + } + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java index 466515b3f..b570d8e7e 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java @@ -25,9 +25,10 @@ package org.geysermc.geyser.command.defaults; +import cloud.commandframework.Command; +import cloud.commandframework.CommandManager; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.MinecraftLocale; public class AdvancedTooltipsCommand extends GeyserCommand { @@ -36,13 +37,14 @@ public class AdvancedTooltipsCommand extends GeyserCommand { } @Override - public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { - if (session != null) { - String onOrOff = session.isAdvancedTooltips() ? "off" : "on"; - session.setAdvancedTooltips(!session.isAdvancedTooltips()); - session.sendMessage("§l§e" + MinecraftLocale.getLocaleString("debug.prefix", session.locale()) + " §r" + MinecraftLocale.getLocaleString("debug.advanced_tooltips." + onOrOff, session.locale())); - session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory()); - } + public Command.Builder builder(CommandManager manager) { + return super.builder(manager) + .handler(context -> context.getSender().connection().ifPresent(session -> { + String onOrOff = session.isAdvancedTooltips() ? "off" : "on"; + session.setAdvancedTooltips(!session.isAdvancedTooltips()); + session.sendMessage("§l§e" + MinecraftLocale.getLocaleString("debug.prefix", session.locale()) + " §r" + MinecraftLocale.getLocaleString("debug.advanced_tooltips." + onOrOff, session.locale())); + session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory()); + })); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java index 28253433f..54edb3179 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java @@ -25,9 +25,10 @@ package org.geysermc.geyser.command.defaults; +import cloud.commandframework.Command; +import cloud.commandframework.CommandManager; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; -import org.geysermc.geyser.session.GeyserSession; public class AdvancementsCommand extends GeyserCommand { public AdvancementsCommand(String name, String description, String permission) { @@ -35,10 +36,11 @@ public class AdvancementsCommand extends GeyserCommand { } @Override - public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { - if (session != null) { - session.getAdvancementsCache().buildAndShowMenuForm(); - } + public Command.Builder builder(CommandManager manager) { + return super.builder(manager) + .handler(context -> + context.getSender().connection().ifPresent(session -> + session.getAdvancementsCache().buildAndShowMenuForm())); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java index 8471fcd3f..8f4b4db83 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java @@ -25,20 +25,25 @@ package org.geysermc.geyser.command.defaults; +import cloud.commandframework.Command; +import cloud.commandframework.CommandManager; +import cloud.commandframework.arguments.standard.IntegerArgument; +import cloud.commandframework.arguments.standard.StringArgument; +import cloud.commandframework.context.CommandContext; import com.fasterxml.jackson.databind.JsonNode; -import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.LoopbackUtil; import org.geysermc.geyser.util.WebUtils; -import org.jetbrains.annotations.Nullable; import java.util.concurrent.CompletableFuture; public class ConnectionTestCommand extends GeyserCommand { + + private static final String ADDRESS = "address"; + private static final String PORT = "port"; + private final GeyserImpl geyser; public ConnectionTestCommand(GeyserImpl geyser, String name, String description, String permission) { @@ -47,64 +52,57 @@ public class ConnectionTestCommand extends GeyserCommand { } @Override - public void execute(@Nullable GeyserSession session, GeyserCommandSource sender, String[] args) { - // Only allow the console to create dumps on Geyser Standalone - if (!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE) { - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale())); - return; - } + public Command.Builder builder(CommandManager manager) { + return super.builder(manager) + .argument(StringArgument.of(ADDRESS)) + .argument(IntegerArgument.builder(PORT) + .asOptionalWithDefault(19132) + .withMax(65535).withMin(0) + .build()) + .handler(this::execute); + } - if (args.length == 0) { - sender.sendMessage("Provide the Bedrock server IP you are trying to connect with. Example: `test.geysermc.org:19132`"); - return; - } - - // Still allow people to not supply a port and fallback to 19132 - String[] fullAddress = args[0].split(":", 2); - int port; - if (fullAddress.length == 2) { - port = Integer.parseInt(fullAddress[1]); - } else { - port = 19132; - } + private void execute(CommandContext context) { + GeyserCommandSource source = context.getSender(); + String address = context.get(ADDRESS); + int port = context.get(PORT); // Issue: do the ports not line up? if (port != geyser.getConfig().getBedrock().port()) { - sender.sendMessage("The port you supplied (" + port + ") does not match the port supplied in Geyser's configuration (" + source.sendMessage("The port you supplied (" + port + ") does not match the port supplied in Geyser's configuration (" + geyser.getConfig().getBedrock().port() + "). You can change it under `bedrock` `port`."); } // Issue: is the `bedrock` `address` in the config different? if (!geyser.getConfig().getBedrock().address().equals("0.0.0.0")) { - sender.sendMessage("The address specified in `bedrock` `address` is not \"0.0.0.0\" - this may cause issues unless this is deliberate and intentional."); + source.sendMessage("The address specified in `bedrock` `address` is not \"0.0.0.0\" - this may cause issues unless this is deliberate and intentional."); } // Issue: did someone turn on enable-proxy-protocol and they didn't mean it? if (geyser.getConfig().getBedrock().isEnableProxyProtocol()) { - sender.sendMessage("You have the `enable-proxy-protocol` setting enabled. " + + source.sendMessage("You have the `enable-proxy-protocol` setting enabled. " + "Unless you're deliberately using additional software that REQUIRES this setting, you may not need it enabled."); } CompletableFuture.runAsync(() -> { try { // Issue: SRV record? - String ip = fullAddress[0]; - String[] record = WebUtils.findSrvRecord(geyser, ip); - if (record != null && !ip.equals(record[3]) && !record[2].equals(String.valueOf(port))) { - sender.sendMessage("Bedrock Edition does not support SRV records. Try connecting to your server using the address " + record[3] + " and the port " + record[2] + String[] record = WebUtils.findSrvRecord(geyser, address); + if (record != null && !address.equals(record[3]) && !record[2].equals(String.valueOf(port))) { + source.sendMessage("Bedrock Edition does not support SRV records. Try connecting to your server using the address " + record[3] + " and the port " + record[2] + ". If that fails, re-run this command with that address and port."); return; } // Issue: does Loopback need applying? if (LoopbackUtil.needsLoopback(GeyserImpl.getInstance().getLogger())) { - sender.sendMessage("Loopback is not applied on this computer! You will have issues connecting from the same computer. " + + source.sendMessage("Loopback is not applied on this computer! You will have issues connecting from the same computer. " + "See here for steps on how to resolve: " + "https://wiki.geysermc.org/geyser/fixing-unable-to-connect-to-world/#using-geyser-on-the-same-computer"); } // mcsrvstatus will likely be replaced in the future with our own service where we can also test // around the OVH workaround without worrying about caching - JsonNode output = WebUtils.getJson("https://api.mcsrvstat.us/bedrock/2/" + args[0]); + JsonNode output = WebUtils.getJson("https://api.mcsrvstat.us/bedrock/2/" + address); long cacheTime = output.get("debug").get("cachetime").asLong(); String when; @@ -115,15 +113,15 @@ public class ConnectionTestCommand extends GeyserCommand { } if (output.get("online").asBoolean()) { - sender.sendMessage("Your server is likely online as of " + when + "!"); - sendLinks(sender); + source.sendMessage("Your server is likely online as of " + when + "!"); + sendLinks(source); return; } - sender.sendMessage("Your server is likely unreachable from outside the network as of " + when + "."); - sendLinks(sender); + source.sendMessage("Your server is likely unreachable from outside the network as of " + when + "."); + sendLinks(source); } catch (Exception e) { - sender.sendMessage("Error while trying to check your connection!"); + source.sendMessage("Error while trying to check your connection!"); geyser.getLogger().error("Error while trying to check your connection!", e); } }); diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java index 544be7446..c7d0c58d5 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java @@ -25,17 +25,18 @@ package org.geysermc.geyser.command.defaults; +import cloud.commandframework.Command; +import cloud.commandframework.CommandManager; +import cloud.commandframework.arguments.standard.StringArrayArgument; +import cloud.commandframework.context.CommandContext; import com.fasterxml.jackson.core.util.DefaultIndenter; import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.dump.DumpInfo; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.AsteriskSerializer; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; @@ -43,11 +44,13 @@ import org.geysermc.geyser.util.WebUtils; import java.io.FileOutputStream; import java.io.IOException; -import java.util.Arrays; import java.util.List; public class DumpCommand extends GeyserCommand { + private static final String ARGUMENTS = "args"; + private static final List SUGGESTIONS = List.of("full", "offline", "logs"); + private final GeyserImpl geyser; private static final ObjectMapper MAPPER = new ObjectMapper(); private static final String DUMP_URL = "https://dump.geysermc.org/"; @@ -59,12 +62,20 @@ public class DumpCommand extends GeyserCommand { } @Override - public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { - // Only allow the console to create dumps on Geyser Standalone - if (!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE) { - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale())); - return; - } + public Command.Builder builder(CommandManager manager) { + return super.builder(manager) + .argument(createArgument()) + .handler(this::execute); + } + + private StringArrayArgument createArgument() { + // todo suggestions might be broken + return StringArrayArgument.optional(ARGUMENTS, (context, input) -> SUGGESTIONS); + } + + public void execute(CommandContext context) { + GeyserCommandSource source = context.getSender(); + String[] args = context.getOrDefault(ARGUMENTS, new String[0]); boolean showSensitive = false; boolean offlineDump = false; @@ -81,7 +92,7 @@ public class DumpCommand extends GeyserCommand { AsteriskSerializer.showSensitive = showSensitive; - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collecting", sender.locale())); + source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collecting", source.locale())); String dumpData; try { if (offlineDump) { @@ -93,7 +104,7 @@ public class DumpCommand extends GeyserCommand { dumpData = MAPPER.writeValueAsString(new DumpInfo(addLog)); } } catch (IOException e) { - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collect_error", sender.locale())); + source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.collect_error", source.locale())); geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.dump.collect_error_short"), e); return; } @@ -101,21 +112,21 @@ public class DumpCommand extends GeyserCommand { String uploadedDumpUrl = ""; if (offlineDump) { - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.writing", sender.locale())); + source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.writing", source.locale())); try { FileOutputStream outputStream = new FileOutputStream(GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("dump.json").toFile()); outputStream.write(dumpData.getBytes()); outputStream.close(); } catch (IOException e) { - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.write_error", sender.locale())); + source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.write_error", source.locale())); geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.dump.write_error_short"), e); return; } uploadedDumpUrl = "dump.json"; } else { - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.uploading", sender.locale())); + source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.uploading", source.locale())); String response; JsonNode responseNode; @@ -123,31 +134,25 @@ public class DumpCommand extends GeyserCommand { response = WebUtils.post(DUMP_URL + "documents", dumpData); responseNode = MAPPER.readTree(response); } catch (IOException e) { - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error", sender.locale())); + source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error", source.locale())); geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.dump.upload_error_short"), e); return; } if (!responseNode.has("key")) { - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error_short", sender.locale()) + ": " + (responseNode.has("message") ? responseNode.get("message").asText() : response)); + source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.dump.upload_error_short", source.locale()) + ": " + (responseNode.has("message") ? responseNode.get("message").asText() : response)); return; } uploadedDumpUrl = DUMP_URL + responseNode.get("key").asText(); } - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.message", sender.locale()) + " " + ChatColor.DARK_AQUA + uploadedDumpUrl); - if (!sender.isConsole()) { - geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.commands.dump.created", sender.name(), uploadedDumpUrl)); + source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.dump.message", source.locale()) + " " + ChatColor.DARK_AQUA + uploadedDumpUrl); + if (!source.isConsole()) { + geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.commands.dump.created", source.name(), uploadedDumpUrl)); } } - @NonNull - @Override - public List subCommands() { - return Arrays.asList("offline", "full", "logs"); - } - @Override public boolean isSuggestedOpOnly() { return true; diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ExtensionsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ExtensionsCommand.java index 30d422b23..8a482ba66 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ExtensionsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ExtensionsCommand.java @@ -25,14 +25,15 @@ package org.geysermc.geyser.command.defaults; +import cloud.commandframework.Command; +import cloud.commandframework.CommandManager; +import cloud.commandframework.context.CommandContext; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; -import org.jetbrains.annotations.Nullable; import java.util.Comparator; import java.util.List; @@ -47,16 +48,23 @@ public class ExtensionsCommand extends GeyserCommand { } @Override - public void execute(@Nullable GeyserSession session, GeyserCommandSource sender, String[] args) { + public Command.Builder builder(CommandManager manager) { + return super.builder(manager) + .handler(this::execute); + } + + public void execute(CommandContext context) { + GeyserCommandSource source = context.getSender(); + // TODO: Pagination int page = 1; int maxPage = 1; - String header = GeyserLocale.getPlayerLocaleString("geyser.commands.extensions.header", sender.locale(), page, maxPage); - sender.sendMessage(header); + String header = GeyserLocale.getPlayerLocaleString("geyser.commands.extensions.header", source.locale(), page, maxPage); + source.sendMessage(header); this.geyser.extensionManager().extensions().stream().sorted(Comparator.comparing(Extension::name)).forEach(extension -> { String extensionName = (extension.isEnabled() ? ChatColor.GREEN : ChatColor.RED) + extension.name(); - sender.sendMessage("- " + extensionName + ChatColor.RESET + " v" + extension.description().version() + formatAuthors(extension.description().authors())); + source.sendMessage("- " + extensionName + ChatColor.RESET + " v" + extension.description().version() + formatAuthors(extension.description().authors())); }); } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java index d75ef3a50..bf2e39a87 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java @@ -25,61 +25,55 @@ package org.geysermc.geyser.command.defaults; -import org.geysermc.geyser.api.util.PlatformType; +import cloud.commandframework.CommandManager; +import cloud.commandframework.context.CommandContext; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; +import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.Map; public class HelpCommand extends GeyserCommand { - private final GeyserImpl geyser; private final String baseCommand; - private final Map commands; + private final Collection commands; public HelpCommand(GeyserImpl geyser, String name, String description, String permission, String baseCommand, Map commands) { super(name, description, permission); - this.geyser = geyser; this.baseCommand = baseCommand; - this.commands = commands; + this.commands = commands.values(); - this.setAliases(Collections.singletonList("?")); + this.aliases(Collections.singletonList("?")); } - /** - * Sends the help menu to a command sender. Will not show certain commands depending on the command sender and session. - * - * @param session The Geyser session of the command sender, if it is a bedrock player. If null, bedrock-only commands will be hidden. - * @param sender The CommandSender to send the help message to. - * @param args Not used. - */ @Override - public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { + public cloud.commandframework.Command.Builder builder(CommandManager manager) { + return super.builder(manager) + .handler(this::execute); + } + + private void execute(CommandContext context) { + GeyserCommandSource source = context.getSender(); + boolean bedrockPlayer = source.connection().isPresent(); + + // todo: pagination int page = 1; int maxPage = 1; - String header = GeyserLocale.getPlayerLocaleString("geyser.commands.help.header", sender.locale(), page, maxPage); - sender.sendMessage(header); + String header = GeyserLocale.getPlayerLocaleString("geyser.commands.help.header", source.locale(), page, maxPage); + source.sendMessage(header); - // todo: commands looks like this doesn't guard against alias keys - this.commands.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(entry -> { - Command cmd = entry.getValue(); - - // Standalone hack-in since it doesn't have a concept of permissions - if (geyser.getPlatformType() == PlatformType.STANDALONE || sender.hasPermission(cmd.permission())) { - // Only list commands the player can actually run - if (cmd.isBedrockOnly() && session == null) { - return; - } - - sender.sendMessage(ChatColor.YELLOW + "/" + baseCommand + " " + entry.getKey() + ChatColor.WHITE + ": " + - GeyserLocale.getPlayerLocaleString(cmd.description(), sender.locale())); - } - }); + this.commands.stream() + .distinct() // remove aliases + .sorted(Comparator.comparing(Command::name)) + .filter(cmd -> source.hasPermission(cmd.permission())) + .filter(cmd -> !cmd.isBedrockOnly() || bedrockPlayer) // remove bedrock only commands if not a bedrock player + .forEach(cmd -> source.sendMessage(ChatColor.YELLOW + "/" + baseCommand + " " + cmd.name() + ChatColor.WHITE + ": " + + GeyserLocale.getPlayerLocaleString(cmd.description(), source.locale()))); } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java index 90446fbb6..9056f2006 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java @@ -25,6 +25,8 @@ package org.geysermc.geyser.command.defaults; +import cloud.commandframework.Command; +import cloud.commandframework.CommandManager; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; @@ -44,12 +46,17 @@ public class ListCommand extends GeyserCommand { } @Override - public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { - String message = GeyserLocale.getPlayerLocaleString("geyser.commands.list.message", sender.locale(), - geyser.getSessionManager().size(), - geyser.getSessionManager().getAllSessions().stream().map(GeyserSession::bedrockUsername).collect(Collectors.joining(" "))); + public Command.Builder builder(CommandManager manager) { + return super.builder(manager) + .handler(context -> { + GeyserCommandSource source = context.getSender(); - sender.sendMessage(message); + String message = GeyserLocale.getPlayerLocaleString("geyser.commands.list.message", source.locale(), + geyser.getSessionManager().size(), + geyser.getSessionManager().getAllSessions().stream().map(GeyserSession::bedrockUsername).collect(Collectors.joining(" "))); + + source.sendMessage(message); + }); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java index 6188e6924..99753ee95 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java @@ -25,6 +25,8 @@ package org.geysermc.geyser.command.defaults; +import cloud.commandframework.Command; +import cloud.commandframework.CommandManager; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; @@ -37,12 +39,9 @@ public class OffhandCommand extends GeyserCommand { } @Override - public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { - if (session == null) { - return; - } - - session.requestOffhandSwap(); + public Command.Builder builder(CommandManager manager) { + return super.builder(manager) + .handler(context -> context.getSender().connection().ifPresent(GeyserSession::requestOffhandSwap)); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java index 8f147cdab..9c6f48dda 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java @@ -25,11 +25,11 @@ package org.geysermc.geyser.command.defaults; -import org.geysermc.geyser.api.util.PlatformType; +import cloud.commandframework.Command; +import cloud.commandframework.CommandManager; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; public class ReloadCommand extends GeyserCommand { @@ -42,17 +42,15 @@ public class ReloadCommand extends GeyserCommand { } @Override - public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { - if (!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE) { - return; - } + public Command.Builder builder(CommandManager manager) { + return super.builder(manager) + .handler(context -> { + GeyserCommandSource source = context.getSender(); + source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.reload.message", source.locale())); - String message = GeyserLocale.getPlayerLocaleString("geyser.commands.reload.message", sender.locale()); - - sender.sendMessage(message); - - geyser.getSessionManager().disconnectAll("geyser.commands.reload.kick"); - geyser.reload(); + geyser.getSessionManager().disconnectAll("geyser.commands.reload.kick"); + geyser.reload(); + }); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java index 7828cf1d2..2b83a20c9 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java @@ -25,10 +25,11 @@ package org.geysermc.geyser.command.defaults; +import cloud.commandframework.Command; +import cloud.commandframework.CommandManager; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.SettingsUtils; public class SettingsCommand extends GeyserCommand { @@ -37,10 +38,11 @@ public class SettingsCommand extends GeyserCommand { } @Override - public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { - if (session != null) { - session.sendForm(SettingsUtils.buildForm(session)); - } + public Command.Builder builder(CommandManager manager) { + return super.builder(manager) + .handler(context -> + context.getSender().connection().ifPresent(session -> + session.sendForm(SettingsUtils.buildForm(session)))); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java index ea2da51df..3b83945a3 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java @@ -25,12 +25,13 @@ package org.geysermc.geyser.command.defaults; +import cloud.commandframework.Command; +import cloud.commandframework.CommandManager; import com.github.steveice10.mc.protocol.data.game.ClientCommand; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundClientCommandPacket; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; -import org.geysermc.geyser.session.GeyserSession; public class StatisticsCommand extends GeyserCommand { @@ -39,12 +40,13 @@ public class StatisticsCommand extends GeyserCommand { } @Override - public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { - if (session == null) return; - - session.setWaitingForStatistics(true); - ServerboundClientCommandPacket ServerboundClientCommandPacket = new ServerboundClientCommandPacket(ClientCommand.STATS); - session.sendDownstreamPacket(ServerboundClientCommandPacket); + public Command.Builder builder(CommandManager manager) { + return super.builder(manager) + .handler(context -> context.getSender().connection().ifPresent(session -> { + session.setWaitingForStatistics(true); + ServerboundClientCommandPacket packet = new ServerboundClientCommandPacket(ClientCommand.STATS); + session.sendDownstreamPacket(packet); + })); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java index 7db539cc5..a5b0689d9 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java @@ -25,12 +25,11 @@ package org.geysermc.geyser.command.defaults; -import org.geysermc.geyser.api.util.PlatformType; +import cloud.commandframework.Command; +import cloud.commandframework.CommandManager; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.text.GeyserLocale; import java.util.Collections; @@ -42,17 +41,13 @@ public class StopCommand extends GeyserCommand { super(name, description, permission); this.geyser = geyser; - this.setAliases(Collections.singletonList("shutdown")); + this.aliases(Collections.singletonList("shutdown")); } @Override - public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { - if (!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE) { - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale())); - return; - } - - geyser.getBootstrap().onDisable(); + public Command.Builder builder(CommandManager manager) { + return super.builder(manager) + .handler(context -> geyser.getBootstrap().onDisable()); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java index 378e43ee0..3f4f4bf63 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java @@ -25,13 +25,15 @@ package org.geysermc.geyser.command.defaults; +import cloud.commandframework.Command; +import cloud.commandframework.CommandManager; +import cloud.commandframework.context.CommandContext; import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec; import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.network.GameProtocol; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.WebUtils; @@ -52,7 +54,14 @@ public class VersionCommand extends GeyserCommand { } @Override - public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) { + public Command.Builder builder(CommandManager manager) { + return super.builder(manager) + .handler(this::execute); + } + + public void execute(CommandContext context) { + GeyserCommandSource source = context.getSender(); + String bedrockVersions; List supportedCodecs = GameProtocol.SUPPORTED_BEDROCK_CODECS; if (supportedCodecs.size() > 1) { @@ -68,12 +77,12 @@ public class VersionCommand extends GeyserCommand { javaVersions = supportedJavaVersions.get(0); } - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.version", sender.locale(), + source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.version", source.locale(), GeyserImpl.NAME, GeyserImpl.VERSION, javaVersions, bedrockVersions)); // Disable update checking in dev mode and for players in Geyser Standalone - if (GeyserImpl.getInstance().isProductionEnvironment() && !(!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE)) { - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.checking", sender.locale())); + if (GeyserImpl.getInstance().isProductionEnvironment() && !(!source.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE)) { + source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.checking", source.locale())); try { String buildXML = WebUtils.getBody("https://ci.opencollab.dev/job/GeyserMC/job/Geyser/job/" + URLEncoder.encode(GeyserImpl.BRANCH, StandardCharsets.UTF_8.toString()) + "/lastSuccessfulBuild/api/xml?xpath=//buildNumber"); @@ -81,17 +90,17 @@ public class VersionCommand extends GeyserCommand { int latestBuildNum = Integer.parseInt(buildXML.replaceAll("<(\\\\)?(/)?buildNumber>", "").trim()); int buildNum = this.geyser.buildNumber(); if (latestBuildNum == buildNum) { - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.no_updates", sender.locale())); + source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.no_updates", source.locale())); } else { - sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.outdated", - sender.locale(), (latestBuildNum - buildNum), "https://ci.geysermc.org/")); + source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.outdated", + source.locale(), (latestBuildNum - buildNum), "https://ci.geysermc.org/")); } } else { throw new AssertionError("buildNumber missing"); } } catch (IOException e) { GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.commands.version.failed"), e); - sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.version.failed", sender.locale())); + source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.commands.version.failed", source.locale())); } } } diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 82a78cb2f..4c709915a 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -1403,7 +1403,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { @Override public String name() { - return null; + return playerEntity.getUsername(); } @Override @@ -1429,11 +1429,23 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { return Optional.of(playerEntity.getUuid()); } + @Override + public Optional connection() { + return Optional.of(this); + } + @Override public String locale() { return clientData.getLanguageCode(); } + @Override + public boolean hasPermission(String permission) { + // for Geyser-Standalone, standalone's permission system will handle it. + // for server platforms, the session will be mapped to a server command sender, and the server's api will be used. + return geyser.commandManager().cloud().hasPermission(this, permission); + } + /** * Sends a chat message to the Java server. */ @@ -1685,18 +1697,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { upstream.sendPacket(gameRulesChangedPacket); } - /** - * Checks if the given session's player has a permission - * - * @param permission The permission node to check - * @return true if the player has the requested permission, false if not - */ - @Override - public boolean hasPermission(String permission) { - // let the cloud implementation handle permission checking - return geyser.commandManager().cloud().hasPermission(this, permission); - } - private static final Ability[] USED_ABILITIES = Ability.values(); /** diff --git a/core/src/main/resources/languages b/core/src/main/resources/languages index fda08c186..e9c9e9bbc 160000 --- a/core/src/main/resources/languages +++ b/core/src/main/resources/languages @@ -1 +1 @@ -Subproject commit fda08c186de979ff89b01d2b66f48daa5e218d01 +Subproject commit e9c9e9bbcfe037f1be85bf323784a583f313c3d1 From 6c932ecc62eb91c1c80ba6c82392b885e29108dc Mon Sep 17 00:00:00 2001 From: Konicai <71294714+konicai@users.noreply.github.com> Date: Wed, 30 Aug 2023 21:24:16 -0400 Subject: [PATCH 12/76] Handle exceptions on standalone, class renaming and moving --- .../bungeecord/GeyserBungeePlugin.java | 10 +- .../platform/fabric/GeyserFabricMod.java | 12 +- .../platform/spigot/GeyserSpigotPlugin.java | 14 +- .../platform/sponge/GeyserSpongePlugin.java | 12 +- .../standalone/GeyserStandaloneBootstrap.java | 12 +- .../GeyserStandaloneCommandManager.java | 5 +- .../standalone/GeyserStandaloneLogger.java | 2 +- .../standalone/gui/GeyserStandaloneGUI.java | 10 +- .../velocity/GeyserVelocityPlugin.java | 10 +- .../org/geysermc/geyser/GeyserBootstrap.java | 8 +- .../java/org/geysermc/geyser/GeyserImpl.java | 9 +- .../geyser/command/CommandBuilder.java | 169 +++++++++++++++ ...mmandManager.java => CommandRegistry.java} | 200 +++++++----------- .../geyser/command/GeyserCommandSource.java | 4 + .../loader/ProviderRegistryLoader.java | 4 +- .../geyser/session/GeyserSession.java | 3 +- .../BedrockCommandRequestTranslator.java | 13 +- .../protocol/java/JavaCommandsTranslator.java | 6 +- 18 files changed, 312 insertions(+), 191 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/command/CommandBuilder.java rename core/src/main/java/org/geysermc/geyser/command/{GeyserCommandManager.java => CommandRegistry.java} (59%) diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java index e7ecce84b..1a4ee490a 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java @@ -39,7 +39,7 @@ import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.CommandSourceConverter; -import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; @@ -65,7 +65,7 @@ import java.util.logging.Level; public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { - private GeyserCommandManager geyserCommandManager; + private CommandRegistry commandRegistry; private GeyserBungeeConfiguration geyserConfig; private GeyserBungeeInjector geyserInjector; private GeyserBungeeLogger geyserLogger; @@ -184,7 +184,7 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { BungeeCommandSource::new, sourceConverter::convert ); - this.geyserCommandManager = new GeyserCommandManager(geyser, cloud); + this.commandRegistry = new CommandRegistry(geyser, cloud); if (geyserConfig.isLegacyPingPassthrough()) { this.geyserBungeePingPassthrough = GeyserLegacyPingPassthrough.init(geyser); @@ -214,8 +214,8 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { } @Override - public GeyserCommandManager getGeyserCommandManager() { - return this.geyserCommandManager; + public CommandRegistry getCommandRegistry() { + return this.commandRegistry; } @Override diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java index 29e2a530e..6f3198c06 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java @@ -41,8 +41,9 @@ import org.apache.logging.log4j.LogManager; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; +import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.command.CommandSourceConverter; -import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; @@ -73,7 +74,7 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { private Path dataFolder; private MinecraftServer server; - private GeyserCommandManager geyserCommandManager; + private CommandRegistry commandRegistry; private GeyserFabricConfiguration geyserConfig; private GeyserFabricLogger geyserLogger; private IGeyserPingPassthrough geyserPingPassthrough; @@ -155,7 +156,7 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { FabricCommandSource::new, sourceConverter::convert ); - this.geyserCommandManager = new GeyserCommandManager(geyser, cloud); + this.commandRegistry = new CommandRegistry(geyser, cloud); this.geyserWorldManager = new GeyserFabricWorldManager(server); } @@ -182,8 +183,8 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { } @Override - public GeyserCommandManager getGeyserCommandManager() { - return geyserCommandManager; + public CommandRegistry getCommandRegistry() { + return commandRegistry; } @Override @@ -211,6 +212,7 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { return this.server.getServerVersion(); } + @SuppressWarnings("ConstantConditions") // Certain IDEA installations think that ip cannot be null @NotNull @Override public String getServerBindAddress() { 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 742e3b953..4c8621218 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 @@ -50,7 +50,7 @@ import org.geysermc.geyser.adapters.spigot.SpigotAdapters; import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.command.CommandSourceConverter; -import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; @@ -83,7 +83,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { */ private static boolean INITIALIZED = false; - private GeyserCommandManager geyserCommandManager; + private CommandRegistry commandRegistry; private GeyserSpigotConfiguration geyserConfig; private GeyserSpigotInjector geyserInjector; private GeyserSpigotLogger geyserLogger; @@ -187,7 +187,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { } } - this.geyserCommandManager = new GeyserCommandManager(geyser, cloud); + this.commandRegistry = new CommandRegistry(geyser, cloud); if (!INITIALIZED) { // Needs to be an anonymous inner class otherwise Bukkit complains about missing classes @@ -277,7 +277,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { if (!INITIALIZED) { // Register permissions so they appear in, for example, LuckPerms' UI // Re-registering permissions throws an error - for (Map.Entry entry : geyserCommandManager.commands().entrySet()) { + for (Map.Entry entry : commandRegistry.commands().entrySet()) { Command command = entry.getValue(); if (command.aliases().contains(entry.getKey())) { // Don't register aliases @@ -290,7 +290,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { } // Register permissions for extension commands - for (Map.Entry> commandEntry : this.geyserCommandManager.extensionCommands().entrySet()) { + for (Map.Entry> commandEntry : this.commandRegistry.extensionCommands().entrySet()) { for (Map.Entry entry : commandEntry.getValue().entrySet()) { Command command = entry.getValue(); if (command.aliases().contains(entry.getKey())) { @@ -353,8 +353,8 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { } @Override - public GeyserCommandManager getGeyserCommandManager() { - return this.geyserCommandManager; + public CommandRegistry getCommandRegistry() { + return this.commandRegistry; } @Override diff --git a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java index ca0418b72..d91ab7104 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java @@ -30,7 +30,7 @@ import org.apache.logging.log4j.Logger; import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; @@ -77,7 +77,7 @@ public class GeyserSpongePlugin implements GeyserBootstrap { private GeyserSpongeConfiguration geyserConfig; private GeyserSpongeLogger geyserLogger; private GeyserImpl geyser; - private GeyserCommandManager geyserCommandManager; + private CommandRegistry commandRegistry; // Available after StartedEngine lifecycle private IGeyserPingPassthrough geyserSpongePingPassthrough; @@ -136,11 +136,11 @@ public class GeyserSpongePlugin implements GeyserBootstrap { this.geyser = GeyserImpl.load(PlatformType.SPONGE, this); - this.geyserCommandManager = new GeyserCommandManager(geyser, Objects.requireNonNull(null)); // todo: commands + this.commandRegistry = new CommandRegistry(geyser, Objects.requireNonNull(null)); // todo: commands } /** - * Construct the {@link GeyserCommandManager} and register the commands + * Construct the {@link CommandRegistry} and register the commands * * @param event required to register the commands */ @@ -185,8 +185,8 @@ public class GeyserSpongePlugin implements GeyserBootstrap { } @Override - public GeyserCommandManager getGeyserCommandManager() { - return geyserCommandManager; + public CommandRegistry getCommandRegistry() { + return commandRegistry; } @Override diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java index 7002537db..cadf6e2e7 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java @@ -41,7 +41,7 @@ import org.apache.logging.log4j.core.appender.ConsoleAppender; import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; @@ -64,7 +64,7 @@ import java.util.stream.Collectors; public class GeyserStandaloneBootstrap implements GeyserBootstrap { - private GeyserCommandManager geyserCommandManager; + private CommandRegistry commandRegistry; private GeyserStandaloneConfiguration geyserConfig; private GeyserStandaloneLogger geyserLogger; private IGeyserPingPassthrough geyserPingPassthrough; @@ -221,11 +221,11 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { GeyserImpl.start(); GeyserStandaloneCommandManager cloud = new GeyserStandaloneCommandManager(geyser); - geyserCommandManager = new GeyserCommandManager(geyser, cloud); + commandRegistry = new CommandRegistry(geyser, cloud); cloud.gatherPermissions(); if (gui != null) { - gui.enableCommands(geyser.getScheduledThread(), geyserCommandManager); + gui.enableCommands(geyser.getScheduledThread(), commandRegistry); } geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); @@ -267,8 +267,8 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { } @Override - public GeyserCommandManager getGeyserCommandManager() { - return geyserCommandManager; + public CommandRegistry getCommandRegistry() { + return commandRegistry; } @Override diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java index 8e70ea3a2..6af18d166 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java @@ -37,7 +37,7 @@ import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionCheckersE import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent; import org.geysermc.geyser.api.permission.PermissionChecker; import org.geysermc.geyser.api.util.TriState; -import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.util.FileUtils; @@ -73,8 +73,9 @@ public class GeyserStandaloneCommandManager extends CommandManager { if (def == TriState.TRUE) { diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java index 44b73dc2d..bc7a56c6b 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java @@ -44,7 +44,7 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements Gey @Override protected void runCommand(String line) { - GeyserImpl.getInstance().commandManager().cloud().executeCommand(this, line); + GeyserImpl.getInstance().commandRegistry().runCommand(this, line); } @Override diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java index 86627e01f..a1eabb3cc 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java @@ -27,7 +27,7 @@ package org.geysermc.geyser.platform.standalone.gui; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; -import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; @@ -273,12 +273,12 @@ public class GeyserStandaloneGUI { * Enable the command input box. * * @param executor the executor for running commands off the GUI thread - * @param commandManager the command manager to delegate commands to + * @param registry the command registry to delegate commands to */ - public void enableCommands(ScheduledExecutorService executor, GeyserCommandManager commandManager) { + public void enableCommands(ScheduledExecutorService executor, CommandRegistry registry) { // we don't want to block the GUI thread with the command execution - // todo: once cloud is used, an AsynchronousCommandExecutionCoordinator can be used to avoid this scheduler - commandListener.handler = cmd -> executor.schedule(() -> commandManager.cloud().executeCommand(logger, cmd), 0, TimeUnit.SECONDS); + // todo: maybe use a AsynchronousCommandExecutionCoordinator (cloud thing) + commandListener.handler = cmd -> executor.execute(() -> registry.runCommand(logger, cmd)); commandInput.setEnabled(true); commandInput.requestFocusInWindow(); } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java index c9390b2b4..5f5fe3c8c 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java @@ -44,7 +44,7 @@ import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.CommandSourceConverter; -import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; @@ -76,7 +76,7 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { @Inject private PluginContainer container; - private GeyserCommandManager geyserCommandManager; + private CommandRegistry commandRegistry; private GeyserVelocityConfiguration geyserConfig; private GeyserVelocityInjector geyserInjector; private GeyserVelocityLogger geyserLogger; @@ -143,7 +143,7 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { VelocityCommandSource::new, sourceConverter::convert ); - this.geyserCommandManager = new GeyserCommandManager(geyser, cloud); + this.commandRegistry = new CommandRegistry(geyser, cloud); if (geyserConfig.isLegacyPingPassthrough()) { this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); @@ -175,8 +175,8 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { } @Override - public GeyserCommandManager getGeyserCommandManager() { - return this.geyserCommandManager; + public CommandRegistry getCommandRegistry() { + return this.commandRegistry; } @Override diff --git a/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java b/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java index e4baeebb5..4338889d3 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java @@ -25,7 +25,7 @@ package org.geysermc.geyser; -import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.level.GeyserWorldManager; @@ -68,11 +68,11 @@ public interface GeyserBootstrap { GeyserLogger getGeyserLogger(); /** - * Returns the current CommandManager + * Returns the current CommandRegistry * - * @return The current CommandManager + * @return The current CommandRegistry */ - GeyserCommandManager getGeyserCommandManager(); + CommandRegistry getCommandRegistry(); /** * Returns the current PingPassthrough manager diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 809604afb..00f8cec2d 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -61,7 +61,7 @@ import org.geysermc.geyser.api.event.lifecycle.GeyserShutdownEvent; import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.api.network.BedrockListener; import org.geysermc.geyser.api.network.RemoteServer; -import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.erosion.UnixSocketClientListener; @@ -71,7 +71,6 @@ import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.network.netty.GeyserServer; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.Registries; -import org.geysermc.geyser.registry.loader.RegistryLoaders; import org.geysermc.geyser.scoreboard.ScoreboardUpdater; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.PendingMicrosoftAuthentication; @@ -601,7 +600,7 @@ public class GeyserImpl implements GeyserApi { skinUploader.close(); } newsHandler.shutdown(); - this.commandManager().clear(); + this.commandRegistry().clear(); if (this.erosionUnixListener != null) { this.erosionUnixListener.close(); @@ -640,8 +639,8 @@ public class GeyserImpl implements GeyserApi { } @NonNull - public GeyserCommandManager commandManager() { - return this.bootstrap.getGeyserCommandManager(); + public CommandRegistry commandRegistry() { + return this.bootstrap.getCommandRegistry(); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandBuilder.java b/core/src/main/java/org/geysermc/geyser/command/CommandBuilder.java new file mode 100644 index 000000000..33dc9a16d --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/command/CommandBuilder.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2019-2023 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.command; + +import cloud.commandframework.CommandManager; +import cloud.commandframework.arguments.standard.StringArgument; +import lombok.RequiredArgsConstructor; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.command.Command; +import org.geysermc.geyser.api.command.CommandExecutor; +import org.geysermc.geyser.api.command.CommandSource; +import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.extension.command.GeyserExtensionCommand; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.Collections; +import java.util.List; + +@RequiredArgsConstructor +public class CommandBuilder implements Command.Builder { + private final Extension extension; + private Class sourceType; + private String name; + private String description = ""; + private String permission = ""; + private List aliases; + private boolean suggestedOpOnly = false; + private boolean executableOnConsole = true; + private boolean bedrockOnly; + private CommandExecutor executor; + + @Override + public Command.Builder source(@NonNull Class sourceType) { + this.sourceType = sourceType; + return this; + } + + public CommandBuilder name(@NonNull String name) { + this.name = name; + return this; + } + + public CommandBuilder description(@NonNull String description) { + this.description = description; + return this; + } + + public CommandBuilder permission(@NonNull String permission) { + this.permission = permission; + return this; + } + + public CommandBuilder aliases(@NonNull List aliases) { + this.aliases = aliases; + return this; + } + + @Override + public Command.Builder suggestedOpOnly(boolean suggestedOpOnly) { + this.suggestedOpOnly = suggestedOpOnly; + return this; + } + + public CommandBuilder executableOnConsole(boolean executableOnConsole) { + this.executableOnConsole = executableOnConsole; + return this; + } + + public CommandBuilder bedrockOnly(boolean bedrockOnly) { + this.bedrockOnly = bedrockOnly; + return this; + } + + public CommandBuilder executor(@NonNull CommandExecutor executor) { + this.executor = executor; + return this; + } + + @NonNull + public GeyserExtensionCommand build() { + if (this.name == null || this.name.isBlank()) { + throw new IllegalArgumentException("Command cannot be null or blank!"); + } + + if (this.sourceType == null) { + throw new IllegalArgumentException("Source type was not defined for command " + this.name + " in extension " + this.extension.name()); + } + + return new GeyserExtensionCommand(this.extension, this.name, this.description, this.permission) { + + @SuppressWarnings("unchecked") + @Override + public cloud.commandframework.Command.Builder builder(CommandManager manager) { + return super.builder(manager) + .argument(StringArgument.optional("args", StringArgument.StringMode.GREEDY)) + .handler(context -> { + GeyserCommandSource source = context.getSender(); + String[] args = context.getOrDefault("args", "").split(" "); + + Class sourceType = CommandBuilder.this.sourceType; + CommandExecutor executor = CommandBuilder.this.executor; + + if (sourceType.isInstance(source)) { + executor.execute((T) source, this, args); + return; + } + + GeyserSession session = source.connection().orElse(null); + if (sourceType.isInstance(session)) { + executor.execute((T) session, this, args); + return; + } + + GeyserImpl.getInstance().getLogger().debug("Ignoring command " + this.name + " due to no suitable sender."); + }); + } + + @NonNull + @Override + public List aliases() { + return CommandBuilder.this.aliases == null ? Collections.emptyList() : CommandBuilder.this.aliases; + } + + @Override + public boolean isSuggestedOpOnly() { + return CommandBuilder.this.suggestedOpOnly; + } + + @Override + public boolean isExecutableOnConsole() { + return CommandBuilder.this.executableOnConsole; + } + + @Override + public boolean isBedrockOnly() { + return CommandBuilder.this.bedrockOnly; + } + + @Override + public String rootCommand() { + return extension().rootCommand(); + } + }; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java similarity index 59% rename from core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java rename to core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java index 8b353d3c9..18021a7e1 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java @@ -26,15 +26,17 @@ package org.geysermc.geyser.command; import cloud.commandframework.CommandManager; -import cloud.commandframework.arguments.standard.StringArgument; +import cloud.commandframework.exceptions.ArgumentParseException; +import cloud.commandframework.exceptions.CommandExecutionException; +import cloud.commandframework.exceptions.InvalidCommandSenderException; +import cloud.commandframework.exceptions.InvalidSyntaxException; +import cloud.commandframework.exceptions.NoPermissionException; +import cloud.commandframework.exceptions.NoSuchCommandException; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import lombok.RequiredArgsConstructor; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.command.Command; -import org.geysermc.geyser.api.command.CommandExecutor; -import org.geysermc.geyser.api.command.CommandSource; import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCommandsEvent; import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.command.defaults.AdvancedTooltipsCommand; @@ -52,16 +54,15 @@ import org.geysermc.geyser.command.defaults.StopCommand; import org.geysermc.geyser.command.defaults.VersionCommand; import org.geysermc.geyser.event.type.GeyserDefineCommandsEventImpl; import org.geysermc.geyser.extension.command.GeyserExtensionCommand; -import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; import org.jetbrains.annotations.NotNull; import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; +import java.util.concurrent.CompletionException; -public class GeyserCommandManager { +public final class CommandRegistry { private final Map commands = new Object2ObjectOpenHashMap<>(13); private final Map> extensionCommands = new Object2ObjectOpenHashMap<>(0); @@ -69,7 +70,7 @@ public class GeyserCommandManager { private final GeyserImpl geyser; private final CommandManager cloud; - public GeyserCommandManager(GeyserImpl geyser, CommandManager cloud) { + public CommandRegistry(GeyserImpl geyser, CommandManager cloud) { this.geyser = geyser; this.cloud = cloud; @@ -171,131 +172,76 @@ public class GeyserCommandManager { return ""; // todo: reimplement } - @RequiredArgsConstructor - public static class CommandBuilder implements Command.Builder { - private final Extension extension; - private Class sourceType; - private String name; - private String description = ""; - private String permission = ""; - private List aliases; - private boolean suggestedOpOnly = false; - private boolean executableOnConsole = true; - private boolean bedrockOnly; - private CommandExecutor executor; - - @Override - public Command.Builder source(@NonNull Class sourceType) { - this.sourceType = sourceType; - return this; - } - - public CommandBuilder name(@NonNull String name) { - this.name = name; - return this; - } - - public CommandBuilder description(@NonNull String description) { - this.description = description; - return this; - } - - public CommandBuilder permission(@NonNull String permission) { - this.permission = permission; - return this; - } - - public CommandBuilder aliases(@NonNull List aliases) { - this.aliases = aliases; - return this; - } - - @Override - public Command.Builder suggestedOpOnly(boolean suggestedOpOnly) { - this.suggestedOpOnly = suggestedOpOnly; - return this; - } - - public CommandBuilder executableOnConsole(boolean executableOnConsole) { - this.executableOnConsole = executableOnConsole; - return this; - } - - public CommandBuilder bedrockOnly(boolean bedrockOnly) { - this.bedrockOnly = bedrockOnly; - return this; - } - - public CommandBuilder executor(@NonNull CommandExecutor executor) { - this.executor = executor; - return this; - } - - @NonNull - public GeyserExtensionCommand build() { - if (this.name == null || this.name.isBlank()) { - throw new IllegalArgumentException("Command cannot be null or blank!"); + /** + * Dispatches a command into cloud and handles any thrown exceptions. + */ + public void runCommand(@NonNull GeyserCommandSource source, @NonNull String command) { + cloud.executeCommand(source, command).whenComplete((result, throwable) -> { + if (throwable == null) { + return; } - if (this.sourceType == null) { - throw new IllegalArgumentException("Source type was not defined for command " + this.name + " in extension " + this.extension.name()); + if (throwable instanceof CompletionException) { + throwable = throwable.getCause(); } - return new GeyserExtensionCommand(this.extension, this.name, this.description, this.permission) { + handleThrowable(source, throwable); + }); + } - @SuppressWarnings("unchecked") - @Override - public cloud.commandframework.Command.Builder builder(CommandManager manager) { - return super.builder(manager) - .argument(StringArgument.optional("args", StringArgument.StringMode.GREEDY)) - .handler(context -> { - GeyserCommandSource source = context.getSender(); - String[] args = context.getOrDefault("args", "").split(" "); + // todo: full localization + private void handleThrowable(@NonNull GeyserCommandSource source, @NonNull Throwable throwable) { + // This is modelled after the command executors of each cloud minecraft implementation. + if (throwable instanceof InvalidSyntaxException syntaxException) { + cloud.handleException( + source, + InvalidSyntaxException.class, + syntaxException, + ($, e) -> source.sendMessage("Invalid Command Syntax. Correct syntax is: " + e.getCorrectSyntax()) + ); + } else if (throwable instanceof InvalidCommandSenderException invalidSenderException) { + cloud.handleException( + source, + InvalidCommandSenderException.class, + invalidSenderException, + ($, e) -> source.sendMessage(throwable.getMessage()) + ); + } else if (throwable instanceof NoPermissionException noPermissionException) { + cloud.handleException( + source, + NoPermissionException.class, + noPermissionException, + ($, e) -> source.sendLocaleString("geyser.bootstrap.command.permission_fail") - Class sourceType = CommandBuilder.this.sourceType; - CommandExecutor executor = CommandBuilder.this.executor; - - if (sourceType.isInstance(source)) { - executor.execute((T) source, this, args); - return; - } - - GeyserSession session = source.connection().orElse(null); - if (sourceType.isInstance(session)) { - executor.execute((T) session, this, args); - return; - } - - GeyserImpl.getInstance().getLogger().debug("Ignoring command " + this.name + " due to no suitable sender."); - }); - } - - @NonNull - @Override - public List aliases() { - return CommandBuilder.this.aliases == null ? Collections.emptyList() : CommandBuilder.this.aliases; - } - - @Override - public boolean isSuggestedOpOnly() { - return CommandBuilder.this.suggestedOpOnly; - } - - @Override - public boolean isExecutableOnConsole() { - return CommandBuilder.this.executableOnConsole; - } - - @Override - public boolean isBedrockOnly() { - return CommandBuilder.this.bedrockOnly; - } - - @Override - public String rootCommand() { - return extension().rootCommand(); - } - }; + ); + } else if (throwable instanceof NoSuchCommandException noCommandException) { + cloud.handleException( + source, + NoSuchCommandException.class, + noCommandException, + ($, e) -> source.sendLocaleString("geyser.bootstrap.command.not_found") + ); + } else if (throwable instanceof ArgumentParseException argumentParseException) { + cloud.handleException( + source, + ArgumentParseException.class, + argumentParseException, + ($, e) -> source.sendMessage("Invalid Command Argument: " + throwable.getCause().getMessage()) + ); + } else if (throwable instanceof CommandExecutionException executionException) { + cloud.handleException( + source, + CommandExecutionException.class, + executionException, + ($, e) -> defaultHandler(source, throwable.getCause()) + ); + } else { + defaultHandler(source, throwable); } } + + private void defaultHandler(GeyserCommandSource source, Throwable throwable) { + source.sendLocaleString("command.failed"); // java edition translation key + GeyserImpl.getInstance().getLogger().error("Exception while executing command handler", throwable); + } } diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java index 1381e5b88..cec89bf9b 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java @@ -55,6 +55,10 @@ public interface GeyserCommandSource extends CommandSource { sendMessage(GeyserLocale.getPlayerLocaleString(key, locale(), values)); } + default void sendLocaleString(String key) { + sendMessage(GeyserLocale.getPlayerLocaleString(key, locale())); + } + @Override default Optional connection() { return playerUuid().map(id -> GeyserImpl.getInstance().connectionByUuid(id)); diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java index 13d7a4d77..a3d8afd76 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java @@ -38,7 +38,7 @@ import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomItemOptions; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; import org.geysermc.geyser.api.pack.PathPackCodec; -import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.CommandBuilder; import org.geysermc.geyser.event.GeyserEventRegistrar; import org.geysermc.geyser.item.GeyserCustomItemData; import org.geysermc.geyser.item.GeyserCustomItemOptions; @@ -63,7 +63,7 @@ public class ProviderRegistryLoader implements RegistryLoader, Prov @Override public Map, ProviderSupplier> load(Map, ProviderSupplier> providers) { // misc - providers.put(Command.Builder.class, args -> new GeyserCommandManager.CommandBuilder<>((Extension) args[0])); + providers.put(Command.Builder.class, args -> new CommandBuilder<>((Extension) args[0])); providers.put(CustomBlockComponents.Builder.class, args -> new GeyserCustomBlockComponents.CustomBlockComponentsBuilder()); providers.put(CustomBlockData.Builder.class, args -> new GeyserCustomBlockData.CustomBlockDataBuilder()); diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index c34e288f8..1a59c457b 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -131,7 +131,6 @@ import org.geysermc.geyser.item.Items; import org.geysermc.geyser.level.JavaDimension; import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.level.physics.CollisionManager; -import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.network.netty.LocalSession; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.BlockMappings; @@ -1469,7 +1468,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { public boolean hasPermission(String permission) { // for Geyser-Standalone, standalone's permission system will handle it. // for server platforms, the session will be mapped to a server command sender, and the server's api will be used. - return geyser.commandManager().cloud().hasPermission(this, permission); + return geyser.commandRegistry().cloud().hasPermission(this, permission); } /** diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java index bf659397b..699105cb6 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java @@ -25,11 +25,10 @@ package org.geysermc.geyser.translator.protocol.bedrock; -import cloud.commandframework.CommandManager; import org.cloudburstmc.protocol.bedrock.packet.CommandRequestPacket; import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.GeyserCommandSource; +import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -50,10 +49,12 @@ public class BedrockCommandRequestTranslator extends PacketTranslator 0) { String root = args[0]; - CommandManager manager = GeyserImpl.getInstance().commandManager().cloud(); - if (manager.rootCommands().contains(root)) { - manager.executeCommand(session, strippedCommand); - return; + // todo: do we want to pass the command to the server + // if cloud gives a NoSuchCommandException? might be more accurate. + CommandRegistry registry = GeyserImpl.getInstance().commandRegistry(); + if (registry.cloud().rootCommands().contains(root)) { + registry.runCommand(session, strippedCommand); + return; // don't pass the command to the java server } } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java index e29570171..771028b1e 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java @@ -44,7 +44,7 @@ import org.cloudburstmc.protocol.bedrock.data.command.*; import org.cloudburstmc.protocol.bedrock.packet.AvailableCommandsPacket; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.event.java.ServerDefineCommandsEvent; -import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.inventory.item.Enchantment; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.Registries; @@ -115,7 +115,7 @@ public class JavaCommandsTranslator extends PacketTranslator commandData = new ArrayList<>(); IntSet commandNodes = new IntOpenHashSet(); @@ -144,7 +144,7 @@ public class JavaCommandsTranslator extends PacketTranslator new HashSet<>()).add(node.getName().toLowerCase()); } From 47031ce2839144b4736387e6e92cce71585a8be9 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Thu, 31 Aug 2023 23:17:31 -0400 Subject: [PATCH 13/76] misc changes for reviews --- .../geysermc/geyser/api/permission/PermissionChecker.java | 1 + .../geyser/command/defaults/AdvancedTooltipsCommand.java | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/permission/PermissionChecker.java b/api/src/main/java/org/geysermc/geyser/api/permission/PermissionChecker.java index 52f19eb5c..4805243c6 100644 --- a/api/src/main/java/org/geysermc/geyser/api/permission/PermissionChecker.java +++ b/api/src/main/java/org/geysermc/geyser/api/permission/PermissionChecker.java @@ -29,6 +29,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.command.CommandSource; import org.geysermc.geyser.api.util.TriState; +@FunctionalInterface public interface PermissionChecker { @NonNull diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java index b570d8e7e..5fd066b19 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java @@ -29,6 +29,7 @@ import cloud.commandframework.Command; import cloud.commandframework.CommandManager; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; +import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.MinecraftLocale; public class AdvancedTooltipsCommand extends GeyserCommand { @@ -42,7 +43,10 @@ public class AdvancedTooltipsCommand extends GeyserCommand { .handler(context -> context.getSender().connection().ifPresent(session -> { String onOrOff = session.isAdvancedTooltips() ? "off" : "on"; session.setAdvancedTooltips(!session.isAdvancedTooltips()); - session.sendMessage("§l§e" + MinecraftLocale.getLocaleString("debug.prefix", session.locale()) + " §r" + MinecraftLocale.getLocaleString("debug.advanced_tooltips." + onOrOff, session.locale())); + session.sendMessage(ChatColor.BOLD + ChatColor.YELLOW + + MinecraftLocale.getLocaleString("debug.prefix", session.locale()) + + " " + ChatColor.RESET + + MinecraftLocale.getLocaleString("debug.advanced_tooltips." + onOrOff, session.locale())); session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory()); })); } From cddc34aeb7fac6684b720bd39b905220ad0ee22c Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Fri, 1 Sep 2023 20:06:37 -0400 Subject: [PATCH 14/76] Cleanup builtin and extension command implementations --- .../geysermc/geyser/api/command/Command.java | 14 ++-- .../geyser/command/CommandBuilder.java | 78 +++++++++---------- .../geyser/command/GeyserCommand.java | 75 +++++++++++++++--- .../defaults/AdvancedTooltipsCommand.java | 13 +--- .../command/defaults/AdvancementsCommand.java | 13 +--- .../defaults/ConnectionTestCommand.java | 5 -- .../geyser/command/defaults/DumpCommand.java | 5 -- .../geyser/command/defaults/HelpCommand.java | 2 +- .../geyser/command/defaults/ListCommand.java | 5 -- .../command/defaults/OffhandCommand.java | 12 +-- .../command/defaults/ReloadCommand.java | 5 -- .../command/defaults/SettingsCommand.java | 13 +--- .../command/defaults/StatisticsCommand.java | 12 +-- .../geyser/command/defaults/StopCommand.java | 7 +- .../command/defaults/VersionCommand.java | 5 -- .../command/GeyserExtensionCommand.java | 21 ++++- 16 files changed, 138 insertions(+), 147 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/command/Command.java b/api/src/main/java/org/geysermc/geyser/api/command/Command.java index 1472c37cb..373b4f27c 100644 --- a/api/src/main/java/org/geysermc/geyser/api/command/Command.java +++ b/api/src/main/java/org/geysermc/geyser/api/command/Command.java @@ -26,6 +26,7 @@ package org.geysermc.geyser.api.command; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.GeyserApi; import org.geysermc.geyser.api.connection.GeyserConnection; import org.geysermc.geyser.api.extension.Extension; @@ -64,9 +65,9 @@ public interface Command { String permission(); /** - * Gets the aliases for this command. + * Gets the aliases for this command, as an unmodifiable list * - * @return the aliases for this command + * @return the aliases for this command as an unmodifiable list */ @NonNull List aliases(); @@ -91,7 +92,7 @@ public interface Command { } /** - * Used to send a deny message to Java players if this command can only be used by Bedrock players. + * Used to send a deny-message to Java players if this command can only be used by Bedrock players. * * @return true if this command can only be used by Bedrock players. */ @@ -152,7 +153,7 @@ public interface Command { * @param description the command description * @return the builder */ - Builder description(@NonNull String description); + Builder description(@Nullable String description); /** * Sets the permission node. @@ -160,7 +161,7 @@ public interface Command { * @param permission the permission node * @return the builder */ - Builder permission(@NonNull String permission); + Builder permission(@Nullable String permission); /** * Sets the aliases. @@ -168,7 +169,7 @@ public interface Command { * @param aliases the aliases * @return the builder */ - Builder aliases(@NonNull List aliases); + Builder aliases(@Nullable List aliases); /** * Sets if this command is designed to be used only by server operators. @@ -176,6 +177,7 @@ public interface Command { * @param suggestedOpOnly if this command is designed to be used only by server operators * @return the builder */ + @Deprecated(forRemoval = true) Builder suggestedOpOnly(boolean suggestedOpOnly); /** diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandBuilder.java b/core/src/main/java/org/geysermc/geyser/command/CommandBuilder.java index 33dc9a16d..bd0470d55 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandBuilder.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandBuilder.java @@ -27,8 +27,8 @@ package org.geysermc.geyser.command; import cloud.commandframework.CommandManager; import cloud.commandframework.arguments.standard.StringArgument; -import lombok.RequiredArgsConstructor; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.api.command.CommandExecutor; @@ -37,44 +37,53 @@ import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.extension.command.GeyserExtensionCommand; import org.geysermc.geyser.session.GeyserSession; +import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; -@RequiredArgsConstructor public class CommandBuilder implements Command.Builder { private final Extension extension; private Class sourceType; private String name; - private String description = ""; - private String permission = ""; + private String description; + private String permission; private List aliases; - private boolean suggestedOpOnly = false; + private boolean suggestedOpOnly = false; // deprecated for removal private boolean executableOnConsole = true; - private boolean bedrockOnly; + private boolean bedrockOnly = false; private CommandExecutor executor; + public CommandBuilder(Extension extension) { + this.extension = Objects.requireNonNull(extension); + } + @Override public Command.Builder source(@NonNull Class sourceType) { this.sourceType = sourceType; return this; } + @Override public CommandBuilder name(@NonNull String name) { this.name = name; return this; } - public CommandBuilder description(@NonNull String description) { + @Override + public CommandBuilder description(@Nullable String description) { this.description = description; return this; } - public CommandBuilder permission(@NonNull String permission) { + @Override + public CommandBuilder permission(@Nullable String permission) { this.permission = permission; return this; } - public CommandBuilder aliases(@NonNull List aliases) { + @Override + public CommandBuilder aliases(@Nullable List aliases) { this.aliases = aliases; return this; } @@ -85,32 +94,39 @@ public class CommandBuilder implements Command.Builder< return this; } + @Override public CommandBuilder executableOnConsole(boolean executableOnConsole) { this.executableOnConsole = executableOnConsole; return this; } + @Override public CommandBuilder bedrockOnly(boolean bedrockOnly) { this.bedrockOnly = bedrockOnly; return this; } + @Override public CommandBuilder executor(@NonNull CommandExecutor executor) { this.executor = executor; return this; } @NonNull + @Override public GeyserExtensionCommand build() { - if (this.name == null || this.name.isBlank()) { - throw new IllegalArgumentException("Command cannot be null or blank!"); + final Class sourceType = this.sourceType; + final boolean suggestedOpOnly = this.suggestedOpOnly; + final CommandExecutor executor = this.executor; + + if (sourceType == null) { + throw new IllegalArgumentException("Source type was not defined for command " + name + " in extension " + extension.name()); + } + if (executor == null) { + throw new IllegalArgumentException("Command executor was not defined for command " + name + " in extension " + extension.name()); } - if (this.sourceType == null) { - throw new IllegalArgumentException("Source type was not defined for command " + this.name + " in extension " + this.extension.name()); - } - - return new GeyserExtensionCommand(this.extension, this.name, this.description, this.permission) { + GeyserExtensionCommand command = new GeyserExtensionCommand(extension, name, description, permission, executableOnConsole, bedrockOnly) { @SuppressWarnings("unchecked") @Override @@ -121,9 +137,6 @@ public class CommandBuilder implements Command.Builder< GeyserCommandSource source = context.getSender(); String[] args = context.getOrDefault("args", "").split(" "); - Class sourceType = CommandBuilder.this.sourceType; - CommandExecutor executor = CommandBuilder.this.executor; - if (sourceType.isInstance(source)) { executor.execute((T) source, this, args); return; @@ -135,35 +148,18 @@ public class CommandBuilder implements Command.Builder< return; } + // todo: send sender message instead GeyserImpl.getInstance().getLogger().debug("Ignoring command " + this.name + " due to no suitable sender."); }); } - @NonNull - @Override - public List aliases() { - return CommandBuilder.this.aliases == null ? Collections.emptyList() : CommandBuilder.this.aliases; - } - @Override public boolean isSuggestedOpOnly() { - return CommandBuilder.this.suggestedOpOnly; - } - - @Override - public boolean isExecutableOnConsole() { - return CommandBuilder.this.executableOnConsole; - } - - @Override - public boolean isBedrockOnly() { - return CommandBuilder.this.bedrockOnly; - } - - @Override - public String rootCommand() { - return extension().rootCommand(); + return suggestedOpOnly; } }; + + command.aliases = aliases != null ? new ArrayList<>(aliases) : Collections.emptyList(); + return command; } } diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java index e753e568b..562dd882c 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java @@ -28,18 +28,13 @@ package org.geysermc.geyser.command; import cloud.commandframework.Command; import cloud.commandframework.CommandManager; import cloud.commandframework.meta.CommandMeta; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.Setter; -import lombok.experimental.Accessors; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.jetbrains.annotations.Contract; import java.util.Collections; import java.util.List; -@Accessors(fluent = true) -@Getter -@RequiredArgsConstructor public abstract class GeyserCommand implements org.geysermc.geyser.api.command.Command { /** @@ -52,15 +47,77 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C */ public static final CommandMeta.Key PLAYER_ONLY = CommandMeta.Key.of(Boolean.class, "player-only", meta -> false); + @NonNull protected final String name; + /** * The description of the command - will attempt to be translated. */ + @NonNull protected final String description; + + @NonNull protected final String permission; - @Setter - private List aliases = Collections.emptyList(); + protected final boolean executableOnConsole; + protected final boolean bedrockOnly; + + protected List aliases = Collections.emptyList(); + + public GeyserCommand(@NonNull String name, @Nullable String description, @Nullable String permission, boolean executableOnConsole, boolean bedrockOnly) { + if (name == null || name.isBlank()) { + throw new IllegalArgumentException("Command cannot be null or blank!"); + } + + this.name = name; + this.description = description != null ? description : ""; + this.permission = permission != null ? permission : ""; + + if (bedrockOnly && executableOnConsole) { + throw new IllegalArgumentException("Command cannot be both bedrockOnly and executableOnConsole"); + } + + this.executableOnConsole = executableOnConsole; + this.bedrockOnly = bedrockOnly; + } + + public GeyserCommand(@NonNull String name, @NonNull String description, @Nullable String permission) { + this(name, description, permission, true, false); + } + + @NonNull + @Override + public final String name() { + return name; + } + + @NonNull + @Override + public final String description() { + return description; + } + + @NonNull + @Override + public final String permission() { + return permission; + } + + @Override + public final boolean isExecutableOnConsole() { + return executableOnConsole; + } + + @Override + public final boolean isBedrockOnly() { + return bedrockOnly; + } + + @NonNull + @Override + public final List aliases() { + return Collections.unmodifiableList(aliases); + } public String rootCommand() { return "geyser"; diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java index 5fd066b19..9506d3ab6 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java @@ -33,8 +33,9 @@ import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.MinecraftLocale; public class AdvancedTooltipsCommand extends GeyserCommand { + public AdvancedTooltipsCommand(String name, String description, String permission) { - super(name, description, permission); + super(name, description, permission, false, true); } @Override @@ -50,14 +51,4 @@ public class AdvancedTooltipsCommand extends GeyserCommand { session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory()); })); } - - @Override - public boolean isExecutableOnConsole() { - return false; - } - - @Override - public boolean isBedrockOnly() { - return true; - } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java index 54edb3179..5f7711df9 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java @@ -31,8 +31,9 @@ import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; public class AdvancementsCommand extends GeyserCommand { + public AdvancementsCommand(String name, String description, String permission) { - super(name, description, permission); + super(name, description, permission, false, true); } @Override @@ -42,14 +43,4 @@ public class AdvancementsCommand extends GeyserCommand { context.getSender().connection().ifPresent(session -> session.getAdvancementsCache().buildAndShowMenuForm())); } - - @Override - public boolean isExecutableOnConsole() { - return false; - } - - @Override - public boolean isBedrockOnly() { - return true; - } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java index 8f4b4db83..85ee3b53b 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java @@ -132,9 +132,4 @@ public class ConnectionTestCommand extends GeyserCommand { "https://wiki.geysermc.org/geyser/supported-hosting-providers/" + ", see this page: " + "https://wiki.geysermc.org/geyser/fixing-unable-to-connect-to-world/" + ", or contact us on our Discord: " + "https://discord.gg/geysermc"); } - - @Override - public boolean isSuggestedOpOnly() { - return true; - } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java index c7d0c58d5..d48c1361e 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java @@ -152,9 +152,4 @@ public class DumpCommand extends GeyserCommand { geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.commands.dump.created", source.name(), uploadedDumpUrl)); } } - - @Override - public boolean isSuggestedOpOnly() { - return true; - } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java index 0cf30c06c..0c54880b0 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java @@ -49,7 +49,7 @@ public class HelpCommand extends GeyserCommand { this.baseCommand = baseCommand; this.commands = commands.values(); - this.aliases(Collections.singletonList("?")); + this.aliases = Collections.singletonList("?"); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java index 9056f2006..04ec04ce7 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java @@ -58,9 +58,4 @@ public class ListCommand extends GeyserCommand { source.sendMessage(message); }); } - - @Override - public boolean isSuggestedOpOnly() { - return true; - } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java index 99753ee95..c0b2a27ba 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java @@ -35,7 +35,7 @@ import org.geysermc.geyser.session.GeyserSession; public class OffhandCommand extends GeyserCommand { public OffhandCommand(GeyserImpl geyser, String name, String description, String permission) { - super(name, description, permission); + super(name, description, permission, false, true); } @Override @@ -43,14 +43,4 @@ public class OffhandCommand extends GeyserCommand { return super.builder(manager) .handler(context -> context.getSender().connection().ifPresent(GeyserSession::requestOffhandSwap)); } - - @Override - public boolean isExecutableOnConsole() { - return false; - } - - @Override - public boolean isBedrockOnly() { - return true; - } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java index 9c6f48dda..d889955ea 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java @@ -52,9 +52,4 @@ public class ReloadCommand extends GeyserCommand { geyser.reload(); }); } - - @Override - public boolean isSuggestedOpOnly() { - return true; - } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java index 2b83a20c9..42d212805 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java @@ -33,8 +33,9 @@ import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.util.SettingsUtils; public class SettingsCommand extends GeyserCommand { + public SettingsCommand(GeyserImpl geyser, String name, String description, String permission) { - super(name, description, permission); + super(name, description, permission, false, true); } @Override @@ -44,14 +45,4 @@ public class SettingsCommand extends GeyserCommand { context.getSender().connection().ifPresent(session -> session.sendForm(SettingsUtils.buildForm(session)))); } - - @Override - public boolean isExecutableOnConsole() { - return false; - } - - @Override - public boolean isBedrockOnly() { - return true; - } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java index 3b83945a3..ea9537755 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java @@ -36,7 +36,7 @@ import org.geysermc.geyser.command.GeyserCommandSource; public class StatisticsCommand extends GeyserCommand { public StatisticsCommand(GeyserImpl geyser, String name, String description, String permission) { - super(name, description, permission); + super(name, description, permission, false, true); } @Override @@ -48,14 +48,4 @@ public class StatisticsCommand extends GeyserCommand { session.sendDownstreamPacket(packet); })); } - - @Override - public boolean isExecutableOnConsole() { - return false; - } - - @Override - public boolean isBedrockOnly() { - return true; - } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java index a5b0689d9..daebdc9dc 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java @@ -41,7 +41,7 @@ public class StopCommand extends GeyserCommand { super(name, description, permission); this.geyser = geyser; - this.aliases(Collections.singletonList("shutdown")); + this.aliases = Collections.singletonList("shutdown"); } @Override @@ -49,9 +49,4 @@ public class StopCommand extends GeyserCommand { return super.builder(manager) .handler(context -> geyser.getBootstrap().onDisable()); } - - @Override - public boolean isSuggestedOpOnly() { - return true; - } } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java index 26103c27d..ac8401d53 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java @@ -105,9 +105,4 @@ public class VersionCommand extends GeyserCommand { } } } - - @Override - public boolean isSuggestedOpOnly() { - return true; - } } diff --git a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java index 4a7830c90..d29d2b522 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java @@ -25,19 +25,32 @@ package org.geysermc.geyser.extension.command; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.command.GeyserCommand; public abstract class GeyserExtensionCommand extends GeyserCommand { + private final Extension extension; + private final String rootCommand; - public GeyserExtensionCommand(Extension extension, String name, String description, String permission) { - super(name, description, permission); - + public GeyserExtensionCommand(@NonNull Extension extension, @NonNull String name, @Nullable String description, @Nullable String permission, boolean executableOnConsole, boolean bedrockOnly) { + super(name, description, permission, executableOnConsole, bedrockOnly); this.extension = extension; + this.rootCommand = extension.rootCommand(); + + if (this.rootCommand == null || this.rootCommand.isBlank()) { + throw new IllegalStateException("rootCommand of extension " + extension.name() + " may not be null or blank"); + } } - public Extension extension() { + public final Extension extension() { return this.extension; } + + @Override + public final String rootCommand() { + return this.rootCommand; + } } From aea2a189954cb09146e4897f74c458a0cd2f1de1 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Fri, 1 Sep 2023 20:10:51 -0400 Subject: [PATCH 15/76] CommandBuilder -> ExtensionCommandBuilder --- ...ilder.java => ExtensionCommandBuilder.java} | 18 +++++++++--------- .../loader/ProviderRegistryLoader.java | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) rename core/src/main/java/org/geysermc/geyser/command/{CommandBuilder.java => ExtensionCommandBuilder.java} (88%) diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandBuilder.java b/core/src/main/java/org/geysermc/geyser/command/ExtensionCommandBuilder.java similarity index 88% rename from core/src/main/java/org/geysermc/geyser/command/CommandBuilder.java rename to core/src/main/java/org/geysermc/geyser/command/ExtensionCommandBuilder.java index bd0470d55..58a40adf5 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandBuilder.java +++ b/core/src/main/java/org/geysermc/geyser/command/ExtensionCommandBuilder.java @@ -42,7 +42,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; -public class CommandBuilder implements Command.Builder { +public class ExtensionCommandBuilder implements Command.Builder { private final Extension extension; private Class sourceType; private String name; @@ -54,7 +54,7 @@ public class CommandBuilder implements Command.Builder< private boolean bedrockOnly = false; private CommandExecutor executor; - public CommandBuilder(Extension extension) { + public ExtensionCommandBuilder(Extension extension) { this.extension = Objects.requireNonNull(extension); } @@ -65,25 +65,25 @@ public class CommandBuilder implements Command.Builder< } @Override - public CommandBuilder name(@NonNull String name) { + public ExtensionCommandBuilder name(@NonNull String name) { this.name = name; return this; } @Override - public CommandBuilder description(@Nullable String description) { + public ExtensionCommandBuilder description(@Nullable String description) { this.description = description; return this; } @Override - public CommandBuilder permission(@Nullable String permission) { + public ExtensionCommandBuilder permission(@Nullable String permission) { this.permission = permission; return this; } @Override - public CommandBuilder aliases(@Nullable List aliases) { + public ExtensionCommandBuilder aliases(@Nullable List aliases) { this.aliases = aliases; return this; } @@ -95,19 +95,19 @@ public class CommandBuilder implements Command.Builder< } @Override - public CommandBuilder executableOnConsole(boolean executableOnConsole) { + public ExtensionCommandBuilder executableOnConsole(boolean executableOnConsole) { this.executableOnConsole = executableOnConsole; return this; } @Override - public CommandBuilder bedrockOnly(boolean bedrockOnly) { + public ExtensionCommandBuilder bedrockOnly(boolean bedrockOnly) { this.bedrockOnly = bedrockOnly; return this; } @Override - public CommandBuilder executor(@NonNull CommandExecutor executor) { + public ExtensionCommandBuilder executor(@NonNull CommandExecutor executor) { this.executor = executor; return this; } diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java index a3d8afd76..8bbb1cbf2 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java @@ -38,7 +38,7 @@ import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomItemOptions; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; import org.geysermc.geyser.api.pack.PathPackCodec; -import org.geysermc.geyser.command.CommandBuilder; +import org.geysermc.geyser.command.ExtensionCommandBuilder; import org.geysermc.geyser.event.GeyserEventRegistrar; import org.geysermc.geyser.item.GeyserCustomItemData; import org.geysermc.geyser.item.GeyserCustomItemOptions; @@ -63,7 +63,7 @@ public class ProviderRegistryLoader implements RegistryLoader, Prov @Override public Map, ProviderSupplier> load(Map, ProviderSupplier> providers) { // misc - providers.put(Command.Builder.class, args -> new CommandBuilder<>((Extension) args[0])); + providers.put(Command.Builder.class, args -> new ExtensionCommandBuilder<>((Extension) args[0])); providers.put(CustomBlockComponents.Builder.class, args -> new GeyserCustomBlockComponents.CustomBlockComponentsBuilder()); providers.put(CustomBlockData.Builder.class, args -> new GeyserCustomBlockData.CustomBlockDataBuilder()); From 7ee26fb2bdc94fe8327c837b11eb1755ba86c19f Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Fri, 1 Sep 2023 21:40:14 -0400 Subject: [PATCH 16/76] Attempt at permission defaults for spigot --- .../geysermc/geyser/api/command/Command.java | 13 ++++ .../lifecycle/GeyserDefineCommandsEvent.java | 4 +- .../GeyserRegisterPermissionsEvent.java | 4 +- .../platform/spigot/GeyserSpigotPlugin.java | 69 ++++++++----------- .../GeyserStandaloneCommandManager.java | 2 +- .../geyser/command/CommandRegistry.java | 61 ++++++++++------ .../command/ExtensionCommandBuilder.java | 13 +++- .../geyser/command/GeyserCommand.java | 47 +++++++++++-- .../type/GeyserDefineCommandsEventImpl.java | 4 +- .../command/GeyserExtensionCommand.java | 8 ++- 10 files changed, 144 insertions(+), 81 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/command/Command.java b/api/src/main/java/org/geysermc/geyser/api/command/Command.java index 373b4f27c..18af89c93 100644 --- a/api/src/main/java/org/geysermc/geyser/api/command/Command.java +++ b/api/src/main/java/org/geysermc/geyser/api/command/Command.java @@ -29,7 +29,9 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.GeyserApi; import org.geysermc.geyser.api.connection.GeyserConnection; +import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent; import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.api.util.TriState; import java.util.Collections; import java.util.List; @@ -163,6 +165,17 @@ public interface Command { */ Builder permission(@Nullable String permission); + /** + * Sets the permission node, and its default value. The usage of the default value is platform dependant + * and may or may not be used. Extensions may instead listen for {@link GeyserRegisterPermissionsEvent} + * to register permissions. + * + * @param permission the permission node + * @param defaultValue the node's default value + * @return the builder + */ + Builder permission(@Nullable String permission, TriState defaultValue); + /** * Sets the aliases. * diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java index 77d5efa65..a0790e2dd 100644 --- a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java +++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java @@ -33,7 +33,7 @@ import java.util.Map; /** * Called when commands are defined within Geyser. - * + *

* This event allows you to register new commands using the {@link #register(Command)} * method and retrieve the default commands defined. */ @@ -50,7 +50,7 @@ public interface GeyserDefineCommandsEvent extends Event { /** * Gets all the registered built-in {@link Command}s. * - * @return all the registered built-in commands + * @return all the registered built-in commands as an unmodifiable map. */ @NonNull Map commands(); diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java index 059ca50b7..7a18d6302 100644 --- a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java +++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java @@ -37,7 +37,7 @@ public interface GeyserRegisterPermissionsEvent extends Event { * Registers a permission node with the permission system being used by Geyser's command manager. * * @param permission the permission node to register - * @param def the default value of the node + * @param defaultValue the default value of the node */ - void register(String permission, TriState def); + void register(String permission, TriState defaultValue); } 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 4c8621218..2ed92cbe3 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 @@ -41,14 +41,14 @@ import org.bukkit.event.Listener; import org.bukkit.event.server.ServerLoadEvent; import org.bukkit.permissions.Permission; import org.bukkit.permissions.PermissionDefault; +import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; +import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent; import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.adapters.spigot.SpigotAdapters; -import org.geysermc.geyser.api.command.Command; -import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.command.CommandSourceConverter; import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.command.GeyserCommandSource; @@ -187,7 +187,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { } } - this.commandRegistry = new CommandRegistry(geyser, cloud); + this.commandRegistry = new CommandRegistry(geyser, cloud); // todo: reimplement subclass for command descriptions if (!INITIALIZED) { // Needs to be an anonymous inner class otherwise Bukkit complains about missing classes @@ -210,6 +210,8 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { private void postStartup() { GeyserImpl.start(); + PluginManager pluginManager = Bukkit.getPluginManager(); + // Turn "(MC: 1.16.4)" into 1.16.4. this.minecraftVersion = Bukkit.getServer().getVersion().split("\\(MC: ")[1].split("\\)")[0]; @@ -276,54 +278,37 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { if (!INITIALIZED) { // Register permissions so they appear in, for example, LuckPerms' UI - // Re-registering permissions throws an error - for (Map.Entry entry : commandRegistry.commands().entrySet()) { - Command command = entry.getValue(); - if (command.aliases().contains(entry.getKey())) { - // Don't register aliases - continue; + // Re-registering permissions without removing it throws an error + + // todo: this can probably always be run regardless if geyser has been initialized once or not, since we are removing the permission + geyser.eventBus().fire((GeyserRegisterPermissionsEvent) (permission, def) -> { + PermissionDefault permissionDefault = switch (def) { + case TRUE -> PermissionDefault.TRUE; + case FALSE -> PermissionDefault.FALSE; + case NOT_SET -> PermissionDefault.OP; + }; + + Permission existingPermission = pluginManager.getPermission(permission); + if (existingPermission != null) { + geyserLogger.debug("permission " + permission + " with a default of " + + existingPermission.getDefault() + " is being overriden by " + permissionDefault); + + pluginManager.removePermission(permission); } - Bukkit.getPluginManager().addPermission(new Permission(command.permission(), - GeyserLocale.getLocaleStringLog(command.description()), - command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE)); - } + pluginManager.addPermission(new Permission(permission, permissionDefault)); + }); - // Register permissions for extension commands - for (Map.Entry> commandEntry : this.commandRegistry.extensionCommands().entrySet()) { - for (Map.Entry entry : commandEntry.getValue().entrySet()) { - Command command = entry.getValue(); - if (command.aliases().contains(entry.getKey())) { - // Don't register aliases - continue; - } - - if (command.permission().isBlank()) { - continue; - } - - // Avoid registering the same permission twice, e.g. for the extension help commands - if (Bukkit.getPluginManager().getPermission(command.permission()) != null) { - GeyserImpl.getInstance().getLogger().debug("Skipping permission " + command.permission() + " as it is already registered"); - continue; - } - - Bukkit.getPluginManager().addPermission(new Permission(command.permission(), - GeyserLocale.getLocaleStringLog(command.description()), - command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE)); - } - } - - Bukkit.getPluginManager().addPermission(new Permission(Constants.UPDATE_PERMISSION, + pluginManager.addPermission(new Permission(Constants.UPDATE_PERMISSION, "Whether update notifications can be seen", PermissionDefault.OP)); // Events cannot be unregistered - re-registering results in duplicate firings GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(geyser, this.geyserWorldManager); - Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this); + pluginManager.registerEvents(blockPlaceListener, this); - Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this); + pluginManager.registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this); - Bukkit.getServer().getPluginManager().registerEvents(new GeyserSpigotUpdateListener(), this); + pluginManager.registerEvents(new GeyserSpigotUpdateListener(), this); } // Check to ensure the current setup can support the protocol version Geyser uses diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java index 6af18d166..445608ace 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java @@ -81,7 +81,7 @@ public class GeyserStandaloneCommandManager extends CommandManager cloud; private final Map commands = new Object2ObjectOpenHashMap<>(13); private final Map> extensionCommands = new Object2ObjectOpenHashMap<>(0); - private final GeyserImpl geyser; - private final CommandManager cloud; + private final Map permissions = new Object2ObjectOpenHashMap<>(0); public CommandRegistry(GeyserImpl geyser, CommandManager cloud) { this.geyser = geyser; @@ -114,6 +119,30 @@ public final class CommandRegistry { String id = entry.getKey().description().id(); registerExtensionCommand(entry.getKey(), new HelpCommand(this.geyser, "help", "geyser.commands.exthelp.desc", "geyser.command.exthelp." + id, id, entry.getValue())); } + + // wait for the right moment to register permissions + geyser.eventBus().subscribe(new GeyserEventRegistrar(this), GeyserRegisterPermissionsEvent.class, this::onRegisterPermissions); + } + + @NotNull + public CommandManager cloud() { + return cloud; + } + + @NotNull + public Map commands() { + return Collections.unmodifiableMap(this.commands); + } + + @NotNull + public Map> extensionCommands() { + return Collections.unmodifiableMap(this.extensionCommands); + } + + public void clear() { + this.commands.clear(); + this.extensionCommands.clear(); + this.permissions.clear(); } /** @@ -140,26 +169,16 @@ public final class CommandRegistry { for (String alias : command.aliases()) { commands.put(alias, command); } + + if (!command.permission().isBlank()) { + permissions.put(command.permission(), command.permissionDefault()); + } } - public void clear() { - this.commands.clear(); - this.extensionCommands.clear(); - } - - @NotNull - public Map commands() { - return Collections.unmodifiableMap(this.commands); - } - - @NotNull - public Map> extensionCommands() { - return Collections.unmodifiableMap(this.extensionCommands); - } - - @NotNull - public CommandManager cloud() { - return cloud; + private void onRegisterPermissions(GeyserRegisterPermissionsEvent event) { + for (Map.Entry permission : permissions.entrySet()) { + event.register(permission.getKey(), permission.getValue()); + } } /** diff --git a/core/src/main/java/org/geysermc/geyser/command/ExtensionCommandBuilder.java b/core/src/main/java/org/geysermc/geyser/command/ExtensionCommandBuilder.java index 58a40adf5..6333a8c9e 100644 --- a/core/src/main/java/org/geysermc/geyser/command/ExtensionCommandBuilder.java +++ b/core/src/main/java/org/geysermc/geyser/command/ExtensionCommandBuilder.java @@ -34,6 +34,7 @@ import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.api.command.CommandExecutor; import org.geysermc.geyser.api.command.CommandSource; import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.extension.command.GeyserExtensionCommand; import org.geysermc.geyser.session.GeyserSession; @@ -48,6 +49,7 @@ public class ExtensionCommandBuilder implements Command private String name; private String description; private String permission; + private TriState permissionDefault = TriState.NOT_SET; private List aliases; private boolean suggestedOpOnly = false; // deprecated for removal private boolean executableOnConsole = true; @@ -82,6 +84,13 @@ public class ExtensionCommandBuilder implements Command return this; } + @Override + public Command.Builder permission(@Nullable String permission, @NonNull TriState defaultValue) { + this.permission = permission; + this.permissionDefault = Objects.requireNonNull(defaultValue, "defaultValue"); + return this; + } + @Override public ExtensionCommandBuilder aliases(@Nullable List aliases) { this.aliases = aliases; @@ -126,7 +135,7 @@ public class ExtensionCommandBuilder implements Command throw new IllegalArgumentException("Command executor was not defined for command " + name + " in extension " + extension.name()); } - GeyserExtensionCommand command = new GeyserExtensionCommand(extension, name, description, permission, executableOnConsole, bedrockOnly) { + GeyserExtensionCommand command = new GeyserExtensionCommand(extension, name, description, permission, permissionDefault, executableOnConsole, bedrockOnly) { @SuppressWarnings("unchecked") @Override @@ -149,7 +158,7 @@ public class ExtensionCommandBuilder implements Command } // todo: send sender message instead - GeyserImpl.getInstance().getLogger().debug("Ignoring command " + this.name + " due to no suitable sender."); + GeyserImpl.getInstance().getLogger().debug("Ignoring command " + name + " due to no suitable sender."); }); } diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java index 562dd882c..1e922d653 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java @@ -30,10 +30,12 @@ import cloud.commandframework.CommandManager; import cloud.commandframework.meta.CommandMeta; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.geyser.api.util.TriState; import org.jetbrains.annotations.Contract; import java.util.Collections; import java.util.List; +import java.util.Objects; public abstract class GeyserCommand implements org.geysermc.geyser.api.command.Command { @@ -47,24 +49,49 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C */ public static final CommandMeta.Key PLAYER_ONLY = CommandMeta.Key.of(Boolean.class, "player-only", meta -> false); + /** + * The second literal of the command. Note: the first literal is {@link GeyserCommand#rootCommand()}. + */ @NonNull - protected final String name; + private final String name; /** * The description of the command - will attempt to be translated. */ @NonNull - protected final String description; + private final String description; + /** + * The permission node required to run the command, or blank if not required. + */ @NonNull - protected final String permission; + private final String permission; - protected final boolean executableOnConsole; - protected final boolean bedrockOnly; + /** + * The default value of the permission node + */ + @NonNull + private final TriState permissionDefault; + /** + * True if the command can be executed by non players + */ + private final boolean executableOnConsole; + + /** + * True if the command can only be run by bedrock players + */ + private final boolean bedrockOnly; + + /** + * The aliases of the command {@link GeyserCommand#name} + */ protected List aliases = Collections.emptyList(); - public GeyserCommand(@NonNull String name, @Nullable String description, @Nullable String permission, boolean executableOnConsole, boolean bedrockOnly) { + public GeyserCommand(@NonNull String name, @Nullable String description, + @Nullable String permission, @NonNull TriState permissionDefault, + boolean executableOnConsole, boolean bedrockOnly) { + if (name == null || name.isBlank()) { throw new IllegalArgumentException("Command cannot be null or blank!"); } @@ -72,6 +99,7 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C this.name = name; this.description = description != null ? description : ""; this.permission = permission != null ? permission : ""; + this.permissionDefault = Objects.requireNonNull(permissionDefault, "permissionDefault"); if (bedrockOnly && executableOnConsole) { throw new IllegalArgumentException("Command cannot be both bedrockOnly and executableOnConsole"); @@ -82,7 +110,7 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C } public GeyserCommand(@NonNull String name, @NonNull String description, @Nullable String permission) { - this(name, description, permission, true, false); + this(name, description, permission, TriState.NOT_SET, true, false); } @NonNull @@ -103,6 +131,11 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C return permission; } + @NonNull + public final TriState permissionDefault() { + return permissionDefault; + } + @Override public final boolean isExecutableOnConsole() { return executableOnConsole; diff --git a/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineCommandsEventImpl.java b/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineCommandsEventImpl.java index e07a62d8a..bf85ed3be 100644 --- a/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineCommandsEventImpl.java +++ b/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineCommandsEventImpl.java @@ -36,11 +36,11 @@ public abstract class GeyserDefineCommandsEventImpl implements GeyserDefineComma private final Map commands; public GeyserDefineCommandsEventImpl(Map commands) { - this.commands = commands; + this.commands = Collections.unmodifiableMap(commands); } @Override public @NonNull Map commands() { - return Collections.unmodifiableMap(this.commands); + return this.commands; } } diff --git a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java index d29d2b522..e4bc8191a 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java @@ -28,6 +28,7 @@ package org.geysermc.geyser.extension.command; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; public abstract class GeyserExtensionCommand extends GeyserCommand { @@ -35,8 +36,11 @@ public abstract class GeyserExtensionCommand extends GeyserCommand { private final Extension extension; private final String rootCommand; - public GeyserExtensionCommand(@NonNull Extension extension, @NonNull String name, @Nullable String description, @Nullable String permission, boolean executableOnConsole, boolean bedrockOnly) { - super(name, description, permission, executableOnConsole, bedrockOnly); + public GeyserExtensionCommand(@NonNull Extension extension, @NonNull String name, @Nullable String description, + @Nullable String permission, @NonNull TriState permissionDefault, + boolean executableOnConsole, boolean bedrockOnly) { + + super(name, description, permission, permissionDefault, executableOnConsole, bedrockOnly); this.extension = extension; this.rootCommand = extension.rootCommand(); From 94d3f6229c6af42157af83a90da1a70b30f57764 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Fri, 1 Sep 2023 21:51:46 -0400 Subject: [PATCH 17/76] Define default permissions of builtin commands --- .../main/java/org/geysermc/geyser/command/GeyserCommand.java | 4 ++-- .../geyser/command/defaults/AdvancedTooltipsCommand.java | 3 ++- .../geysermc/geyser/command/defaults/AdvancementsCommand.java | 3 ++- .../geyser/command/defaults/ConnectionTestCommand.java | 3 ++- .../org/geysermc/geyser/command/defaults/DumpCommand.java | 3 ++- .../geysermc/geyser/command/defaults/ExtensionsCommand.java | 3 ++- .../org/geysermc/geyser/command/defaults/HelpCommand.java | 3 ++- .../org/geysermc/geyser/command/defaults/ListCommand.java | 3 ++- .../org/geysermc/geyser/command/defaults/OffhandCommand.java | 3 ++- .../org/geysermc/geyser/command/defaults/ReloadCommand.java | 3 ++- .../org/geysermc/geyser/command/defaults/SettingsCommand.java | 3 ++- .../geysermc/geyser/command/defaults/StatisticsCommand.java | 3 ++- .../org/geysermc/geyser/command/defaults/StopCommand.java | 3 ++- .../org/geysermc/geyser/command/defaults/VersionCommand.java | 3 ++- 14 files changed, 28 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java index 1e922d653..54372e256 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java @@ -109,8 +109,8 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C this.bedrockOnly = bedrockOnly; } - public GeyserCommand(@NonNull String name, @NonNull String description, @Nullable String permission) { - this(name, description, permission, TriState.NOT_SET, true, false); + public GeyserCommand(@NonNull String name, @NonNull String description, @Nullable String permission, @NonNull TriState permissionDefault) { + this(name, description, permission, permissionDefault, true, false); } @NonNull diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java index 9506d3ab6..d9875adb8 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java @@ -27,6 +27,7 @@ package org.geysermc.geyser.command.defaults; import cloud.commandframework.Command; import cloud.commandframework.CommandManager; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.text.ChatColor; @@ -35,7 +36,7 @@ import org.geysermc.geyser.text.MinecraftLocale; public class AdvancedTooltipsCommand extends GeyserCommand { public AdvancedTooltipsCommand(String name, String description, String permission) { - super(name, description, permission, false, true); + super(name, description, permission, TriState.TRUE, false, true); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java index 5f7711df9..e15a3fef0 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java @@ -27,13 +27,14 @@ package org.geysermc.geyser.command.defaults; import cloud.commandframework.Command; import cloud.commandframework.CommandManager; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; public class AdvancementsCommand extends GeyserCommand { public AdvancementsCommand(String name, String description, String permission) { - super(name, description, permission, false, true); + super(name, description, permission, TriState.TRUE, false, true); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java index 85ee3b53b..483d11b2b 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java @@ -32,6 +32,7 @@ import cloud.commandframework.arguments.standard.StringArgument; import cloud.commandframework.context.CommandContext; import com.fasterxml.jackson.databind.JsonNode; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.util.LoopbackUtil; @@ -47,7 +48,7 @@ public class ConnectionTestCommand extends GeyserCommand { private final GeyserImpl geyser; public ConnectionTestCommand(GeyserImpl geyser, String name, String description, String permission) { - super(name, description, permission); + super(name, description, permission, TriState.NOT_SET); this.geyser = geyser; } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java index d48c1361e..5898e5c19 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java @@ -34,6 +34,7 @@ import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.dump.DumpInfo; @@ -56,7 +57,7 @@ public class DumpCommand extends GeyserCommand { private static final String DUMP_URL = "https://dump.geysermc.org/"; public DumpCommand(GeyserImpl geyser, String name, String description, String permission) { - super(name, description, permission); + super(name, description, permission, TriState.NOT_SET); this.geyser = geyser; } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ExtensionsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ExtensionsCommand.java index 8a482ba66..923d9f0b9 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ExtensionsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ExtensionsCommand.java @@ -30,6 +30,7 @@ import cloud.commandframework.CommandManager; import cloud.commandframework.context.CommandContext; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.text.ChatColor; @@ -42,7 +43,7 @@ public class ExtensionsCommand extends GeyserCommand { private final GeyserImpl geyser; public ExtensionsCommand(GeyserImpl geyser, String name, String description, String permission) { - super(name, description, permission); + super(name, description, permission, TriState.TRUE); this.geyser = geyser; } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java index 0c54880b0..d43384177 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java @@ -29,6 +29,7 @@ import cloud.commandframework.CommandManager; import cloud.commandframework.context.CommandContext; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.command.Command; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.text.ChatColor; @@ -45,7 +46,7 @@ public class HelpCommand extends GeyserCommand { public HelpCommand(GeyserImpl geyser, String name, String description, String permission, String baseCommand, Map commands) { - super(name, description, permission); + super(name, description, permission, TriState.TRUE); this.baseCommand = baseCommand; this.commands = commands.values(); diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java index 04ec04ce7..6f5a5ac4e 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java @@ -28,6 +28,7 @@ package org.geysermc.geyser.command.defaults; import cloud.commandframework.Command; import cloud.commandframework.CommandManager; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; @@ -40,7 +41,7 @@ public class ListCommand extends GeyserCommand { private final GeyserImpl geyser; public ListCommand(GeyserImpl geyser, String name, String description, String permission) { - super(name, description, permission); + super(name, description, permission, TriState.NOT_SET); this.geyser = geyser; } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java index c0b2a27ba..a57982725 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java @@ -28,6 +28,7 @@ package org.geysermc.geyser.command.defaults; import cloud.commandframework.Command; import cloud.commandframework.CommandManager; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; @@ -35,7 +36,7 @@ import org.geysermc.geyser.session.GeyserSession; public class OffhandCommand extends GeyserCommand { public OffhandCommand(GeyserImpl geyser, String name, String description, String permission) { - super(name, description, permission, false, true); + super(name, description, permission, TriState.TRUE, false, true); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java index d889955ea..5331b9b1f 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java @@ -28,6 +28,7 @@ package org.geysermc.geyser.command.defaults; import cloud.commandframework.Command; import cloud.commandframework.CommandManager; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.text.GeyserLocale; @@ -37,7 +38,7 @@ public class ReloadCommand extends GeyserCommand { private final GeyserImpl geyser; public ReloadCommand(GeyserImpl geyser, String name, String description, String permission) { - super(name, description, permission); + super(name, description, permission, TriState.NOT_SET); this.geyser = geyser; } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java index 42d212805..7bfd7f523 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java @@ -28,6 +28,7 @@ package org.geysermc.geyser.command.defaults; import cloud.commandframework.Command; import cloud.commandframework.CommandManager; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.util.SettingsUtils; @@ -35,7 +36,7 @@ import org.geysermc.geyser.util.SettingsUtils; public class SettingsCommand extends GeyserCommand { public SettingsCommand(GeyserImpl geyser, String name, String description, String permission) { - super(name, description, permission, false, true); + super(name, description, permission, TriState.TRUE, false, true); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java index ea9537755..da7eb7209 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java @@ -30,13 +30,14 @@ import cloud.commandframework.CommandManager; import com.github.steveice10.mc.protocol.data.game.ClientCommand; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundClientCommandPacket; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; public class StatisticsCommand extends GeyserCommand { public StatisticsCommand(GeyserImpl geyser, String name, String description, String permission) { - super(name, description, permission, false, true); + super(name, description, permission, TriState.TRUE, false, true); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java index daebdc9dc..0dd79e306 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java @@ -28,6 +28,7 @@ package org.geysermc.geyser.command.defaults; import cloud.commandframework.Command; import cloud.commandframework.CommandManager; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; @@ -38,7 +39,7 @@ public class StopCommand extends GeyserCommand { private final GeyserImpl geyser; public StopCommand(GeyserImpl geyser, String name, String description, String permission) { - super(name, description, permission); + super(name, description, permission, TriState.NOT_SET); this.geyser = geyser; this.aliases = Collections.singletonList("shutdown"); diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java index ac8401d53..2491040e6 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java @@ -32,6 +32,7 @@ import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec; import org.geysermc.geyser.Constants; import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.network.GameProtocol; @@ -49,7 +50,7 @@ public class VersionCommand extends GeyserCommand { private final GeyserImpl geyser; public VersionCommand(GeyserImpl geyser, String name, String description, String permission) { - super(name, description, permission); + super(name, description, permission, TriState.NOT_SET); this.geyser = geyser; } From 4b893e6475807abad92a8a7b23f9a4a362ee7b79 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Fri, 1 Sep 2023 23:17:56 -0400 Subject: [PATCH 18/76] Undo some changes, some legacy compat for suggestedOpOnly --- .../java/org/geysermc/geyser/api/command/Command.java | 8 ++------ .../geysermc/geyser/command/ExtensionCommandBuilder.java | 4 ++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/command/Command.java b/api/src/main/java/org/geysermc/geyser/api/command/Command.java index 18af89c93..ed7fa6540 100644 --- a/api/src/main/java/org/geysermc/geyser/api/command/Command.java +++ b/api/src/main/java/org/geysermc/geyser/api/command/Command.java @@ -89,18 +89,14 @@ public interface Command { * * @return if this command is executable on console */ - default boolean isExecutableOnConsole() { - return true; - } + boolean isExecutableOnConsole(); /** * Used to send a deny-message to Java players if this command can only be used by Bedrock players. * * @return true if this command can only be used by Bedrock players. */ - default boolean isBedrockOnly() { - return false; - } + boolean isBedrockOnly(); /** * Gets the subcommands associated with this diff --git a/core/src/main/java/org/geysermc/geyser/command/ExtensionCommandBuilder.java b/core/src/main/java/org/geysermc/geyser/command/ExtensionCommandBuilder.java index 6333a8c9e..4f62ea45d 100644 --- a/core/src/main/java/org/geysermc/geyser/command/ExtensionCommandBuilder.java +++ b/core/src/main/java/org/geysermc/geyser/command/ExtensionCommandBuilder.java @@ -100,6 +100,10 @@ public class ExtensionCommandBuilder implements Command @Override public Command.Builder suggestedOpOnly(boolean suggestedOpOnly) { this.suggestedOpOnly = suggestedOpOnly; + if (suggestedOpOnly) { + // the most amount of legacy/deprecated behaviour I'm willing to support + this.permissionDefault = TriState.NOT_SET; + } return this; } From 2ab03e7415a5411107b7065146fc8b0fe8050a3a Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Sat, 2 Sep 2023 03:04:29 -0400 Subject: [PATCH 19/76] Allow commands without aliases to have their permissions registered --- .../java/org/geysermc/geyser/command/CommandRegistry.java | 4 ---- .../main/java/org/geysermc/geyser/command/GeyserCommand.java | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java index 5c79829b5..37f165e7d 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java @@ -162,10 +162,6 @@ public class CommandRegistry { commands.put(command.name(), command); geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.commands.registered", command.name())); - if (command.aliases().isEmpty()) { - return; - } - for (String alias : command.aliases()) { commands.put(alias, command); } diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java index 54372e256..60be00579 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java @@ -50,7 +50,7 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C public static final CommandMeta.Key PLAYER_ONLY = CommandMeta.Key.of(Boolean.class, "player-only", meta -> false); /** - * The second literal of the command. Note: the first literal is {@link GeyserCommand#rootCommand()}. + * The second literal of the command. Note: the first literal is {@link #rootCommand()}. */ @NonNull private final String name; @@ -84,7 +84,7 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C private final boolean bedrockOnly; /** - * The aliases of the command {@link GeyserCommand#name} + * The aliases of the command {@link #name} */ protected List aliases = Collections.emptyList(); From 887509bde76f06aa9ad2f439d321f8509adb6547 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Sat, 2 Sep 2023 03:16:54 -0400 Subject: [PATCH 20/76] Don't force perm registration of perms used by commands feeling kinda icky but idk --- .../java/org/geysermc/geyser/api/command/Command.java | 2 +- .../org/geysermc/geyser/command/CommandRegistry.java | 2 +- .../geyser/command/ExtensionCommandBuilder.java | 2 +- .../org/geysermc/geyser/command/GeyserCommand.java | 11 ++++++----- .../extension/command/GeyserExtensionCommand.java | 2 +- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/command/Command.java b/api/src/main/java/org/geysermc/geyser/api/command/Command.java index ed7fa6540..957002b7d 100644 --- a/api/src/main/java/org/geysermc/geyser/api/command/Command.java +++ b/api/src/main/java/org/geysermc/geyser/api/command/Command.java @@ -170,7 +170,7 @@ public interface Command { * @param defaultValue the node's default value * @return the builder */ - Builder permission(@Nullable String permission, TriState defaultValue); + Builder permission(@Nullable String permission, @NonNull TriState defaultValue); /** * Sets the aliases. diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java index 37f165e7d..393cef60c 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java @@ -166,7 +166,7 @@ public class CommandRegistry { commands.put(alias, command); } - if (!command.permission().isBlank()) { + if (!command.permission().isBlank() && command.permissionDefault() != null) { permissions.put(command.permission(), command.permissionDefault()); } } diff --git a/core/src/main/java/org/geysermc/geyser/command/ExtensionCommandBuilder.java b/core/src/main/java/org/geysermc/geyser/command/ExtensionCommandBuilder.java index 4f62ea45d..5179997ba 100644 --- a/core/src/main/java/org/geysermc/geyser/command/ExtensionCommandBuilder.java +++ b/core/src/main/java/org/geysermc/geyser/command/ExtensionCommandBuilder.java @@ -49,7 +49,7 @@ public class ExtensionCommandBuilder implements Command private String name; private String description; private String permission; - private TriState permissionDefault = TriState.NOT_SET; + private TriState permissionDefault; private List aliases; private boolean suggestedOpOnly = false; // deprecated for removal private boolean executableOnConsole = true; diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java index 60be00579..2c92ec06d 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java @@ -68,9 +68,10 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C private final String permission; /** - * The default value of the permission node + * The default value of the permission node. + * A null value indicates that the permission node should not be registered whatsoever. */ - @NonNull + @Nullable private final TriState permissionDefault; /** @@ -89,7 +90,7 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C protected List aliases = Collections.emptyList(); public GeyserCommand(@NonNull String name, @Nullable String description, - @Nullable String permission, @NonNull TriState permissionDefault, + @Nullable String permission, @Nullable TriState permissionDefault, boolean executableOnConsole, boolean bedrockOnly) { if (name == null || name.isBlank()) { @@ -99,7 +100,7 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C this.name = name; this.description = description != null ? description : ""; this.permission = permission != null ? permission : ""; - this.permissionDefault = Objects.requireNonNull(permissionDefault, "permissionDefault"); + this.permissionDefault = permissionDefault; if (bedrockOnly && executableOnConsole) { throw new IllegalArgumentException("Command cannot be both bedrockOnly and executableOnConsole"); @@ -131,7 +132,7 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C return permission; } - @NonNull + @Nullable public final TriState permissionDefault() { return permissionDefault; } diff --git a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java index e4bc8191a..bb51e5058 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java @@ -37,7 +37,7 @@ public abstract class GeyserExtensionCommand extends GeyserCommand { private final String rootCommand; public GeyserExtensionCommand(@NonNull Extension extension, @NonNull String name, @Nullable String description, - @Nullable String permission, @NonNull TriState permissionDefault, + @Nullable String permission, @Nullable TriState permissionDefault, boolean executableOnConsole, boolean bedrockOnly) { super(name, description, permission, permissionDefault, executableOnConsole, bedrockOnly); From 1eba87d4e2ec0d1c46b2ef8d4b2323275efa45fc Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Sat, 2 Sep 2023 23:06:22 -0400 Subject: [PATCH 21/76] fix commands on fabric - create cloud earlier - trigger resource reload after geyser registers commands to cloud so that cloud registers them to the server --- .../platform/fabric/GeyserFabricMod.java | 66 ++++++++++++------- 1 file changed, 43 insertions(+), 23 deletions(-) diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java index 6f3198c06..d3b7a216d 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java @@ -28,6 +28,7 @@ package org.geysermc.geyser.platform.fabric; import cloud.commandframework.CommandManager; import cloud.commandframework.execution.CommandExecutionCoordinator; import cloud.commandframework.fabric.FabricServerCommandManager; +import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.fabricmc.api.EnvType; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; @@ -65,7 +66,7 @@ import java.util.Optional; import java.util.UUID; public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { - private static GeyserFabricMod instance; + private static GeyserFabricMod INSTANCE; private boolean reloading; @@ -74,6 +75,7 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { private Path dataFolder; private MinecraftServer server; + private CommandManager cloud; private CommandRegistry commandRegistry; private GeyserFabricConfiguration geyserConfig; private GeyserFabricLogger geyserLogger; @@ -82,7 +84,7 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { @Override public void onInitialize() { - instance = this; + INSTANCE = this; mod = FabricLoader.getInstance().getModContainer("geyser-fabric").orElseThrow(); this.onEnable(); @@ -119,17 +121,33 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { this.geyser = GeyserImpl.load(PlatformType.FABRIC, this); - if (server == null) { - // Server has yet to start - // Register onDisable so players are properly kicked - ServerLifecycleEvents.SERVER_STOPPING.register((server) -> onDisable()); - - ServerPlayConnectionEvents.JOIN.register((handler, $, $$) -> GeyserFabricUpdateListener.onPlayReady(handler)); - } else { + if (server != null) { // Server has started and this is a reload startGeyser(this.server); reloading = false; + return; } + + // Server has yet to start + // Register onDisable so players are properly kicked + ServerLifecycleEvents.SERVER_STOPPING.register((server) -> onDisable()); + + ServerPlayConnectionEvents.JOIN.register((handler, $, $$) -> GeyserFabricUpdateListener.onPlayReady(handler)); + + // Cloud requires the command manager itself to be created during mod init + var sourceConverter = CommandSourceConverter.layered( + CommandSourceStack.class, + id -> server.getPlayerList().getPlayer(id), + Player::createCommandSourceStack, + () -> server.createCommandSourceStack() // note: method reference here will cause NPE + ); + cloud = new FabricServerCommandManager<>( + CommandExecutionCoordinator.simpleCoordinator(), + FabricCommandSource::new, + sourceConverter::convert + ); + // Geyser registers commands after the server has started + cloud.setSetting(CommandManager.ManagerSettings.ALLOW_UNSAFE_REGISTRATION, true); } /** @@ -144,21 +162,23 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { GeyserImpl.start(); this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); - - var sourceConverter = CommandSourceConverter.layered( - CommandSourceStack.class, - id -> server.getPlayerList().getPlayer(id), - Player::createCommandSourceStack, - server::createCommandSourceStack - ); - CommandManager cloud = new FabricServerCommandManager<>( - CommandExecutionCoordinator.simpleCoordinator(), - FabricCommandSource::new, - sourceConverter::convert - ); + this.geyserWorldManager = new GeyserFabricWorldManager(server); this.commandRegistry = new CommandRegistry(geyser, cloud); - this.geyserWorldManager = new GeyserFabricWorldManager(server); + // the minecraft command dispatcher can be rebuilt at any time, at which point CommandRegistrationCallback.EVENT is fired. + // the dispatcher is initially created after mod init but before the server starts. + // that event is not required to register commands, but cloud uses it so that commands are properly + // registered anytime the dispatcher is rebuilt. + + // we just registered commands to cloud, after the server started, meaning we missed the initial registration event. + // this means cloud didn't register the commands to the server + // hack: we now trigger a resource reload so that the dispatcher is rebuilt, the event is fired, and cloud registers the commands. + geyserLogger.info("Reloading resources so that Geyser commands can be successfully registered"); + try { + server.getCommands().getDispatcher().execute("reload", server.createCommandSourceStack()); + } catch (CommandSyntaxException e) { + geyserLogger.error("Failed to reload in order to register commands", e); + } } @Override @@ -258,6 +278,6 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { } public static GeyserFabricMod getInstance() { - return instance; + return INSTANCE; } } From c7826b5e731a387dc198053321039b3cbdd9b5d0 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Sun, 3 Sep 2023 23:16:16 -0400 Subject: [PATCH 22/76] Move command registry creation to mod init on fabric --- .../platform/fabric/GeyserFabricMod.java | 35 ++----------------- 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java index d3b7a216d..d50f45bd8 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java @@ -68,14 +68,11 @@ import java.util.UUID; public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { private static GeyserFabricMod INSTANCE; - private boolean reloading; - private GeyserImpl geyser; private ModContainer mod; private Path dataFolder; private MinecraftServer server; - private CommandManager cloud; private CommandRegistry commandRegistry; private GeyserFabricConfiguration geyserConfig; private GeyserFabricLogger geyserLogger; @@ -123,8 +120,7 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { if (server != null) { // Server has started and this is a reload - startGeyser(this.server); - reloading = false; + startGeyser(server); return; } @@ -134,20 +130,18 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { ServerPlayConnectionEvents.JOIN.register((handler, $, $$) -> GeyserFabricUpdateListener.onPlayReady(handler)); - // Cloud requires the command manager itself to be created during mod init var sourceConverter = CommandSourceConverter.layered( CommandSourceStack.class, id -> server.getPlayerList().getPlayer(id), Player::createCommandSourceStack, () -> server.createCommandSourceStack() // note: method reference here will cause NPE ); - cloud = new FabricServerCommandManager<>( + CommandManager cloud = new FabricServerCommandManager<>( CommandExecutionCoordinator.simpleCoordinator(), FabricCommandSource::new, sourceConverter::convert ); - // Geyser registers commands after the server has started - cloud.setSetting(CommandManager.ManagerSettings.ALLOW_UNSAFE_REGISTRATION, true); + commandRegistry = new CommandRegistry(geyser, cloud); } /** @@ -163,22 +157,6 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); this.geyserWorldManager = new GeyserFabricWorldManager(server); - this.commandRegistry = new CommandRegistry(geyser, cloud); - - // the minecraft command dispatcher can be rebuilt at any time, at which point CommandRegistrationCallback.EVENT is fired. - // the dispatcher is initially created after mod init but before the server starts. - // that event is not required to register commands, but cloud uses it so that commands are properly - // registered anytime the dispatcher is rebuilt. - - // we just registered commands to cloud, after the server started, meaning we missed the initial registration event. - // this means cloud didn't register the commands to the server - // hack: we now trigger a resource reload so that the dispatcher is rebuilt, the event is fired, and cloud registers the commands. - geyserLogger.info("Reloading resources so that Geyser commands can be successfully registered"); - try { - server.getCommands().getDispatcher().execute("reload", server.createCommandSourceStack()); - } catch (CommandSyntaxException e) { - geyserLogger.error("Failed to reload in order to register commands", e); - } } @Override @@ -187,9 +165,6 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { geyser.shutdown(); geyser = null; } - if (!reloading) { - this.server = null; - } } @Override @@ -273,10 +248,6 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { } } - public void setReloading(boolean reloading) { - this.reloading = reloading; // todo: commands - } - public static GeyserFabricMod getInstance() { return INSTANCE; } From 1c45bb4420c188b1084df042e8ee4ed91260f0b3 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Sun, 3 Sep 2023 23:23:01 -0400 Subject: [PATCH 23/76] Temporary warning if command source converter receives atypical argument --- .../geysermc/geyser/command/CommandSourceConverter.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java b/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java index b92e6478b..9d87caf9c 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java @@ -25,6 +25,9 @@ package org.geysermc.geyser.command; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.session.GeyserSession; + import java.util.UUID; import java.util.function.Function; import java.util.function.Supplier; @@ -41,6 +44,7 @@ public record CommandSourceConverter(Class senderType, Function playerLookup, Supplier consoleProvider) { + @SuppressWarnings("unchecked") public S convert(GeyserCommandSource source) throws IllegalArgumentException { Object handle = source.handle(); if (senderType.isInstance(handle)) { @@ -51,6 +55,11 @@ public record CommandSourceConverter(Class senderType, return consoleProvider.get(); // one of the loggers } + if (!(source instanceof GeyserSession)) { + GeyserImpl.getInstance().getLogger().warning("Falling back to UUID for command sender lookup for a command source that is not a GeyserSession: " + source); + Thread.dumpStack(); + } + // Handles GeyserSession return source.playerUuid() .map(playerLookup) From 26f53d22546adb0cc6b90fd3bc07b095de429d5a Mon Sep 17 00:00:00 2001 From: Konicai <71294714+konicai@users.noreply.github.com> Date: Mon, 4 Sep 2023 15:08:13 -0400 Subject: [PATCH 24/76] Make extension command builder an inner class of ExtensionCommand --- .../geyser/command/CommandRegistry.java | 1 + .../command/ExtensionCommandBuilder.java | 178 ------------------ .../command/GeyserExtensionCommand.java | 153 ++++++++++++++- .../loader/ProviderRegistryLoader.java | 4 +- 4 files changed, 153 insertions(+), 183 deletions(-) delete mode 100644 core/src/main/java/org/geysermc/geyser/command/ExtensionCommandBuilder.java diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java index 393cef60c..81174c4b3 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java @@ -184,6 +184,7 @@ public class CommandRegistry { * @return Command description */ public String description(String command) { + // todo: the commands contained in this registry store their descriptions, so those should be checked return ""; // todo: reimplement } diff --git a/core/src/main/java/org/geysermc/geyser/command/ExtensionCommandBuilder.java b/core/src/main/java/org/geysermc/geyser/command/ExtensionCommandBuilder.java deleted file mode 100644 index 5179997ba..000000000 --- a/core/src/main/java/org/geysermc/geyser/command/ExtensionCommandBuilder.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (c) 2019-2023 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.command; - -import cloud.commandframework.CommandManager; -import cloud.commandframework.arguments.standard.StringArgument; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.command.Command; -import org.geysermc.geyser.api.command.CommandExecutor; -import org.geysermc.geyser.api.command.CommandSource; -import org.geysermc.geyser.api.extension.Extension; -import org.geysermc.geyser.api.util.TriState; -import org.geysermc.geyser.extension.command.GeyserExtensionCommand; -import org.geysermc.geyser.session.GeyserSession; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; - -public class ExtensionCommandBuilder implements Command.Builder { - private final Extension extension; - private Class sourceType; - private String name; - private String description; - private String permission; - private TriState permissionDefault; - private List aliases; - private boolean suggestedOpOnly = false; // deprecated for removal - private boolean executableOnConsole = true; - private boolean bedrockOnly = false; - private CommandExecutor executor; - - public ExtensionCommandBuilder(Extension extension) { - this.extension = Objects.requireNonNull(extension); - } - - @Override - public Command.Builder source(@NonNull Class sourceType) { - this.sourceType = sourceType; - return this; - } - - @Override - public ExtensionCommandBuilder name(@NonNull String name) { - this.name = name; - return this; - } - - @Override - public ExtensionCommandBuilder description(@Nullable String description) { - this.description = description; - return this; - } - - @Override - public ExtensionCommandBuilder permission(@Nullable String permission) { - this.permission = permission; - return this; - } - - @Override - public Command.Builder permission(@Nullable String permission, @NonNull TriState defaultValue) { - this.permission = permission; - this.permissionDefault = Objects.requireNonNull(defaultValue, "defaultValue"); - return this; - } - - @Override - public ExtensionCommandBuilder aliases(@Nullable List aliases) { - this.aliases = aliases; - return this; - } - - @Override - public Command.Builder suggestedOpOnly(boolean suggestedOpOnly) { - this.suggestedOpOnly = suggestedOpOnly; - if (suggestedOpOnly) { - // the most amount of legacy/deprecated behaviour I'm willing to support - this.permissionDefault = TriState.NOT_SET; - } - return this; - } - - @Override - public ExtensionCommandBuilder executableOnConsole(boolean executableOnConsole) { - this.executableOnConsole = executableOnConsole; - return this; - } - - @Override - public ExtensionCommandBuilder bedrockOnly(boolean bedrockOnly) { - this.bedrockOnly = bedrockOnly; - return this; - } - - @Override - public ExtensionCommandBuilder executor(@NonNull CommandExecutor executor) { - this.executor = executor; - return this; - } - - @NonNull - @Override - public GeyserExtensionCommand build() { - final Class sourceType = this.sourceType; - final boolean suggestedOpOnly = this.suggestedOpOnly; - final CommandExecutor executor = this.executor; - - if (sourceType == null) { - throw new IllegalArgumentException("Source type was not defined for command " + name + " in extension " + extension.name()); - } - if (executor == null) { - throw new IllegalArgumentException("Command executor was not defined for command " + name + " in extension " + extension.name()); - } - - GeyserExtensionCommand command = new GeyserExtensionCommand(extension, name, description, permission, permissionDefault, executableOnConsole, bedrockOnly) { - - @SuppressWarnings("unchecked") - @Override - public cloud.commandframework.Command.Builder builder(CommandManager manager) { - return super.builder(manager) - .argument(StringArgument.optional("args", StringArgument.StringMode.GREEDY)) - .handler(context -> { - GeyserCommandSource source = context.getSender(); - String[] args = context.getOrDefault("args", "").split(" "); - - if (sourceType.isInstance(source)) { - executor.execute((T) source, this, args); - return; - } - - GeyserSession session = source.connection().orElse(null); - if (sourceType.isInstance(session)) { - executor.execute((T) session, this, args); - return; - } - - // todo: send sender message instead - GeyserImpl.getInstance().getLogger().debug("Ignoring command " + name + " due to no suitable sender."); - }); - } - - @Override - public boolean isSuggestedOpOnly() { - return suggestedOpOnly; - } - }; - - command.aliases = aliases != null ? new ArrayList<>(aliases) : Collections.emptyList(); - return command; - } -} diff --git a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java index bb51e5058..bcc1382ef 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java @@ -25,11 +25,24 @@ package org.geysermc.geyser.extension.command; +import cloud.commandframework.CommandManager; +import cloud.commandframework.arguments.standard.StringArgument; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.command.Command; +import org.geysermc.geyser.api.command.CommandExecutor; +import org.geysermc.geyser.api.command.CommandSource; import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; +import org.geysermc.geyser.command.GeyserCommandSource; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; public abstract class GeyserExtensionCommand extends GeyserCommand { @@ -42,10 +55,10 @@ public abstract class GeyserExtensionCommand extends GeyserCommand { super(name, description, permission, permissionDefault, executableOnConsole, bedrockOnly); this.extension = extension; - this.rootCommand = extension.rootCommand(); + this.rootCommand = Objects.requireNonNull(extension.rootCommand()); - if (this.rootCommand == null || this.rootCommand.isBlank()) { - throw new IllegalStateException("rootCommand of extension " + extension.name() + " may not be null or blank"); + if (this.rootCommand.isBlank()) { + throw new IllegalStateException("rootCommand of extension " + extension.name() + " may not be blank"); } } @@ -57,4 +70,138 @@ public abstract class GeyserExtensionCommand extends GeyserCommand { public final String rootCommand() { return this.rootCommand; } + + public static class Builder implements Command.Builder { + private final Extension extension; + private Class sourceType; + private String name; + private String description; + private String permission; + private TriState permissionDefault; + private List aliases; + private boolean suggestedOpOnly = false; // deprecated for removal + private boolean executableOnConsole = true; + private boolean bedrockOnly = false; + private CommandExecutor executor; + + public Builder(Extension extension) { + this.extension = Objects.requireNonNull(extension); + } + + @Override + public Command.Builder source(@NonNull Class sourceType) { + this.sourceType = sourceType; + return this; + } + + @Override + public Builder name(@NonNull String name) { + this.name = name; + return this; + } + + @Override + public Builder description(@Nullable String description) { + this.description = description; + return this; + } + + @Override + public Builder permission(@Nullable String permission) { + this.permission = permission; + return this; + } + + @Override + public Builder permission(@Nullable String permission, @NonNull TriState defaultValue) { + this.permission = permission; + this.permissionDefault = Objects.requireNonNull(defaultValue, "defaultValue"); + return this; + } + + @Override + public Builder aliases(@Nullable List aliases) { + this.aliases = aliases; + return this; + } + + @Override + public Builder suggestedOpOnly(boolean suggestedOpOnly) { + this.suggestedOpOnly = suggestedOpOnly; + if (suggestedOpOnly) { + // the most amount of legacy/deprecated behaviour I'm willing to support + this.permissionDefault = TriState.NOT_SET; + } + return this; + } + + @Override + public Builder executableOnConsole(boolean executableOnConsole) { + this.executableOnConsole = executableOnConsole; + return this; + } + + @Override + public Builder bedrockOnly(boolean bedrockOnly) { + this.bedrockOnly = bedrockOnly; + return this; + } + + @Override + public Builder executor(@NonNull CommandExecutor executor) { + this.executor = executor; + return this; + } + + @NonNull + @Override + public GeyserExtensionCommand build() { + final Class sourceType = this.sourceType; + final boolean suggestedOpOnly = this.suggestedOpOnly; + final CommandExecutor executor = this.executor; + + if (sourceType == null) { + throw new IllegalArgumentException("Source type was not defined for command " + name + " in extension " + extension.name()); + } + if (executor == null) { + throw new IllegalArgumentException("Command executor was not defined for command " + name + " in extension " + extension.name()); + } + + GeyserExtensionCommand command = new GeyserExtensionCommand(extension, name, description, permission, permissionDefault, executableOnConsole, bedrockOnly) { + + @SuppressWarnings("unchecked") + @Override + public cloud.commandframework.Command.Builder builder(CommandManager manager) { + return super.builder(manager) + .argument(StringArgument.optional("args", StringArgument.StringMode.GREEDY)) + .handler(context -> { + GeyserCommandSource source = context.getSender(); + String[] args = context.getOrDefault("args", "").split(" "); + + if (sourceType.isInstance(source)) { + executor.execute((T) source, this, args); + return; + } + + GeyserSession session = source.connection().orElse(null); + if (sourceType.isInstance(session)) { + executor.execute((T) session, this, args); + return; + } + + // todo: send sender message instead + GeyserImpl.getInstance().getLogger().warning("Ignoring command " + name + " due to no suitable sender."); + }); + } + + @Override + public boolean isSuggestedOpOnly() { + return suggestedOpOnly; + } + }; + + command.aliases = aliases != null ? new ArrayList<>(aliases) : Collections.emptyList(); + return command; + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java index 8bbb1cbf2..34828e7b7 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java @@ -38,8 +38,8 @@ import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomItemOptions; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; import org.geysermc.geyser.api.pack.PathPackCodec; -import org.geysermc.geyser.command.ExtensionCommandBuilder; import org.geysermc.geyser.event.GeyserEventRegistrar; +import org.geysermc.geyser.extension.command.GeyserExtensionCommand; import org.geysermc.geyser.item.GeyserCustomItemData; import org.geysermc.geyser.item.GeyserCustomItemOptions; import org.geysermc.geyser.item.GeyserNonVanillaCustomItemData; @@ -63,7 +63,7 @@ public class ProviderRegistryLoader implements RegistryLoader, Prov @Override public Map, ProviderSupplier> load(Map, ProviderSupplier> providers) { // misc - providers.put(Command.Builder.class, args -> new ExtensionCommandBuilder<>((Extension) args[0])); + providers.put(Command.Builder.class, args -> new GeyserExtensionCommand.Builder<>((Extension) args[0])); providers.put(CustomBlockComponents.Builder.class, args -> new GeyserCustomBlockComponents.CustomBlockComponentsBuilder()); providers.put(CustomBlockData.Builder.class, args -> new GeyserCustomBlockData.CustomBlockDataBuilder()); From 065abb055009e350d6bd2c13181280de6abf222b Mon Sep 17 00:00:00 2001 From: Konicai <71294714+konicai@users.noreply.github.com> Date: Mon, 4 Sep 2023 15:59:22 -0400 Subject: [PATCH 25/76] Bootstrap consistency: register commands after PreInitEvent, before PostInitEvent --- .../bungeecord/GeyserBungeePlugin.java | 22 +++++++++------- .../platform/fabric/GeyserFabricMod.java | 2 +- .../standalone/GeyserStandaloneBootstrap.java | 7 +++-- .../GeyserStandaloneCommandManager.java | 1 - .../velocity/GeyserVelocityPlugin.java | 26 +++++++++++-------- .../geyser/command/CommandRegistry.java | 2 +- 6 files changed, 35 insertions(+), 25 deletions(-) diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java index 1a4ee490a..21691a368 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java @@ -172,20 +172,24 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { } private void postStartup() { + var sourceConverter = new CommandSourceConverter<>( + CommandSender.class, + id -> getProxy().getPlayer(id), + () -> getProxy().getConsole() + ); + CommandManager cloud = new BungeeCommandManager<>( + this, + CommandExecutionCoordinator.simpleCoordinator(), + BungeeCommandSource::new, + sourceConverter::convert + ); + this.commandRegistry = new CommandRegistry(geyser, cloud); + GeyserImpl.start(); this.geyserInjector = new GeyserBungeeInjector(this); this.geyserInjector.initializeLocalChannel(this); - var sourceConverter = new CommandSourceConverter<>(CommandSender.class, id -> getProxy().getPlayer(id), () -> getProxy().getConsole()); - CommandManager cloud = new BungeeCommandManager<>( - this, - CommandExecutionCoordinator.simpleCoordinator(), - BungeeCommandSource::new, - sourceConverter::convert - ); - this.commandRegistry = new CommandRegistry(geyser, cloud); - if (geyserConfig.isLegacyPingPassthrough()) { this.geyserBungeePingPassthrough = GeyserLegacyPingPassthrough.init(geyser); } else { diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java index d50f45bd8..e6f07c13b 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java @@ -134,7 +134,7 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { CommandSourceStack.class, id -> server.getPlayerList().getPlayer(id), Player::createCommandSourceStack, - () -> server.createCommandSourceStack() // note: method reference here will cause NPE + () -> server.createCommandSourceStack() // NPE if method reference is used, since server is not available yet ); CommandManager cloud = new FabricServerCommandManager<>( CommandExecutionCoordinator.simpleCoordinator(), diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java index cadf6e2e7..ce1a21efb 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java @@ -218,11 +218,14 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { logger.get().setLevel(geyserConfig.isDebugMode() ? Level.DEBUG : Level.INFO); geyser = GeyserImpl.load(PlatformType.STANDALONE, this); - GeyserImpl.start(); + // fire GeyserDefineCommandsEvent after PreInitEvent, before PostInitEvent, for consistency with other bootstraps GeyserStandaloneCommandManager cloud = new GeyserStandaloneCommandManager(geyser); commandRegistry = new CommandRegistry(geyser, cloud); - cloud.gatherPermissions(); + + GeyserImpl.start(); + + cloud.gatherPermissions(); // event must be fired after CommandRegistry has subscribed its listener if (gui != null) { gui.enableCommands(geyser.getScheduledThread(), commandRegistry); diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java index 445608ace..390ba26d6 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java @@ -75,7 +75,6 @@ public class GeyserStandaloneCommandManager extends CommandManager { if (def == TriState.TRUE) { diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java index 5f5fe3c8c..a588bbdf4 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java @@ -130,21 +130,25 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { } private void postStartup() { - GeyserImpl.start(); - - this.geyserInjector = new GeyserVelocityInjector(proxyServer); - // Will be initialized after the proxy has been bound - - var sourceConverter = new CommandSourceConverter<>(CommandSource.class, id -> proxyServer.getPlayer(id).orElse(null), proxyServer::getConsoleCommandSource); + var sourceConverter = new CommandSourceConverter<>( + CommandSource.class, + id -> proxyServer.getPlayer(id).orElse(null), + proxyServer::getConsoleCommandSource + ); CommandManager cloud = new VelocityCommandManager<>( - container, - proxyServer, - CommandExecutionCoordinator.simpleCoordinator(), - VelocityCommandSource::new, - sourceConverter::convert + container, + proxyServer, + CommandExecutionCoordinator.simpleCoordinator(), + VelocityCommandSource::new, + sourceConverter::convert ); this.commandRegistry = new CommandRegistry(geyser, cloud); + GeyserImpl.start(); + + // Will be initialized after the proxy has been bound + this.geyserInjector = new GeyserVelocityInjector(proxyServer); + if (geyserConfig.isLegacyPingPassthrough()) { this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); } else { diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java index 81174c4b3..d2c4284af 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java @@ -120,7 +120,7 @@ public class CommandRegistry { registerExtensionCommand(entry.getKey(), new HelpCommand(this.geyser, "help", "geyser.commands.exthelp.desc", "geyser.command.exthelp." + id, id, entry.getValue())); } - // wait for the right moment to register permissions + // wait for the right moment (depends on the platform) to register permissions geyser.eventBus().subscribe(new GeyserEventRegistrar(this), GeyserRegisterPermissionsEvent.class, this::onRegisterPermissions); } From acd53a451964f71c3a284fb1aa72a8b6a275f354 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Mon, 4 Sep 2023 21:02:40 -0400 Subject: [PATCH 26/76] Refactor command abstraction and implement `/geyser` as help --- .../geyser/command/GeyserCommand.java | 43 +++++++++++++--- .../defaults/AdvancedTooltipsCommand.java | 25 +++++----- .../command/defaults/AdvancementsCommand.java | 12 ++--- .../defaults/ConnectionTestCommand.java | 9 ++-- .../geyser/command/defaults/DumpCommand.java | 8 +-- .../command/defaults/ExtensionsCommand.java | 8 --- .../geyser/command/defaults/HelpCommand.java | 15 ++++-- .../geyser/command/defaults/ListCommand.java | 19 +++---- .../command/defaults/OffhandCommand.java | 9 ++-- .../command/defaults/ReloadCommand.java | 16 +++--- .../command/defaults/SettingsCommand.java | 12 ++--- .../command/defaults/StatisticsCommand.java | 17 +++---- .../geyser/command/defaults/StopCommand.java | 9 ++-- .../command/defaults/VersionCommand.java | 9 ---- .../command/GeyserExtensionCommand.java | 50 ++++++++++++------- 15 files changed, 136 insertions(+), 125 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java index 2c92ec06d..dd7b19b33 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java @@ -27,6 +27,7 @@ package org.geysermc.geyser.command; import cloud.commandframework.Command; import cloud.commandframework.CommandManager; +import cloud.commandframework.context.CommandContext; import cloud.commandframework.meta.CommandMeta; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -35,7 +36,6 @@ import org.jetbrains.annotations.Contract; import java.util.Collections; import java.util.List; -import java.util.Objects; public abstract class GeyserCommand implements org.geysermc.geyser.api.command.Command { @@ -153,20 +153,49 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C return Collections.unmodifiableList(aliases); } + /** + * @return the first (literal) argument of this command, which comes before {@link #name()}. + */ public String rootCommand() { return "geyser"; } + /** + * Creates a new command builder with {@link #rootCommand()}, {@link #name()}, and {@link #aliases()} built on it. + * The Applicable from {@link #meta()} is also applied to the builder. + */ @Contract(value = "_ -> new", pure = true) - public Command.Builder builder(CommandManager manager) { + public final Command.Builder baseBuilder(CommandManager manager) { return manager.commandBuilder(rootCommand()) .literal(name, aliases.toArray(new String[0])) - .meta(BEDROCK_ONLY, isBedrockOnly()) - .meta(PLAYER_ONLY, !isExecutableOnConsole()) - .permission(permission); + .permission(permission) + .apply(meta()); } - public void register(CommandManager manager) { - manager.command(builder(manager)); + /** + * @return an Applicable that applies {@link #BEDROCK_ONLY} and {@link #PLAYER_ONLY} as meta, + * according to {@link #isBedrockOnly()} and {@link #isExecutableOnConsole()} (respectively). + */ + public Command.Builder.Applicable meta() { + return builder -> builder + .meta(BEDROCK_ONLY, isBedrockOnly()) + .meta(PLAYER_ONLY, !isExecutableOnConsole()); } + + /** + * Registers this command to the given command manager. + * This method may be overridden to register more than one command. + *

+ * The default implementation is that {@link #baseBuilder(CommandManager)} with {@link #execute(CommandContext)} + * applied as the handler is registered to the manager. + */ + public void register(CommandManager manager) { + manager.command(baseBuilder(manager).handler(this::execute)); + } + + /** + * Executes this command + * @param context the context with which this command should be executed + */ + public abstract void execute(CommandContext context); } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java index d9875adb8..5a683b31f 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java @@ -25,11 +25,11 @@ package org.geysermc.geyser.command.defaults; -import cloud.commandframework.Command; -import cloud.commandframework.CommandManager; +import cloud.commandframework.context.CommandContext; import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.MinecraftLocale; @@ -40,16 +40,15 @@ public class AdvancedTooltipsCommand extends GeyserCommand { } @Override - public Command.Builder builder(CommandManager manager) { - return super.builder(manager) - .handler(context -> context.getSender().connection().ifPresent(session -> { - String onOrOff = session.isAdvancedTooltips() ? "off" : "on"; - session.setAdvancedTooltips(!session.isAdvancedTooltips()); - session.sendMessage(ChatColor.BOLD + ChatColor.YELLOW - + MinecraftLocale.getLocaleString("debug.prefix", session.locale()) - + " " + ChatColor.RESET - + MinecraftLocale.getLocaleString("debug.advanced_tooltips." + onOrOff, session.locale())); - session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory()); - })); + public void execute(CommandContext context) { + GeyserSession session = context.getSender().connection().orElseThrow(); + + String onOrOff = session.isAdvancedTooltips() ? "off" : "on"; + session.setAdvancedTooltips(!session.isAdvancedTooltips()); + session.sendMessage(ChatColor.BOLD + ChatColor.YELLOW + + MinecraftLocale.getLocaleString("debug.prefix", session.locale()) + + " " + ChatColor.RESET + + MinecraftLocale.getLocaleString("debug.advanced_tooltips." + onOrOff, session.locale())); + session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory()); } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java index e15a3fef0..a9047ef5a 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java @@ -25,11 +25,11 @@ package org.geysermc.geyser.command.defaults; -import cloud.commandframework.Command; -import cloud.commandframework.CommandManager; +import cloud.commandframework.context.CommandContext; import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; +import org.geysermc.geyser.session.GeyserSession; public class AdvancementsCommand extends GeyserCommand { @@ -38,10 +38,8 @@ public class AdvancementsCommand extends GeyserCommand { } @Override - public Command.Builder builder(CommandManager manager) { - return super.builder(manager) - .handler(context -> - context.getSender().connection().ifPresent(session -> - session.getAdvancementsCache().buildAndShowMenuForm())); + public void execute(CommandContext context) { + GeyserSession session = context.getSender().connection().orElseThrow(); + session.getAdvancementsCache().buildAndShowMenuForm(); } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java index 483d11b2b..6901a12e1 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java @@ -53,17 +53,18 @@ public class ConnectionTestCommand extends GeyserCommand { } @Override - public Command.Builder builder(CommandManager manager) { - return super.builder(manager) + public void register(CommandManager manager) { + manager.command(baseBuilder(manager) .argument(StringArgument.of(ADDRESS)) .argument(IntegerArgument.builder(PORT) .asOptionalWithDefault(19132) .withMax(65535).withMin(0) .build()) - .handler(this::execute); + .handler(this::execute)); } - private void execute(CommandContext context) { + @Override + public void execute(CommandContext context) { GeyserCommandSource source = context.getSender(); String address = context.get(ADDRESS); int port = context.get(PORT); diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java index 5898e5c19..881fa297e 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java @@ -58,15 +58,14 @@ public class DumpCommand extends GeyserCommand { public DumpCommand(GeyserImpl geyser, String name, String description, String permission) { super(name, description, permission, TriState.NOT_SET); - this.geyser = geyser; } @Override - public Command.Builder builder(CommandManager manager) { - return super.builder(manager) + public void register(CommandManager manager) { + manager.command(baseBuilder(manager) .argument(createArgument()) - .handler(this::execute); + .handler(this::execute)); } private StringArrayArgument createArgument() { @@ -74,6 +73,7 @@ public class DumpCommand extends GeyserCommand { return StringArrayArgument.optional(ARGUMENTS, (context, input) -> SUGGESTIONS); } + @Override public void execute(CommandContext context) { GeyserCommandSource source = context.getSender(); String[] args = context.getOrDefault(ARGUMENTS, new String[0]); diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ExtensionsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ExtensionsCommand.java index 923d9f0b9..2b4b32539 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ExtensionsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ExtensionsCommand.java @@ -25,8 +25,6 @@ package org.geysermc.geyser.command.defaults; -import cloud.commandframework.Command; -import cloud.commandframework.CommandManager; import cloud.commandframework.context.CommandContext; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.extension.Extension; @@ -44,16 +42,10 @@ public class ExtensionsCommand extends GeyserCommand { public ExtensionsCommand(GeyserImpl geyser, String name, String description, String permission) { super(name, description, permission, TriState.TRUE); - this.geyser = geyser; } @Override - public Command.Builder builder(CommandManager manager) { - return super.builder(manager) - .handler(this::execute); - } - public void execute(CommandContext context) { GeyserCommandSource source = context.getSender(); diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java index d43384177..083290149 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java @@ -49,17 +49,22 @@ public class HelpCommand extends GeyserCommand { super(name, description, permission, TriState.TRUE); this.baseCommand = baseCommand; this.commands = commands.values(); - this.aliases = Collections.singletonList("?"); } @Override - public cloud.commandframework.Command.Builder builder(CommandManager manager) { - return super.builder(manager) - .handler(this::execute); + public void register(CommandManager manager) { + super.register(manager); + + // Also register just `/geyser` + manager.command(manager.commandBuilder(rootCommand()) + .permission(permission()) + .apply(meta()) + .handler(this::execute)); } - private void execute(CommandContext context) { + @Override + public void execute(CommandContext context) { GeyserCommandSource source = context.getSender(); boolean bedrockPlayer = source.connection().isPresent(); diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java index 6f5a5ac4e..d5b1112ee 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java @@ -25,8 +25,7 @@ package org.geysermc.geyser.command.defaults; -import cloud.commandframework.Command; -import cloud.commandframework.CommandManager; +import cloud.commandframework.context.CommandContext; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; @@ -42,21 +41,17 @@ public class ListCommand extends GeyserCommand { public ListCommand(GeyserImpl geyser, String name, String description, String permission) { super(name, description, permission, TriState.NOT_SET); - this.geyser = geyser; } @Override - public Command.Builder builder(CommandManager manager) { - return super.builder(manager) - .handler(context -> { - GeyserCommandSource source = context.getSender(); + public void execute(CommandContext context) { + GeyserCommandSource source = context.getSender(); - String message = GeyserLocale.getPlayerLocaleString("geyser.commands.list.message", source.locale(), - geyser.getSessionManager().size(), - geyser.getSessionManager().getAllSessions().stream().map(GeyserSession::bedrockUsername).collect(Collectors.joining(" "))); + String message = GeyserLocale.getPlayerLocaleString("geyser.commands.list.message", source.locale(), + geyser.getSessionManager().size(), + geyser.getSessionManager().getAllSessions().stream().map(GeyserSession::bedrockUsername).collect(Collectors.joining(" "))); - source.sendMessage(message); - }); + source.sendMessage(message); } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java index a57982725..af893d06c 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java @@ -25,8 +25,7 @@ package org.geysermc.geyser.command.defaults; -import cloud.commandframework.Command; -import cloud.commandframework.CommandManager; +import cloud.commandframework.context.CommandContext; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; @@ -40,8 +39,8 @@ public class OffhandCommand extends GeyserCommand { } @Override - public Command.Builder builder(CommandManager manager) { - return super.builder(manager) - .handler(context -> context.getSender().connection().ifPresent(GeyserSession::requestOffhandSwap)); + public void execute(CommandContext context) { + GeyserSession session = context.getSender().connection().orElseThrow(); + session.requestOffhandSwap(); } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java index 5331b9b1f..a8ad4c6e3 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java @@ -25,8 +25,7 @@ package org.geysermc.geyser.command.defaults; -import cloud.commandframework.Command; -import cloud.commandframework.CommandManager; +import cloud.commandframework.context.CommandContext; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; @@ -43,14 +42,11 @@ public class ReloadCommand extends GeyserCommand { } @Override - public Command.Builder builder(CommandManager manager) { - return super.builder(manager) - .handler(context -> { - GeyserCommandSource source = context.getSender(); - source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.reload.message", source.locale())); + public void execute(CommandContext context) { + GeyserCommandSource source = context.getSender(); + source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.reload.message", source.locale())); - geyser.getSessionManager().disconnectAll("geyser.commands.reload.kick"); - geyser.reload(); - }); + geyser.getSessionManager().disconnectAll("geyser.commands.reload.kick"); + geyser.reload(); } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java index 7bfd7f523..a3ce3c69e 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java @@ -25,12 +25,12 @@ package org.geysermc.geyser.command.defaults; -import cloud.commandframework.Command; -import cloud.commandframework.CommandManager; +import cloud.commandframework.context.CommandContext; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.SettingsUtils; public class SettingsCommand extends GeyserCommand { @@ -40,10 +40,8 @@ public class SettingsCommand extends GeyserCommand { } @Override - public Command.Builder builder(CommandManager manager) { - return super.builder(manager) - .handler(context -> - context.getSender().connection().ifPresent(session -> - session.sendForm(SettingsUtils.buildForm(session)))); + public void execute(CommandContext context) { + GeyserSession session = context.getSender().connection().orElseThrow(); + session.sendForm(SettingsUtils.buildForm(session)); } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java index da7eb7209..646a8ceaa 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java @@ -25,14 +25,14 @@ package org.geysermc.geyser.command.defaults; -import cloud.commandframework.Command; -import cloud.commandframework.CommandManager; +import cloud.commandframework.context.CommandContext; import com.github.steveice10.mc.protocol.data.game.ClientCommand; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundClientCommandPacket; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; +import org.geysermc.geyser.session.GeyserSession; public class StatisticsCommand extends GeyserCommand { @@ -41,12 +41,11 @@ public class StatisticsCommand extends GeyserCommand { } @Override - public Command.Builder builder(CommandManager manager) { - return super.builder(manager) - .handler(context -> context.getSender().connection().ifPresent(session -> { - session.setWaitingForStatistics(true); - ServerboundClientCommandPacket packet = new ServerboundClientCommandPacket(ClientCommand.STATS); - session.sendDownstreamPacket(packet); - })); + public void execute(CommandContext context) { + GeyserSession session = context.getSender().connection().orElseThrow(); + + session.setWaitingForStatistics(true); + ServerboundClientCommandPacket packet = new ServerboundClientCommandPacket(ClientCommand.STATS); + session.sendDownstreamPacket(packet); } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java index 0dd79e306..e2b7cdbb1 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java @@ -25,8 +25,7 @@ package org.geysermc.geyser.command.defaults; -import cloud.commandframework.Command; -import cloud.commandframework.CommandManager; +import cloud.commandframework.context.CommandContext; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; @@ -41,13 +40,11 @@ public class StopCommand extends GeyserCommand { public StopCommand(GeyserImpl geyser, String name, String description, String permission) { super(name, description, permission, TriState.NOT_SET); this.geyser = geyser; - this.aliases = Collections.singletonList("shutdown"); } @Override - public Command.Builder builder(CommandManager manager) { - return super.builder(manager) - .handler(context -> geyser.getBootstrap().onDisable()); + public void execute(CommandContext context) { + geyser.getBootstrap().onDisable(); } } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java index 2491040e6..8fec5b238 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java @@ -25,8 +25,6 @@ package org.geysermc.geyser.command.defaults; -import cloud.commandframework.Command; -import cloud.commandframework.CommandManager; import cloud.commandframework.context.CommandContext; import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec; import org.geysermc.geyser.Constants; @@ -51,16 +49,9 @@ public class VersionCommand extends GeyserCommand { public VersionCommand(GeyserImpl geyser, String name, String description, String permission) { super(name, description, permission, TriState.NOT_SET); - this.geyser = geyser; } - @Override - public Command.Builder builder(CommandManager manager) { - return super.builder(manager) - .handler(this::execute); - } - public void execute(CommandContext context) { GeyserCommandSource source = context.getSender(); diff --git a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java index bcc1382ef..a4d51f6fd 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java @@ -27,12 +27,13 @@ package org.geysermc.geyser.extension.command; import cloud.commandframework.CommandManager; import cloud.commandframework.arguments.standard.StringArgument; +import cloud.commandframework.context.CommandContext; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.api.command.CommandExecutor; import org.geysermc.geyser.api.command.CommandSource; +import org.geysermc.geyser.api.connection.GeyserConnection; import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; @@ -167,31 +168,42 @@ public abstract class GeyserExtensionCommand extends GeyserCommand { throw new IllegalArgumentException("Command executor was not defined for command " + name + " in extension " + extension.name()); } + // if the source type is a GeyserConnection then it is inherently bedrockOnly + final boolean bedrockOnly = GeyserConnection.class.isAssignableFrom(sourceType) || this.bedrockOnly; + // a similar check would exist for executableOnConsole, but there is not a logger type exposed in the api + GeyserExtensionCommand command = new GeyserExtensionCommand(extension, name, description, permission, permissionDefault, executableOnConsole, bedrockOnly) { + @Override + public void register(CommandManager manager) { + // todo: if we don't find a way to expose cloud in the api, we should implement a way + // to not have the [args] if its not necessary for this command. and maybe tab completion. + manager.command(baseBuilder(manager) + .argument(StringArgument.optional("args", StringArgument.StringMode.GREEDY)) + .handler(this::execute)); + } + @SuppressWarnings("unchecked") @Override - public cloud.commandframework.Command.Builder builder(CommandManager manager) { - return super.builder(manager) - .argument(StringArgument.optional("args", StringArgument.StringMode.GREEDY)) - .handler(context -> { - GeyserCommandSource source = context.getSender(); - String[] args = context.getOrDefault("args", "").split(" "); + public void execute(CommandContext context) { + GeyserCommandSource source = context.getSender(); + String[] args = context.getOrDefault("args", "").split(" "); - if (sourceType.isInstance(source)) { - executor.execute((T) source, this, args); - return; - } + if (sourceType.isInstance(source)) { + executor.execute((T) source, this, args); + return; + } - GeyserSession session = source.connection().orElse(null); - if (sourceType.isInstance(session)) { - executor.execute((T) session, this, args); - return; - } + GeyserSession session = source.connection().orElse(null); + if (sourceType.isInstance(session)) { + executor.execute((T) session, this, args); + return; + } - // todo: send sender message instead - GeyserImpl.getInstance().getLogger().warning("Ignoring command " + name + " due to no suitable sender."); - }); + // currently, the only subclass of CommandSource exposed in the api is GeyserConnection. + // when this command was registered, we enabled bedrockOnly if the sourceType was a GeyserConnection. + // as a result, SenderTypeProcessor should handle that case and this method shouldn't even be reached. + source.sendMessage("You must be a " + sourceType.getSimpleName() + " to run this command."); } @Override From 12928f103bdc4e4bda0daf8f91b3b2a06f043d15 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Mon, 4 Sep 2023 21:18:25 -0400 Subject: [PATCH 27/76] Missing Override annotation for version command --- .../org/geysermc/geyser/command/defaults/VersionCommand.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java index 8fec5b238..6e63eaaee 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java @@ -52,6 +52,7 @@ public class VersionCommand extends GeyserCommand { this.geyser = geyser; } + @Override public void execute(CommandContext context) { GeyserCommandSource source = context.getSender(); From bdbef04e8eab4315f8de053d6e4c25239a3493f4 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Tue, 5 Sep 2023 00:08:23 -0400 Subject: [PATCH 28/76] Customize exception handlers on all platforms --- .../geyser/command/CommandRegistry.java | 110 ++++++++++-------- ...rocessor.java => SourceTypeProcessor.java} | 2 +- 2 files changed, 61 insertions(+), 51 deletions(-) rename core/src/main/java/org/geysermc/geyser/command/{SenderTypeProcessor.java => SourceTypeProcessor.java} (97%) diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java index d2c4284af..cce6288e0 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java @@ -32,7 +32,9 @@ import cloud.commandframework.exceptions.InvalidCommandSenderException; import cloud.commandframework.exceptions.InvalidSyntaxException; import cloud.commandframework.exceptions.NoPermissionException; import cloud.commandframework.exceptions.NoSuchCommandException; +import cloud.commandframework.execution.CommandExecutionCoordinator; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import lombok.AllArgsConstructor; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent; import org.geysermc.geyser.api.util.PlatformType; @@ -62,8 +64,10 @@ import org.jetbrains.annotations.NotNull; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.CompletionException; +import java.util.function.BiConsumer; public class CommandRegistry { @@ -73,14 +77,35 @@ public class CommandRegistry { private final Map commands = new Object2ObjectOpenHashMap<>(13); private final Map> extensionCommands = new Object2ObjectOpenHashMap<>(0); - private final Map permissions = new Object2ObjectOpenHashMap<>(0); + private final Map permissions = new Object2ObjectOpenHashMap<>(13); + + /** + * The order and behaviour of these exception handlers is designed to mirror the typical cloud implementations. + * For example: https://github.com/Incendo/cloud/blob/a4cc749b91564af57bb7bba36dd8011b556c2b3a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricExecutor.java#L94-L173 + */ + // todo: full localization + private final List> exceptionHandlers = List.of( + new ExceptionHandler<>(InvalidSyntaxException.class, (src, e) -> src.sendMessage("Invalid Command Syntax. Correct syntax is: " + e.getCorrectSyntax())), + new ExceptionHandler<>(InvalidCommandSenderException.class, (src, e) -> src.sendMessage(e.getMessage())), + new ExceptionHandler<>(NoPermissionException.class, (src, e) -> src.sendLocaleString("geyser.bootstrap.command.permission_fail")), + new ExceptionHandler<>(NoSuchCommandException.class, (src, e) -> src.sendLocaleString("geyser.bootstrap.command.not_found")), + new ExceptionHandler<>(ArgumentParseException.class, (src, e) -> src.sendMessage("Invalid Command Argument: " + e.getCause().getMessage())), + new ExceptionHandler<>(CommandExecutionException.class, (src, e) -> handleUnexpectedThrowable(src, e.getCause())) + ); public CommandRegistry(GeyserImpl geyser, CommandManager cloud) { this.geyser = geyser; this.cloud = cloud; - cloud.registerCommandPostProcessor(new SenderTypeProcessor()); + // Restricts command source types from executing commands they don't have access to + cloud.registerCommandPostProcessor(new SourceTypeProcessor()); + // Override the default exception handlers that the typical cloud implementations provide so that we can perform localization. + // This is kind of meaningless for our Geyser-Standalone implementation since these handlers are the default exception handlers in that case. + for (ExceptionHandler handler : exceptionHandlers) { + handler.register(cloud); + } + // begin command registration registerBuiltInCommand(new HelpCommand(geyser, "help", "geyser.commands.help.desc", "geyser.command.help", "geyser", this.commands)); registerBuiltInCommand(new ListCommand(geyser, "list", "geyser.commands.list.desc", "geyser.command.list")); registerBuiltInCommand(new ReloadCommand(geyser, "reload", "geyser.commands.reload.desc", "geyser.command.reload")); @@ -190,6 +215,7 @@ public class CommandRegistry { /** * Dispatches a command into cloud and handles any thrown exceptions. + * This method may or may not be blocking, depending on the {@link CommandExecutionCoordinator} in use by cloud. */ public void runCommand(@NonNull GeyserCommandSource source, @NonNull String command) { cloud.executeCommand(source, command).whenComplete((result, throwable) -> { @@ -197,6 +223,7 @@ public class CommandRegistry { return; } + // mirrors typical cloud implementations if (throwable instanceof CompletionException) { throwable = throwable.getCause(); } @@ -205,59 +232,42 @@ public class CommandRegistry { }); } - // todo: full localization private void handleThrowable(@NonNull GeyserCommandSource source, @NonNull Throwable throwable) { - // This is modelled after the command executors of each cloud minecraft implementation. - if (throwable instanceof InvalidSyntaxException syntaxException) { - cloud.handleException( - source, - InvalidSyntaxException.class, - syntaxException, - ($, e) -> source.sendMessage("Invalid Command Syntax. Correct syntax is: " + e.getCorrectSyntax()) - ); - } else if (throwable instanceof InvalidCommandSenderException invalidSenderException) { - cloud.handleException( - source, - InvalidCommandSenderException.class, - invalidSenderException, - ($, e) -> source.sendMessage(throwable.getMessage()) - ); - } else if (throwable instanceof NoPermissionException noPermissionException) { - cloud.handleException( - source, - NoPermissionException.class, - noPermissionException, - ($, e) -> source.sendLocaleString("geyser.bootstrap.command.permission_fail") - - ); - } else if (throwable instanceof NoSuchCommandException noCommandException) { - cloud.handleException( - source, - NoSuchCommandException.class, - noCommandException, - ($, e) -> source.sendLocaleString("geyser.bootstrap.command.not_found") - ); - } else if (throwable instanceof ArgumentParseException argumentParseException) { - cloud.handleException( - source, - ArgumentParseException.class, - argumentParseException, - ($, e) -> source.sendMessage("Invalid Command Argument: " + throwable.getCause().getMessage()) - ); - } else if (throwable instanceof CommandExecutionException executionException) { - cloud.handleException( - source, - CommandExecutionException.class, - executionException, - ($, e) -> defaultHandler(source, throwable.getCause()) - ); - } else { - defaultHandler(source, throwable); + if (throwable instanceof Exception exception) { + for (ExceptionHandler handler : exceptionHandlers) { + if (handler.handle(source, exception)) { + return; + } + } } + handleUnexpectedThrowable(source, throwable); } - private void defaultHandler(GeyserCommandSource source, Throwable throwable) { + private void handleUnexpectedThrowable(GeyserCommandSource source, Throwable throwable) { source.sendLocaleString("command.failed"); // java edition translation key GeyserImpl.getInstance().getLogger().error("Exception while executing command handler", throwable); } + + @AllArgsConstructor + private class ExceptionHandler { + + private final Class type; + private final BiConsumer handler; + + @SuppressWarnings("unchecked") + boolean handle(GeyserCommandSource source, Exception exception) { + if (type.isInstance(exception)) { + E e = (E) exception; + // if cloud has a registered exception handler for this type, use it, otherwise use this handler. + // we register all the exception handlers to cloud, so it will likely just be cloud invoking this same handler. + cloud.handleException(source, type, e, handler); + return true; + } + return false; + } + + void register(CommandManager manager) { + manager.registerExceptionHandler(type, handler); + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/command/SenderTypeProcessor.java b/core/src/main/java/org/geysermc/geyser/command/SourceTypeProcessor.java similarity index 97% rename from core/src/main/java/org/geysermc/geyser/command/SenderTypeProcessor.java rename to core/src/main/java/org/geysermc/geyser/command/SourceTypeProcessor.java index 40aeac728..c4bf5fa1e 100644 --- a/core/src/main/java/org/geysermc/geyser/command/SenderTypeProcessor.java +++ b/core/src/main/java/org/geysermc/geyser/command/SourceTypeProcessor.java @@ -33,7 +33,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; -public class SenderTypeProcessor implements CommandPostprocessor { +public class SourceTypeProcessor implements CommandPostprocessor { @Override public void accept(@NonNull CommandPostprocessingContext processContext) { From cdf06ce35057e6355ef863c76fefb67531ce0fe7 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Tue, 5 Sep 2023 04:24:18 -0400 Subject: [PATCH 29/76] Don't suggest opts that have already been added for dump command --- .../geyser/command/defaults/DumpCommand.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java index 881fa297e..b59d35303 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.command.defaults; -import cloud.commandframework.Command; import cloud.commandframework.CommandManager; import cloud.commandframework.arguments.standard.StringArrayArgument; import cloud.commandframework.context.CommandContext; @@ -45,6 +44,7 @@ import org.geysermc.geyser.util.WebUtils; import java.io.FileOutputStream; import java.io.IOException; +import java.util.ArrayList; import java.util.List; public class DumpCommand extends GeyserCommand { @@ -69,8 +69,21 @@ public class DumpCommand extends GeyserCommand { } private StringArrayArgument createArgument() { - // todo suggestions might be broken - return StringArrayArgument.optional(ARGUMENTS, (context, input) -> SUGGESTIONS); + return StringArrayArgument.optional(ARGUMENTS, (context, currentInput) -> { + // currentInput only provides the word currently being typed, so it's useless for checking the previous ones + List input = context.getRawInput(); + if (input.size() <= 2) { + return SUGGESTIONS; // only `geyser dump` was typed (2 literals) + } + + // the rest of the input after `geyser dump` is for this argument + input = input.subList(2, input.size()); + + // don't suggest any words they have already typed + List suggestions = new ArrayList<>(SUGGESTIONS); + suggestions.removeAll(input); + return suggestions; + }); } @Override From 821ce46909351de2a64a1a488d8a7a85a2e71a8e Mon Sep 17 00:00:00 2001 From: Konicai <71294714+konicai@users.noreply.github.com> Date: Wed, 6 Sep 2023 17:15:44 -0400 Subject: [PATCH 30/76] Update translations --- .../org/geysermc/geyser/command/CommandRegistry.java | 10 +++++----- .../geysermc/geyser/command/SourceTypeProcessor.java | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java index cce6288e0..0b73a2b51 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java @@ -85,11 +85,11 @@ public class CommandRegistry { */ // todo: full localization private final List> exceptionHandlers = List.of( - new ExceptionHandler<>(InvalidSyntaxException.class, (src, e) -> src.sendMessage("Invalid Command Syntax. Correct syntax is: " + e.getCorrectSyntax())), - new ExceptionHandler<>(InvalidCommandSenderException.class, (src, e) -> src.sendMessage(e.getMessage())), - new ExceptionHandler<>(NoPermissionException.class, (src, e) -> src.sendLocaleString("geyser.bootstrap.command.permission_fail")), - new ExceptionHandler<>(NoSuchCommandException.class, (src, e) -> src.sendLocaleString("geyser.bootstrap.command.not_found")), - new ExceptionHandler<>(ArgumentParseException.class, (src, e) -> src.sendMessage("Invalid Command Argument: " + e.getCause().getMessage())), + new ExceptionHandler<>(InvalidSyntaxException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_syntax", e.getCorrectSyntax())), + new ExceptionHandler<>(InvalidCommandSenderException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_sender", e.getCommandSender().getClass().getSimpleName(), e.getRequiredSender().getSimpleName())), + new ExceptionHandler<>(NoPermissionException.class, (src, e) -> src.sendLocaleString("geyser.command.permission_fail")), + new ExceptionHandler<>(NoSuchCommandException.class, (src, e) -> src.sendLocaleString("geyser.command.not_found")), + new ExceptionHandler<>(ArgumentParseException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_argument", e.getCause().getMessage())), new ExceptionHandler<>(CommandExecutionException.class, (src, e) -> handleUnexpectedThrowable(src, e.getCause())) ); diff --git a/core/src/main/java/org/geysermc/geyser/command/SourceTypeProcessor.java b/core/src/main/java/org/geysermc/geyser/command/SourceTypeProcessor.java index c4bf5fa1e..7088bc5fe 100644 --- a/core/src/main/java/org/geysermc/geyser/command/SourceTypeProcessor.java +++ b/core/src/main/java/org/geysermc/geyser/command/SourceTypeProcessor.java @@ -42,7 +42,7 @@ public class SourceTypeProcessor implements CommandPostprocessor Date: Wed, 6 Sep 2023 22:27:36 -0400 Subject: [PATCH 31/76] Target languages PR --- core/src/main/resources/languages | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/languages b/core/src/main/resources/languages index f6685c4cc..ad99e5d76 160000 --- a/core/src/main/resources/languages +++ b/core/src/main/resources/languages @@ -1 +1 @@ -Subproject commit f6685c4ccc6e77b07402d45cb41213559004b7d6 +Subproject commit ad99e5d76c6b6fe543b7ac6ec98105c69e905311 From 55e50fecb91e2916123032f6703770989c160407 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Wed, 6 Sep 2023 23:28:50 -0400 Subject: [PATCH 32/76] Remove trailing whitespace on Standalone GUI --- .../geyser/platform/standalone/gui/GeyserStandaloneGUI.java | 3 ++- .../main/java/org/geysermc/geyser/command/CommandRegistry.java | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java index a1eabb3cc..76e81014a 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java @@ -347,7 +347,8 @@ public class GeyserStandaloneGUI { @Override public void actionPerformed(ActionEvent e) { - String command = commandInput.getText(); + // the headless variant of Standalone strips trailing whitespace for us - we need to manually + String command = commandInput.getText().stripTrailing(); appendConsole(command + "\n"); // show what was run in the console handler.accept(command); // run the command commandInput.setText(""); // clear the input diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java index 0b73a2b51..295dd2c47 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java @@ -83,7 +83,6 @@ public class CommandRegistry { * The order and behaviour of these exception handlers is designed to mirror the typical cloud implementations. * For example: https://github.com/Incendo/cloud/blob/a4cc749b91564af57bb7bba36dd8011b556c2b3a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricExecutor.java#L94-L173 */ - // todo: full localization private final List> exceptionHandlers = List.of( new ExceptionHandler<>(InvalidSyntaxException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_syntax", e.getCorrectSyntax())), new ExceptionHandler<>(InvalidCommandSenderException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_sender", e.getCommandSender().getClass().getSimpleName(), e.getRequiredSender().getSimpleName())), From 3ec2125d3f2b3c7fdec43c7d136ec5a3558bf721 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Fri, 8 Sep 2023 01:45:06 -0400 Subject: [PATCH 33/76] Stop Bedrock commands from appearing in Java players' brig suggestions move bedrock-only and player-only checks to the permission check, instead of being a postprocessor --- .../geyser/command/CommandRegistry.java | 32 +++++++++- .../geyser/command/GeyserCommand.java | 17 +++++- .../geyser/command/SourceTypeProcessor.java | 58 ------------------- .../BedrockCommandRequestTranslator.java | 1 + 4 files changed, 45 insertions(+), 63 deletions(-) delete mode 100644 core/src/main/java/org/geysermc/geyser/command/SourceTypeProcessor.java diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java index 295dd2c47..d6c0be459 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java @@ -26,6 +26,7 @@ package org.geysermc.geyser.command; import cloud.commandframework.CommandManager; +import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.exceptions.ArgumentParseException; import cloud.commandframework.exceptions.CommandExecutionException; import cloud.commandframework.exceptions.InvalidCommandSenderException; @@ -33,6 +34,7 @@ import cloud.commandframework.exceptions.InvalidSyntaxException; import cloud.commandframework.exceptions.NoPermissionException; import cloud.commandframework.exceptions.NoSuchCommandException; import cloud.commandframework.execution.CommandExecutionCoordinator; +import cloud.commandframework.meta.CommandMeta; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import lombok.AllArgsConstructor; import org.checkerframework.checker.nullness.qual.NonNull; @@ -59,6 +61,7 @@ import org.geysermc.geyser.command.defaults.VersionCommand; import org.geysermc.geyser.event.GeyserEventRegistrar; import org.geysermc.geyser.event.type.GeyserDefineCommandsEventImpl; import org.geysermc.geyser.extension.command.GeyserExtensionCommand; +import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; import org.jetbrains.annotations.NotNull; @@ -66,6 +69,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.CompletionException; import java.util.function.BiConsumer; @@ -86,7 +90,7 @@ public class CommandRegistry { private final List> exceptionHandlers = List.of( new ExceptionHandler<>(InvalidSyntaxException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_syntax", e.getCorrectSyntax())), new ExceptionHandler<>(InvalidCommandSenderException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_sender", e.getCommandSender().getClass().getSimpleName(), e.getRequiredSender().getSimpleName())), - new ExceptionHandler<>(NoPermissionException.class, (src, e) -> src.sendLocaleString("geyser.command.permission_fail")), + new ExceptionHandler<>(NoPermissionException.class, this::handleNoPermission), new ExceptionHandler<>(NoSuchCommandException.class, (src, e) -> src.sendLocaleString("geyser.command.not_found")), new ExceptionHandler<>(ArgumentParseException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_argument", e.getCause().getMessage())), new ExceptionHandler<>(CommandExecutionException.class, (src, e) -> handleUnexpectedThrowable(src, e.getCause())) @@ -96,8 +100,6 @@ public class CommandRegistry { this.geyser = geyser; this.cloud = cloud; - // Restricts command source types from executing commands they don't have access to - cloud.registerCommandPostProcessor(new SourceTypeProcessor()); // Override the default exception handlers that the typical cloud implementations provide so that we can perform localization. // This is kind of meaningless for our Geyser-Standalone implementation since these handlers are the default exception handlers in that case. for (ExceptionHandler handler : exceptionHandlers) { @@ -242,6 +244,30 @@ public class CommandRegistry { handleUnexpectedThrowable(source, throwable); } + private void handleNoPermission(GeyserCommandSource source, NoPermissionException exception) { + // we basically recheck bedrock-only and player-only to see if they were the cause of this + + // find the Command and its Meta that the source tried executing + List> argumentChain = exception.getCurrentChain(); + CommandArgument argument = argumentChain.get(argumentChain.size() - 1); + CommandMeta meta = Objects.requireNonNull(argument.getOwningCommand()).getCommandMeta(); + + // See GeyserCommand#baseBuilder() + if (meta.getOrDefault(GeyserCommand.BEDROCK_ONLY, false)) { + if (source.connection().isEmpty()) { + source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.command.bedrock_only", source.locale())); + return; + } + } else if (meta.getOrDefault(GeyserCommand.PLAYER_ONLY, false)) { + if (source.isConsole()) { + source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.command.player_only", source.locale())); + return; + } + } + + source.sendLocaleString("geyser.command.permission_fail"); + } + private void handleUnexpectedThrowable(GeyserCommandSource source, Throwable throwable) { source.sendLocaleString("command.failed"); // java edition translation key GeyserImpl.getInstance().getLogger().error("Exception while executing command handler", throwable); diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java index dd7b19b33..c9059c399 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java @@ -162,13 +162,26 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C /** * Creates a new command builder with {@link #rootCommand()}, {@link #name()}, and {@link #aliases()} built on it. - * The Applicable from {@link #meta()} is also applied to the builder. + * A permission predicate that takes into account {@link #permission()}, {@link #isBedrockOnly()}, and {@link #isExecutableOnConsole()} + * is applied. The Applicable from {@link #meta()} is also applied to the builder. */ @Contract(value = "_ -> new", pure = true) public final Command.Builder baseBuilder(CommandManager manager) { return manager.commandBuilder(rootCommand()) .literal(name, aliases.toArray(new String[0])) - .permission(permission) + .permission(source -> { + if (bedrockOnly) { + if (source.connection().isEmpty()) { + return false; + } + // connection is present -> it is a player -> executableOnConsole is irrelevant + } else if (!executableOnConsole) { + if (source.isConsole()) { + return false; // not executable on console but is console + } + } + return manager.hasPermission(source, permission); + }) .apply(meta()); } diff --git a/core/src/main/java/org/geysermc/geyser/command/SourceTypeProcessor.java b/core/src/main/java/org/geysermc/geyser/command/SourceTypeProcessor.java deleted file mode 100644 index 7088bc5fe..000000000 --- a/core/src/main/java/org/geysermc/geyser/command/SourceTypeProcessor.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2019-2023 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.command; - -import cloud.commandframework.execution.postprocessor.CommandPostprocessingContext; -import cloud.commandframework.execution.postprocessor.CommandPostprocessor; -import cloud.commandframework.meta.CommandMeta; -import cloud.commandframework.services.types.ConsumerService; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.text.ChatColor; -import org.geysermc.geyser.text.GeyserLocale; - -public class SourceTypeProcessor implements CommandPostprocessor { - - @Override - public void accept(@NonNull CommandPostprocessingContext processContext) { - CommandMeta meta = processContext.getCommand().getCommandMeta(); - GeyserCommandSource source = processContext.getCommandContext().getSender(); - - if (meta.getOrDefault(GeyserCommand.BEDROCK_ONLY, false)) { - if (source.connection().isEmpty()) { - source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.command.bedrock_only", source.locale())); - ConsumerService.interrupt(); - } - } else if (meta.getOrDefault(GeyserCommand.PLAYER_ONLY, false)) { - // it should be fine to use else-if here, because if the command is bedrock-only, - // performing the bedrock player check is also inherently a player check - - if (source.playerUuid().isEmpty()) { - source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.command.player_only", source.locale())); - ConsumerService.interrupt(); - } - } - } -} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java index 699105cb6..bd51fda86 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java @@ -53,6 +53,7 @@ public class BedrockCommandRequestTranslator extends PacketTranslator Date: Tue, 12 Sep 2023 20:23:37 -0400 Subject: [PATCH 34/76] Reimplement command descriptions --- .../platform/spigot/GeyserSpigotPlugin.java | 4 +- .../spigot/command/SpigotCommandRegistry.java | 81 +++++++++++++++++++ .../geyser/command/CommandRegistry.java | 57 ++++++++++--- .../geyser/command/GeyserCommand.java | 6 +- .../protocol/java/JavaCommandsTranslator.java | 6 +- core/src/main/resources/languages | 2 +- 6 files changed, 139 insertions(+), 17 deletions(-) create mode 100644 bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandRegistry.java 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 2ed92cbe3..c106e0982 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 @@ -58,6 +58,7 @@ import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; +import org.geysermc.geyser.platform.spigot.command.SpigotCommandRegistry; import org.geysermc.geyser.platform.spigot.command.SpigotCommandSource; import org.geysermc.geyser.platform.spigot.world.GeyserPistonListener; import org.geysermc.geyser.platform.spigot.world.GeyserSpigotBlockPlaceListener; @@ -73,7 +74,6 @@ import java.io.IOException; import java.net.SocketAddress; import java.nio.file.Path; import java.util.List; -import java.util.Map; import java.util.UUID; import java.util.logging.Level; @@ -187,7 +187,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { } } - this.commandRegistry = new CommandRegistry(geyser, cloud); // todo: reimplement subclass for command descriptions + this.commandRegistry = new SpigotCommandRegistry(geyser, cloud); if (!INITIALIZED) { // Needs to be an anonymous inner class otherwise Bukkit complains about missing classes diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandRegistry.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandRegistry.java new file mode 100644 index 000000000..0363b9c27 --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandRegistry.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2019-2023 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 cloud.commandframework.CommandManager; +import org.bukkit.Bukkit; +import org.bukkit.Server; +import org.bukkit.command.Command; +import org.bukkit.command.CommandMap; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandRegistry; +import org.geysermc.geyser.command.GeyserCommandSource; + +import java.lang.reflect.Field; + +public class SpigotCommandRegistry extends CommandRegistry { + + private final CommandMap commandMap; + + public SpigotCommandRegistry(GeyserImpl geyser, CommandManager cloud) { + super(geyser, cloud); + + CommandMap commandMap = null; + try { + // 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 (Exception ex) { + geyser.getLogger().error("Failed to get Spigot's CommandMap", ex); + } + } + this.commandMap = commandMap; + } + + @NonNull + @Override + public String description(@NonNull String command, @NonNull String locale) { + // check if the command is /geyser or an extension command so that we can localize the description + String description = super.description(command, locale); + if (!description.isBlank()) { + return description; + } + + if (commandMap != null) { + Command cmd = commandMap.getCommand(command); + if (cmd != null) { + return cmd.getDescription(); + } + } + return ""; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java index d6c0be459..76beb7a6d 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java @@ -78,9 +78,24 @@ public class CommandRegistry { private final GeyserImpl geyser; private final CommandManager cloud; + /** + * Map of Geyser subcommands to their Commands + */ private final Map commands = new Object2ObjectOpenHashMap<>(13); + + /** + * Map of Extensions to Maps of their subcommands + */ private final Map> extensionCommands = new Object2ObjectOpenHashMap<>(0); + /** + * Map of root commands (that are for extensions) to Extensions + */ + private final Map extensionRootCommands = new Object2ObjectOpenHashMap<>(0); + + /** + * Map of permission nodes to their default values + */ private final Map permissions = new Object2ObjectOpenHashMap<>(13); /** @@ -122,7 +137,7 @@ public class CommandRegistry { registerBuiltInCommand(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop")); } - if (this.geyser.extensionManager().extensions().size() > 0) { + if (!this.geyser.extensionManager().extensions().isEmpty()) { registerBuiltInCommand(new ExtensionsCommand(this.geyser, "extensions", "geyser.commands.extensions.desc", "geyser.command.extensions")); } @@ -140,10 +155,21 @@ public class CommandRegistry { this.geyser.eventBus().fire(defineCommandsEvent); - // Register help commands for all extensions with commands for (Map.Entry> entry : this.extensionCommands.entrySet()) { - String id = entry.getKey().description().id(); - registerExtensionCommand(entry.getKey(), new HelpCommand(this.geyser, "help", "geyser.commands.exthelp.desc", "geyser.command.exthelp." + id, id, entry.getValue())); + Extension extension = entry.getKey(); + + // Register this extension's root command + extensionRootCommands.put(extension.rootCommand(), extension); + + // Register help commands for all extensions with commands + String id = extension.description().id(); + registerExtensionCommand(extension, new HelpCommand( + this.geyser, + "help", + "geyser.commands.exthelp.desc", + "geyser.command.exthelp." + id, + extension.rootCommand(), + entry.getValue())); } // wait for the right moment (depends on the platform) to register permissions @@ -206,12 +232,21 @@ public class CommandRegistry { /** * Returns the description of the given command * - * @param command Command to get the description for - * @return Command description + * @param command the root command node + * @param locale the ideal locale that the description should be in + * @return a description if found, otherwise an empty string. The locale is not guaranteed. */ - public String description(String command) { - // todo: the commands contained in this registry store their descriptions, so those should be checked - return ""; // todo: reimplement + @NonNull + public String description(@NonNull String command, @NonNull String locale) { + if (command.equals(GeyserCommand.DEFAULT_ROOT_COMMAND)) { + return GeyserLocale.getPlayerLocaleString("geyser.command.geyser_desc", locale); + } + + Extension extension = extensionRootCommands.get(command); + if (extension != null) { + return GeyserLocale.getPlayerLocaleString("geyser.command.extension_desc", locale, extension.description().name()); + } + return ""; } /** @@ -276,8 +311,8 @@ public class CommandRegistry { @AllArgsConstructor private class ExceptionHandler { - private final Class type; - private final BiConsumer handler; + final Class type; + final BiConsumer handler; @SuppressWarnings("unchecked") boolean handle(GeyserCommandSource source, Exception exception) { diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java index c9059c399..c1b0db7f7 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java @@ -32,6 +32,7 @@ import cloud.commandframework.meta.CommandMeta; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.util.TriState; +import org.geysermc.geyser.text.GeyserLocale; import org.jetbrains.annotations.Contract; import java.util.Collections; @@ -39,6 +40,8 @@ import java.util.List; public abstract class GeyserCommand implements org.geysermc.geyser.api.command.Command { + public static final String DEFAULT_ROOT_COMMAND = "geyser"; + /** * CommandMeta to indicate that the command is only available to bedrock players. default of false. */ @@ -157,7 +160,7 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C * @return the first (literal) argument of this command, which comes before {@link #name()}. */ public String rootCommand() { - return "geyser"; + return DEFAULT_ROOT_COMMAND; } /** @@ -191,6 +194,7 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C */ public Command.Builder.Applicable meta() { return builder -> builder + .meta(CommandMeta.DESCRIPTION, GeyserLocale.getLocaleStringLog(description)) .meta(BEDROCK_ONLY, isBedrockOnly()) .meta(PLAYER_ONLY, !isExecutableOnConsole()); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java index 771028b1e..65f3361b3 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java @@ -144,8 +144,10 @@ public class JavaCommandsTranslator extends PacketTranslator new HashSet<>()).add(node.getName().toLowerCase()); + String name = node.getName().toLowerCase(Locale.ROOT); + String description = registry.description(name, session.locale()); + BedrockCommandInfo info = new BedrockCommandInfo(name, description, params); + commands.computeIfAbsent(info, $ -> new HashSet<>()).add(name); } var eventBus = session.getGeyser().eventBus(); diff --git a/core/src/main/resources/languages b/core/src/main/resources/languages index ad99e5d76..764f7ac3b 160000 --- a/core/src/main/resources/languages +++ b/core/src/main/resources/languages @@ -1 +1 @@ -Subproject commit ad99e5d76c6b6fe543b7ac6ec98105c69e905311 +Subproject commit 764f7ac3bfba91beedd3e39196caf83bc9a1a546 From 2aa2621adc634761ce54edab9cbac36c9e820afe Mon Sep 17 00:00:00 2001 From: Konicai <71294714+konicai@users.noreply.github.com> Date: Thu, 14 Sep 2023 13:24:42 -0400 Subject: [PATCH 35/76] javadocs --- .../geysermc/geyser/api/command/Command.java | 44 +++++++++---------- .../geyser/api/command/CommandSource.java | 6 ++- .../lifecycle/GeyserDefineCommandsEvent.java | 2 +- ...GeyserRegisterPermissionCheckersEvent.java | 2 +- .../GeyserRegisterPermissionsEvent.java | 5 ++- .../geyser/api/extension/Extension.java | 3 +- .../api/permission/PermissionChecker.java | 12 +++++ .../platform/fabric/GeyserFabricMod.java | 7 ++- .../GeyserStandaloneCommandManager.java | 18 ++++++-- .../standalone/GeyserStandaloneLogger.java | 2 + .../standalone/gui/GeyserStandaloneGUI.java | 13 +++--- .../src/main/resources/permissions.yml | 6 +-- .../geyser/command/CommandRegistry.java | 10 ++--- .../command/CommandSourceConverter.java | 3 +- .../geyser/command/GeyserCommand.java | 2 +- .../geyser/command/GeyserCommandSource.java | 3 +- .../command/GeyserExtensionCommand.java | 2 +- .../BedrockCommandRequestTranslator.java | 4 +- 18 files changed, 87 insertions(+), 57 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/command/Command.java b/api/src/main/java/org/geysermc/geyser/api/command/Command.java index 957002b7d..aeb2cf857 100644 --- a/api/src/main/java/org/geysermc/geyser/api/command/Command.java +++ b/api/src/main/java/org/geysermc/geyser/api/command/Command.java @@ -61,7 +61,7 @@ public interface Command { * Gets the permission node associated with * this command. * - * @return the permission node for this command + * @return the permission node for this command if defined, otherwise an empty string */ @NonNull String permission(); @@ -78,6 +78,7 @@ public interface Command { * Gets if this command is designed to be used only by server operators. * * @return if this command is designated to be used only by server operators. + * @deprecated this method is not guaranteed to provide meaningful or expected results. */ @Deprecated(forRemoval = true) default boolean isSuggestedOpOnly() { @@ -99,11 +100,7 @@ public interface Command { boolean isBedrockOnly(); /** - * Gets the subcommands associated with this - * command. Mainly used within the Geyser Standalone - * GUI to know what subcommands are supported. - * - * @return the subcommands associated with this command + * @deprecated this method will always return an empty list */ @Deprecated(forRemoval = true) @NonNull @@ -133,7 +130,7 @@ public interface Command { * is an instance of this source. * * @param sourceType the source type - * @return the builder + * @return this builder */ Builder source(@NonNull Class sourceType); @@ -141,7 +138,7 @@ public interface Command { * Sets the command name. * * @param name the command name - * @return the builder + * @return this builder */ Builder name(@NonNull String name); @@ -149,26 +146,27 @@ public interface Command { * Sets the command description. * * @param description the command description - * @return the builder + * @return this builder */ Builder description(@Nullable String description); /** - * Sets the permission node. + * Sets the permission node required to run this command.
+ * It will not be registered anywhere, unlike {@link #permission(String, TriState)}. * * @param permission the permission node - * @return the builder + * @return this builder */ Builder permission(@Nullable String permission); /** - * Sets the permission node, and its default value. The usage of the default value is platform dependant - * and may or may not be used. Extensions may instead listen for {@link GeyserRegisterPermissionsEvent} - * to register permissions. + * Sets the permission node and its default value. The usage of the default value is platform dependant + * and may or may not be used.
+ * Extensions may instead listen for {@link GeyserRegisterPermissionsEvent} to register permissions. * * @param permission the permission node * @param defaultValue the node's default value - * @return the builder + * @return this builder */ Builder permission(@Nullable String permission, @NonNull TriState defaultValue); @@ -176,7 +174,7 @@ public interface Command { * Sets the aliases. * * @param aliases the aliases - * @return the builder + * @return this builder */ Builder aliases(@Nullable List aliases); @@ -184,7 +182,8 @@ public interface Command { * Sets if this command is designed to be used only by server operators. * * @param suggestedOpOnly if this command is designed to be used only by server operators - * @return the builder + * @return this builder + * @deprecated this method is not guaranteed to produce meaningful or expected results */ @Deprecated(forRemoval = true) Builder suggestedOpOnly(boolean suggestedOpOnly); @@ -193,7 +192,7 @@ public interface Command { * Sets if this command is executable on console. * * @param executableOnConsole if this command is executable on console - * @return the builder + * @return this builder */ Builder executableOnConsole(boolean executableOnConsole); @@ -201,7 +200,7 @@ public interface Command { * Sets if this command is bedrock only. * * @param bedrockOnly if this command is bedrock only - * @return the builder + * @return this builder */ Builder bedrockOnly(boolean bedrockOnly); @@ -209,7 +208,8 @@ public interface Command { * Sets the subcommands. * * @param subCommands the subcommands - * @return the builder + * @return this builder + * @deprecated this method has no effect */ @Deprecated(forRemoval = true) default Builder subCommands(@NonNull List subCommands) { @@ -220,14 +220,14 @@ public interface Command { * Sets the {@link CommandExecutor} for this command. * * @param executor the command executor - * @return the builder + * @return this builder */ Builder executor(@NonNull CommandExecutor executor); /** * Builds the command. * - * @return the command + * @return a new command from this builder */ @NonNull Command build(); diff --git a/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java b/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java index f20fd0f94..0041872b0 100644 --- a/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java +++ b/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java @@ -69,10 +69,14 @@ public interface CommandSource { boolean isConsole(); /** - * todo: commands + * @return an Optional containing a Java UUID if this source represents any player, otherwise empty */ Optional playerUuid(); + /** + * @return an Optional with a present Connection if this source represents a Bedrock player that is connected + * to this Geyser instance. Otherwise, returns an empty optional. + */ Optional connection(); /** diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java index a0790e2dd..1db64a6c7 100644 --- a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java +++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java @@ -50,7 +50,7 @@ public interface GeyserDefineCommandsEvent extends Event { /** * Gets all the registered built-in {@link Command}s. * - * @return all the registered built-in commands as an unmodifiable map. + * @return all the registered built-in commands as an unmodifiable map */ @NonNull Map commands(); diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionCheckersEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionCheckersEvent.java index 264344d98..25349a0cb 100644 --- a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionCheckersEvent.java +++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionCheckersEvent.java @@ -31,7 +31,7 @@ import org.geysermc.geyser.api.permission.PermissionChecker; /** * Fired by any permission manager implementations that wish to add support for custom permission checking. - * This event is not guaranteed to be fired. + * This event is not guaranteed to be fired - it is currently only fired on Geyser-Standalone. *

* Subscribing to this event with an earlier {@link PostOrder} and registering a {@link PermissionChecker} * will result in that checker having a higher priority than others. diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java index 7a18d6302..27e256ea3 100644 --- a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java +++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java @@ -29,12 +29,13 @@ import org.geysermc.event.Event; import org.geysermc.geyser.api.util.TriState; /** - * Fired by any implementations that want to gather permission nodes and defaults + * Fired by anything that wishes to gather permission nodes and defaults. + * This event is not guaranteed to be fired, as certain Geyser platforms do not have a native permission system. */ public interface GeyserRegisterPermissionsEvent extends Event { /** - * Registers a permission node with the permission system being used by Geyser's command manager. + * Registers a permission node and its default value with the firer. * * @param permission the permission node to register * @param defaultValue the default value of the node diff --git a/api/src/main/java/org/geysermc/geyser/api/extension/Extension.java b/api/src/main/java/org/geysermc/geyser/api/extension/Extension.java index ee3ac640c..1eacfea9a 100644 --- a/api/src/main/java/org/geysermc/geyser/api/extension/Extension.java +++ b/api/src/main/java/org/geysermc/geyser/api/extension/Extension.java @@ -108,7 +108,8 @@ public interface Extension extends EventRegistrar { } /** - * todo: commands + * @return the root command that all of this extension's commands will stem from. + * By default, this is the extension's id. */ @NonNull default String rootCommand() { diff --git a/api/src/main/java/org/geysermc/geyser/api/permission/PermissionChecker.java b/api/src/main/java/org/geysermc/geyser/api/permission/PermissionChecker.java index 4805243c6..c0d4af2f4 100644 --- a/api/src/main/java/org/geysermc/geyser/api/permission/PermissionChecker.java +++ b/api/src/main/java/org/geysermc/geyser/api/permission/PermissionChecker.java @@ -29,9 +29,21 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.command.CommandSource; import org.geysermc.geyser.api.util.TriState; +/** + * Something capable of checking if a {@link CommandSource} has a permission + */ @FunctionalInterface public interface PermissionChecker { + /** + * Checks if the given source has a permission + * + * @param source the {@link CommandSource} whose permissions should be queried + * @param permission the permission node to check + * @return a {@link TriState} as the value of the node. {@link TriState#NOT_SET} generally means that the permission + * node itself was not found, and the source does not have such permission. + * {@link TriState#TRUE} and {@link TriState#FALSE} represent explicitly set values. + */ @NonNull TriState hasPermission(@NonNull CommandSource source, @NonNull String permission); } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java index e6f07c13b..e86932c3f 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java @@ -28,7 +28,6 @@ package org.geysermc.geyser.platform.fabric; import cloud.commandframework.CommandManager; import cloud.commandframework.execution.CommandExecutionCoordinator; import cloud.commandframework.fabric.FabricServerCommandManager; -import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.fabricmc.api.EnvType; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; @@ -39,6 +38,8 @@ import net.minecraft.commands.CommandSourceStack; import net.minecraft.server.MinecraftServer; import net.minecraft.world.entity.player.Player; import org.apache.logging.log4j.LogManager; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; @@ -55,8 +56,6 @@ import org.geysermc.geyser.platform.fabric.command.FabricCommandSource; import org.geysermc.geyser.platform.fabric.world.GeyserFabricWorldManager; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; @@ -208,7 +207,7 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { } @SuppressWarnings("ConstantConditions") // Certain IDEA installations think that ip cannot be null - @NotNull + @NonNull @Override public String getServerBindAddress() { String ip = this.server.getLocalIp(); diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java index 390ba26d6..c76104262 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java @@ -49,11 +49,21 @@ import java.util.Set; public class GeyserStandaloneCommandManager extends CommandManager { private final GeyserImpl geyser; + + /** + * The checkers we use to test if a command source has a permission + */ private final List permissionCheckers = new ArrayList<>(); + + /** + * Any permissions that all connections have + */ private final Set basePermissions = new ObjectOpenHashSet<>(); public GeyserStandaloneCommandManager(GeyserImpl geyser) { super(CommandExecutionCoordinator.simpleCoordinator(), CommandRegistrationHandler.nullCommandRegistrationHandler()); + // simpleCoordinator: execute commands immediately on the calling thread. + // nullCommandRegistrationHandler: cloud is not responsible for handling our CommandRegistry, which is fairly decoupled. this.geyser = geyser; // allow any extensions to customize permissions @@ -66,8 +76,7 @@ public class GeyserStandaloneCommandManager extends CommandManager { if (def == TriState.TRUE) { basePermissions.add(permission); - } else if (def == TriState.FALSE) { - basePermissions.remove(permission); // todo: maybe remove this case? } }); } @@ -101,7 +108,10 @@ public class GeyserStandaloneCommandManager extends CommandManager executor.execute(() -> registry.runCommand(logger, cmd)); + commandListener.dispatcher = cmd -> executor.execute(() -> registry.runCommand(logger, cmd)); commandInput.setEnabled(true); commandInput.requestFocusInWindow(); } @@ -343,14 +342,14 @@ public class GeyserStandaloneGUI { private class CommandListener implements ActionListener { - private Consumer handler; + private Consumer dispatcher; @Override public void actionPerformed(ActionEvent e) { // the headless variant of Standalone strips trailing whitespace for us - we need to manually String command = commandInput.getText().stripTrailing(); appendConsole(command + "\n"); // show what was run in the console - handler.accept(command); // run the command + dispatcher.accept(command); // run the command commandInput.setText(""); // clear the input } } diff --git a/bootstrap/standalone/src/main/resources/permissions.yml b/bootstrap/standalone/src/main/resources/permissions.yml index c7a135140..d6e3d8406 100644 --- a/bootstrap/standalone/src/main/resources/permissions.yml +++ b/bootstrap/standalone/src/main/resources/permissions.yml @@ -1,9 +1,9 @@ -# Add any permissions here that all players should have. +# Add any permissions here that all connections should have. # Permissions for builtin geyser commands do not have to be listed here. -# If an extension/plugin hooks into the permission system of Geyser-Standalone, entries here may be ignored/overridden. -# If extensions don't register their permissions, they must be added here manually. +# If an extension/plugin registers their permissions with default values, entries here are typically unnecessary. +# If extensions don't register their permissions, permissions that everyone should have must be added here manually. default-permissions: - geyser.command.help # this is unnecessary diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java index 76beb7a6d..87c905e2c 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java @@ -94,9 +94,9 @@ public class CommandRegistry { private final Map extensionRootCommands = new Object2ObjectOpenHashMap<>(0); /** - * Map of permission nodes to their default values + * Map containing only permissions that have been registered with a default value */ - private final Map permissions = new Object2ObjectOpenHashMap<>(13); + private final Map permissionDefaults = new Object2ObjectOpenHashMap<>(13); /** * The order and behaviour of these exception handlers is designed to mirror the typical cloud implementations. @@ -194,7 +194,7 @@ public class CommandRegistry { public void clear() { this.commands.clear(); this.extensionCommands.clear(); - this.permissions.clear(); + this.permissionDefaults.clear(); } /** @@ -219,12 +219,12 @@ public class CommandRegistry { } if (!command.permission().isBlank() && command.permissionDefault() != null) { - permissions.put(command.permission(), command.permissionDefault()); + permissionDefaults.put(command.permission(), command.permissionDefault()); } } private void onRegisterPermissions(GeyserRegisterPermissionsEvent event) { - for (Map.Entry permission : permissions.entrySet()) { + for (Map.Entry permission : permissionDefaults.entrySet()) { event.register(permission.getKey(), permission.getValue()); } } diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java b/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java index 9d87caf9c..7dc76701f 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java @@ -56,6 +56,7 @@ public record CommandSourceConverter(Class senderType, } if (!(source instanceof GeyserSession)) { + // todo cloud remove this GeyserImpl.getInstance().getLogger().warning("Falling back to UUID for command sender lookup for a command source that is not a GeyserSession: " + source); Thread.dumpStack(); } @@ -68,7 +69,7 @@ public record CommandSourceConverter(Class senderType, /** * Creates a new CommandSourceConverter for a server platform - * in which the player type is not the command sender type, and must be mapped. + * in which the player type is not a command sender type, and must be mapped. * * @param senderType class of the command sender type * @param playerLookup function for looking up a player by UUID diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java index c1b0db7f7..bd9322f41 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java @@ -215,4 +215,4 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C * @param context the context with which this command should be executed */ public abstract void execute(CommandContext context); -} \ No newline at end of file +} diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java index cec89bf9b..7bd1fb4ca 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java @@ -65,7 +65,8 @@ public interface GeyserCommandSource extends CommandSource { } /** - * todo: commands + * @return the underlying platform handle that this source represents. + * If such handle doesn't exist, this itself is returned. */ default Object handle() { return this; diff --git a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java index a4d51f6fd..67afd9391 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java @@ -202,7 +202,7 @@ public abstract class GeyserExtensionCommand extends GeyserCommand { // currently, the only subclass of CommandSource exposed in the api is GeyserConnection. // when this command was registered, we enabled bedrockOnly if the sourceType was a GeyserConnection. - // as a result, SenderTypeProcessor should handle that case and this method shouldn't even be reached. + // as a result, the permission checker should handle that case and this method shouldn't even be reached. source.sendMessage("You must be a " + sourceType.getSimpleName() + " to run this command."); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java index bd51fda86..497377d05 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java @@ -49,11 +49,11 @@ public class BedrockCommandRequestTranslator extends PacketTranslator 0) { String root = args[0]; - // todo: do we want to pass the command to the server + // todo cloud do we want to pass the command to the server // if cloud gives a NoSuchCommandException? might be more accurate. CommandRegistry registry = GeyserImpl.getInstance().commandRegistry(); if (registry.cloud().rootCommands().contains(root)) { - // todo: cloud might not like the trailing whitespace either + // todo cloud might not like the trailing whitespace either registry.runCommand(session, strippedCommand); return; // don't pass the command to the java server } From 6b22cf707d64dad2ada1d949bf4b8d8a4f417e13 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Fri, 15 Sep 2023 07:43:35 -0400 Subject: [PATCH 36/76] cloud 1.8.4 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4af88cbfd..04c44b393 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,7 +25,7 @@ terminalconsoleappender = "1.2.0" folia = "1.19.4-R0.1-SNAPSHOT" viaversion = "4.0.0" adapters = "1.9-SNAPSHOT" -cloud = "1.8.3" +cloud = "1.8.4" commodore = "2.2" bungeecord = "a7c6ede" velocity = "3.1.1" From 53140047ec219300ba425aea107b684064f9006b Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Mon, 18 Sep 2023 23:02:35 -0400 Subject: [PATCH 37/76] Strip trailing whitespace for cloud on Standalone as well --- .../bedrock/BedrockCommandRequestTranslator.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java index 497377d05..341762e6a 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java @@ -40,12 +40,18 @@ public class BedrockCommandRequestTranslator extends PacketTranslator 0) { String root = args[0]; @@ -54,7 +60,7 @@ public class BedrockCommandRequestTranslator extends PacketTranslator Date: Mon, 18 Sep 2023 23:23:56 -0400 Subject: [PATCH 38/76] Fix usage of HelpCommand for extensions --- .../geyser/command/defaults/HelpCommand.java | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java index 083290149..f4cc309da 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java @@ -41,25 +41,32 @@ import java.util.Comparator; import java.util.Map; public class HelpCommand extends GeyserCommand { - private final String baseCommand; + private final String rootCommand; private final Collection commands; public HelpCommand(GeyserImpl geyser, String name, String description, String permission, - String baseCommand, Map commands) { + String rootCommand, Map commands) { super(name, description, permission, TriState.TRUE); - this.baseCommand = baseCommand; + this.rootCommand = rootCommand; this.commands = commands.values(); this.aliases = Collections.singletonList("?"); } + @Override + public String rootCommand() { + return rootCommand; + } + @Override public void register(CommandManager manager) { super.register(manager); - // Also register just `/geyser` - manager.command(manager.commandBuilder(rootCommand()) + // Also register just the root (ie `/geyser` or `/extensionId`) + // note: this doesn't do the other permission checks that GeyserCommand#baseBuilder does, + // but it's fine because the help command can be executed by non-bedrock players and by the console. + manager.command(manager.commandBuilder(rootCommand) .permission(permission()) - .apply(meta()) + .apply(meta()) // shouldn't be necessary - just for consistency .handler(this::execute)); } @@ -71,16 +78,18 @@ public class HelpCommand extends GeyserCommand { // todo: pagination int page = 1; int maxPage = 1; - String translationKey = this.baseCommand.equals("geyser") ? "geyser.commands.help.header" : "geyser.commands.extensions.header"; + String translationKey = this.rootCommand.equals(GeyserCommand.DEFAULT_ROOT_COMMAND) ? "geyser.commands.help.header" : "geyser.commands.extensions.header"; String header = GeyserLocale.getPlayerLocaleString(translationKey, source.locale(), page, maxPage); source.sendMessage(header); this.commands.stream() .distinct() // remove aliases - .sorted(Comparator.comparing(Command::name)) - .filter(cmd -> source.hasPermission(cmd.permission())) .filter(cmd -> !cmd.isBedrockOnly() || bedrockPlayer) // remove bedrock only commands if not a bedrock player - .forEach(cmd -> source.sendMessage(ChatColor.YELLOW + "/" + baseCommand + " " + cmd.name() + ChatColor.WHITE + ": " + - GeyserLocale.getPlayerLocaleString(cmd.description(), source.locale()))); + .filter(cmd -> source.hasPermission(cmd.permission())) + .sorted(Comparator.comparing(Command::name)) + .forEach(cmd -> { + String description = GeyserLocale.getPlayerLocaleString(cmd.description(), source.locale()); + source.sendMessage(ChatColor.YELLOW + "/" + rootCommand + " " + cmd.name() + ChatColor.WHITE + ": " + description); + }); } } From 7580b8b3a7b4d76c22904a266fe5d9816f9126db Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Mon, 18 Sep 2023 23:35:17 -0400 Subject: [PATCH 39/76] address some simple reviews --- .../api/event/lifecycle/GeyserRegisterPermissionsEvent.java | 3 +++ bootstrap/standalone/src/main/resources/permissions.yml | 4 ++-- .../java/org/geysermc/geyser/command/GeyserCommand.java | 6 +++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java index 27e256ea3..209ef3c5d 100644 --- a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java +++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java @@ -30,7 +30,10 @@ import org.geysermc.geyser.api.util.TriState; /** * Fired by anything that wishes to gather permission nodes and defaults. + *

* This event is not guaranteed to be fired, as certain Geyser platforms do not have a native permission system. + * It can be expected to fire on Geyser-Spigot and Geyser-Standalone. + * It may still be fired on other platforms due to a 3rd party. */ public interface GeyserRegisterPermissionsEvent extends Event { diff --git a/bootstrap/standalone/src/main/resources/permissions.yml b/bootstrap/standalone/src/main/resources/permissions.yml index d6e3d8406..4da9251e8 100644 --- a/bootstrap/standalone/src/main/resources/permissions.yml +++ b/bootstrap/standalone/src/main/resources/permissions.yml @@ -1,6 +1,6 @@ -# Add any permissions here that all connections should have. -# Permissions for builtin geyser commands do not have to be listed here. +# Add any permissions here that all players should have. +# Permissions for builtin Geyser commands do not have to be listed here. # If an extension/plugin registers their permissions with default values, entries here are typically unnecessary. # If extensions don't register their permissions, permissions that everyone should have must be added here manually. diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java index bd9322f41..226ab9c6b 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java @@ -43,12 +43,12 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C public static final String DEFAULT_ROOT_COMMAND = "geyser"; /** - * CommandMeta to indicate that the command is only available to bedrock players. default of false. + * CommandMeta to indicate that the command is only available to bedrock players. Default of false. */ public static final CommandMeta.Key BEDROCK_ONLY = CommandMeta.Key.of(Boolean.class, "bedrock-only", meta -> false); /** - * CommandMeta to indicate that the command is only available to players. default of false. + * CommandMeta to indicate that the command is only available to players. Default of false. */ public static final CommandMeta.Key PLAYER_ONLY = CommandMeta.Key.of(Boolean.class, "player-only", meta -> false); @@ -113,7 +113,7 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C this.bedrockOnly = bedrockOnly; } - public GeyserCommand(@NonNull String name, @NonNull String description, @Nullable String permission, @NonNull TriState permissionDefault) { + public GeyserCommand(@NonNull String name, @NonNull String description, @Nullable String permission, @Nullable TriState permissionDefault) { this(name, description, permission, permissionDefault, true, false); } From e9d4802a478e62dc93838d5bd511d795b0f24f0a Mon Sep 17 00:00:00 2001 From: Konicai <71294714+konicai@users.noreply.github.com> Date: Fri, 29 Sep 2023 18:28:14 -0400 Subject: [PATCH 40/76] 2.2.0 -> 2.3.0 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index b6425f76d..9309481a1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,5 +7,5 @@ org.gradle.vfs.watch=false group=org.geysermc id=geyser -version=2.2.0-SNAPSHOT +version=2.3.0-SNAPSHOT description=Allows for players from Minecraft: Bedrock Edition to join Minecraft: Java Edition servers. \ No newline at end of file From d97ed142f416581a153a832fa1171c3c2f626228 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+konicai@users.noreply.github.com> Date: Fri, 29 Sep 2023 18:51:17 -0400 Subject: [PATCH 41/76] Don't explicitly clear the CommandRegistry Discarding old registries is fine, and clearing one doesn't remove the registrations with cloud anyway. --- core/src/main/java/org/geysermc/geyser/GeyserImpl.java | 4 +++- .../java/org/geysermc/geyser/command/CommandRegistry.java | 6 ------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 00f8cec2d..f69119434 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -600,7 +600,6 @@ public class GeyserImpl implements GeyserApi { skinUploader.close(); } newsHandler.shutdown(); - this.commandRegistry().clear(); if (this.erosionUnixListener != null) { this.erosionUnixListener.close(); @@ -638,6 +637,9 @@ public class GeyserImpl implements GeyserApi { return this.extensionManager; } + /** + * @return the current CommandRegistry in use. The instance may change over the lifecycle of the Geyser runtime. + */ @NonNull public CommandRegistry commandRegistry() { return this.bootstrap.getCommandRegistry(); diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java index 87c905e2c..1cdc67fb5 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java @@ -191,12 +191,6 @@ public class CommandRegistry { return Collections.unmodifiableMap(this.extensionCommands); } - public void clear() { - this.commands.clear(); - this.extensionCommands.clear(); - this.permissionDefaults.clear(); - } - /** * For internal Geyser commands */ From c715c8689bed228bb76c055300215f7a39653a2b Mon Sep 17 00:00:00 2001 From: Konicai <71294714+konicai@users.noreply.github.com> Date: Fri, 29 Sep 2023 19:01:16 -0400 Subject: [PATCH 42/76] Move warning to debug --- .../geyser/command/CommandSourceConverter.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java b/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java index 7dc76701f..c5b14328f 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java @@ -26,6 +26,7 @@ package org.geysermc.geyser.command; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.session.GeyserSession; import java.util.UUID; @@ -56,12 +57,14 @@ public record CommandSourceConverter(Class senderType, } if (!(source instanceof GeyserSession)) { - // todo cloud remove this - GeyserImpl.getInstance().getLogger().warning("Falling back to UUID for command sender lookup for a command source that is not a GeyserSession: " + source); - Thread.dumpStack(); + GeyserLogger logger = GeyserImpl.getInstance().getLogger(); + if (logger.isDebug()) { + logger.debug("Falling back to UUID for command sender lookup for a command source that is not a GeyserSession: " + source); + Thread.dumpStack(); + } } - // Handles GeyserSession + // Ideally this should only handle GeyserSession return source.playerUuid() .map(playerLookup) .orElseThrow(() -> new IllegalArgumentException("failed to find sender for name=%s, uuid=%s".formatted(source.name(), source.playerUuid()))); From 799972d5a96cb6102c1e2f3e094b1980819233e0 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+konicai@users.noreply.github.com> Date: Fri, 29 Sep 2023 19:24:48 -0400 Subject: [PATCH 43/76] Reverse nullability changes for `Command.Builder#description/permission/aliases` --- .../java/org/geysermc/geyser/api/command/Command.java | 8 ++++---- .../geyser/extension/command/GeyserExtensionCommand.java | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/command/Command.java b/api/src/main/java/org/geysermc/geyser/api/command/Command.java index aeb2cf857..55628a850 100644 --- a/api/src/main/java/org/geysermc/geyser/api/command/Command.java +++ b/api/src/main/java/org/geysermc/geyser/api/command/Command.java @@ -148,7 +148,7 @@ public interface Command { * @param description the command description * @return this builder */ - Builder description(@Nullable String description); + Builder description(@NonNull String description); /** * Sets the permission node required to run this command.
@@ -157,7 +157,7 @@ public interface Command { * @param permission the permission node * @return this builder */ - Builder permission(@Nullable String permission); + Builder permission(@NonNull String permission); /** * Sets the permission node and its default value. The usage of the default value is platform dependant @@ -168,7 +168,7 @@ public interface Command { * @param defaultValue the node's default value * @return this builder */ - Builder permission(@Nullable String permission, @NonNull TriState defaultValue); + Builder permission(@NonNull String permission, @NonNull TriState defaultValue); /** * Sets the aliases. @@ -176,7 +176,7 @@ public interface Command { * @param aliases the aliases * @return this builder */ - Builder aliases(@Nullable List aliases); + Builder aliases(@NonNull List aliases); /** * Sets if this command is designed to be used only by server operators. diff --git a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java index 67afd9391..9f3421d7e 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java @@ -102,26 +102,26 @@ public abstract class GeyserExtensionCommand extends GeyserCommand { } @Override - public Builder description(@Nullable String description) { + public Builder description(@NonNull String description) { this.description = description; return this; } @Override - public Builder permission(@Nullable String permission) { + public Builder permission(@NonNull String permission) { this.permission = permission; return this; } @Override - public Builder permission(@Nullable String permission, @NonNull TriState defaultValue) { + public Builder permission(@NonNull String permission, @NonNull TriState defaultValue) { this.permission = permission; this.permissionDefault = Objects.requireNonNull(defaultValue, "defaultValue"); return this; } @Override - public Builder aliases(@Nullable List aliases) { + public Builder aliases(@NonNull List aliases) { this.aliases = aliases; return this; } From 24323d4e82468e5d999f6b44f8395b1113a7b6d6 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+konicai@users.noreply.github.com> Date: Fri, 29 Sep 2023 20:00:18 -0400 Subject: [PATCH 44/76] Cleanup nullabilities relating to extension commands --- .../GeyserStandaloneCommandManager.java | 3 +- .../geyser/command/GeyserCommand.java | 12 +++---- .../command/GeyserExtensionCommand.java | 32 +++++++++++-------- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java index c76104262..e3d68da11 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java @@ -115,8 +115,9 @@ public class GeyserStandaloneCommandManager extends CommandManager aliases = Collections.emptyList(); - public GeyserCommand(@NonNull String name, @Nullable String description, - @Nullable String permission, @Nullable TriState permissionDefault, + public GeyserCommand(@NonNull String name, @NonNull String description, + @NonNull String permission, @Nullable TriState permissionDefault, boolean executableOnConsole, boolean bedrockOnly) { - if (name == null || name.isBlank()) { + if (name.isBlank()) { throw new IllegalArgumentException("Command cannot be null or blank!"); } this.name = name; - this.description = description != null ? description : ""; - this.permission = permission != null ? permission : ""; + this.description = description; + this.permission = permission; this.permissionDefault = permissionDefault; if (bedrockOnly && executableOnConsole) { @@ -113,7 +113,7 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C this.bedrockOnly = bedrockOnly; } - public GeyserCommand(@NonNull String name, @NonNull String description, @Nullable String permission, @Nullable TriState permissionDefault) { + public GeyserCommand(@NonNull String name, @NonNull String description, @NonNull String permission, @Nullable TriState permissionDefault) { this(name, description, permission, permissionDefault, true, false); } diff --git a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java index 9f3421d7e..459fb6092 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java @@ -50,8 +50,8 @@ public abstract class GeyserExtensionCommand extends GeyserCommand { private final Extension extension; private final String rootCommand; - public GeyserExtensionCommand(@NonNull Extension extension, @NonNull String name, @Nullable String description, - @Nullable String permission, @Nullable TriState permissionDefault, + public GeyserExtensionCommand(@NonNull Extension extension, @NonNull String name, @NonNull String description, + @NonNull String permission, @Nullable TriState permissionDefault, boolean executableOnConsole, boolean bedrockOnly) { super(name, description, permission, permissionDefault, executableOnConsole, bedrockOnly); @@ -73,17 +73,17 @@ public abstract class GeyserExtensionCommand extends GeyserCommand { } public static class Builder implements Command.Builder { - private final Extension extension; - private Class sourceType; - private String name; - private String description; - private String permission; - private TriState permissionDefault; - private List aliases; + @NonNull private final Extension extension; + @Nullable private Class sourceType; + @Nullable private String name; + @NonNull private String description = ""; + @NonNull private String permission = ""; + @Nullable private TriState permissionDefault; + @Nullable private List aliases; private boolean suggestedOpOnly = false; // deprecated for removal private boolean executableOnConsole = true; private boolean bedrockOnly = false; - private CommandExecutor executor; + @Nullable private CommandExecutor executor; public Builder(Extension extension) { this.extension = Objects.requireNonNull(extension); @@ -103,20 +103,20 @@ public abstract class GeyserExtensionCommand extends GeyserCommand { @Override public Builder description(@NonNull String description) { - this.description = description; + this.description = Objects.requireNonNull(description, "command description"); return this; } @Override public Builder permission(@NonNull String permission) { - this.permission = permission; + this.permission = Objects.requireNonNull(permission, "command permission"); return this; } @Override public Builder permission(@NonNull String permission, @NonNull TriState defaultValue) { - this.permission = permission; - this.permissionDefault = Objects.requireNonNull(defaultValue, "defaultValue"); + this.permission = Objects.requireNonNull(permission, "command permission"); + this.permissionDefault = Objects.requireNonNull(defaultValue, "command permission defaultValue"); return this; } @@ -157,10 +157,14 @@ public abstract class GeyserExtensionCommand extends GeyserCommand { @NonNull @Override public GeyserExtensionCommand build() { + // These are captured in the anonymous lambda below and shouldn't change even if the builder does final Class sourceType = this.sourceType; final boolean suggestedOpOnly = this.suggestedOpOnly; final CommandExecutor executor = this.executor; + if (name == null) { + throw new IllegalArgumentException("name was not provided for a command in extension " + extension.name()); + } if (sourceType == null) { throw new IllegalArgumentException("Source type was not defined for command " + name + " in extension " + extension.name()); } From 215f1fea94a69038281ee8b4286be9a51692d8cf Mon Sep 17 00:00:00 2001 From: Konicai <71294714+konicai@users.noreply.github.com> Date: Fri, 29 Sep 2023 20:12:45 -0400 Subject: [PATCH 45/76] some misc changes --- api/src/main/java/org/geysermc/geyser/api/command/Command.java | 3 +-- .../main/java/org/geysermc/geyser/command/GeyserCommand.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/command/Command.java b/api/src/main/java/org/geysermc/geyser/api/command/Command.java index 55628a850..59f6024f8 100644 --- a/api/src/main/java/org/geysermc/geyser/api/command/Command.java +++ b/api/src/main/java/org/geysermc/geyser/api/command/Command.java @@ -26,7 +26,6 @@ package org.geysermc.geyser.api.command; import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.GeyserApi; import org.geysermc.geyser.api.connection.GeyserConnection; import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent; @@ -100,7 +99,7 @@ public interface Command { boolean isBedrockOnly(); /** - * @deprecated this method will always return an empty list + * @deprecated this method will always return an immutable list */ @Deprecated(forRemoval = true) @NonNull diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java index 07e373575..852d9da07 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java @@ -88,7 +88,7 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C private final boolean bedrockOnly; /** - * The aliases of the command {@link #name} + * The aliases of the command {@link #name}. This should not be modified after construction. */ protected List aliases = Collections.emptyList(); From 0fedd8b086b5f85fb09e92045ea671bb908f6a52 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+konicai@users.noreply.github.com> Date: Fri, 29 Sep 2023 20:16:26 -0400 Subject: [PATCH 46/76] Simplify alias setting for extension commands --- .../geyser/extension/command/GeyserExtensionCommand.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java index 459fb6092..b039caf27 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java @@ -216,7 +216,9 @@ public abstract class GeyserExtensionCommand extends GeyserCommand { } }; - command.aliases = aliases != null ? new ArrayList<>(aliases) : Collections.emptyList(); + if (aliases != null) { + command.aliases = new ArrayList<>(aliases); + } return command; } } From 3a1797f852acaa7141a627c49fef9760eb83583a Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Sun, 1 Oct 2023 17:03:36 -0400 Subject: [PATCH 47/76] Update lang keys for root command descriptions --- .../java/org/geysermc/geyser/command/CommandRegistry.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java index 1cdc67fb5..ba1b704ef 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java @@ -233,12 +233,12 @@ public class CommandRegistry { @NonNull public String description(@NonNull String command, @NonNull String locale) { if (command.equals(GeyserCommand.DEFAULT_ROOT_COMMAND)) { - return GeyserLocale.getPlayerLocaleString("geyser.command.geyser_desc", locale); + return GeyserLocale.getPlayerLocaleString("geyser.command.root.geyser", locale); } Extension extension = extensionRootCommands.get(command); if (extension != null) { - return GeyserLocale.getPlayerLocaleString("geyser.command.extension_desc", locale, extension.description().name()); + return GeyserLocale.getPlayerLocaleString("geyser.command.root.extension", locale, extension.name()); } return ""; } From 8d708b62126197d7c809ec76a0db4bd52fd18f05 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Sun, 1 Oct 2023 17:05:47 -0400 Subject: [PATCH 48/76] Update languages --- core/src/main/resources/languages | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/languages b/core/src/main/resources/languages index 764f7ac3b..f28856bd7 160000 --- a/core/src/main/resources/languages +++ b/core/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 764f7ac3bfba91beedd3e39196caf83bc9a1a546 +Subproject commit f28856bd770f9347c0ffd63fe44d9b21b6f0492c From 7c083f05413704ac1a008774c062101ec8d18b2f Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Sun, 1 Oct 2023 17:17:43 -0400 Subject: [PATCH 49/76] better javadocs on Command.Builder#permission --- .../java/org/geysermc/geyser/api/command/Command.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/command/Command.java b/api/src/main/java/org/geysermc/geyser/api/command/Command.java index 59f6024f8..382c0a5ec 100644 --- a/api/src/main/java/org/geysermc/geyser/api/command/Command.java +++ b/api/src/main/java/org/geysermc/geyser/api/command/Command.java @@ -151,7 +151,8 @@ public interface Command { /** * Sets the permission node required to run this command.
- * It will not be registered anywhere, unlike {@link #permission(String, TriState)}. + * It will not be registered with any permission registries, such as an underlying server, + * or a permissions Extension (unlike {@link #permission(String, TriState)}). * * @param permission the permission node * @return this builder @@ -160,8 +161,9 @@ public interface Command { /** * Sets the permission node and its default value. The usage of the default value is platform dependant - * and may or may not be used.
- * Extensions may instead listen for {@link GeyserRegisterPermissionsEvent} to register permissions. + * and may or may not be used. For example, it may be registered to an underlying server.

+ * Extensions may instead listen for {@link GeyserRegisterPermissionsEvent} to register permissions, which + * should be used if the same permission is required by multiple commands. * * @param permission the permission node * @param defaultValue the node's default value From 1cf6d97c09a69488135838882817e22e2063e85d Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Sun, 1 Oct 2023 18:58:54 -0400 Subject: [PATCH 50/76] remove some outdated todos --- .../protocol/bedrock/BedrockCommandRequestTranslator.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java index 341762e6a..f7036cf9b 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java @@ -55,11 +55,8 @@ public class BedrockCommandRequestTranslator extends PacketTranslator 0) { String root = args[0]; - // todo cloud do we want to pass the command to the server - // if cloud gives a NoSuchCommandException? might be more accurate. CommandRegistry registry = GeyserImpl.getInstance().commandRegistry(); if (registry.cloud().rootCommands().contains(root)) { - // todo cloud might not like the trailing whitespace either registry.runCommand(session, command); return; // don't pass the command to the java server } From f5b245718e1212ee7566d2c8351cb13641324bd8 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Sun, 1 Oct 2023 20:06:09 -0400 Subject: [PATCH 51/76] don't block standalone terminal with commands --- .../geyser/platform/standalone/GeyserStandaloneLogger.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java index 7683aee90..4340e409a 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java @@ -44,9 +44,9 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements Gey @Override protected void runCommand(String line) { - // seems like terminal console appender invokes this method async off the terminal, - // so we can probably invoke the command directly on the current thread - GeyserImpl.getInstance().commandRegistry().runCommand(this, line); + // don't block the terminal! + GeyserImpl geyser = GeyserImpl.getInstance(); + geyser.getScheduledThread().execute(() -> geyser.commandRegistry().runCommand(this, line)); } @Override From 0cfee52e4837110a31fa7f920e211d761c69af16 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Sun, 1 Oct 2023 23:21:47 -0400 Subject: [PATCH 52/76] don't let (unexpected) exceptions thrown while handling command exceptions be swallowed --- .../geyser/command/CommandRegistry.java | 37 ++++++++++++------- .../geyser/command/GeyserCommand.java | 2 +- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java index ba1b704ef..6dc7dfc94 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java @@ -69,7 +69,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.concurrent.CompletionException; import java.util.function.BiConsumer; @@ -258,7 +257,13 @@ public class CommandRegistry { throwable = throwable.getCause(); } - handleThrowable(source, throwable); + try { + handleThrowable(source, throwable); + } catch (Throwable secondary) { + // otherwise it gets swallowed by whenComplete. + // we assume this won't throw. + handleUnexpectedThrowable(source, secondary); + } }); } @@ -279,18 +284,24 @@ public class CommandRegistry { // find the Command and its Meta that the source tried executing List> argumentChain = exception.getCurrentChain(); CommandArgument argument = argumentChain.get(argumentChain.size() - 1); - CommandMeta meta = Objects.requireNonNull(argument.getOwningCommand()).getCommandMeta(); + cloud.commandframework.Command owner = argument.getOwningCommand(); - // See GeyserCommand#baseBuilder() - if (meta.getOrDefault(GeyserCommand.BEDROCK_ONLY, false)) { - if (source.connection().isEmpty()) { - source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.command.bedrock_only", source.locale())); - return; - } - } else if (meta.getOrDefault(GeyserCommand.PLAYER_ONLY, false)) { - if (source.isConsole()) { - source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.command.player_only", source.locale())); - return; + if (owner == null) { + GeyserImpl.getInstance().getLogger().debug("Unable to check for command meta because " + argument + " does not have an owning command"); + } else { + CommandMeta meta = owner.getCommandMeta(); + + // See GeyserCommand#baseBuilder() + if (meta.getOrDefault(GeyserCommand.BEDROCK_ONLY, false)) { + if (source.connection().isEmpty()) { + source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.command.bedrock_only", source.locale())); + return; + } + } else if (meta.getOrDefault(GeyserCommand.PLAYER_ONLY, false)) { + if (source.isConsole()) { + source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.command.player_only", source.locale())); + return; + } } } diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java index 852d9da07..c1a8e5a71 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java @@ -192,7 +192,7 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C * @return an Applicable that applies {@link #BEDROCK_ONLY} and {@link #PLAYER_ONLY} as meta, * according to {@link #isBedrockOnly()} and {@link #isExecutableOnConsole()} (respectively). */ - public Command.Builder.Applicable meta() { + protected Command.Builder.Applicable meta() { return builder -> builder .meta(CommandMeta.DESCRIPTION, GeyserLocale.getLocaleStringLog(description)) .meta(BEDROCK_ONLY, isBedrockOnly()) From 956d60721c0774148048ceab88c09846502b57f8 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+konicai@users.noreply.github.com> Date: Mon, 2 Oct 2023 18:04:50 -0400 Subject: [PATCH 53/76] Don't use Optional for CommandSource#playerUuid/connection --- .../geysermc/geyser/api/command/CommandSource.java | 12 ++++++------ .../bungeecord/command/BungeeCommandSource.java | 8 ++++---- .../platform/fabric/command/FabricCommandSource.java | 8 ++++---- .../platform/spigot/command/SpigotCommandSource.java | 7 ++++--- .../velocity/command/VelocityCommandSource.java | 7 ++++--- .../main/java/org/geysermc/geyser/GeyserLogger.java | 7 +++---- .../geyser/command/CommandSourceConverter.java | 11 +++++++---- .../geysermc/geyser/command/GeyserCommandSource.java | 11 ++++++++--- .../org/geysermc/geyser/session/GeyserSession.java | 8 ++++---- 9 files changed, 44 insertions(+), 35 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java b/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java index 0041872b0..3663898cf 100644 --- a/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java +++ b/api/src/main/java/org/geysermc/geyser/api/command/CommandSource.java @@ -26,9 +26,9 @@ package org.geysermc.geyser.api.command; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.api.connection.Connection; -import java.util.Optional; import java.util.UUID; /** @@ -69,15 +69,15 @@ public interface CommandSource { boolean isConsole(); /** - * @return an Optional containing a Java UUID if this source represents any player, otherwise empty + * @return a Java UUID if this source represents a player, otherwise null */ - Optional playerUuid(); + @Nullable UUID playerUuid(); /** - * @return an Optional with a present Connection if this source represents a Bedrock player that is connected - * to this Geyser instance. Otherwise, returns an empty optional. + * @return a Connection if this source represents a Bedrock player that is connected + * to this Geyser instance, otherwise null */ - Optional connection(); + @Nullable Connection connection(); /** * Returns the locale of the command source. diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java index 24676ec00..f5bd7610f 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java @@ -31,11 +31,11 @@ import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.connection.ProxiedPlayer; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.text.GeyserLocale; import java.util.Locale; -import java.util.Optional; import java.util.UUID; public class BungeeCommandSource implements GeyserCommandSource { @@ -76,11 +76,11 @@ public class BungeeCommandSource implements GeyserCommandSource { } @Override - public Optional playerUuid() { + public @Nullable UUID playerUuid() { if (handle instanceof ProxiedPlayer player) { - return Optional.of(player.getUniqueId()); + return player.getUniqueId(); } - return Optional.empty(); + return null; } @Override diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSource.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSource.java index 9a7543efb..e23a781c1 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSource.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSource.java @@ -31,11 +31,11 @@ import net.minecraft.commands.CommandSourceStack; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.text.ChatColor; -import java.util.Optional; import java.util.UUID; public class FabricCommandSource implements GeyserCommandSource { @@ -76,11 +76,11 @@ public class FabricCommandSource implements GeyserCommandSource { } @Override - public Optional playerUuid() { + public @Nullable UUID playerUuid() { if (source.getEntity() instanceof ServerPlayer player) { - return Optional.of(player.getUUID()); + return player.getUUID(); } - return Optional.empty(); + return null; } @Override diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSource.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSource.java index 28fe06038..7000125ba 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSource.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandSource.java @@ -31,6 +31,7 @@ import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; import org.bukkit.entity.Player; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.platform.spigot.PaperAdventure; import org.geysermc.geyser.text.GeyserLocale; @@ -79,11 +80,11 @@ public class SpigotCommandSource implements GeyserCommandSource { } @Override - public Optional playerUuid() { + public @Nullable UUID playerUuid() { if (handle instanceof Player player) { - return Optional.of(player.getUniqueId()); + return player.getUniqueId(); } - return Optional.empty(); + return null; } @Override diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSource.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSource.java index 041d31abd..740ceca06 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSource.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSource.java @@ -31,6 +31,7 @@ import com.velocitypowered.api.proxy.Player; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.text.GeyserLocale; @@ -75,11 +76,11 @@ public class VelocityCommandSource implements GeyserCommandSource { } @Override - public Optional playerUuid() { + public @Nullable UUID playerUuid() { if (handle instanceof Player player) { - return Optional.of(player.getUniqueId()); + return player.getUniqueId(); } - return Optional.empty(); + return null; } @Override diff --git a/core/src/main/java/org/geysermc/geyser/GeyserLogger.java b/core/src/main/java/org/geysermc/geyser/GeyserLogger.java index f0336d93e..f408de29c 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserLogger.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserLogger.java @@ -27,10 +27,9 @@ package org.geysermc.geyser; import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.command.GeyserCommandSource; -import javax.annotation.Nullable; -import java.util.Optional; import java.util.UUID; public interface GeyserLogger extends GeyserCommandSource { @@ -132,8 +131,8 @@ public interface GeyserLogger extends GeyserCommandSource { } @Override - default Optional playerUuid() { - return Optional.empty(); + default @Nullable UUID playerUuid() { + return null; } @Override diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java b/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java index c5b14328f..3d5597e3f 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java @@ -64,10 +64,13 @@ public record CommandSourceConverter(Class senderType, } } - // Ideally this should only handle GeyserSession - return source.playerUuid() - .map(playerLookup) - .orElseThrow(() -> new IllegalArgumentException("failed to find sender for name=%s, uuid=%s".formatted(source.name(), source.playerUuid()))); + // Ideally lookup should only be necessary for GeyserSession + UUID uuid = source.playerUuid(); + if (uuid != null) { + return playerLookup.apply(uuid); + } + + throw new IllegalArgumentException("failed to find sender for name=%s, uuid=%s".formatted(source.name(), source.playerUuid())); } /** diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java index 7bd1fb4ca..c14767496 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandSource.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.command; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.command.CommandSource; import org.geysermc.geyser.session.GeyserSession; @@ -32,7 +33,7 @@ import org.geysermc.geyser.text.GeyserLocale; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; -import java.util.Optional; +import java.util.UUID; /** * Implemented on top of any class that can send a command. @@ -60,8 +61,12 @@ public interface GeyserCommandSource extends CommandSource { } @Override - default Optional connection() { - return playerUuid().map(id -> GeyserImpl.getInstance().connectionByUuid(id)); + default @Nullable GeyserSession connection() { + UUID uuid = playerUuid(); + if (uuid == null) { + return null; + } + return GeyserImpl.getInstance().connectionByUuid(uuid); } /** diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index c950302da..4a1e9be31 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -1451,13 +1451,13 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { } @Override - public Optional playerUuid() { - return Optional.of(playerEntity.getUuid()); + public @NonNull UUID playerUuid() { + return playerEntity.getUuid(); } @Override - public Optional connection() { - return Optional.of(this); + public @NonNull GeyserSession connection() { + return this; } @Override From 37807a5115f052b6e0517e14229f4f0ea6ca3737 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+konicai@users.noreply.github.com> Date: Mon, 2 Oct 2023 18:20:17 -0400 Subject: [PATCH 54/76] hnnnng --- .../java/org/geysermc/geyser/command/CommandRegistry.java | 2 +- .../java/org/geysermc/geyser/command/GeyserCommand.java | 2 +- .../geyser/command/defaults/AdvancedTooltipsCommand.java | 4 +++- .../geyser/command/defaults/AdvancementsCommand.java | 4 +++- .../geyser/command/defaults/ConnectionTestCommand.java | 6 ------ .../org/geysermc/geyser/command/defaults/HelpCommand.java | 2 +- .../geysermc/geyser/command/defaults/OffhandCommand.java | 4 +++- .../geysermc/geyser/command/defaults/SettingsCommand.java | 4 +++- .../geysermc/geyser/command/defaults/StatisticsCommand.java | 4 +++- .../geyser/extension/command/GeyserExtensionCommand.java | 2 +- 10 files changed, 19 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java index 6dc7dfc94..b89b2870b 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java @@ -293,7 +293,7 @@ public class CommandRegistry { // See GeyserCommand#baseBuilder() if (meta.getOrDefault(GeyserCommand.BEDROCK_ONLY, false)) { - if (source.connection().isEmpty()) { + if (source.connection() == null) { source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.command.bedrock_only", source.locale())); return; } diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java index c1a8e5a71..68f3f56a2 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java @@ -174,7 +174,7 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C .literal(name, aliases.toArray(new String[0])) .permission(source -> { if (bedrockOnly) { - if (source.connection().isEmpty()) { + if (source.connection() == null) { return false; } // connection is present -> it is a player -> executableOnConsole is irrelevant diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java index 5a683b31f..94637332c 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java @@ -33,6 +33,8 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.MinecraftLocale; +import java.util.Objects; + public class AdvancedTooltipsCommand extends GeyserCommand { public AdvancedTooltipsCommand(String name, String description, String permission) { @@ -41,7 +43,7 @@ public class AdvancedTooltipsCommand extends GeyserCommand { @Override public void execute(CommandContext context) { - GeyserSession session = context.getSender().connection().orElseThrow(); + GeyserSession session = Objects.requireNonNull(context.getSender().connection()); String onOrOff = session.isAdvancedTooltips() ? "off" : "on"; session.setAdvancedTooltips(!session.isAdvancedTooltips()); diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java index a9047ef5a..02f94ae41 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java @@ -31,6 +31,8 @@ import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; +import java.util.Objects; + public class AdvancementsCommand extends GeyserCommand { public AdvancementsCommand(String name, String description, String permission) { @@ -39,7 +41,7 @@ public class AdvancementsCommand extends GeyserCommand { @Override public void execute(CommandContext context) { - GeyserSession session = context.getSender().connection().orElseThrow(); + GeyserSession session = Objects.requireNonNull(context.getSender().connection()); session.getAdvancementsCache().buildAndShowMenuForm(); } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java index 1a54b1ca0..7b56e35d8 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java @@ -25,24 +25,18 @@ package org.geysermc.geyser.command.defaults; -import cloud.commandframework.Command; import cloud.commandframework.CommandManager; import cloud.commandframework.arguments.standard.IntegerArgument; import cloud.commandframework.arguments.standard.StringArgument; import cloud.commandframework.context.CommandContext; -import cloud.commandframework.CommandManager; -import cloud.commandframework.arguments.standard.IntegerArgument; -import cloud.commandframework.arguments.standard.StringArgument; import com.fasterxml.jackson.databind.JsonNode; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.util.TriState; -import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.util.LoopbackUtil; import org.geysermc.geyser.util.WebUtils; -import java.util.Objects; import java.util.Random; import java.util.concurrent.CompletableFuture; diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java index f4cc309da..49149732a 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java @@ -73,7 +73,7 @@ public class HelpCommand extends GeyserCommand { @Override public void execute(CommandContext context) { GeyserCommandSource source = context.getSender(); - boolean bedrockPlayer = source.connection().isPresent(); + boolean bedrockPlayer = source.connection() != null; // todo: pagination int page = 1; diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java index af893d06c..c46bdb836 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java @@ -32,6 +32,8 @@ import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; +import java.util.Objects; + public class OffhandCommand extends GeyserCommand { public OffhandCommand(GeyserImpl geyser, String name, String description, String permission) { @@ -40,7 +42,7 @@ public class OffhandCommand extends GeyserCommand { @Override public void execute(CommandContext context) { - GeyserSession session = context.getSender().connection().orElseThrow(); + GeyserSession session = Objects.requireNonNull(context.getSender().connection()); session.requestOffhandSwap(); } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java index a3ce3c69e..3c603b9cb 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java @@ -33,6 +33,8 @@ import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.SettingsUtils; +import java.util.Objects; + public class SettingsCommand extends GeyserCommand { public SettingsCommand(GeyserImpl geyser, String name, String description, String permission) { @@ -41,7 +43,7 @@ public class SettingsCommand extends GeyserCommand { @Override public void execute(CommandContext context) { - GeyserSession session = context.getSender().connection().orElseThrow(); + GeyserSession session = Objects.requireNonNull(context.getSender().connection()); session.sendForm(SettingsUtils.buildForm(session)); } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java index 646a8ceaa..e61299601 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java @@ -34,6 +34,8 @@ import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; +import java.util.Objects; + public class StatisticsCommand extends GeyserCommand { public StatisticsCommand(GeyserImpl geyser, String name, String description, String permission) { @@ -42,7 +44,7 @@ public class StatisticsCommand extends GeyserCommand { @Override public void execute(CommandContext context) { - GeyserSession session = context.getSender().connection().orElseThrow(); + GeyserSession session = Objects.requireNonNull(context.getSender().connection()); session.setWaitingForStatistics(true); ServerboundClientCommandPacket packet = new ServerboundClientCommandPacket(ClientCommand.STATS); diff --git a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java index b039caf27..2f36a99cb 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java @@ -198,7 +198,7 @@ public abstract class GeyserExtensionCommand extends GeyserCommand { return; } - GeyserSession session = source.connection().orElse(null); + @Nullable GeyserSession session = source.connection(); if (sourceType.isInstance(session)) { executor.execute((T) session, this, args); return; From 35e08cc088d2946b8e04a62060ac9f5894f610e2 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+konicai@users.noreply.github.com> Date: Fri, 6 Oct 2023 17:13:29 -0400 Subject: [PATCH 55/76] Improve permission messages, deprecate executableOnConsole for playerOnly --- .../geysermc/geyser/api/command/Command.java | 31 +++++-- .../geyser/command/CommandRegistry.java | 38 ++++---- .../geyser/command/GeyserCommand.java | 64 ++++++------- .../geyser/command/GeyserPermission.java | 89 +++++++++++++++++++ .../defaults/AdvancedTooltipsCommand.java | 2 +- .../command/defaults/AdvancementsCommand.java | 2 +- .../geyser/command/defaults/HelpCommand.java | 2 +- .../command/defaults/OffhandCommand.java | 2 +- .../command/defaults/SettingsCommand.java | 2 +- .../command/defaults/StatisticsCommand.java | 2 +- .../command/GeyserExtensionCommand.java | 19 ++-- gradle/libs.versions.toml | 2 +- 12 files changed, 171 insertions(+), 84 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/command/GeyserPermission.java diff --git a/api/src/main/java/org/geysermc/geyser/api/command/Command.java b/api/src/main/java/org/geysermc/geyser/api/command/Command.java index 382c0a5ec..9e8ed174b 100644 --- a/api/src/main/java/org/geysermc/geyser/api/command/Command.java +++ b/api/src/main/java/org/geysermc/geyser/api/command/Command.java @@ -85,16 +85,21 @@ public interface Command { } /** - * Gets if this command is executable on console. - * - * @return if this command is executable on console + * @return true if this command is executable on console + * @deprecated use {@link #isPlayerOnly()} instead (inverted) */ - boolean isExecutableOnConsole(); + @Deprecated(forRemoval = true) + default boolean isExecutableOnConsole() { + return !isPlayerOnly(); + } /** - * Used to send a deny-message to Java players if this command can only be used by Bedrock players. - * - * @return true if this command can only be used by Bedrock players. + * @return true if this command can only be used by players + */ + boolean isPlayerOnly(); + + /** + * @return true if this command can only be used by Bedrock players */ boolean isBedrockOnly(); @@ -194,11 +199,21 @@ public interface Command { * * @param executableOnConsole if this command is executable on console * @return this builder + * @deprecated use {@link #isPlayerOnly()} instead (inverted) */ + @Deprecated(forRemoval = true) Builder executableOnConsole(boolean executableOnConsole); /** - * Sets if this command is bedrock only. + * Sets if this command can only be executed by players. + * + * @param playerOnly if this command is player only + * @return this builder + */ + Builder playerOnly(boolean playerOnly); + + /** + * Sets if this command can only be executed by bedrock players. * * @param bedrockOnly if this command is bedrock only * @return this builder diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java index b89b2870b..09412abf7 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java @@ -26,7 +26,6 @@ package org.geysermc.geyser.command; import cloud.commandframework.CommandManager; -import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.exceptions.ArgumentParseException; import cloud.commandframework.exceptions.CommandExecutionException; import cloud.commandframework.exceptions.InvalidCommandSenderException; @@ -34,10 +33,10 @@ import cloud.commandframework.exceptions.InvalidSyntaxException; import cloud.commandframework.exceptions.NoPermissionException; import cloud.commandframework.exceptions.NoSuchCommandException; import cloud.commandframework.execution.CommandExecutionCoordinator; -import cloud.commandframework.meta.CommandMeta; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import lombok.AllArgsConstructor; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent; import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.GeyserImpl; @@ -280,31 +279,24 @@ public class CommandRegistry { private void handleNoPermission(GeyserCommandSource source, NoPermissionException exception) { // we basically recheck bedrock-only and player-only to see if they were the cause of this - - // find the Command and its Meta that the source tried executing - List> argumentChain = exception.getCurrentChain(); - CommandArgument argument = argumentChain.get(argumentChain.size() - 1); - cloud.commandframework.Command owner = argument.getOwningCommand(); - - if (owner == null) { - GeyserImpl.getInstance().getLogger().debug("Unable to check for command meta because " + argument + " does not have an owning command"); + if (exception.missingPermission() instanceof GeyserPermission permission) { + GeyserPermission.Result result = permission.check(source); + if (result == GeyserPermission.Result.NOT_BEDROCK) { + source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.command.bedrock_only", source.locale())); + return; + } + if (result == GeyserPermission.Result.NOT_PLAYER) { + source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.command.player_only", source.locale())); + return; + } } else { - CommandMeta meta = owner.getCommandMeta(); - - // See GeyserCommand#baseBuilder() - if (meta.getOrDefault(GeyserCommand.BEDROCK_ONLY, false)) { - if (source.connection() == null) { - source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.command.bedrock_only", source.locale())); - return; - } - } else if (meta.getOrDefault(GeyserCommand.PLAYER_ONLY, false)) { - if (source.isConsole()) { - source.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.command.player_only", source.locale())); - return; - } + GeyserLogger logger = GeyserImpl.getInstance().getLogger(); + if (logger.isDebug()) { + logger.debug("Expected a GeyserPermission for %s but instead got %s".formatted(exception.getCurrentChain(), exception.missingPermission())); } } + // Result.NO_PERMISSION, or we're unable to recheck source.sendLocaleString("geyser.command.permission_fail"); } diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java index 68f3f56a2..a232d0185 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java @@ -29,6 +29,7 @@ import cloud.commandframework.Command; import cloud.commandframework.CommandManager; import cloud.commandframework.context.CommandContext; import cloud.commandframework.meta.CommandMeta; +import cloud.commandframework.permission.CommandPermission; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.util.TriState; @@ -42,16 +43,6 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C public static final String DEFAULT_ROOT_COMMAND = "geyser"; - /** - * CommandMeta to indicate that the command is only available to bedrock players. Default of false. - */ - public static final CommandMeta.Key BEDROCK_ONLY = CommandMeta.Key.of(Boolean.class, "bedrock-only", meta -> false); - - /** - * CommandMeta to indicate that the command is only available to players. Default of false. - */ - public static final CommandMeta.Key PLAYER_ONLY = CommandMeta.Key.of(Boolean.class, "player-only", meta -> false); - /** * The second literal of the command. Note: the first literal is {@link #rootCommand()}. */ @@ -78,12 +69,12 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C private final TriState permissionDefault; /** - * True if the command can be executed by non players + * True if this command can be executed by players */ - private final boolean executableOnConsole; + private final boolean playerOnly; /** - * True if the command can only be run by bedrock players + * True if this command can only be run by bedrock players */ private final boolean bedrockOnly; @@ -94,7 +85,7 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C public GeyserCommand(@NonNull String name, @NonNull String description, @NonNull String permission, @Nullable TriState permissionDefault, - boolean executableOnConsole, boolean bedrockOnly) { + boolean playerOnly, boolean bedrockOnly) { if (name.isBlank()) { throw new IllegalArgumentException("Command cannot be null or blank!"); @@ -105,16 +96,16 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C this.permission = permission; this.permissionDefault = permissionDefault; - if (bedrockOnly && executableOnConsole) { - throw new IllegalArgumentException("Command cannot be both bedrockOnly and executableOnConsole"); + if (bedrockOnly && !playerOnly) { + throw new IllegalArgumentException("Command cannot be bedrockOnly if it is not playerOnly"); } - this.executableOnConsole = executableOnConsole; + this.playerOnly = playerOnly; this.bedrockOnly = bedrockOnly; } public GeyserCommand(@NonNull String name, @NonNull String description, @NonNull String permission, @Nullable TriState permissionDefault) { - this(name, description, permission, permissionDefault, true, false); + this(name, description, permission, permissionDefault, false, false); } @NonNull @@ -141,8 +132,8 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C } @Override - public final boolean isExecutableOnConsole() { - return executableOnConsole; + public final boolean isPlayerOnly() { + return playerOnly; } @Override @@ -163,40 +154,35 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C return DEFAULT_ROOT_COMMAND; } + /** + * Returns a {@link CommandPermission} that handles {@link #isBedrockOnly()}, {@link #isPlayerOnly()}, and {@link #permission()}. + * + * @param manager the manager to be used for permission node checking + * @return a permission that will properly restrict usage of this command + */ + public final GeyserPermission commandPermission(CommandManager manager) { + return new GeyserPermission(bedrockOnly, playerOnly, permission, manager); + } + /** * Creates a new command builder with {@link #rootCommand()}, {@link #name()}, and {@link #aliases()} built on it. - * A permission predicate that takes into account {@link #permission()}, {@link #isBedrockOnly()}, and {@link #isExecutableOnConsole()} + * A permission predicate that takes into account {@link #permission()}, {@link #isBedrockOnly()}, and {@link #isPlayerOnly()} * is applied. The Applicable from {@link #meta()} is also applied to the builder. */ @Contract(value = "_ -> new", pure = true) public final Command.Builder baseBuilder(CommandManager manager) { return manager.commandBuilder(rootCommand()) .literal(name, aliases.toArray(new String[0])) - .permission(source -> { - if (bedrockOnly) { - if (source.connection() == null) { - return false; - } - // connection is present -> it is a player -> executableOnConsole is irrelevant - } else if (!executableOnConsole) { - if (source.isConsole()) { - return false; // not executable on console but is console - } - } - return manager.hasPermission(source, permission); - }) + .permission(commandPermission(manager)) .apply(meta()); } /** - * @return an Applicable that applies {@link #BEDROCK_ONLY} and {@link #PLAYER_ONLY} as meta, - * according to {@link #isBedrockOnly()} and {@link #isExecutableOnConsole()} (respectively). + * @return an Applicable that applies this command's description as {@link CommandMeta#DESCRIPTION} */ protected Command.Builder.Applicable meta() { return builder -> builder - .meta(CommandMeta.DESCRIPTION, GeyserLocale.getLocaleStringLog(description)) - .meta(BEDROCK_ONLY, isBedrockOnly()) - .meta(PLAYER_ONLY, !isExecutableOnConsole()); + .meta(CommandMeta.DESCRIPTION, GeyserLocale.getLocaleStringLog(description)); // used in cloud-bukkit impl } /** diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserPermission.java b/core/src/main/java/org/geysermc/geyser/command/GeyserPermission.java new file mode 100644 index 000000000..370bd5d56 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserPermission.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2019-2023 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.command; + +import cloud.commandframework.CommandManager; +import cloud.commandframework.permission.PredicatePermission; +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class GeyserPermission implements PredicatePermission { + + private final boolean bedrockOnly; + private final boolean playerOnly; + private final String permission; + private final CommandManager manager; + + @Override + public boolean hasPermission(GeyserCommandSource source) { + return check(source).toBoolean(); + } + + public Result check(GeyserCommandSource source) { + if (bedrockOnly) { + if (source.connection() == null) { + return Result.NOT_BEDROCK; + } + // connection is present -> it is a player -> playerOnly is irrelevant + } else if (playerOnly) { + if (source.isConsole()) { + return Result.NOT_PLAYER; // must be a player but is console + } + } + + if (manager.hasPermission(source, permission)) { + return Result.ALLOWED; + } + return Result.NO_PERMISSION; + } + + public enum Result { + + /** + * The source must be a bedrock player, but is not. + */ + NOT_BEDROCK, + + /** + * The source must be a player, but is not. + */ + NOT_PLAYER, + + /** + * The source does not have a required permission node. + */ + NO_PERMISSION, + + /** + * The source meets all requirements. + */ + ALLOWED; + + public boolean toBoolean() { + return this == ALLOWED; + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java index 94637332c..e54aae677 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java @@ -38,7 +38,7 @@ import java.util.Objects; public class AdvancedTooltipsCommand extends GeyserCommand { public AdvancedTooltipsCommand(String name, String description, String permission) { - super(name, description, permission, TriState.TRUE, false, true); + super(name, description, permission, TriState.TRUE, true, true); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java index 02f94ae41..b1ccee942 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java @@ -36,7 +36,7 @@ import java.util.Objects; public class AdvancementsCommand extends GeyserCommand { public AdvancementsCommand(String name, String description, String permission) { - super(name, description, permission, TriState.TRUE, false, true); + super(name, description, permission, TriState.TRUE, true, true); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java index 49149732a..665b5ceb5 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java @@ -65,7 +65,7 @@ public class HelpCommand extends GeyserCommand { // note: this doesn't do the other permission checks that GeyserCommand#baseBuilder does, // but it's fine because the help command can be executed by non-bedrock players and by the console. manager.command(manager.commandBuilder(rootCommand) - .permission(permission()) + .permission(commandPermission(manager)) .apply(meta()) // shouldn't be necessary - just for consistency .handler(this::execute)); } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java index c46bdb836..760be79dc 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java @@ -37,7 +37,7 @@ import java.util.Objects; public class OffhandCommand extends GeyserCommand { public OffhandCommand(GeyserImpl geyser, String name, String description, String permission) { - super(name, description, permission, TriState.TRUE, false, true); + super(name, description, permission, TriState.TRUE, true, true); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java index 3c603b9cb..13090935a 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java @@ -38,7 +38,7 @@ import java.util.Objects; public class SettingsCommand extends GeyserCommand { public SettingsCommand(GeyserImpl geyser, String name, String description, String permission) { - super(name, description, permission, TriState.TRUE, false, true); + super(name, description, permission, TriState.TRUE, true, true); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java index e61299601..18d953dda 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java @@ -39,7 +39,7 @@ import java.util.Objects; public class StatisticsCommand extends GeyserCommand { public StatisticsCommand(GeyserImpl geyser, String name, String description, String permission) { - super(name, description, permission, TriState.TRUE, false, true); + super(name, description, permission, TriState.TRUE, true, true); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java index 2f36a99cb..cf147569f 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java @@ -41,7 +41,6 @@ import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Objects; @@ -52,9 +51,9 @@ public abstract class GeyserExtensionCommand extends GeyserCommand { public GeyserExtensionCommand(@NonNull Extension extension, @NonNull String name, @NonNull String description, @NonNull String permission, @Nullable TriState permissionDefault, - boolean executableOnConsole, boolean bedrockOnly) { + boolean playerOnly, boolean bedrockOnly) { - super(name, description, permission, permissionDefault, executableOnConsole, bedrockOnly); + super(name, description, permission, permissionDefault, playerOnly, bedrockOnly); this.extension = extension; this.rootCommand = Objects.requireNonNull(extension.rootCommand()); @@ -81,7 +80,7 @@ public abstract class GeyserExtensionCommand extends GeyserCommand { @Nullable private TriState permissionDefault; @Nullable private List aliases; private boolean suggestedOpOnly = false; // deprecated for removal - private boolean executableOnConsole = true; + private boolean playerOnly = false; private boolean bedrockOnly = false; @Nullable private CommandExecutor executor; @@ -138,7 +137,13 @@ public abstract class GeyserExtensionCommand extends GeyserCommand { @Override public Builder executableOnConsole(boolean executableOnConsole) { - this.executableOnConsole = executableOnConsole; + this.playerOnly = !executableOnConsole; + return this; + } + + @Override + public Command.Builder playerOnly(boolean playerOnly) { + this.playerOnly = playerOnly; return this; } @@ -173,10 +178,10 @@ public abstract class GeyserExtensionCommand extends GeyserCommand { } // if the source type is a GeyserConnection then it is inherently bedrockOnly - final boolean bedrockOnly = GeyserConnection.class.isAssignableFrom(sourceType) || this.bedrockOnly; + final boolean bedrockOnly = this.bedrockOnly || GeyserConnection.class.isAssignableFrom(sourceType); // a similar check would exist for executableOnConsole, but there is not a logger type exposed in the api - GeyserExtensionCommand command = new GeyserExtensionCommand(extension, name, description, permission, permissionDefault, executableOnConsole, bedrockOnly) { + GeyserExtensionCommand command = new GeyserExtensionCommand(extension, name, description, permission, permissionDefault, playerOnly, bedrockOnly) { @Override public void register(CommandManager manager) { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index da4579bb4..615b77b4b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,7 +25,7 @@ terminalconsoleappender = "1.2.0" folia = "1.19.4-R0.1-SNAPSHOT" viaversion = "4.0.0" adapters = "1.10-SNAPSHOT" -cloud = "1.8.4" +cloud = "1.9.0-20231006.162445-16" # move to release once 1.9.0 is released commodore = "2.2" bungeecord = "a7c6ede" velocity = "3.1.1" From f1ad466c75de6042cb7fd650b8f7c59003b27e43 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+konicai@users.noreply.github.com> Date: Fri, 6 Oct 2023 17:28:49 -0400 Subject: [PATCH 56/76] loom is the bane of my existence --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index aa742b838..c9bbb2559 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,7 +25,7 @@ terminalconsoleappender = "1.2.0" folia = "1.19.4-R0.1-SNAPSHOT" viaversion = "4.0.0" adapters = "1.10-SNAPSHOT" -cloud = "1.9.0-20231006.162445-16" # move to release once 1.9.0 is released +cloud = "1.9.0-SNAPSHOT" # 1.9.0-20231006.162445-16 move to release once 1.9.0 is released commodore = "2.2" bungeecord = "a7c6ede" velocity = "3.1.1" From bfc5b7f10c067e201afb8304e310003c8e4b3af9 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+konicai@users.noreply.github.com> Date: Fri, 6 Oct 2023 17:34:49 -0400 Subject: [PATCH 57/76] Revert "loom is the bane of my existence" This reverts commit f1ad466c75de6042cb7fd650b8f7c59003b27e43. --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c9bbb2559..aa742b838 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,7 +25,7 @@ terminalconsoleappender = "1.2.0" folia = "1.19.4-R0.1-SNAPSHOT" viaversion = "4.0.0" adapters = "1.10-SNAPSHOT" -cloud = "1.9.0-SNAPSHOT" # 1.9.0-20231006.162445-16 move to release once 1.9.0 is released +cloud = "1.9.0-20231006.162445-16" # move to release once 1.9.0 is released commodore = "2.2" bungeecord = "a7c6ede" velocity = "3.1.1" From aeca04a2c1a88e802c6e1a71bac6d76f622b0fb2 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Mon, 16 Oct 2023 23:46:34 -0400 Subject: [PATCH 58/76] update gradle, lombok, loom --- bootstrap/fabric/build.gradle.kts | 2 +- build.gradle.kts | 2 +- gradle/libs.versions.toml | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 60756 -> 62076 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 19 ++++++++++++------- gradlew.bat | 1 + 7 files changed, 17 insertions(+), 11 deletions(-) diff --git a/bootstrap/fabric/build.gradle.kts b/bootstrap/fabric/build.gradle.kts index 24788b16c..abc9652bf 100644 --- a/bootstrap/fabric/build.gradle.kts +++ b/bootstrap/fabric/build.gradle.kts @@ -1,7 +1,7 @@ import net.fabricmc.loom.task.RemapJarTask plugins { - id("fabric-loom") version "1.0-SNAPSHOT" + id("fabric-loom") version "1.4-SNAPSHOT" id("com.modrinth.minotaur") version "2.+" } diff --git a/build.gradle.kts b/build.gradle.kts index 9eb8a6ed0..bd7be00c5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,7 @@ plugins { `java-library` id("geyser.build-logic") - id("io.freefair.lombok") version "6.3.0" apply false + id("io.freefair.lombok") version "8.4" apply false } allprojects { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index aa742b838..c9bbb2559 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,7 +25,7 @@ terminalconsoleappender = "1.2.0" folia = "1.19.4-R0.1-SNAPSHOT" viaversion = "4.0.0" adapters = "1.10-SNAPSHOT" -cloud = "1.9.0-20231006.162445-16" # move to release once 1.9.0 is released +cloud = "1.9.0-SNAPSHOT" # 1.9.0-20231006.162445-16 move to release once 1.9.0 is released commodore = "2.2" bungeecord = "a7c6ede" velocity = "3.1.1" diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 249e5832f090a2944b7473328c07c9755baa3196..c1962a79e29d3e0ab67b14947c167a862655af9b 100644 GIT binary patch delta 39834 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 G$^QWPgI3l6 delta 38507 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 Date: Wed, 28 Feb 2024 13:57:41 -0500 Subject: [PATCH 59/76] javadocs fixes Co-authored-by: chris --- .../main/java/org/geysermc/geyser/api/command/Command.java | 5 +++-- .../api/event/lifecycle/GeyserDefineCommandsEvent.java | 2 +- .../api/event/lifecycle/GeyserRegisterPermissionsEvent.java | 2 +- .../main/java/org/geysermc/geyser/command/GeyserCommand.java | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/command/Command.java b/api/src/main/java/org/geysermc/geyser/api/command/Command.java index 9e8ed174b..348c89f1e 100644 --- a/api/src/main/java/org/geysermc/geyser/api/command/Command.java +++ b/api/src/main/java/org/geysermc/geyser/api/command/Command.java @@ -104,7 +104,7 @@ public interface Command { boolean isBedrockOnly(); /** - * @deprecated this method will always return an immutable list + * @deprecated this method will always return an empty immutable list */ @Deprecated(forRemoval = true) @NonNull @@ -166,7 +166,8 @@ public interface Command { /** * Sets the permission node and its default value. The usage of the default value is platform dependant - * and may or may not be used. For example, it may be registered to an underlying server.

+ * and may or may not be used. For example, it may be registered to an underlying server. +

* Extensions may instead listen for {@link GeyserRegisterPermissionsEvent} to register permissions, which * should be used if the same permission is required by multiple commands. * diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java index 1db64a6c7..d136202bd 100644 --- a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java +++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCommandsEvent.java @@ -33,7 +33,7 @@ import java.util.Map; /** * Called when commands are defined within Geyser. - *

+ *

* This event allows you to register new commands using the {@link #register(Command)} * method and retrieve the default commands defined. */ diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java index 209ef3c5d..fa81c03ce 100644 --- a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java +++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java @@ -30,7 +30,7 @@ import org.geysermc.geyser.api.util.TriState; /** * Fired by anything that wishes to gather permission nodes and defaults. - *

+ *

* This event is not guaranteed to be fired, as certain Geyser platforms do not have a native permission system. * It can be expected to fire on Geyser-Spigot and Geyser-Standalone. * It may still be fired on other platforms due to a 3rd party. diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java index a232d0185..7392205f2 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java @@ -188,7 +188,7 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C /** * Registers this command to the given command manager. * This method may be overridden to register more than one command. - *

+ *

* The default implementation is that {@link #baseBuilder(CommandManager)} with {@link #execute(CommandContext)} * applied as the handler is registered to the manager. */ From c867b5414155bd1cbf9192d76adfd1930d24e143 Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Thu, 7 Mar 2024 12:33:17 +0100 Subject: [PATCH 60/76] attempt at updating the cloud pr; todo neoforge or even general testing --- .../bungeecord/GeyserBungeePlugin.java | 7 +- .../command/BungeeCommandSource.java | 2 +- bootstrap/mod/fabric/build.gradle.kts | 3 + .../fabric/GeyserFabricBootstrap.java | 33 +++++-- .../neoforge/GeyserNeoForgeBootstrap.java | 7 +- .../GeyserNeoForgePermissionHandler.java | 26 ++--- .../platform/mod/GeyserModBootstrap.java | 31 +----- .../platform/mod/GeyserModUpdateListener.java | 4 +- .../mod/command/GeyserModCommandExecutor.java | 0 .../mod/command/ModCommandSender.java | 0 .../mod/command/ModCommandSource.java | 96 +++++++++++++++++++ .../platform/spigot/GeyserSpigotPlugin.java | 10 +- .../standalone/GeyserStandaloneBootstrap.java | 5 +- .../velocity/GeyserVelocityPlugin.java | 18 +--- .../viaproxy/GeyserViaProxyPlugin.java | 27 ++---- .../src/main/resources/permissions.yml | 9 ++ .../geyser/command/GeyserCommandExecutor.java | 0 .../geyser/command/GeyserCommandManager.java | 0 .../standalone/PermissionConfiguration.java | 4 +- .../StandaloneCloudCommandManager.java | 8 +- .../BedrockCommandRequestTranslator.java | 5 +- .../entity/spawn/JavaAddEntityTranslator.java | 3 + core/src/main/resources/languages | 2 +- 23 files changed, 187 insertions(+), 113 deletions(-) delete mode 100644 bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/GeyserModCommandExecutor.java delete mode 100644 bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSender.java create mode 100644 bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSource.java create mode 100644 bootstrap/viaproxy/src/main/resources/permissions.yml delete mode 100644 core/src/main/java/org/geysermc/geyser/command/GeyserCommandExecutor.java delete mode 100644 core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java rename {bootstrap/standalone/src/main/java/org/geysermc/geyser/platform => core/src/main/java/org/geysermc/geyser/command}/standalone/PermissionConfiguration.java (93%) rename bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java => core/src/main/java/org/geysermc/geyser/command/standalone/StandaloneCloudCommandManager.java (95%) diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java index b91c1f563..8d86c5200 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java @@ -38,10 +38,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.command.Command; -import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.api.util.PlatformType; -import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.command.CommandSourceConverter; import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.command.GeyserCommandSource; @@ -73,7 +70,6 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { private GeyserBungeeInjector geyserInjector; private GeyserBungeeLogger geyserLogger; private IGeyserPingPassthrough geyserBungeePingPassthrough; - private GeyserImpl geyser; @Override @@ -106,6 +102,9 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); this.geyser = GeyserImpl.load(PlatformType.BUNGEECORD, this); this.geyserInjector = new GeyserBungeeInjector(this); + + // Registration of listeners occurs only once + this.getProxy().getPluginManager().registerListener(this, new GeyserBungeeUpdateListener()); } @Override diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java index f5bd7610f..db87a4fe6 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/command/BungeeCommandSource.java @@ -88,7 +88,7 @@ public class BungeeCommandSource implements GeyserCommandSource { if (handle instanceof ProxiedPlayer player) { Locale locale = player.getLocale(); if (locale != null) { - // Locale can be null early on in the conneciton + // Locale can be null early on in the connection return GeyserLocale.formatLocale(locale.getLanguage() + "_" + locale.getCountry()); } } diff --git a/bootstrap/mod/fabric/build.gradle.kts b/bootstrap/mod/fabric/build.gradle.kts index dac042ad7..9948d0c9c 100644 --- a/bootstrap/mod/fabric/build.gradle.kts +++ b/bootstrap/mod/fabric/build.gradle.kts @@ -25,6 +25,9 @@ dependencies { modImplementation(libs.fabric.permissions) include(libs.fabric.permissions) + + modImplementation(libs.cloud.fabric) + include(libs.cloud.fabric) } application { diff --git a/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricBootstrap.java b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricBootstrap.java index 81e329c03..11bcc6b55 100644 --- a/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricBootstrap.java +++ b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricBootstrap.java @@ -25,6 +25,9 @@ package org.geysermc.geyser.platform.fabric; +import cloud.commandframework.CommandManager; +import cloud.commandframework.execution.CommandExecutionCoordinator; +import cloud.commandframework.fabric.FabricServerCommandManager; import me.lucko.fabric.api.permissions.v0.Permissions; import net.fabricmc.api.EnvType; import net.fabricmc.api.ModInitializer; @@ -33,9 +36,14 @@ import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.commands.CommandSourceStack; import net.minecraft.world.entity.player.Player; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandRegistry; +import org.geysermc.geyser.command.CommandSourceConverter; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.platform.mod.GeyserModBootstrap; import org.geysermc.geyser.platform.mod.GeyserModUpdateListener; -import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.platform.mod.command.ModCommandSource; public class GeyserFabricBootstrap extends GeyserModBootstrap implements ModInitializer { @@ -58,15 +66,24 @@ public class GeyserFabricBootstrap extends GeyserModBootstrap implements ModInit ServerPlayConnectionEvents.JOIN.register((handler, $, $$) -> GeyserModUpdateListener.onPlayReady(handler.getPlayer())); this.onGeyserInitialize(); + + // TODO: verify + var sourceConverter = CommandSourceConverter.layered( + CommandSourceStack.class, + id -> getServer().getPlayerList().getPlayer(id), + Player::createCommandSourceStack, + () -> getServer().createCommandSourceStack() // NPE if method reference is used, since server is not available yet + ); + CommandManager cloud = new FabricServerCommandManager<>( + CommandExecutionCoordinator.simpleCoordinator(), + ModCommandSource::new, + sourceConverter::convert + ); + this.setCommandRegistry(new CommandRegistry(GeyserImpl.getInstance(), cloud)); } @Override - public boolean hasPermission(@NonNull Player source, @NonNull String permissionNode) { - return Permissions.check(source, permissionNode); - } - - @Override - public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel) { - return Permissions.check(source, permissionNode, permissionLevel); + public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode) { + return Permissions.check(source, permissionNode, source.getServer().getOperatorUserPermissionLevel()); } } diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java index 67cad1683..55a815a6c 100644 --- a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java +++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java @@ -26,7 +26,6 @@ package org.geysermc.geyser.platform.neoforge; import net.minecraft.commands.CommandSourceStack; -import net.minecraft.world.entity.player.Player; import net.neoforged.api.distmarker.Dist; import net.neoforged.fml.common.Mod; import net.neoforged.fml.loading.FMLLoader; @@ -72,12 +71,8 @@ public class GeyserNeoForgeBootstrap extends GeyserModBootstrap { } @Override - public boolean hasPermission(@NonNull Player source, @NonNull String permissionNode) { + public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode) { return this.permissionHandler.hasPermission(source, permissionNode); } - @Override - public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel) { - return this.permissionHandler.hasPermission(source, permissionNode, permissionLevel); - } } diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePermissionHandler.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePermissionHandler.java index 0a5f8f052..e13205b54 100644 --- a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePermissionHandler.java +++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePermissionHandler.java @@ -27,7 +27,6 @@ package org.geysermc.geyser.platform.neoforge; import net.minecraft.commands.CommandSourceStack; import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.entity.player.Player; import net.neoforged.neoforge.server.permission.PermissionAPI; import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent; import net.neoforged.neoforge.server.permission.nodes.PermissionDynamicContextKey; @@ -38,7 +37,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.command.Command; -import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.CommandRegistry; import java.lang.reflect.Constructor; import java.util.HashMap; @@ -69,7 +68,7 @@ public class GeyserNeoForgePermissionHandler { public void onPermissionGather(PermissionGatherEvent.Nodes event) { this.registerNode(Constants.UPDATE_PERMISSION, event); - GeyserCommandManager commandManager = GeyserImpl.getInstance().commandManager(); + CommandRegistry commandManager = GeyserImpl.getInstance().commandRegistry(); for (Map.Entry entry : commandManager.commands().entrySet()) { Command command = entry.getValue(); @@ -95,27 +94,18 @@ public class GeyserNeoForgePermissionHandler { } } - public boolean hasPermission(@NonNull Player source, @NonNull String permissionNode) { + public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode) { + ServerPlayer player = source.getPlayer(); + if (!source.isPlayer() || player == null) { + return true; + } PermissionNode node = this.permissionNodes.get(permissionNode); if (node == null) { GeyserImpl.getInstance().getLogger().warning("Unable to find permission node " + permissionNode); return false; } - return PermissionAPI.getPermission((ServerPlayer) source, node); - } - - public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel) { - if (!source.isPlayer()) { - return true; - } - assert source.getPlayer() != null; - boolean permission = this.hasPermission(source.getPlayer(), permissionNode); - if (!permission) { - return source.getPlayer().hasPermissions(permissionLevel); - } - - return true; + return PermissionAPI.getPermission(player, node); } private void registerNode(String node, PermissionGatherEvent.Nodes event) { diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java index cde65f418..37ed5234d 100644 --- a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java @@ -25,31 +25,23 @@ package org.geysermc.geyser.platform.mod; -import com.mojang.brigadier.arguments.StringArgumentType; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; import net.minecraft.commands.CommandSourceStack; import net.minecraft.server.MinecraftServer; -import net.minecraft.world.entity.player.Player; import org.apache.logging.log4j.LogManager; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; -import org.geysermc.geyser.api.command.Command; -import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.command.CommandRegistry; -import org.geysermc.geyser.command.GeyserCommand; -import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; -import org.geysermc.geyser.platform.mod.command.GeyserModCommandExecutor; import org.geysermc.geyser.platform.mod.platform.GeyserModPlatform; import org.geysermc.geyser.platform.mod.world.GeyserModWorldManager; import org.geysermc.geyser.text.GeyserLocale; @@ -59,7 +51,6 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; -import java.util.Map; import java.util.UUID; @RequiredArgsConstructor @@ -73,9 +64,10 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap { private GeyserImpl geyser; private Path dataFolder; - @Setter + @Setter @Getter private MinecraftServer server; + @Setter private CommandRegistry commandRegistry; private GeyserModConfiguration geyserConfig; private GeyserModInjector geyserInjector; @@ -94,21 +86,6 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap { this.geyserLogger = new GeyserModLogger(geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); this.geyser = GeyserImpl.load(this.platform.platformType(), this); - - // Create command manager here, since the permission handler on neo needs it - //TODO differentiate Fabric/NeoForge!? - var sourceConverter = CommandSourceConverter.layered( - CommandSourceStack.class, - id -> server.getPlayerList().getPlayer(id), - Player::createCommandSourceStack, - () -> server.createCommandSourceStack() // NPE if method reference is used, since server is not available yet - ); - CommandManager cloud = new FabricServerCommandManager<>( - CommandExecutionCoordinator.simpleCoordinator(), - FabricCommandSource::new, - sourceConverter::convert - ); - commandRegistry = new CommandRegistry(geyser, cloud); } public void onGeyserEnable() { @@ -224,9 +201,7 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap { return this.platform.resolveResource(resource); } - public abstract boolean hasPermission(@NonNull Player source, @NonNull String permissionNode); - - public abstract boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel); + public abstract boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode); @SuppressWarnings("BooleanMethodIsAlwaysInverted") private boolean loadConfig() { diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModUpdateListener.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModUpdateListener.java index 0762e16f7..1fabaa899 100644 --- a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModUpdateListener.java +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModUpdateListener.java @@ -34,8 +34,8 @@ import org.geysermc.geyser.util.VersionCheckUtils; public final class GeyserModUpdateListener { public static void onPlayReady(Player player) { CommandSourceStack stack = player.createCommandSourceStack(); - if (GeyserModBootstrap.getInstance().hasPermission(stack, Constants.UPDATE_PERMISSION, 2)) { - VersionCheckUtils.checkForGeyserUpdate(() -> new ModCommandSender(stack)); + if (GeyserModBootstrap.getInstance().hasPermission(stack, Constants.UPDATE_PERMISSION)) { + VersionCheckUtils.checkForGeyserUpdate(() -> new ModCommandSource(stack)); } } diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/GeyserModCommandExecutor.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/GeyserModCommandExecutor.java deleted file mode 100644 index e69de29bb..000000000 diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSender.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSender.java deleted file mode 100644 index e69de29bb..000000000 diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSource.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSource.java new file mode 100644 index 000000000..0c85b533f --- /dev/null +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSource.java @@ -0,0 +1,96 @@ +/* + * 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.mod.command; + +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.GeyserCommandSource; +import org.geysermc.geyser.platform.mod.GeyserModBootstrap; +import org.geysermc.geyser.text.ChatColor; + +import java.util.Objects; +import java.util.UUID; + +public class ModCommandSource implements GeyserCommandSource { + + private final CommandSourceStack source; + + public ModCommandSource(CommandSourceStack source) { + this.source = source; + } + + @Override + public String name() { + return source.getTextName(); + } + + @Override + public void sendMessage(@NonNull String message) { + if (source.getEntity() instanceof ServerPlayer) { + ((ServerPlayer) source.getEntity()).displayClientMessage(Component.literal(message), false); + } else { + GeyserImpl.getInstance().getLogger().info(ChatColor.toANSI(message + ChatColor.RESET)); + } + } + + @Override + public void sendMessage(net.kyori.adventure.text.Component message) { + if (source.getEntity() instanceof ServerPlayer player) { + String decoded = GsonComponentSerializer.gson().serialize(message); + player.displayClientMessage(Objects.requireNonNull(Component.Serializer.fromJson(decoded)), false); + return; + } + GeyserCommandSource.super.sendMessage(message); + } + + @Override + public boolean isConsole() { + return !(source.getEntity() instanceof ServerPlayer); + } + + @Override + public @Nullable UUID playerUuid() { + if (source.getEntity() instanceof ServerPlayer player) { + return player.getUUID(); + } + return null; + } + + @Override + public boolean hasPermission(String permission) { + return GeyserModBootstrap.getInstance().hasPermission(source, permission); + } + + @Override + public Object handle() { + return source; + } +} \ No newline at end of file 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 a8d39d5f4..2fbc71f4d 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 @@ -162,7 +162,12 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { @Override public void onEnable() { - var sourceConverter = new CommandSourceConverter<>(CommandSender.class, Bukkit::getPlayer, Bukkit::getConsoleSender); + // Create command manager early so we can add Geyser extension commands + var sourceConverter = new CommandSourceConverter<>( + CommandSender.class, + Bukkit::getPlayer, + Bukkit::getConsoleSender + ); PaperCommandManager cloud; try { cloud = new PaperCommandManager<>( @@ -190,7 +195,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { // Needs to be an anonymous inner class otherwise Bukkit complains about missing classes Bukkit.getPluginManager().registerEvents(new Listener() { - @EventHandler public void onServerLoaded(ServerLoadEvent event) { if (event.getType() == ServerLoadEvent.LoadType.RELOAD) { @@ -230,7 +234,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { } geyserLogger.debug("Spigot ping passthrough type: " + (this.geyserSpigotPingPassthrough == null ? null : this.geyserSpigotPingPassthrough.getClass())); - // Don't need to re-create the world manager/re-register commands/reinject when reloading + // Don't need to re-create the world manager/reinject when reloading if (GeyserImpl.getInstance().isReloading()) { return; } diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java index 432a674de..ff78a8301 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java @@ -43,6 +43,7 @@ import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.command.CommandRegistry; +import org.geysermc.geyser.command.standalone.StandaloneCloudCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; @@ -225,7 +226,7 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { geyser = GeyserImpl.load(PlatformType.STANDALONE, this); // fire GeyserDefineCommandsEvent after PreInitEvent, before PostInitEvent, for consistency with other bootstraps - GeyserStandaloneCommandManager cloud = new GeyserStandaloneCommandManager(geyser); + StandaloneCloudCommandManager cloud = new StandaloneCloudCommandManager(geyser); commandRegistry = new CommandRegistry(geyser, cloud); GeyserImpl.start(); @@ -261,7 +262,7 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { @Override public void onGeyserDisable() { // We can re-register commands on standalone, so why not - GeyserImpl.getInstance().commandManager().getCommands().clear(); + commandRegistry.commands().clear(); geyser.disable(); } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java index dc0fa3031..e18d18b33 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java @@ -143,22 +143,10 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { this.geyserPingPassthrough = new GeyserVelocityPingPassthrough(proxyServer); } - // No need to re-register commands when reloading - if (GeyserImpl.getInstance().isReloading()) { - return; + // No need to re-register events + if (!GeyserImpl.getInstance().isReloading()) { + proxyServer.getEventManager().register(this, new GeyserVelocityUpdateListener()); } - - this.commandManager.register("geyser", new GeyserVelocityCommandExecutor(geyser, geyserCommandManager.getCommands())); - for (Map.Entry> entry : this.geyserCommandManager.extensionCommands().entrySet()) { - Map commands = entry.getValue(); - if (commands.isEmpty()) { - continue; - } - - this.commandManager.register(entry.getKey().description().id(), new GeyserVelocityCommandExecutor(this.geyser, commands)); - } - - proxyServer.getEventManager().register(this, new GeyserVelocityUpdateListener()); } @Override diff --git a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyPlugin.java b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyPlugin.java index 47745df7d..5095be11e 100644 --- a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyPlugin.java +++ b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyPlugin.java @@ -30,17 +30,18 @@ import net.raphimc.viaproxy.ViaProxy; import net.raphimc.viaproxy.cli.options.Options; import net.raphimc.viaproxy.plugins.PluginManager; import net.raphimc.viaproxy.plugins.ViaProxyPlugin; -import net.raphimc.viaproxy.plugins.events.ConsoleCommandEvent; import net.raphimc.viaproxy.plugins.events.ProxyStartEvent; import net.raphimc.viaproxy.plugins.events.ProxyStopEvent; import net.raphimc.viaproxy.plugins.events.ShouldVerifyOnlineModeEvent; import org.apache.logging.log4j.LogManager; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.api.util.PlatformType; -import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.command.CommandRegistry; +import org.geysermc.geyser.command.standalone.StandaloneCloudCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; @@ -49,7 +50,6 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; import org.geysermc.geyser.util.LoopbackUtil; -import org.jetbrains.annotations.NotNull; import java.io.File; import java.io.IOException; @@ -65,7 +65,7 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst private final GeyserViaProxyLogger logger = new GeyserViaProxyLogger(LogManager.getLogger("Geyser")); private GeyserViaProxyConfiguration config; private GeyserImpl geyser; - private GeyserCommandManager commandManager; + private CommandRegistry commandRegistry; private IGeyserPingPassthrough pingPassthrough; @Override @@ -83,14 +83,6 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst this.onGeyserShutdown(); } - @EventHandler - private void onConsoleCommand(final ConsoleCommandEvent event) { - final String command = event.getCommand().startsWith("/") ? event.getCommand().substring(1) : event.getCommand(); - if (this.getGeyserCommandManager().runCommand(this.getGeyserLogger(), command + " " + String.join(" ", event.getArgs()))) { - event.setCancelled(true); - } - } - @EventHandler private void onShouldVerifyOnlineModeEvent(final ShouldVerifyOnlineModeEvent event) { final UUID uuid = event.getProxyConnection().getGameProfile().getId(); @@ -132,8 +124,8 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst } } - this.commandManager = new GeyserCommandManager(this.geyser); - this.commandManager.init(); + StandaloneCloudCommandManager cloud = new StandaloneCloudCommandManager(geyser); + this.commandRegistry = new CommandRegistry(geyser, cloud); GeyserImpl.start(); @@ -164,8 +156,8 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst } @Override - public GeyserCommandManager getGeyserCommandManager() { - return this.commandManager; + public CommandRegistry getCommandRegistry() { + return this.commandRegistry; } @Override @@ -183,7 +175,7 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst return new GeyserViaProxyDumpInfo(); } - @NotNull + @NonNull @Override public String getServerBindAddress() { if (Options.BIND_ADDRESS instanceof InetSocketAddress socketAddress) { @@ -207,6 +199,7 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst return false; } + @SuppressWarnings("BooleanMethodIsAlwaysInverted") private boolean loadConfig() { try { final File configFile = FileUtils.fileOrCopiedFromResource(new File(ROOT_FOLDER, "config.yml"), "config.yml", s -> s.replaceAll("generateduuid", UUID.randomUUID().toString()), this); diff --git a/bootstrap/viaproxy/src/main/resources/permissions.yml b/bootstrap/viaproxy/src/main/resources/permissions.yml new file mode 100644 index 000000000..4da9251e8 --- /dev/null +++ b/bootstrap/viaproxy/src/main/resources/permissions.yml @@ -0,0 +1,9 @@ + +# Add any permissions here that all players should have. +# Permissions for builtin Geyser commands do not have to be listed here. + +# If an extension/plugin registers their permissions with default values, entries here are typically unnecessary. +# If extensions don't register their permissions, permissions that everyone should have must be added here manually. + +default-permissions: + - geyser.command.help # this is unnecessary diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandExecutor.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandExecutor.java deleted file mode 100644 index e69de29bb..000000000 diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommandManager.java deleted file mode 100644 index e69de29bb..000000000 diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/PermissionConfiguration.java b/core/src/main/java/org/geysermc/geyser/command/standalone/PermissionConfiguration.java similarity index 93% rename from bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/PermissionConfiguration.java rename to core/src/main/java/org/geysermc/geyser/command/standalone/PermissionConfiguration.java index 28edddaaa..edacd49ff 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/PermissionConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/command/standalone/PermissionConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2024 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 @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.platform.standalone; +package org.geysermc.geyser.command.standalone; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java b/core/src/main/java/org/geysermc/geyser/command/standalone/StandaloneCloudCommandManager.java similarity index 95% rename from bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java rename to core/src/main/java/org/geysermc/geyser/command/standalone/StandaloneCloudCommandManager.java index e3d68da11..302e36e40 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneCommandManager.java +++ b/core/src/main/java/org/geysermc/geyser/command/standalone/StandaloneCloudCommandManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2024 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 @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.platform.standalone; +package org.geysermc.geyser.command.standalone; import cloud.commandframework.CommandManager; import cloud.commandframework.execution.CommandExecutionCoordinator; @@ -46,7 +46,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; -public class GeyserStandaloneCommandManager extends CommandManager { +public class StandaloneCloudCommandManager extends CommandManager { private final GeyserImpl geyser; @@ -60,7 +60,7 @@ public class GeyserStandaloneCommandManager extends CommandManager basePermissions = new ObjectOpenHashSet<>(); - public GeyserStandaloneCommandManager(GeyserImpl geyser) { + public StandaloneCloudCommandManager(GeyserImpl geyser) { super(CommandExecutionCoordinator.simpleCoordinator(), CommandRegistrationHandler.nullCommandRegistrationHandler()); // simpleCoordinator: execute commands immediately on the calling thread. // nullCommandRegistrationHandler: cloud is not responsible for handling our CommandRegistry, which is fairly decoupled. diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java index f7036cf9b..4a4495493 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockCommandRequestTranslator.java @@ -48,8 +48,9 @@ public class BedrockCommandRequestTranslator extends PacketTranslator 0) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddEntityTranslator.java index e058594c3..1f88622aa 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddEntityTranslator.java @@ -112,6 +112,9 @@ public class JavaAddEntityTranslator extends PacketTranslator Date: Thu, 7 Mar 2024 13:59:38 +0100 Subject: [PATCH 61/76] some more merge issues --- .../platform/bungeecord/GeyserBungeePlugin.java | 7 +++++++ .../command/GeyserPaperCommandListener.java | 0 .../command/GeyserSpigotCommandExecutor.java | 0 .../main/java/org/geysermc/geyser/GeyserImpl.java | 1 - .../registry/loader/ProviderRegistryLoader.java | 1 - gradle/libs.versions.toml | 15 ++++++++------- 6 files changed, 15 insertions(+), 9 deletions(-) delete mode 100644 bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserPaperCommandListener.java delete mode 100644 bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandExecutor.java diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java index 8d86c5200..24ecf2607 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java @@ -72,6 +72,9 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { private IGeyserPingPassthrough geyserBungeePingPassthrough; private GeyserImpl geyser; + // We can't disable the plugin; hence we need to keep track of it manually + private boolean disabled; + @Override public void onLoad() { onGeyserInitialize(); @@ -96,6 +99,7 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { } if (!this.loadConfig()) { + disabled = true; return; } this.geyserLogger = new GeyserBungeeLogger(getLogger(), geyserConfig.isDebugMode()); @@ -109,6 +113,9 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { @Override public void onEnable() { + if (disabled) { + return; // Config did not load properly! + } // Big hack - Bungee does not provide us an event to listen to, so schedule a repeating // task that waits for a field to be filled which is set after the plugin enable // process is complete 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 deleted file mode 100644 index e69de29bb..000000000 diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandExecutor.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/GeyserSpigotCommandExecutor.java deleted file mode 100644 index e69de29bb..000000000 diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index d1a79f480..d8ab575f2 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -656,7 +656,6 @@ public class GeyserImpl implements GeyserApi { if (isEnabled) { this.disable(); } - this.commandManager().getCommands().clear(); // Disable extensions, fire the shutdown event this.eventBus.fire(new GeyserShutdownEvent(this.extensionManager, this.eventBus)); diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java index 0b3b81d1c..94de0c298 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java @@ -42,7 +42,6 @@ import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; import org.geysermc.geyser.api.pack.PathPackCodec; import org.geysermc.geyser.impl.camera.GeyserCameraFade; import org.geysermc.geyser.impl.camera.GeyserCameraPosition; -import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.event.GeyserEventRegistrar; import org.geysermc.geyser.extension.command.GeyserExtensionCommand; import org.geysermc.geyser.item.GeyserCustomItemData; diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c790406d5..cd96a8347 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,7 +25,7 @@ terminalconsoleappender = "1.2.0" folia = "1.19.4-R0.1-SNAPSHOT" viaversion = "4.9.2" adapters = "1.11-SNAPSHOT" -cloud = "1.9.0-SNAPSHOT" # 1.9.0-20231006.162445-16 move to release once 1.9.0 is released +cloud = "2.0.0-beta.2" commodore = "2.2" bungeecord = "a7c6ede" velocity = "3.1.1" @@ -41,7 +41,7 @@ mixin = "0.8.5" indra = "3.1.3" shadow = "7.1.3-SNAPSHOT" architectury-plugin = "3.4-SNAPSHOT" -architectury-loom = "1.4-SNAPSHOT" +architectury-loom = "1.5-SNAPSHOT" minotaur = "2.8.7" lombok = "8.4" blossom = "1.2.0" @@ -85,11 +85,12 @@ jline-terminal = { group = "org.jline", name = "jline-terminal", version.ref = " jline-terminal-jna = { group = "org.jline", name = "jline-terminal-jna", version.ref = "jline" } jline-reader = { group = "org.jline", name = "jline-reader", version.ref = "jline" } -cloud-core = { group = "cloud.commandframework", name = "cloud-core", version.ref = "cloud" } -cloud-paper = { group = "cloud.commandframework", name = "cloud-paper", version.ref = "cloud" } -cloud-velocity = { group = "cloud.commandframework", name = "cloud-velocity", version.ref = "cloud" } -cloud-bungee = { group = "cloud.commandframework", name = "cloud-bungee", version.ref = "cloud" } -cloud-fabric = { group = "cloud.commandframework", name = "cloud-fabric", version.ref = "cloud" } +cloud-core = { group = "org.incendo", name = "cloud-core", version.ref = "cloud" } +cloud-paper = { group = "org.incendo", name = "cloud-paper", version.ref = "cloud" } +cloud-velocity = { group = "org.incendo", name = "cloud-velocity", version.ref = "cloud" } +cloud-bungee = { group = "org.incendo", name = "cloud-bungee", version.ref = "cloud" } +cloud-fabric = { group = "org.incendo", name = "cloud-fabric", version.ref = "cloud" } +cloud-neoforge = { group = "org.incendo", name = "cloud-neoforge", version.ref = "cloud" } folia-api = { group = "dev.folia", name = "folia-api", version.ref = "folia" } From dc1826c5f911eb24dacd183d6e7a6f2af6823722 Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Thu, 7 Mar 2024 19:55:17 +0100 Subject: [PATCH 62/76] Start on cloud 2.0 changes --- bootstrap/bungeecord/build.gradle.kts | 2 +- .../bungeecord/GeyserBungeePlugin.java | 16 ++--- .../fabric/GeyserFabricBootstrap.java | 14 ++-- bootstrap/mod/neoforge/build.gradle.kts | 3 + .../neoforge/GeyserNeoForgeBootstrap.java | 23 ++++++ bootstrap/spigot/build.gradle.kts | 2 +- .../platform/spigot/GeyserSpigotPlugin.java | 35 +++++---- .../spigot/command/SpigotCommandRegistry.java | 2 +- bootstrap/velocity/build.gradle.kts | 2 +- .../velocity/GeyserVelocityPlugin.java | 18 ++--- bootstrap/viaproxy/build.gradle.kts | 1 + .../geyser/command/CommandRegistry.java | 39 +++++----- .../command/CommandSourceConverter.java | 71 +++++++++++-------- .../geyser/command/GeyserCommand.java | 15 ++-- .../geyser/command/GeyserPermission.java | 4 +- .../defaults/AdvancedTooltipsCommand.java | 4 +- .../command/defaults/AdvancementsCommand.java | 4 +- .../defaults/ConnectionTestCommand.java | 11 +-- .../geyser/command/defaults/DumpCommand.java | 10 +-- .../command/defaults/ExtensionsCommand.java | 4 +- .../geyser/command/defaults/HelpCommand.java | 6 +- .../geyser/command/defaults/ListCommand.java | 4 +- .../command/defaults/OffhandCommand.java | 4 +- .../command/defaults/ReloadCommand.java | 4 +- .../command/defaults/SettingsCommand.java | 4 +- .../command/defaults/StatisticsCommand.java | 4 +- .../geyser/command/defaults/StopCommand.java | 2 +- .../command/defaults/VersionCommand.java | 4 +- .../StandaloneCloudCommandManager.java | 12 ++-- .../command/GeyserExtensionCommand.java | 11 +-- 30 files changed, 185 insertions(+), 150 deletions(-) diff --git a/bootstrap/bungeecord/build.gradle.kts b/bootstrap/bungeecord/build.gradle.kts index e144bd3ff..684a1c46a 100644 --- a/bootstrap/bungeecord/build.gradle.kts +++ b/bootstrap/bungeecord/build.gradle.kts @@ -9,7 +9,7 @@ platformRelocate("net.md_5.bungee.jni") platformRelocate("com.fasterxml.jackson") platformRelocate("io.netty.channel.kqueue") // This is not used because relocating breaks natives, but we must include it or else we get ClassDefNotFound platformRelocate("net.kyori") -platformRelocate("cloud.commandframework") +platformRelocate("org.incendo") platformRelocate("org.yaml") // Broken as of 1.20 // These dependencies are already present on the platform diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java index 24ecf2607..0816f98c8 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java @@ -25,9 +25,6 @@ package org.geysermc.geyser.platform.bungeecord; -import cloud.commandframework.CommandManager; -import cloud.commandframework.bungee.BungeeCommandManager; -import cloud.commandframework.execution.CommandExecutionCoordinator; import io.netty.channel.Channel; import net.md_5.bungee.BungeeCord; import net.md_5.bungee.api.CommandSender; @@ -39,8 +36,8 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.util.PlatformType; -import org.geysermc.geyser.command.CommandSourceConverter; import org.geysermc.geyser.command.CommandRegistry; +import org.geysermc.geyser.command.CommandSourceConverter; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; @@ -49,6 +46,9 @@ import org.geysermc.geyser.ping.IGeyserPingPassthrough; import org.geysermc.geyser.platform.bungeecord.command.BungeeCommandSource; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.bungee.BungeeCommandManager; +import org.incendo.cloud.execution.ExecutionCoordinator; import java.io.File; import java.io.IOException; @@ -159,13 +159,13 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { var sourceConverter = new CommandSourceConverter<>( CommandSender.class, id -> getProxy().getPlayer(id), - () -> getProxy().getConsole() + () -> getProxy().getConsole(), + BungeeCommandSource::new ); CommandManager cloud = new BungeeCommandManager<>( this, - CommandExecutionCoordinator.simpleCoordinator(), - BungeeCommandSource::new, - sourceConverter::convert + ExecutionCoordinator.simpleCoordinator(), + sourceConverter ); this.commandRegistry = new CommandRegistry(geyser, cloud); diff --git a/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricBootstrap.java b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricBootstrap.java index 11bcc6b55..31db15976 100644 --- a/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricBootstrap.java +++ b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricBootstrap.java @@ -25,9 +25,6 @@ package org.geysermc.geyser.platform.fabric; -import cloud.commandframework.CommandManager; -import cloud.commandframework.execution.CommandExecutionCoordinator; -import cloud.commandframework.fabric.FabricServerCommandManager; import me.lucko.fabric.api.permissions.v0.Permissions; import net.fabricmc.api.EnvType; import net.fabricmc.api.ModInitializer; @@ -44,6 +41,9 @@ import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.platform.mod.GeyserModBootstrap; import org.geysermc.geyser.platform.mod.GeyserModUpdateListener; import org.geysermc.geyser.platform.mod.command.ModCommandSource; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.execution.ExecutionCoordinator; +import org.incendo.cloud.fabric.FabricServerCommandManager; public class GeyserFabricBootstrap extends GeyserModBootstrap implements ModInitializer { @@ -72,12 +72,12 @@ public class GeyserFabricBootstrap extends GeyserModBootstrap implements ModInit CommandSourceStack.class, id -> getServer().getPlayerList().getPlayer(id), Player::createCommandSourceStack, - () -> getServer().createCommandSourceStack() // NPE if method reference is used, since server is not available yet + () -> getServer().createCommandSourceStack(), // NPE if method reference is used, since server is not available yet + ModCommandSource::new ); CommandManager cloud = new FabricServerCommandManager<>( - CommandExecutionCoordinator.simpleCoordinator(), - ModCommandSource::new, - sourceConverter::convert + ExecutionCoordinator.simpleCoordinator(), + sourceConverter ); this.setCommandRegistry(new CommandRegistry(GeyserImpl.getInstance(), cloud)); } diff --git a/bootstrap/mod/neoforge/build.gradle.kts b/bootstrap/mod/neoforge/build.gradle.kts index d85087542..08627028b 100644 --- a/bootstrap/mod/neoforge/build.gradle.kts +++ b/bootstrap/mod/neoforge/build.gradle.kts @@ -27,6 +27,9 @@ dependencies { exclude(group = "org.slf4j") exclude(group = "io.netty.incubator") } + + modImplementation(libs.cloud.neoforge) + include(libs.cloud.neoforge) } application { diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java index 55a815a6c..977897bf6 100644 --- a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java +++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java @@ -26,6 +26,7 @@ package org.geysermc.geyser.platform.neoforge; import net.minecraft.commands.CommandSourceStack; +import net.minecraft.world.entity.player.Player; import net.neoforged.api.distmarker.Dist; import net.neoforged.fml.common.Mod; import net.neoforged.fml.loading.FMLLoader; @@ -34,8 +35,16 @@ import net.neoforged.neoforge.event.entity.player.PlayerEvent; import net.neoforged.neoforge.event.server.ServerStartedEvent; import net.neoforged.neoforge.event.server.ServerStoppingEvent; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandRegistry; +import org.geysermc.geyser.command.CommandSourceConverter; +import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.platform.mod.GeyserModBootstrap; import org.geysermc.geyser.platform.mod.GeyserModUpdateListener; +import org.geysermc.geyser.platform.mod.command.ModCommandSource; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.execution.ExecutionCoordinator; +import org.incendo.cloud.neoforge.NeoForgeServerCommandManager; @Mod(ModConstants.MOD_ID) public class GeyserNeoForgeBootstrap extends GeyserModBootstrap { @@ -55,6 +64,20 @@ public class GeyserNeoForgeBootstrap extends GeyserModBootstrap { NeoForge.EVENT_BUS.addListener(this.permissionHandler::onPermissionGather); this.onGeyserInitialize(); + + // TODO: verify; idek how to make permissions on neoforge work with this... + var sourceConverter = CommandSourceConverter.layered( + CommandSourceStack.class, + id -> getServer().getPlayerList().getPlayer(id), + Player::createCommandSourceStack, + () -> getServer().createCommandSourceStack(), + ModCommandSource::new + ); + CommandManager cloud = new NeoForgeServerCommandManager<>( + ExecutionCoordinator.simpleCoordinator(), + sourceConverter + ); + this.setCommandRegistry(new CommandRegistry(GeyserImpl.getInstance(), cloud)); } private void onServerStarted(ServerStartedEvent event) { diff --git a/bootstrap/spigot/build.gradle.kts b/bootstrap/spigot/build.gradle.kts index 100b39b96..74ecad314 100644 --- a/bootstrap/spigot/build.gradle.kts +++ b/bootstrap/spigot/build.gradle.kts @@ -27,7 +27,7 @@ platformRelocate("com.fasterxml.jackson") platformRelocate("net.kyori", "net.kyori.adventure.text.logger.slf4j.ComponentLogger") platformRelocate("org.objectweb.asm") platformRelocate("me.lucko.commodore") -platformRelocate("cloud.commandframework") +platformRelocate("org.incendo") platformRelocate("org.yaml") // Broken as of 1.20 // These dependencies are already present on the platform 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 2fbc71f4d..0ceff43cb 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,9 +25,6 @@ package org.geysermc.geyser.platform.spigot; -import cloud.commandframework.bukkit.BukkitCommandManager; -import cloud.commandframework.execution.CommandExecutionCoordinator; -import cloud.commandframework.paper.PaperCommandManager; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.data.MappingData; import com.viaversion.viaversion.api.protocol.ProtocolPathEntry; @@ -44,14 +41,14 @@ import org.bukkit.permissions.PermissionDefault; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent; -import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.adapters.spigot.SpigotAdapters; -import org.geysermc.geyser.command.CommandSourceConverter; +import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent; +import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.command.CommandRegistry; +import org.geysermc.geyser.command.CommandSourceConverter; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; @@ -68,6 +65,10 @@ import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotNativeWorld import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; +import org.incendo.cloud.bukkit.BukkitCommandManager; +import org.incendo.cloud.bukkit.CloudBukkitCapabilities; +import org.incendo.cloud.execution.ExecutionCoordinator; +import org.incendo.cloud.paper.PaperCommandManager; import java.io.File; import java.io.IOException; @@ -166,28 +167,26 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { var sourceConverter = new CommandSourceConverter<>( CommandSender.class, Bukkit::getPlayer, - Bukkit::getConsoleSender + Bukkit::getConsoleSender, + SpigotCommandSource::new ); PaperCommandManager cloud; try { cloud = new PaperCommandManager<>( this, - CommandExecutionCoordinator.simpleCoordinator(), - SpigotCommandSource::new, - sourceConverter::convert + ExecutionCoordinator.simpleCoordinator(), + sourceConverter ); } catch (Exception e) { throw new RuntimeException(e); } - try { - // Should always be available on 1.13 and up - cloud.registerBrigadier(); - } catch (BukkitCommandManager.BrigadierFailureException e) { - geyserLogger.debug("Failed to initialize Brigadier support: " + e.getMessage()); - if (e.getReason() == BukkitCommandManager.BrigadierFailureReason.VERSION_TOO_HIGH) { - // Commodore brig only supports Spigot 1.13 - 1.18.2 - geyserLogger.debug("Using Paper instead of Spigot will likely fix this."); + if (cloud.hasCapability(CloudBukkitCapabilities.BRIGADIER)) { + try { + // Should always be available on 1.13 and up + cloud.registerBrigadier(); + } catch (BukkitCommandManager.BrigadierInitializationException e) { + geyserLogger.debug("Failed to initialize Brigadier support: " + e.getMessage()); } } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandRegistry.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandRegistry.java index 0363b9c27..39496d2c6 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandRegistry.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/command/SpigotCommandRegistry.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.platform.spigot.command; -import cloud.commandframework.CommandManager; import org.bukkit.Bukkit; import org.bukkit.Server; import org.bukkit.command.Command; @@ -34,6 +33,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.command.GeyserCommandSource; +import org.incendo.cloud.CommandManager; import java.lang.reflect.Field; diff --git a/bootstrap/velocity/build.gradle.kts b/bootstrap/velocity/build.gradle.kts index fdd0edc46..ba5544f55 100644 --- a/bootstrap/velocity/build.gradle.kts +++ b/bootstrap/velocity/build.gradle.kts @@ -7,7 +7,7 @@ dependencies { platformRelocate("com.fasterxml.jackson") platformRelocate("it.unimi.dsi.fastutil") platformRelocate("net.kyori.adventure.text.serializer.gson.legacyimpl") -platformRelocate("cloud.commandframework") +platformRelocate("org.incendo") exclude("com.google.*:*") diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java index e18d18b33..7026909d2 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java @@ -25,9 +25,6 @@ package org.geysermc.geyser.platform.velocity; -import cloud.commandframework.CommandManager; -import cloud.commandframework.execution.CommandExecutionCoordinator; -import cloud.commandframework.velocity.VelocityCommandManager; import com.google.inject.Inject; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.event.Subscribe; @@ -42,11 +39,11 @@ import com.velocitypowered.api.proxy.ProxyServer; import lombok.Getter; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandSourceConverter; +import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.command.CommandRegistry; +import org.geysermc.geyser.command.CommandSourceConverter; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; @@ -56,6 +53,9 @@ import org.geysermc.geyser.ping.IGeyserPingPassthrough; import org.geysermc.geyser.platform.velocity.command.VelocityCommandSource; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.execution.ExecutionCoordinator; +import org.incendo.cloud.velocity.VelocityCommandManager; import org.slf4j.Logger; import java.io.File; @@ -123,14 +123,14 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { var sourceConverter = new CommandSourceConverter<>( CommandSource.class, id -> proxyServer.getPlayer(id).orElse(null), - proxyServer::getConsoleCommandSource + proxyServer::getConsoleCommandSource, + VelocityCommandSource::new ); CommandManager cloud = new VelocityCommandManager<>( container, proxyServer, - CommandExecutionCoordinator.simpleCoordinator(), - VelocityCommandSource::new, - sourceConverter::convert + ExecutionCoordinator.simpleCoordinator(), + sourceConverter ); this.commandRegistry = new CommandRegistry(geyser, cloud); } diff --git a/bootstrap/viaproxy/build.gradle.kts b/bootstrap/viaproxy/build.gradle.kts index 4d5d4f949..45efc8b0d 100644 --- a/bootstrap/viaproxy/build.gradle.kts +++ b/bootstrap/viaproxy/build.gradle.kts @@ -6,6 +6,7 @@ platformRelocate("net.kyori") platformRelocate("org.yaml") platformRelocate("it.unimi.dsi.fastutil") platformRelocate("org.cloudburstmc.netty") +platformRelocate("org.incendo") // These dependencies are already present on the platform provided(libs.viaproxy) diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java index 09412abf7..df97b8ace 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java @@ -25,24 +25,16 @@ package org.geysermc.geyser.command; -import cloud.commandframework.CommandManager; -import cloud.commandframework.exceptions.ArgumentParseException; -import cloud.commandframework.exceptions.CommandExecutionException; -import cloud.commandframework.exceptions.InvalidCommandSenderException; -import cloud.commandframework.exceptions.InvalidSyntaxException; -import cloud.commandframework.exceptions.NoPermissionException; -import cloud.commandframework.exceptions.NoSuchCommandException; -import cloud.commandframework.execution.CommandExecutionCoordinator; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import lombok.AllArgsConstructor; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.GeyserLogger; -import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent; -import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCommandsEvent; +import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent; import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.defaults.AdvancedTooltipsCommand; import org.geysermc.geyser.command.defaults.AdvancementsCommand; @@ -62,7 +54,14 @@ import org.geysermc.geyser.event.type.GeyserDefineCommandsEventImpl; import org.geysermc.geyser.extension.command.GeyserExtensionCommand; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; -import org.jetbrains.annotations.NotNull; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.exception.ArgumentParseException; +import org.incendo.cloud.exception.CommandExecutionException; +import org.incendo.cloud.exception.InvalidCommandSenderException; +import org.incendo.cloud.exception.InvalidSyntaxException; +import org.incendo.cloud.exception.NoPermissionException; +import org.incendo.cloud.exception.NoSuchCommandException; +import org.incendo.cloud.execution.ExecutionCoordinator; import java.util.Collections; import java.util.HashMap; @@ -101,8 +100,8 @@ public class CommandRegistry { * For example: https://github.com/Incendo/cloud/blob/a4cc749b91564af57bb7bba36dd8011b556c2b3a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricExecutor.java#L94-L173 */ private final List> exceptionHandlers = List.of( - new ExceptionHandler<>(InvalidSyntaxException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_syntax", e.getCorrectSyntax())), - new ExceptionHandler<>(InvalidCommandSenderException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_sender", e.getCommandSender().getClass().getSimpleName(), e.getRequiredSender().getSimpleName())), + new ExceptionHandler<>(InvalidSyntaxException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_syntax", e.correctSyntax())), + new ExceptionHandler<>(InvalidCommandSenderException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_sender", e.commandSender().getClass().getSimpleName(), e.requiredSender())), new ExceptionHandler<>(NoPermissionException.class, this::handleNoPermission), new ExceptionHandler<>(NoSuchCommandException.class, (src, e) -> src.sendLocaleString("geyser.command.not_found")), new ExceptionHandler<>(ArgumentParseException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_argument", e.getCause().getMessage())), @@ -174,17 +173,17 @@ public class CommandRegistry { geyser.eventBus().subscribe(new GeyserEventRegistrar(this), GeyserRegisterPermissionsEvent.class, this::onRegisterPermissions); } - @NotNull + @NonNull public CommandManager cloud() { return cloud; } - @NotNull + @NonNull public Map commands() { return Collections.unmodifiableMap(this.commands); } - @NotNull + @NonNull public Map> extensionCommands() { return Collections.unmodifiableMap(this.extensionCommands); } @@ -243,10 +242,10 @@ public class CommandRegistry { /** * Dispatches a command into cloud and handles any thrown exceptions. - * This method may or may not be blocking, depending on the {@link CommandExecutionCoordinator} in use by cloud. + * This method may or may not be blocking, depending on the {@link ExecutionCoordinator} in use by cloud. */ public void runCommand(@NonNull GeyserCommandSource source, @NonNull String command) { - cloud.executeCommand(source, command).whenComplete((result, throwable) -> { + cloud.commandExecutor().executeCommand(source, command).whenComplete((result, throwable) -> { if (throwable == null) { return; } @@ -292,7 +291,7 @@ public class CommandRegistry { } else { GeyserLogger logger = GeyserImpl.getInstance().getLogger(); if (logger.isDebug()) { - logger.debug("Expected a GeyserPermission for %s but instead got %s".formatted(exception.getCurrentChain(), exception.missingPermission())); + logger.debug("Expected a GeyserPermission for %s but instead got %s".formatted(exception.currentChain(), exception.missingPermission())); } } diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java b/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java index 3d5597e3f..23d893c17 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandSourceConverter.java @@ -25,28 +25,67 @@ package org.geysermc.geyser.command; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.session.GeyserSession; +import org.incendo.cloud.SenderMapper; import java.util.UUID; import java.util.function.Function; import java.util.function.Supplier; /** - * Converts {@link GeyserCommandSource}s to the server's command sender type in a lenient manner. + * Converts {@link GeyserCommandSource}s to the server's command sender type (and back) in a lenient manner. * * @param senderType class of the server command sender type * @param playerLookup function for looking up a player command sender by UUID * @param consoleProvider supplier of the console command sender + * @param commandSourceLookup supplier of the platform implementation of the {@link GeyserCommandSource} * @param server command sender type */ public record CommandSourceConverter(Class senderType, Function playerLookup, - Supplier consoleProvider) { + Supplier consoleProvider, + Function commandSourceLookup +) implements SenderMapper { + + /** + * Creates a new CommandSourceConverter for a server platform + * in which the player type is not a command sender type, and must be mapped. + * + * @param senderType class of the command sender type + * @param playerLookup function for looking up a player by UUID + * @param senderLookup function for converting a player to a command sender + * @param consoleProvider supplier of the console command sender + * @param commandSourceLookup supplier of the platform implementation of {@link GeyserCommandSource} + * @return a new CommandSourceConverter + * @param

server player type + * @param server command sender type + */ + public static CommandSourceConverter layered(Class senderType, + Function playerLookup, + Function senderLookup, + Supplier consoleProvider, + Function commandSourceLookup) { + Function lookup = uuid -> { + P player = playerLookup.apply(uuid); + if (player == null) { + return null; + } + return senderLookup.apply(player); + }; + return new CommandSourceConverter<>(senderType, lookup, consoleProvider, commandSourceLookup); + } + + @Override + public @NonNull GeyserCommandSource map(@NonNull S base) { + return commandSourceLookup.apply(base); + } @SuppressWarnings("unchecked") - public S convert(GeyserCommandSource source) throws IllegalArgumentException { + @Override + public @NonNull S reverse(GeyserCommandSource source) throws IllegalArgumentException { Object handle = source.handle(); if (senderType.isInstance(handle)) { return (S) handle; // one of the server platform implementations @@ -72,30 +111,4 @@ public record CommandSourceConverter(Class senderType, throw new IllegalArgumentException("failed to find sender for name=%s, uuid=%s".formatted(source.name(), source.playerUuid())); } - - /** - * Creates a new CommandSourceConverter for a server platform - * in which the player type is not a command sender type, and must be mapped. - * - * @param senderType class of the command sender type - * @param playerLookup function for looking up a player by UUID - * @param senderLookup function for converting a player to a command sender - * @param consoleProvider supplier of the console command sender - * @return a new CommandSourceConverter - * @param

server player type - * @param server command sender type - */ - public static CommandSourceConverter layered(Class senderType, - Function playerLookup, - Function senderLookup, - Supplier consoleProvider) { - Function lookup = uuid -> { - P player = playerLookup.apply(uuid); - if (player == null) { - return null; - } - return senderLookup.apply(player); - }; - return new CommandSourceConverter<>(senderType, lookup, consoleProvider); - } } diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java index a232d0185..ab446dd21 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java @@ -25,15 +25,14 @@ package org.geysermc.geyser.command; -import cloud.commandframework.Command; -import cloud.commandframework.CommandManager; -import cloud.commandframework.context.CommandContext; -import cloud.commandframework.meta.CommandMeta; -import cloud.commandframework.permission.CommandPermission; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.text.GeyserLocale; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.description.CommandDescription; import org.jetbrains.annotations.Contract; import java.util.Collections; @@ -155,7 +154,7 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C } /** - * Returns a {@link CommandPermission} that handles {@link #isBedrockOnly()}, {@link #isPlayerOnly()}, and {@link #permission()}. + * Returns a {@link org.incendo.cloud.permission.Permission} that handles {@link #isBedrockOnly()}, {@link #isPlayerOnly()}, and {@link #permission()}. * * @param manager the manager to be used for permission node checking * @return a permission that will properly restrict usage of this command @@ -178,11 +177,11 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C } /** - * @return an Applicable that applies this command's description as {@link CommandMeta#DESCRIPTION} + * @return an Applicable that applies this command's description */ protected Command.Builder.Applicable meta() { return builder -> builder - .meta(CommandMeta.DESCRIPTION, GeyserLocale.getLocaleStringLog(description)); // used in cloud-bukkit impl + .commandDescription(CommandDescription.commandDescription(GeyserLocale.getLocaleStringLog(description))); // used in cloud-bukkit impl } /** diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserPermission.java b/core/src/main/java/org/geysermc/geyser/command/GeyserPermission.java index 370bd5d56..ed70d3267 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserPermission.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserPermission.java @@ -25,9 +25,9 @@ package org.geysermc.geyser.command; -import cloud.commandframework.CommandManager; -import cloud.commandframework.permission.PredicatePermission; import lombok.AllArgsConstructor; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.permission.PredicatePermission; @AllArgsConstructor public class GeyserPermission implements PredicatePermission { diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java index e54aae677..75b9252da 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java @@ -25,13 +25,13 @@ package org.geysermc.geyser.command.defaults; -import cloud.commandframework.context.CommandContext; import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.MinecraftLocale; +import org.incendo.cloud.context.CommandContext; import java.util.Objects; @@ -43,7 +43,7 @@ public class AdvancedTooltipsCommand extends GeyserCommand { @Override public void execute(CommandContext context) { - GeyserSession session = Objects.requireNonNull(context.getSender().connection()); + GeyserSession session = Objects.requireNonNull(context.sender().connection()); String onOrOff = session.isAdvancedTooltips() ? "off" : "on"; session.setAdvancedTooltips(!session.isAdvancedTooltips()); diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java index b1ccee942..0cba28f33 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancementsCommand.java @@ -25,11 +25,11 @@ package org.geysermc.geyser.command.defaults; -import cloud.commandframework.context.CommandContext; import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; +import org.incendo.cloud.context.CommandContext; import java.util.Objects; @@ -41,7 +41,7 @@ public class AdvancementsCommand extends GeyserCommand { @Override public void execute(CommandContext context) { - GeyserSession session = Objects.requireNonNull(context.getSender().connection()); + GeyserSession session = Objects.requireNonNull(context.sender().connection()); session.getAdvancementsCache().buildAndShowMenuForm(); } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java index 98ff082bd..679049829 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java @@ -25,21 +25,16 @@ package org.geysermc.geyser.command.defaults; -import cloud.commandframework.CommandManager; -import cloud.commandframework.arguments.standard.IntegerArgument; -import cloud.commandframework.arguments.standard.StringArgument; -import cloud.commandframework.context.CommandContext; import com.fasterxml.jackson.databind.JsonNode; -import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.configuration.GeyserConfiguration; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.LoopbackUtil; import org.geysermc.geyser.util.WebUtils; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.context.CommandContext; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; @@ -78,7 +73,7 @@ public class ConnectionTestCommand extends GeyserCommand { @Override public void execute(CommandContext context) { - GeyserCommandSource source = context.getSender(); + GeyserCommandSource source = context.sender(); String ipArgument = context.get(ADDRESS); Integer portArgument = context.getOrDefault(PORT, null); // null if port was not specified diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java index a96563046..a9aadf93c 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java @@ -25,9 +25,6 @@ package org.geysermc.geyser.command.defaults; -import cloud.commandframework.CommandManager; -import cloud.commandframework.arguments.standard.StringArrayArgument; -import cloud.commandframework.context.CommandContext; import com.fasterxml.jackson.core.util.DefaultIndenter; import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; import com.fasterxml.jackson.databind.JsonNode; @@ -41,6 +38,9 @@ import org.geysermc.geyser.text.AsteriskSerializer; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.WebUtils; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.component.CommandComponent; +import org.incendo.cloud.context.CommandContext; import java.io.FileOutputStream; import java.io.IOException; @@ -68,7 +68,7 @@ public class DumpCommand extends GeyserCommand { .handler(this::execute)); } - private StringArrayArgument createArgument() { + private CommandComponent createArgument() { return StringArrayArgument.optional(ARGUMENTS, (context, currentInput) -> { // currentInput only provides the word currently being typed, so it's useless for checking the previous ones List input = context.getRawInput(); @@ -88,7 +88,7 @@ public class DumpCommand extends GeyserCommand { @Override public void execute(CommandContext context) { - GeyserCommandSource source = context.getSender(); + GeyserCommandSource source = context.sender(); String[] args = context.getOrDefault(ARGUMENTS, new String[0]); boolean showSensitive = false; diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ExtensionsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ExtensionsCommand.java index 2b4b32539..24881f2ca 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ExtensionsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ExtensionsCommand.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.command.defaults; -import cloud.commandframework.context.CommandContext; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.api.util.TriState; @@ -33,6 +32,7 @@ import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; +import org.incendo.cloud.context.CommandContext; import java.util.Comparator; import java.util.List; @@ -47,7 +47,7 @@ public class ExtensionsCommand extends GeyserCommand { @Override public void execute(CommandContext context) { - GeyserCommandSource source = context.getSender(); + GeyserCommandSource source = context.sender(); // TODO: Pagination int page = 1; diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java index 665b5ceb5..7e567c9a2 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java @@ -25,8 +25,6 @@ package org.geysermc.geyser.command.defaults; -import cloud.commandframework.CommandManager; -import cloud.commandframework.context.CommandContext; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.api.util.TriState; @@ -34,6 +32,8 @@ import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.context.CommandContext; import java.util.Collection; import java.util.Collections; @@ -72,7 +72,7 @@ public class HelpCommand extends GeyserCommand { @Override public void execute(CommandContext context) { - GeyserCommandSource source = context.getSender(); + GeyserCommandSource source = context.sender(); boolean bedrockPlayer = source.connection() != null; // todo: pagination diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java index d5b1112ee..5a76ab902 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ListCommand.java @@ -25,13 +25,13 @@ package org.geysermc.geyser.command.defaults; -import cloud.commandframework.context.CommandContext; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; +import org.incendo.cloud.context.CommandContext; import java.util.stream.Collectors; @@ -46,7 +46,7 @@ public class ListCommand extends GeyserCommand { @Override public void execute(CommandContext context) { - GeyserCommandSource source = context.getSender(); + GeyserCommandSource source = context.sender(); String message = GeyserLocale.getPlayerLocaleString("geyser.commands.list.message", source.locale(), geyser.getSessionManager().size(), diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java index 760be79dc..cb4e36db6 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java @@ -25,12 +25,12 @@ package org.geysermc.geyser.command.defaults; -import cloud.commandframework.context.CommandContext; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; +import org.incendo.cloud.context.CommandContext; import java.util.Objects; @@ -42,7 +42,7 @@ public class OffhandCommand extends GeyserCommand { @Override public void execute(CommandContext context) { - GeyserSession session = Objects.requireNonNull(context.getSender().connection()); + GeyserSession session = Objects.requireNonNull(context.sender().connection()); session.requestOffhandSwap(); } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java index 8582d8188..e54b83ddf 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ReloadCommand.java @@ -25,12 +25,12 @@ package org.geysermc.geyser.command.defaults; -import cloud.commandframework.context.CommandContext; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.text.GeyserLocale; +import org.incendo.cloud.context.CommandContext; import java.util.concurrent.TimeUnit; @@ -45,7 +45,7 @@ public class ReloadCommand extends GeyserCommand { @Override public void execute(CommandContext context) { - GeyserCommandSource source = context.getSender(); + GeyserCommandSource source = context.sender(); source.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.reload.message", source.locale())); geyser.getSessionManager().disconnectAll("geyser.commands.reload.kick"); diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java index 13090935a..915354cba 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/SettingsCommand.java @@ -25,13 +25,13 @@ package org.geysermc.geyser.command.defaults; -import cloud.commandframework.context.CommandContext; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.SettingsUtils; +import org.incendo.cloud.context.CommandContext; import java.util.Objects; @@ -43,7 +43,7 @@ public class SettingsCommand extends GeyserCommand { @Override public void execute(CommandContext context) { - GeyserSession session = Objects.requireNonNull(context.getSender().connection()); + GeyserSession session = Objects.requireNonNull(context.sender().connection()); session.sendForm(SettingsUtils.buildForm(session)); } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java index 769eabe85..bf2c57ef5 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/StatisticsCommand.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.command.defaults; -import cloud.commandframework.context.CommandContext; import com.github.steveice10.mc.protocol.data.game.ClientCommand; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundClientCommandPacket; import org.geysermc.geyser.GeyserImpl; @@ -33,6 +32,7 @@ import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; +import org.incendo.cloud.context.CommandContext; import java.util.Objects; @@ -44,7 +44,7 @@ public class StatisticsCommand extends GeyserCommand { @Override public void execute(CommandContext context) { - GeyserSession session = Objects.requireNonNull(context.getSender().connection()); + GeyserSession session = Objects.requireNonNull(context.sender().connection()); session.setWaitingForStatistics(true); ServerboundClientCommandPacket packet = new ServerboundClientCommandPacket(ClientCommand.STATS); diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java index 48a1ff6f1..f6dc1610a 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/StopCommand.java @@ -25,11 +25,11 @@ package org.geysermc.geyser.command.defaults; -import cloud.commandframework.context.CommandContext; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; +import org.incendo.cloud.context.CommandContext; import java.util.Collections; diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java index 80962b9e3..50f802a77 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.command.defaults; -import cloud.commandframework.context.CommandContext; import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec; import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserImpl; @@ -37,6 +36,7 @@ import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.WebUtils; +import org.incendo.cloud.context.CommandContext; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; @@ -53,7 +53,7 @@ public class VersionCommand extends GeyserCommand { @Override public void execute(CommandContext context) { - GeyserCommandSource source = context.getSender(); + GeyserCommandSource source = context.sender(); String bedrockVersions; List supportedCodecs = GameProtocol.SUPPORTED_BEDROCK_CODECS; diff --git a/core/src/main/java/org/geysermc/geyser/command/standalone/StandaloneCloudCommandManager.java b/core/src/main/java/org/geysermc/geyser/command/standalone/StandaloneCloudCommandManager.java index 302e36e40..2788cb201 100644 --- a/core/src/main/java/org/geysermc/geyser/command/standalone/StandaloneCloudCommandManager.java +++ b/core/src/main/java/org/geysermc/geyser/command/standalone/StandaloneCloudCommandManager.java @@ -25,11 +25,6 @@ package org.geysermc.geyser.command.standalone; -import cloud.commandframework.CommandManager; -import cloud.commandframework.execution.CommandExecutionCoordinator; -import cloud.commandframework.internal.CommandRegistrationHandler; -import cloud.commandframework.meta.CommandMeta; -import cloud.commandframework.meta.SimpleCommandMeta; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.GeyserImpl; @@ -40,6 +35,11 @@ import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.util.FileUtils; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.execution.ExecutionCoordinator; +import org.incendo.cloud.internal.CommandRegistrationHandler; +import org.incendo.cloud.meta.CommandMeta; +import org.incendo.cloud.meta.SimpleCommandMeta; import java.io.File; import java.util.ArrayList; @@ -61,7 +61,7 @@ public class StandaloneCloudCommandManager extends CommandManager basePermissions = new ObjectOpenHashSet<>(); public StandaloneCloudCommandManager(GeyserImpl geyser) { - super(CommandExecutionCoordinator.simpleCoordinator(), CommandRegistrationHandler.nullCommandRegistrationHandler()); + super(ExecutionCoordinator.simpleCoordinator(), CommandRegistrationHandler.nullCommandRegistrationHandler()); // simpleCoordinator: execute commands immediately on the calling thread. // nullCommandRegistrationHandler: cloud is not responsible for handling our CommandRegistry, which is fairly decoupled. this.geyser = geyser; diff --git a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java index cf147569f..4f322c56a 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java @@ -25,9 +25,6 @@ package org.geysermc.geyser.extension.command; -import cloud.commandframework.CommandManager; -import cloud.commandframework.arguments.standard.StringArgument; -import cloud.commandframework.context.CommandContext; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.api.command.Command; @@ -39,6 +36,9 @@ import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.annotation.specifier.Greedy; +import org.incendo.cloud.context.CommandContext; import java.util.ArrayList; import java.util.List; @@ -125,6 +125,7 @@ public abstract class GeyserExtensionCommand extends GeyserCommand { return this; } + @SuppressWarnings("removal") // this is our doing @Override public Builder suggestedOpOnly(boolean suggestedOpOnly) { this.suggestedOpOnly = suggestedOpOnly; @@ -135,6 +136,7 @@ public abstract class GeyserExtensionCommand extends GeyserCommand { return this; } + @SuppressWarnings("removal") // this is our doing @Override public Builder executableOnConsole(boolean executableOnConsole) { this.playerOnly = !executableOnConsole; @@ -195,7 +197,7 @@ public abstract class GeyserExtensionCommand extends GeyserCommand { @SuppressWarnings("unchecked") @Override public void execute(CommandContext context) { - GeyserCommandSource source = context.getSender(); + GeyserCommandSource source = context.sender(); String[] args = context.getOrDefault("args", "").split(" "); if (sourceType.isInstance(source)) { @@ -215,6 +217,7 @@ public abstract class GeyserExtensionCommand extends GeyserCommand { source.sendMessage("You must be a " + sourceType.getSimpleName() + " to run this command."); } + @SuppressWarnings("removal") // this is our doing @Override public boolean isSuggestedOpOnly() { return suggestedOpOnly; From f2a3feac056bd63bcb39ddd616b0c417bc4b8bcd Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Thu, 7 Mar 2024 20:11:04 +0100 Subject: [PATCH 63/76] 2.0 command arguments --- .../org/geysermc/geyser/command/CommandRegistry.java | 1 + .../geyser/command/defaults/ConnectionTestCommand.java | 10 +++++----- .../extension/command/GeyserExtensionCommand.java | 5 +++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java index df97b8ace..b5286ab22 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java @@ -316,6 +316,7 @@ public class CommandRegistry { E e = (E) exception; // if cloud has a registered exception handler for this type, use it, otherwise use this handler. // we register all the exception handlers to cloud, so it will likely just be cloud invoking this same handler. + cloud.exceptionController().handleException(,exception); cloud.handleException(source, type, e, handler); return true; } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java index 679049829..d2066dba1 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/ConnectionTestCommand.java @@ -41,6 +41,9 @@ import java.nio.charset.StandardCharsets; import java.util.Random; import java.util.concurrent.CompletableFuture; +import static org.incendo.cloud.parser.standard.IntegerParser.integerParser; +import static org.incendo.cloud.parser.standard.StringParser.stringParser; + public class ConnectionTestCommand extends GeyserCommand { /* @@ -63,11 +66,8 @@ public class ConnectionTestCommand extends GeyserCommand { @Override public void register(CommandManager manager) { manager.command(baseBuilder(manager) - .argument(StringArgument.of(ADDRESS)) - .argument(IntegerArgument.builder(PORT) - .withMax(65535).withMin(0) - .asOptional() - .build()) + .required(ADDRESS, stringParser()) + .optional(PORT, integerParser(0, 65535)) .handler(this::execute)); } diff --git a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java index 4f322c56a..f90045e9a 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java @@ -37,13 +37,14 @@ import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.session.GeyserSession; import org.incendo.cloud.CommandManager; -import org.incendo.cloud.annotation.specifier.Greedy; import org.incendo.cloud.context.CommandContext; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import static org.incendo.cloud.parser.standard.StringParser.greedyStringParser; + public abstract class GeyserExtensionCommand extends GeyserCommand { private final Extension extension; @@ -190,7 +191,7 @@ public abstract class GeyserExtensionCommand extends GeyserCommand { // todo: if we don't find a way to expose cloud in the api, we should implement a way // to not have the [args] if its not necessary for this command. and maybe tab completion. manager.command(baseBuilder(manager) - .argument(StringArgument.optional("args", StringArgument.StringMode.GREEDY)) + .optional("args", greedyStringParser()) .handler(this::execute)); } From 677f79dbd737e9aeb7234f411760f91009ff388f Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Fri, 8 Mar 2024 19:42:05 +0100 Subject: [PATCH 64/76] - removed fabric permission api, done by cloud now - updated GeyserPermission to 2.0 - removed mod platform specific permission checkers TODO: - test neoforge permission registration event - fix: exception handling --- .../GeyserRegisterPermissionsEvent.java | 5 +- bootstrap/mod/fabric/build.gradle.kts | 3 -- .../fabric/GeyserFabricBootstrap.java | 7 --- .../neoforge/GeyserNeoForgeBootstrap.java | 14 ++--- .../GeyserNeoForgePermissionHandler.java | 51 +------------------ .../platform/mod/GeyserModBootstrap.java | 4 -- .../platform/mod/GeyserModUpdateListener.java | 7 ++- .../mod/command/ModCommandSource.java | 6 ++- .../command/VelocityCommandSource.java | 1 - .../geyser/command/CommandRegistry.java | 9 ++-- .../geyser/command/GeyserPermission.java | 23 ++++++--- .../command/GeyserExtensionCommand.java | 13 +++++ gradle/libs.versions.toml | 2 - 13 files changed, 49 insertions(+), 96 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java index 209ef3c5d..2fcb12b6b 100644 --- a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java +++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java @@ -30,9 +30,10 @@ import org.geysermc.geyser.api.util.TriState; /** * Fired by anything that wishes to gather permission nodes and defaults. - *

+ *

* This event is not guaranteed to be fired, as certain Geyser platforms do not have a native permission system. - * It can be expected to fire on Geyser-Spigot and Geyser-Standalone. + * It can be expected to fire on Geyser-Spigot, Geyser-NeoForge and Geyser-Standalone. Note: NeoForge allows registering permissions, + * but does so without registering default values. * It may still be fired on other platforms due to a 3rd party. */ public interface GeyserRegisterPermissionsEvent extends Event { diff --git a/bootstrap/mod/fabric/build.gradle.kts b/bootstrap/mod/fabric/build.gradle.kts index 9948d0c9c..93805b6d7 100644 --- a/bootstrap/mod/fabric/build.gradle.kts +++ b/bootstrap/mod/fabric/build.gradle.kts @@ -23,9 +23,6 @@ dependencies { exclude(group = "io.netty.incubator") } - modImplementation(libs.fabric.permissions) - include(libs.fabric.permissions) - modImplementation(libs.cloud.fabric) include(libs.cloud.fabric) } diff --git a/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricBootstrap.java b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricBootstrap.java index 31db15976..d1045e3e3 100644 --- a/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricBootstrap.java +++ b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricBootstrap.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.platform.fabric; -import me.lucko.fabric.api.permissions.v0.Permissions; import net.fabricmc.api.EnvType; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; @@ -33,7 +32,6 @@ import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.commands.CommandSourceStack; import net.minecraft.world.entity.player.Player; -import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.command.CommandSourceConverter; @@ -67,7 +65,6 @@ public class GeyserFabricBootstrap extends GeyserModBootstrap implements ModInit this.onGeyserInitialize(); - // TODO: verify var sourceConverter = CommandSourceConverter.layered( CommandSourceStack.class, id -> getServer().getPlayerList().getPlayer(id), @@ -82,8 +79,4 @@ public class GeyserFabricBootstrap extends GeyserModBootstrap implements ModInit this.setCommandRegistry(new CommandRegistry(GeyserImpl.getInstance(), cloud)); } - @Override - public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode) { - return Permissions.check(source, permissionNode, source.getServer().getOperatorUserPermissionLevel()); - } } diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java index 977897bf6..d1978c4a7 100644 --- a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java +++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java @@ -34,7 +34,6 @@ import net.neoforged.neoforge.common.NeoForge; import net.neoforged.neoforge.event.entity.player.PlayerEvent; import net.neoforged.neoforge.event.server.ServerStartedEvent; import net.neoforged.neoforge.event.server.ServerStoppingEvent; -import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.command.CommandSourceConverter; @@ -49,8 +48,6 @@ import org.incendo.cloud.neoforge.NeoForgeServerCommandManager; @Mod(ModConstants.MOD_ID) public class GeyserNeoForgeBootstrap extends GeyserModBootstrap { - private final GeyserNeoForgePermissionHandler permissionHandler = new GeyserNeoForgePermissionHandler(); - public GeyserNeoForgeBootstrap() { super(new GeyserNeoForgePlatform()); @@ -61,11 +58,12 @@ public class GeyserNeoForgeBootstrap extends GeyserModBootstrap { NeoForge.EVENT_BUS.addListener(this::onServerStopping); NeoForge.EVENT_BUS.addListener(this::onPlayerJoin); - NeoForge.EVENT_BUS.addListener(this.permissionHandler::onPermissionGather); + + GeyserNeoForgePermissionHandler permissionHandler = new GeyserNeoForgePermissionHandler(); + NeoForge.EVENT_BUS.addListener(permissionHandler::onPermissionGather); this.onGeyserInitialize(); - // TODO: verify; idek how to make permissions on neoforge work with this... var sourceConverter = CommandSourceConverter.layered( CommandSourceStack.class, id -> getServer().getPlayerList().getPlayer(id), @@ -92,10 +90,4 @@ public class GeyserNeoForgeBootstrap extends GeyserModBootstrap { private void onPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) { GeyserModUpdateListener.onPlayReady(event.getEntity()); } - - @Override - public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode) { - return this.permissionHandler.hasPermission(source, permissionNode); - } - } diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePermissionHandler.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePermissionHandler.java index e13205b54..732687ae8 100644 --- a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePermissionHandler.java +++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePermissionHandler.java @@ -25,23 +25,16 @@ package org.geysermc.geyser.platform.neoforge; -import net.minecraft.commands.CommandSourceStack; -import net.minecraft.server.level.ServerPlayer; -import net.neoforged.neoforge.server.permission.PermissionAPI; import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent; import net.neoforged.neoforge.server.permission.nodes.PermissionDynamicContextKey; import net.neoforged.neoforge.server.permission.nodes.PermissionNode; import net.neoforged.neoforge.server.permission.nodes.PermissionType; import net.neoforged.neoforge.server.permission.nodes.PermissionTypes; -import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.command.Command; -import org.geysermc.geyser.command.CommandRegistry; +import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent; import java.lang.reflect.Constructor; -import java.util.HashMap; -import java.util.Map; public class GeyserNeoForgePermissionHandler { @@ -63,49 +56,10 @@ public class GeyserNeoForgePermissionHandler { } } - private final Map> permissionNodes = new HashMap<>(); - public void onPermissionGather(PermissionGatherEvent.Nodes event) { this.registerNode(Constants.UPDATE_PERMISSION, event); - CommandRegistry commandManager = GeyserImpl.getInstance().commandRegistry(); - for (Map.Entry entry : commandManager.commands().entrySet()) { - Command command = entry.getValue(); - - // Don't register aliases - if (!command.name().equals(entry.getKey())) { - continue; - } - - this.registerNode(command.permission(), event); - } - - for (Map commands : commandManager.extensionCommands().values()) { - for (Map.Entry entry : commands.entrySet()) { - Command command = entry.getValue(); - - // Don't register aliases - if (!command.name().equals(entry.getKey())) { - continue; - } - - this.registerNode(command.permission(), event); - } - } - } - - public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode) { - ServerPlayer player = source.getPlayer(); - if (!source.isPlayer() || player == null) { - return true; - } - PermissionNode node = this.permissionNodes.get(permissionNode); - if (node == null) { - GeyserImpl.getInstance().getLogger().warning("Unable to find permission node " + permissionNode); - return false; - } - - return PermissionAPI.getPermission(player, node); + GeyserImpl.getInstance().eventBus().fire((GeyserRegisterPermissionsEvent) (permission, defaultValue) -> this.registerNode(permission, event)); } private void registerNode(String node, PermissionGatherEvent.Nodes event) { @@ -114,7 +68,6 @@ public class GeyserNeoForgePermissionHandler { // NeoForge likes to crash if you try and register a duplicate node if (!event.getNodes().contains(permissionNode)) { event.addNodes(permissionNode); - this.permissionNodes.put(node, permissionNode); } } diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java index 37ed5234d..fb9f8666e 100644 --- a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java @@ -28,7 +28,6 @@ package org.geysermc.geyser.platform.mod; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; -import net.minecraft.commands.CommandSourceStack; import net.minecraft.server.MinecraftServer; import org.apache.logging.log4j.LogManager; import org.checkerframework.checker.nullness.qual.NonNull; @@ -60,7 +59,6 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap { private static GeyserModBootstrap instance; private final GeyserModPlatform platform; - private GeyserImpl geyser; private Path dataFolder; @@ -201,8 +199,6 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap { return this.platform.resolveResource(resource); } - public abstract boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode); - @SuppressWarnings("BooleanMethodIsAlwaysInverted") private boolean loadConfig() { try { diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModUpdateListener.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModUpdateListener.java index 1fabaa899..6a58b2dd9 100644 --- a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModUpdateListener.java +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModUpdateListener.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.platform.mod; -import net.minecraft.commands.CommandSourceStack; import net.minecraft.world.entity.player.Player; import org.geysermc.geyser.Constants; import org.geysermc.geyser.platform.mod.command.ModCommandSource; @@ -33,9 +32,9 @@ import org.geysermc.geyser.util.VersionCheckUtils; public final class GeyserModUpdateListener { public static void onPlayReady(Player player) { - CommandSourceStack stack = player.createCommandSourceStack(); - if (GeyserModBootstrap.getInstance().hasPermission(stack, Constants.UPDATE_PERMISSION)) { - VersionCheckUtils.checkForGeyserUpdate(() -> new ModCommandSource(stack)); + ModCommandSource source = new ModCommandSource(player.createCommandSourceStack()); + if (source.hasPermission(Constants.UPDATE_PERMISSION)) { + VersionCheckUtils.checkForGeyserUpdate(() -> source); } } diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSource.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSource.java index 0c85b533f..9ba89b4dd 100644 --- a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSource.java +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSource.java @@ -33,7 +33,6 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.GeyserCommandSource; -import org.geysermc.geyser.platform.mod.GeyserModBootstrap; import org.geysermc.geyser.text.ChatColor; import java.util.Objects; @@ -86,7 +85,10 @@ public class ModCommandSource implements GeyserCommandSource { @Override public boolean hasPermission(String permission) { - return GeyserModBootstrap.getInstance().hasPermission(source, permission); + // Unlike other bootstraps; we delegate to cloud here too: + // On NeoForge; we'd have to keep track of all PermissionNodes - cloud already does that + // For Fabric, we won't need to include the Fabric Permissions API anymore - cloud already does that too :p + return GeyserImpl.getInstance().commandRegistry().cloud().hasPermission(this, permission); } @Override diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSource.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSource.java index 740ceca06..66e714417 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSource.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/command/VelocityCommandSource.java @@ -36,7 +36,6 @@ import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.text.GeyserLocale; import java.util.Locale; -import java.util.Optional; import java.util.UUID; public class VelocityCommandSource implements GeyserCommandSource { diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java index b5286ab22..89b5b9b4a 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java @@ -55,6 +55,7 @@ import org.geysermc.geyser.extension.command.GeyserExtensionCommand; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; import org.incendo.cloud.CommandManager; +import org.incendo.cloud.context.CommandContext; import org.incendo.cloud.exception.ArgumentParseException; import org.incendo.cloud.exception.CommandExecutionException; import org.incendo.cloud.exception.InvalidCommandSenderException; @@ -256,7 +257,7 @@ public class CommandRegistry { } try { - handleThrowable(source, throwable); + handleThrowable(source, result.commandContext(), throwable); } catch (Throwable secondary) { // otherwise it gets swallowed by whenComplete. // we assume this won't throw. @@ -265,8 +266,9 @@ public class CommandRegistry { }); } - private void handleThrowable(@NonNull GeyserCommandSource source, @NonNull Throwable throwable) { + private void handleThrowable(@NonNull GeyserCommandSource source, @NonNull CommandContext commandContext, @NonNull Throwable throwable) { if (throwable instanceof Exception exception) { + cloud.exceptionController().handleException(commandContext, exception); for (ExceptionHandler handler : exceptionHandlers) { if (handler.handle(source, exception)) { return; @@ -316,8 +318,7 @@ public class CommandRegistry { E e = (E) exception; // if cloud has a registered exception handler for this type, use it, otherwise use this handler. // we register all the exception handlers to cloud, so it will likely just be cloud invoking this same handler. - cloud.exceptionController().handleException(,exception); - cloud.handleException(source, type, e, handler); + cloud.exceptionController().handleException(source, type, e, handler); return true; } return false; diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserPermission.java b/core/src/main/java/org/geysermc/geyser/command/GeyserPermission.java index ed70d3267..7af32d570 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserPermission.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserPermission.java @@ -26,7 +26,11 @@ package org.geysermc.geyser.command; import lombok.AllArgsConstructor; +import org.checkerframework.checker.nullness.qual.NonNull; import org.incendo.cloud.CommandManager; +import org.incendo.cloud.key.CloudKey; +import org.incendo.cloud.permission.Permission; +import org.incendo.cloud.permission.PermissionResult; import org.incendo.cloud.permission.PredicatePermission; @AllArgsConstructor @@ -37,11 +41,6 @@ public class GeyserPermission implements PredicatePermission manager; - @Override - public boolean hasPermission(GeyserCommandSource source) { - return check(source).toBoolean(); - } - public Result check(GeyserCommandSource source) { if (bedrockOnly) { if (source.connection() == null) { @@ -60,6 +59,16 @@ public class GeyserPermission implements PredicatePermission key() { + return CloudKey.cloudKey(permission); + } + + @Override + public @NonNull PermissionResult testPermission(@NonNull GeyserCommandSource sender) { + return check(sender).toPermission(permission); + } + public enum Result { /** @@ -82,8 +91,8 @@ public class GeyserPermission implements PredicatePermission permission(@NonNull String permission) { this.permission = Objects.requireNonNull(permission, "command permission"); + if (!permission.contains(".")) { + String newPermission = extension.description().id() + "." + permission; + GeyserImpl.getInstance().getLogger().error("Extension " + extension.name() + " tried to register an invalid permission (" + permission + ")." + + "Changing it to " + newPermission + "!"); + this.permission = newPermission; + } return this; } @@ -117,6 +124,12 @@ public abstract class GeyserExtensionCommand extends GeyserCommand { public Builder permission(@NonNull String permission, @NonNull TriState defaultValue) { this.permission = Objects.requireNonNull(permission, "command permission"); this.permissionDefault = Objects.requireNonNull(defaultValue, "command permission defaultValue"); + if (!permission.contains(".")) { + String newPermission = extension.description().id() + "." + permission; + GeyserImpl.getInstance().getLogger().error("Extension " + extension.name() + " tried to register an invalid permission (" + permission + ")." + + "Changing it to " + newPermission + "!"); + this.permission = newPermission; + } return this; } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cd96a8347..ee53b3763 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -33,7 +33,6 @@ viaproxy = "3.2.0-SNAPSHOT" fabric-minecraft = "1.20.4" fabric-loader = "0.15.2" fabric-api = "0.91.2+1.20.4" -fabric-permissions = "0.2-SNAPSHOT" neoforge-minecraft = "20.4.48-beta" mixin = "0.8.5" @@ -100,7 +99,6 @@ mixin = { group = "org.spongepowered", name = "mixin", version.ref = "mixin" } fabric-minecraft = { group = "com.mojang", name = "minecraft", version.ref = "fabric-minecraft" } fabric-loader = { group = "net.fabricmc", name = "fabric-loader", version.ref = "fabric-loader" } fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version.ref = "fabric-api" } -fabric-permissions = { group = "me.lucko", name = "fabric-permissions-api", version.ref = "fabric-permissions" } neoforge-minecraft = { group = "net.neoforged", name = "neoforge", version.ref = "neoforge-minecraft" } From ba3bd43e6e667a91a03a9770967aa285456292ad Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Fri, 8 Mar 2024 19:48:55 +0100 Subject: [PATCH 65/76] revert attempts related to exception handling --- .../java/org/geysermc/geyser/command/CommandRegistry.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java index 89b5b9b4a..df97b8ace 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java @@ -55,7 +55,6 @@ import org.geysermc.geyser.extension.command.GeyserExtensionCommand; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; import org.incendo.cloud.CommandManager; -import org.incendo.cloud.context.CommandContext; import org.incendo.cloud.exception.ArgumentParseException; import org.incendo.cloud.exception.CommandExecutionException; import org.incendo.cloud.exception.InvalidCommandSenderException; @@ -257,7 +256,7 @@ public class CommandRegistry { } try { - handleThrowable(source, result.commandContext(), throwable); + handleThrowable(source, throwable); } catch (Throwable secondary) { // otherwise it gets swallowed by whenComplete. // we assume this won't throw. @@ -266,9 +265,8 @@ public class CommandRegistry { }); } - private void handleThrowable(@NonNull GeyserCommandSource source, @NonNull CommandContext commandContext, @NonNull Throwable throwable) { + private void handleThrowable(@NonNull GeyserCommandSource source, @NonNull Throwable throwable) { if (throwable instanceof Exception exception) { - cloud.exceptionController().handleException(commandContext, exception); for (ExceptionHandler handler : exceptionHandlers) { if (handler.handle(source, exception)) { return; @@ -318,7 +316,7 @@ public class CommandRegistry { E e = (E) exception; // if cloud has a registered exception handler for this type, use it, otherwise use this handler. // we register all the exception handlers to cloud, so it will likely just be cloud invoking this same handler. - cloud.exceptionController().handleException(source, type, e, handler); + cloud.handleException(source, type, e, handler); return true; } return false; From 69111054d964f3b3a01d178b4dbbdb43b971c38f Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Sat, 13 Apr 2024 22:15:09 +0200 Subject: [PATCH 66/76] yeet paper mojang api depend --- bootstrap/spigot/build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/bootstrap/spigot/build.gradle.kts b/bootstrap/spigot/build.gradle.kts index 60ffae36b..4f119df19 100644 --- a/bootstrap/spigot/build.gradle.kts +++ b/bootstrap/spigot/build.gradle.kts @@ -14,7 +14,6 @@ dependencies { implementation(libs.adventure.text.serializer.bungeecord) compileOnly(libs.folia.api) - compileOnly(libs.paper.mojangapi) compileOnlyApi(libs.viaversion) } From 5079ce574c7a5cc46247477fd57d5b399596536d Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Sun, 14 Apr 2024 00:49:29 +0200 Subject: [PATCH 67/76] maybe the dump command works? --- .../geyser/command/CommandRegistry.java | 81 ++++++------------- .../geyser/command/defaults/DumpCommand.java | 49 +++++------ 2 files changed, 49 insertions(+), 81 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java index df97b8ace..4f84ab9c1 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java @@ -61,13 +61,15 @@ import org.incendo.cloud.exception.InvalidCommandSenderException; import org.incendo.cloud.exception.InvalidSyntaxException; import org.incendo.cloud.exception.NoPermissionException; import org.incendo.cloud.exception.NoSuchCommandException; +import org.incendo.cloud.exception.handling.ExceptionContext; +import org.incendo.cloud.exception.handling.ExceptionController; +import org.incendo.cloud.exception.handling.ExceptionHandler; import org.incendo.cloud.execution.ExecutionCoordinator; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.CompletionException; import java.util.function.BiConsumer; public class CommandRegistry { @@ -81,7 +83,7 @@ public class CommandRegistry { private final Map commands = new Object2ObjectOpenHashMap<>(13); /** - * Map of Extensions to Maps of their subcommands + * Map of Extensions to maps of their subcommands */ private final Map> extensionCommands = new Object2ObjectOpenHashMap<>(0); @@ -95,26 +97,22 @@ public class CommandRegistry { */ private final Map permissionDefaults = new Object2ObjectOpenHashMap<>(13); - /** - * The order and behaviour of these exception handlers is designed to mirror the typical cloud implementations. - * For example: https://github.com/Incendo/cloud/blob/a4cc749b91564af57bb7bba36dd8011b556c2b3a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricExecutor.java#L94-L173 - */ - private final List> exceptionHandlers = List.of( - new ExceptionHandler<>(InvalidSyntaxException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_syntax", e.correctSyntax())), - new ExceptionHandler<>(InvalidCommandSenderException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_sender", e.commandSender().getClass().getSimpleName(), e.requiredSender())), - new ExceptionHandler<>(NoPermissionException.class, this::handleNoPermission), - new ExceptionHandler<>(NoSuchCommandException.class, (src, e) -> src.sendLocaleString("geyser.command.not_found")), - new ExceptionHandler<>(ArgumentParseException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_argument", e.getCause().getMessage())), - new ExceptionHandler<>(CommandExecutionException.class, (src, e) -> handleUnexpectedThrowable(src, e.getCause())) - ); - public CommandRegistry(GeyserImpl geyser, CommandManager cloud) { this.geyser = geyser; this.cloud = cloud; - // Override the default exception handlers that the typical cloud implementations provide so that we can perform localization. + // Yeet the default exception handlers that the typical cloud implementations provide so that we can perform localization. // This is kind of meaningless for our Geyser-Standalone implementation since these handlers are the default exception handlers in that case. - for (ExceptionHandler handler : exceptionHandlers) { + cloud.exceptionController().clearHandlers(); + List> exceptionHandlers = List.of( + new GeyserExceptionHandler<>(InvalidSyntaxException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_syntax", e.correctSyntax())), + new GeyserExceptionHandler<>(InvalidCommandSenderException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_sender", e.commandSender().getClass().getSimpleName(), e.requiredSender())), + new GeyserExceptionHandler<>(NoPermissionException.class, this::handleNoPermission), + new GeyserExceptionHandler<>(NoSuchCommandException.class, (src, e) -> src.sendLocaleString("geyser.command.not_found")), + new GeyserExceptionHandler<>(ArgumentParseException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_argument", e.getCause().getMessage())), + new GeyserExceptionHandler<>(CommandExecutionException.class, (src, e) -> handleUnexpectedThrowable(src, e.getCause())) + ); + for (GeyserExceptionHandler handler : exceptionHandlers) { handler.register(cloud); } @@ -183,11 +181,6 @@ public class CommandRegistry { return Collections.unmodifiableMap(this.commands); } - @NonNull - public Map> extensionCommands() { - return Collections.unmodifiableMap(this.extensionCommands); - } - /** * For internal Geyser commands */ @@ -250,32 +243,10 @@ public class CommandRegistry { return; } - // mirrors typical cloud implementations - if (throwable instanceof CompletionException) { - throwable = throwable.getCause(); - } - - try { - handleThrowable(source, throwable); - } catch (Throwable secondary) { - // otherwise it gets swallowed by whenComplete. - // we assume this won't throw. - handleUnexpectedThrowable(source, secondary); - } + // todo check if we need to do anything here? }); } - private void handleThrowable(@NonNull GeyserCommandSource source, @NonNull Throwable throwable) { - if (throwable instanceof Exception exception) { - for (ExceptionHandler handler : exceptionHandlers) { - if (handler.handle(source, exception)) { - return; - } - } - } - handleUnexpectedThrowable(source, throwable); - } - private void handleNoPermission(GeyserCommandSource source, NoPermissionException exception) { // we basically recheck bedrock-only and player-only to see if they were the cause of this if (exception.missingPermission() instanceof GeyserPermission permission) { @@ -305,25 +276,19 @@ public class CommandRegistry { } @AllArgsConstructor - private class ExceptionHandler { + private static class GeyserExceptionHandler implements ExceptionHandler { final Class type; final BiConsumer handler; - @SuppressWarnings("unchecked") - boolean handle(GeyserCommandSource source, Exception exception) { - if (type.isInstance(exception)) { - E e = (E) exception; - // if cloud has a registered exception handler for this type, use it, otherwise use this handler. - // we register all the exception handlers to cloud, so it will likely just be cloud invoking this same handler. - cloud.handleException(source, type, e, handler); - return true; - } - return false; + void register(CommandManager manager) { + manager.exceptionController().registerHandler(type, this); } - void register(CommandManager manager) { - manager.registerExceptionHandler(type, handler); + @Override + public void handle(@NonNull ExceptionContext context) throws Throwable { + Throwable unwrapped = ExceptionController.unwrapCompletionException(context.exception()); + handler.accept((GeyserCommandSource) context.context().sender(), type.cast(unwrapped)); } } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java index a9aadf93c..090f7ed4f 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java @@ -39,18 +39,20 @@ import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.WebUtils; import org.incendo.cloud.CommandManager; -import org.incendo.cloud.component.CommandComponent; import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.suggestion.SuggestionProvider; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import static org.incendo.cloud.parser.standard.StringArrayParser.stringArrayParser; + public class DumpCommand extends GeyserCommand { private static final String ARGUMENTS = "args"; - private static final List SUGGESTIONS = List.of("full", "offline", "logs"); + private static final Iterable SUGGESTIONS = List.of("full", "offline", "logs"); private final GeyserImpl geyser; private static final ObjectMapper MAPPER = new ObjectMapper(); @@ -61,30 +63,31 @@ public class DumpCommand extends GeyserCommand { this.geyser = geyser; } - @Override - public void register(CommandManager manager) { - manager.command(baseBuilder(manager) - .argument(createArgument()) - .handler(this::execute)); - } + @Override + public void register(CommandManager manager) { + manager.command(baseBuilder(manager) + .optional(ARGUMENTS, stringArrayParser(), SuggestionProvider.blockingStrings((ctx, input) -> { + // parse suggestions here + List inputs = new ArrayList<>(); + while (input.hasRemainingInput()) { + inputs.add(input.readStringSkipWhitespace()); + } - private CommandComponent createArgument() { - return StringArrayArgument.optional(ARGUMENTS, (context, currentInput) -> { - // currentInput only provides the word currently being typed, so it's useless for checking the previous ones - List input = context.getRawInput(); - if (input.size() <= 2) { - return SUGGESTIONS; // only `geyser dump` was typed (2 literals) - } + if (inputs.size() <= 2) { + return SUGGESTIONS; // only `geyser dump` was typed (2 literals) + } - // the rest of the input after `geyser dump` is for this argument - input = input.subList(2, input.size()); + // the rest of the input after `geyser dump` is for this argument + inputs = inputs.subList(2, inputs.size()); - // don't suggest any words they have already typed - List suggestions = new ArrayList<>(SUGGESTIONS); - suggestions.removeAll(input); - return suggestions; - }); - } + // don't suggest any words they have already typed + List suggestions = new ArrayList<>(); + SUGGESTIONS.forEach(suggestions::add); + suggestions.removeAll(inputs); + return suggestions; + })) + .handler(this::execute)); + } @Override public void execute(CommandContext context) { From ad546b64fc46fa88b9ef733fd441c3bc15fa3fd1 Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Sun, 14 Apr 2024 15:55:34 +0200 Subject: [PATCH 68/76] it builds! --- bootstrap/mod/neoforge/build.gradle.kts | 8 ++++++++ gradle/libs.versions.toml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/bootstrap/mod/neoforge/build.gradle.kts b/bootstrap/mod/neoforge/build.gradle.kts index 37009ce5a..ddff323da 100644 --- a/bootstrap/mod/neoforge/build.gradle.kts +++ b/bootstrap/mod/neoforge/build.gradle.kts @@ -45,6 +45,14 @@ application { } tasks { + // Cloud-NeoForge is currently providing cloud-core twice (once directly, once indirectly via modded-common) + distTar { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } + distZip { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } + remapJar { archiveBaseName.set("Geyser-NeoForge") } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3df1f55ac..1e466d4e8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,7 +25,7 @@ terminalconsoleappender = "1.2.0" folia = "1.19.4-R0.1-SNAPSHOT" viaversion = "4.9.2" adapters = "1.11-SNAPSHOT" -cloud = "2.0.0-beta.2" +cloud = "2.0.0-beta.4" commodore = "2.2" bungeecord = "a7c6ede" velocity = "3.1.1" From e0175320aaf6cfcb479e2a52a63df3cc753afc9f Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Sun, 14 Apr 2024 16:49:57 +0200 Subject: [PATCH 69/76] - Don't throw when a throwable has been handled - Warn when a geyser dump arg isn't valid - don't register the `geyser` root literal with the geyser help permission --- .../java/org/geysermc/geyser/command/CommandRegistry.java | 8 +------- .../java/org/geysermc/geyser/command/GeyserCommand.java | 2 +- .../org/geysermc/geyser/command/defaults/DumpCommand.java | 1 + .../org/geysermc/geyser/command/defaults/HelpCommand.java | 6 +++++- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java index 4f84ab9c1..7702379c4 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java @@ -238,13 +238,7 @@ public class CommandRegistry { * This method may or may not be blocking, depending on the {@link ExecutionCoordinator} in use by cloud. */ public void runCommand(@NonNull GeyserCommandSource source, @NonNull String command) { - cloud.commandExecutor().executeCommand(source, command).whenComplete((result, throwable) -> { - if (throwable == null) { - return; - } - - // todo check if we need to do anything here? - }); + cloud.commandExecutor().executeCommand(source, command); } private void handleNoPermission(GeyserCommandSource source, NoPermissionException exception) { diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java index ab446dd21..939edebe7 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java @@ -187,7 +187,7 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C /** * Registers this command to the given command manager. * This method may be overridden to register more than one command. - *

+ *

* The default implementation is that {@link #baseBuilder(CommandManager)} with {@link #execute(CommandContext)} * applied as the handler is registered to the manager. */ diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java index 090f7ed4f..45100f525 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/DumpCommand.java @@ -103,6 +103,7 @@ public class DumpCommand extends GeyserCommand { case "full" -> showSensitive = true; case "offline" -> offlineDump = true; case "logs" -> addLog = true; + default -> context.sender().sendMessage("Invalid geyser dump option " + arg + "! Fallback to no arguments."); } } } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java index 7e567c9a2..f2c275351 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java @@ -65,7 +65,6 @@ public class HelpCommand extends GeyserCommand { // note: this doesn't do the other permission checks that GeyserCommand#baseBuilder does, // but it's fine because the help command can be executed by non-bedrock players and by the console. manager.command(manager.commandBuilder(rootCommand) - .permission(commandPermission(manager)) .apply(meta()) // shouldn't be necessary - just for consistency .handler(this::execute)); } @@ -75,6 +74,11 @@ public class HelpCommand extends GeyserCommand { GeyserCommandSource source = context.sender(); boolean bedrockPlayer = source.connection() != null; + if (!source.hasPermission("geyser.command.help")) { + source.sendLocaleString("geyser.command.permission_fail"); + return; + } + // todo: pagination int page = 1; int maxPage = 1; From 834148007f64a0f9b7800e30f83bb529846c5ca0 Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Sun, 14 Apr 2024 17:09:41 +0200 Subject: [PATCH 70/76] add comment on paper command manager --- .../geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 0ceff43cb..7b4dec7cf 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 @@ -172,6 +172,8 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { ); PaperCommandManager cloud; try { + // PaperCommandManager is a cloud impl for all Bukkit based platforms + // https://github.com/Incendo/cloud-minecraft/blob/master/cloud-paper/src/main/java/org/incendo/cloud/paper/PaperCommandManager.java#L47-L49 cloud = new PaperCommandManager<>( this, ExecutionCoordinator.simpleCoordinator(), @@ -217,7 +219,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { GeyserImpl.start(); - PluginManager pluginManager = Bukkit.getPluginManager(); if (geyserConfig.isLegacyPingPassthrough()) { this.geyserSpigotPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); @@ -280,6 +281,8 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { // Register permissions so they appear in, for example, LuckPerms' UI // Re-registering permissions without removing it throws an error + PluginManager pluginManager = Bukkit.getPluginManager(); + // todo: this can probably always be run regardless if geyser has been initialized once or not, since we are removing the permission geyser.eventBus().fire((GeyserRegisterPermissionsEvent) (permission, def) -> { PermissionDefault permissionDefault = switch (def) { From f2611376c373378c0b64537f03c7f164e42606b4 Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Mon, 15 Apr 2024 14:39:36 +0200 Subject: [PATCH 71/76] - yeet application plugin from mod/plugin build scripts, set main class manifest attribute directly - fix viaproxy plugin plugin warnings - update languages module --- bootstrap/bungeecord/build.gradle.kts | 4 ++-- bootstrap/mod/fabric/build.gradle.kts | 8 ++------ bootstrap/mod/neoforge/build.gradle.kts | 16 ++-------------- bootstrap/spigot/build.gradle.kts | 4 ++-- bootstrap/standalone/build.gradle.kts | 4 ++++ bootstrap/velocity/build.gradle.kts | 4 ++-- bootstrap/viaproxy/build.gradle.kts | 4 ++-- .../platform/viaproxy/GeyserViaProxyMain.java | 3 +-- .../geyser.platform-conventions.gradle.kts | 1 - core/src/main/resources/languages | 2 +- 10 files changed, 18 insertions(+), 32 deletions(-) diff --git a/bootstrap/bungeecord/build.gradle.kts b/bootstrap/bungeecord/build.gradle.kts index 09f4d38b4..1db149e33 100644 --- a/bootstrap/bungeecord/build.gradle.kts +++ b/bootstrap/bungeecord/build.gradle.kts @@ -16,8 +16,8 @@ platformRelocate("org.yaml") // Broken as of 1.20 // These dependencies are already present on the platform provided(libs.bungeecord.proxy) -application { - mainClass.set("org.geysermc.geyser.platform.bungeecord.GeyserBungeeMain") +tasks.withType { + manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.bungeecord.GeyserBungeeMain" } tasks.withType { diff --git a/bootstrap/mod/fabric/build.gradle.kts b/bootstrap/mod/fabric/build.gradle.kts index 8ecd38656..bf3115f72 100644 --- a/bootstrap/mod/fabric/build.gradle.kts +++ b/bootstrap/mod/fabric/build.gradle.kts @@ -1,7 +1,3 @@ -plugins { - application -} - architectury { platformSetupLoomIde() fabric() @@ -42,8 +38,8 @@ dependencies { include(libs.cloud.fabric) } -application { - mainClass.set("org.geysermc.geyser.platform.fabric.GeyserFabricMain") +tasks.withType { + manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.fabric.GeyserFabricMain" } relocate("org.cloudburstmc.netty") diff --git a/bootstrap/mod/neoforge/build.gradle.kts b/bootstrap/mod/neoforge/build.gradle.kts index ddff323da..355106114 100644 --- a/bootstrap/mod/neoforge/build.gradle.kts +++ b/bootstrap/mod/neoforge/build.gradle.kts @@ -1,7 +1,3 @@ -plugins { - application -} - // This is provided by "org.cloudburstmc.math.mutable" too, so yeet. // NeoForge's class loader is *really* annoying. provided("org.cloudburstmc.math", "api") @@ -40,19 +36,11 @@ dependencies { include(libs.cloud.neoforge) } -application { - mainClass.set("org.geysermc.geyser.platform.forge.GeyserNeoForgeMain") +tasks.withType { + manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.neoforge.GeyserNeoForgeMain" } tasks { - // Cloud-NeoForge is currently providing cloud-core twice (once directly, once indirectly via modded-common) - distTar { - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - } - distZip { - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - } - remapJar { archiveBaseName.set("Geyser-NeoForge") } diff --git a/bootstrap/spigot/build.gradle.kts b/bootstrap/spigot/build.gradle.kts index 4f119df19..a9ff42956 100644 --- a/bootstrap/spigot/build.gradle.kts +++ b/bootstrap/spigot/build.gradle.kts @@ -30,8 +30,8 @@ platformRelocate("org.yaml") // Broken as of 1.20 // These dependencies are already present on the platform provided(libs.viaversion) -application { - mainClass.set("org.geysermc.geyser.platform.spigot.GeyserSpigotMain") +tasks.withType { + manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.spigot.GeyserSpigotMain" } tasks.withType { diff --git a/bootstrap/standalone/build.gradle.kts b/bootstrap/standalone/build.gradle.kts index eaf895108..fd81dad63 100644 --- a/bootstrap/standalone/build.gradle.kts +++ b/bootstrap/standalone/build.gradle.kts @@ -1,5 +1,9 @@ import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer +plugins { + application +} + val terminalConsoleVersion = "1.2.0" val jlineVersion = "3.21.0" diff --git a/bootstrap/velocity/build.gradle.kts b/bootstrap/velocity/build.gradle.kts index 3e028d855..a0bd38cec 100644 --- a/bootstrap/velocity/build.gradle.kts +++ b/bootstrap/velocity/build.gradle.kts @@ -39,8 +39,8 @@ exclude("net.kyori:adventure-nbt:*") // These dependencies are already present on the platform provided(libs.velocity.api) -application { - mainClass.set("org.geysermc.geyser.platform.velocity.GeyserVelocityMain") +tasks.withType { + manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.velocity.GeyserVelocityMain" } tasks.withType { diff --git a/bootstrap/viaproxy/build.gradle.kts b/bootstrap/viaproxy/build.gradle.kts index 977107f30..1dfb40114 100644 --- a/bootstrap/viaproxy/build.gradle.kts +++ b/bootstrap/viaproxy/build.gradle.kts @@ -13,8 +13,8 @@ platformRelocate("org.incendo") // These dependencies are already present on the platform provided(libs.viaproxy) -application { - mainClass.set("org.geysermc.geyser.platform.viaproxy.GeyserViaProxyMain") +tasks.withType { + manifest.attributes["Main-Class"] = "org.geysermc.geyser.platform.viaproxy.GeyserViaProxyMain" } tasks.withType { diff --git a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyMain.java b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyMain.java index 675c92534..582a97d78 100644 --- a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyMain.java +++ b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyMain.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.platform.viaproxy; -import net.raphimc.viaproxy.plugins.PluginManager; import org.geysermc.geyser.GeyserMain; public class GeyserViaProxyMain extends GeyserMain { @@ -39,7 +38,7 @@ public class GeyserViaProxyMain extends GeyserMain { } public String getPluginFolder() { - return PluginManager.PLUGINS_DIR.getName(); + return "plugins"; } } diff --git a/build-logic/src/main/kotlin/geyser.platform-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.platform-conventions.gradle.kts index 81d224906..410e67404 100644 --- a/build-logic/src/main/kotlin/geyser.platform-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.platform-conventions.gradle.kts @@ -1,4 +1,3 @@ plugins { - application id("geyser.publish-conventions") } \ No newline at end of file diff --git a/core/src/main/resources/languages b/core/src/main/resources/languages index afbf78bbe..6704730a0 160000 --- a/core/src/main/resources/languages +++ b/core/src/main/resources/languages @@ -1 +1 @@ -Subproject commit afbf78bbe0b39d0a076a42c228828c12f7f7da90 +Subproject commit 6704730a0ad611efdc5a71c3bc958511a66abf11 From 9aa6553901626b95d0ebd1be08bec477813f9a05 Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Mon, 15 Apr 2024 15:23:52 +0200 Subject: [PATCH 72/76] help command changes to ensure root command does require the help command permission --- .../geyser/command/defaults/HelpCommand.java | 14 ++++++++------ .../extension/command/GeyserExtensionCommand.java | 4 ++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java index f2c275351..8a8f2814a 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java @@ -66,7 +66,14 @@ public class HelpCommand extends GeyserCommand { // but it's fine because the help command can be executed by non-bedrock players and by the console. manager.command(manager.commandBuilder(rootCommand) .apply(meta()) // shouldn't be necessary - just for consistency - .handler(this::execute)); + .handler((commandContext -> { + GeyserCommandSource source = commandContext.sender(); + if (!source.hasPermission(this.permission())) { + source.sendLocaleString("geyser.command.permission_fail"); + return; + } + this.execute(commandContext); + }))); } @Override @@ -74,11 +81,6 @@ public class HelpCommand extends GeyserCommand { GeyserCommandSource source = context.sender(); boolean bedrockPlayer = source.connection() != null; - if (!source.hasPermission("geyser.command.help")) { - source.sendLocaleString("geyser.command.permission_fail"); - return; - } - // todo: pagination int page = 1; int maxPage = 1; diff --git a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java index 451b291ff..5613cba1b 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java @@ -111,7 +111,7 @@ public abstract class GeyserExtensionCommand extends GeyserCommand { @Override public Builder permission(@NonNull String permission) { this.permission = Objects.requireNonNull(permission, "command permission"); - if (!permission.contains(".")) { + if (!permission.contains(".") && !permission.isBlank()) { String newPermission = extension.description().id() + "." + permission; GeyserImpl.getInstance().getLogger().error("Extension " + extension.name() + " tried to register an invalid permission (" + permission + ")." + "Changing it to " + newPermission + "!"); @@ -124,7 +124,7 @@ public abstract class GeyserExtensionCommand extends GeyserCommand { public Builder permission(@NonNull String permission, @NonNull TriState defaultValue) { this.permission = Objects.requireNonNull(permission, "command permission"); this.permissionDefault = Objects.requireNonNull(defaultValue, "command permission defaultValue"); - if (!permission.contains(".")) { + if (!permission.contains(".") && !permission.isBlank()) { String newPermission = extension.description().id() + "." + permission; GeyserImpl.getInstance().getLogger().error("Extension " + extension.name() + " tried to register an invalid permission (" + permission + ")." + "Changing it to " + newPermission + "!"); From 8245b0f8a56382246bed6c006e598975b8691be7 Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Fri, 19 Apr 2024 15:53:28 +0200 Subject: [PATCH 73/76] don't jij cloud stuff, cloud-platform does that already --- build-logic/src/main/kotlin/geyser.modded-conventions.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/build-logic/src/main/kotlin/geyser.modded-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.modded-conventions.gradle.kts index 3d41dbbb4..881bf2349 100644 --- a/build-logic/src/main/kotlin/geyser.modded-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.modded-conventions.gradle.kts @@ -36,6 +36,7 @@ provided("io.netty", "netty-transport") provided("io.netty", "netty-codec") provided("io.netty", "netty-resolver-dns") provided("io.netty", "netty-resolver-dns-native-macos") +provided("org.incendo", ".*") // fabric-cloud/neoforge-cloud jij's all cloud depends already provided("org.ow2.asm", "asm") architectury { From 55ebb95270220376e515aaad16cdb6ca9683be72 Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Sat, 20 Apr 2024 13:21:41 +0200 Subject: [PATCH 74/76] Revert add entity translator shenanigans, re-add viaproxy console command event handler to deal with console commands --- .../platform/viaproxy/GeyserViaProxyPlugin.java | 11 +++++++++++ .../java/entity/spawn/JavaAddEntityTranslator.java | 3 --- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyPlugin.java b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyPlugin.java index 5095be11e..8e6f1edc2 100644 --- a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyPlugin.java +++ b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyPlugin.java @@ -30,6 +30,7 @@ import net.raphimc.viaproxy.ViaProxy; import net.raphimc.viaproxy.cli.options.Options; import net.raphimc.viaproxy.plugins.PluginManager; import net.raphimc.viaproxy.plugins.ViaProxyPlugin; +import net.raphimc.viaproxy.plugins.events.ConsoleCommandEvent; import net.raphimc.viaproxy.plugins.events.ProxyStartEvent; import net.raphimc.viaproxy.plugins.events.ProxyStopEvent; import net.raphimc.viaproxy.plugins.events.ShouldVerifyOnlineModeEvent; @@ -83,6 +84,16 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst this.onGeyserShutdown(); } + @EventHandler + private void onConsoleCommand(final ConsoleCommandEvent event) { + final String command = event.getCommand().startsWith("/") ? event.getCommand().substring(1) : event.getCommand(); + CommandRegistry registry = this.getCommandRegistry(); + if (registry.cloud().rootCommands().contains(command)) { + registry.runCommand(this.getGeyserLogger(), command + " " + String.join(" ", event.getArgs())); + event.setCancelled(true); + } + } + @EventHandler private void onShouldVerifyOnlineModeEvent(final ShouldVerifyOnlineModeEvent event) { final UUID uuid = event.getProxyConnection().getGameProfile().getId(); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddEntityTranslator.java index 1f88622aa..e058594c3 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/spawn/JavaAddEntityTranslator.java @@ -112,9 +112,6 @@ public class JavaAddEntityTranslator extends PacketTranslator Date: Sun, 21 Apr 2024 02:19:10 +0200 Subject: [PATCH 75/76] - add permission (+default) for base `/geyser` command - update languages - remove permission from the spigot plugin.yml - catch RuntimeExceptions - properly send command.failed lang string --- bootstrap/spigot/src/main/resources/plugin.yml | 3 --- .../geysermc/geyser/command/CommandRegistry.java | 13 ++++++++++--- .../geyser/command/defaults/HelpCommand.java | 14 +++++--------- core/src/main/resources/languages | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/bootstrap/spigot/src/main/resources/plugin.yml b/bootstrap/spigot/src/main/resources/plugin.yml index 085ce13af..14e98f577 100644 --- a/bootstrap/spigot/src/main/resources/plugin.yml +++ b/bootstrap/spigot/src/main/resources/plugin.yml @@ -6,6 +6,3 @@ version: ${version} softdepend: ["ViaVersion", "floodgate"] api-version: 1.13 folia-supported: true -permissions: - geyser.command: - default: true diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java index 7702379c4..1a60b746c 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java @@ -54,6 +54,7 @@ import org.geysermc.geyser.event.type.GeyserDefineCommandsEventImpl; import org.geysermc.geyser.extension.command.GeyserExtensionCommand; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.text.MinecraftLocale; import org.incendo.cloud.CommandManager; import org.incendo.cloud.exception.ArgumentParseException; import org.incendo.cloud.exception.CommandExecutionException; @@ -110,14 +111,15 @@ public class CommandRegistry { new GeyserExceptionHandler<>(NoPermissionException.class, this::handleNoPermission), new GeyserExceptionHandler<>(NoSuchCommandException.class, (src, e) -> src.sendLocaleString("geyser.command.not_found")), new GeyserExceptionHandler<>(ArgumentParseException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_argument", e.getCause().getMessage())), - new GeyserExceptionHandler<>(CommandExecutionException.class, (src, e) -> handleUnexpectedThrowable(src, e.getCause())) + new GeyserExceptionHandler<>(CommandExecutionException.class, (src, e) -> handleUnexpectedThrowable(src, e.getCause())), + new GeyserExceptionHandler<>(RuntimeException.class, (src, e) -> handleUnexpectedThrowable(src, e.getCause())) ); for (GeyserExceptionHandler handler : exceptionHandlers) { handler.register(cloud); } // begin command registration - registerBuiltInCommand(new HelpCommand(geyser, "help", "geyser.commands.help.desc", "geyser.command.help", "geyser", this.commands)); + registerBuiltInCommand(new HelpCommand(geyser, "help", "geyser.commands.help.desc", "geyser.command.help", "geyser", "geyser.command", this.commands)); registerBuiltInCommand(new ListCommand(geyser, "list", "geyser.commands.list.desc", "geyser.command.list")); registerBuiltInCommand(new ReloadCommand(geyser, "reload", "geyser.commands.reload.desc", "geyser.command.reload")); registerBuiltInCommand(new OffhandCommand(geyser, "offhand", "geyser.commands.offhand.desc", "geyser.command.offhand")); @@ -164,6 +166,7 @@ public class CommandRegistry { "geyser.commands.exthelp.desc", "geyser.command.exthelp." + id, extension.rootCommand(), + extension.description().id() + ".command", entry.getValue())); } @@ -205,6 +208,10 @@ public class CommandRegistry { if (!command.permission().isBlank() && command.permissionDefault() != null) { permissionDefaults.put(command.permission(), command.permissionDefault()); } + + if (command instanceof HelpCommand helpCommand) { + permissionDefaults.put(helpCommand.rootCommand(), helpCommand.permissionDefault()); + } } private void onRegisterPermissions(GeyserRegisterPermissionsEvent event) { @@ -265,7 +272,7 @@ public class CommandRegistry { } private void handleUnexpectedThrowable(GeyserCommandSource source, Throwable throwable) { - source.sendLocaleString("command.failed"); // java edition translation key + source.sendMessage(MinecraftLocale.getLocaleString("command.failed", source.locale())); // java edition translation key GeyserImpl.getInstance().getLogger().error("Exception while executing command handler", throwable); } diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java index 8a8f2814a..85334cc17 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/HelpCommand.java @@ -42,12 +42,14 @@ import java.util.Map; public class HelpCommand extends GeyserCommand { private final String rootCommand; + private final String rootCommandPermission; private final Collection commands; public HelpCommand(GeyserImpl geyser, String name, String description, String permission, - String rootCommand, Map commands) { + String rootCommand, String rootCommandPermission, Map commands) { super(name, description, permission, TriState.TRUE); this.rootCommand = rootCommand; + this.rootCommandPermission = rootCommandPermission; this.commands = commands.values(); this.aliases = Collections.singletonList("?"); } @@ -66,14 +68,8 @@ public class HelpCommand extends GeyserCommand { // but it's fine because the help command can be executed by non-bedrock players and by the console. manager.command(manager.commandBuilder(rootCommand) .apply(meta()) // shouldn't be necessary - just for consistency - .handler((commandContext -> { - GeyserCommandSource source = commandContext.sender(); - if (!source.hasPermission(this.permission())) { - source.sendLocaleString("geyser.command.permission_fail"); - return; - } - this.execute(commandContext); - }))); + .permission(rootCommandPermission) + .handler((this::execute))); } @Override diff --git a/core/src/main/resources/languages b/core/src/main/resources/languages index 6704730a0..5267df408 160000 --- a/core/src/main/resources/languages +++ b/core/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 6704730a0ad611efdc5a71c3bc958511a66abf11 +Subproject commit 5267df408359e5f84ecca3835e465a559fd9acb8 From 7456ed3c1fd61e18ffbf35c571f5eaf67c15e3d7 Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Sun, 21 Apr 2024 04:34:26 +0200 Subject: [PATCH 76/76] - add default handlers for neoforge - dont register blank permissions - handle neoforge's picky hasPermission - register all Geyser non-command permissions as unset permissions --- .../GeyserRegisterPermissionsEvent.java | 3 +- .../neoforge/GeyserNeoForgeBootstrap.java | 6 +-- .../GeyserNeoForgeCommandRegistry.java | 49 +++++++++++++++++++ .../GeyserNeoForgePermissionHandler.java | 32 ++++++++---- .../mod/command/ModCommandSource.java | 2 +- .../platform/spigot/GeyserSpigotPlugin.java | 11 ++--- .../java/org/geysermc/geyser/Constants.java | 2 + .../geyser/command/CommandRegistry.java | 14 +++++- .../geyser/command/GeyserCommand.java | 3 ++ .../geyser/command/GeyserPermission.java | 3 ++ .../StandaloneCloudCommandManager.java | 3 ++ .../command/GeyserExtensionCommand.java | 13 ----- .../geyser/session/GeyserSession.java | 4 +- .../BedrockSetDefaultGameTypeTranslator.java | 3 +- .../BedrockSetDifficultyTranslator.java | 3 +- .../BedrockSetPlayerGameTypeTranslator.java | 3 +- .../geysermc/geyser/util/SettingsUtils.java | 3 +- 17 files changed, 112 insertions(+), 45 deletions(-) create mode 100644 bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeCommandRegistry.java diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java index 2fcb12b6b..d5cdaccdb 100644 --- a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java +++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserRegisterPermissionsEvent.java @@ -32,8 +32,7 @@ import org.geysermc.geyser.api.util.TriState; * Fired by anything that wishes to gather permission nodes and defaults. *

* This event is not guaranteed to be fired, as certain Geyser platforms do not have a native permission system. - * It can be expected to fire on Geyser-Spigot, Geyser-NeoForge and Geyser-Standalone. Note: NeoForge allows registering permissions, - * but does so without registering default values. + * It can be expected to fire on Geyser-Spigot, Geyser-NeoForge and Geyser-Standalone. * It may still be fired on other platforms due to a 3rd party. */ public interface GeyserRegisterPermissionsEvent extends Event { diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java index d1978c4a7..591ef8673 100644 --- a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java +++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java @@ -28,6 +28,7 @@ package org.geysermc.geyser.platform.neoforge; import net.minecraft.commands.CommandSourceStack; import net.minecraft.world.entity.player.Player; import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.EventPriority; import net.neoforged.fml.common.Mod; import net.neoforged.fml.loading.FMLLoader; import net.neoforged.neoforge.common.NeoForge; @@ -35,7 +36,6 @@ import net.neoforged.neoforge.event.entity.player.PlayerEvent; import net.neoforged.neoforge.event.server.ServerStartedEvent; import net.neoforged.neoforge.event.server.ServerStoppingEvent; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.command.CommandSourceConverter; import org.geysermc.geyser.command.GeyserCommandSource; import org.geysermc.geyser.platform.mod.GeyserModBootstrap; @@ -60,7 +60,7 @@ public class GeyserNeoForgeBootstrap extends GeyserModBootstrap { NeoForge.EVENT_BUS.addListener(this::onPlayerJoin); GeyserNeoForgePermissionHandler permissionHandler = new GeyserNeoForgePermissionHandler(); - NeoForge.EVENT_BUS.addListener(permissionHandler::onPermissionGather); + NeoForge.EVENT_BUS.addListener(EventPriority.HIGHEST, permissionHandler::onPermissionGather); this.onGeyserInitialize(); @@ -75,7 +75,7 @@ public class GeyserNeoForgeBootstrap extends GeyserModBootstrap { ExecutionCoordinator.simpleCoordinator(), sourceConverter ); - this.setCommandRegistry(new CommandRegistry(GeyserImpl.getInstance(), cloud)); + this.setCommandRegistry(new GeyserNeoForgeCommandRegistry(GeyserImpl.getInstance(), cloud)); } private void onServerStarted(ServerStartedEvent event) { diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeCommandRegistry.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeCommandRegistry.java new file mode 100644 index 000000000..e4af78abf --- /dev/null +++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeCommandRegistry.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019-2024 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.neoforge; + +import com.google.common.util.concurrent.UncheckedExecutionException; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.command.CommandRegistry; +import org.geysermc.geyser.command.GeyserCommandSource; +import org.incendo.cloud.CommandManager; + +public class GeyserNeoForgeCommandRegistry extends CommandRegistry { + public GeyserNeoForgeCommandRegistry(GeyserImpl geyser, CommandManager cloud) { + super(geyser, cloud); + } + + // todo yeet once cloud enforced method contract here: + // https://github.com/Incendo/cloud/blob/master/cloud-core/src/main/java/org/incendo/cloud/CommandManager.java#L441-L449 + @Override + public boolean hasPermission(GeyserCommandSource source, String permission) { + try { + return super.hasPermission(source, permission); + } catch (UncheckedExecutionException e) { + return false; + } + } +} diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePermissionHandler.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePermissionHandler.java index 732687ae8..6fc334adb 100644 --- a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePermissionHandler.java +++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePermissionHandler.java @@ -30,9 +30,9 @@ import net.neoforged.neoforge.server.permission.nodes.PermissionDynamicContextKe import net.neoforged.neoforge.server.permission.nodes.PermissionNode; import net.neoforged.neoforge.server.permission.nodes.PermissionType; import net.neoforged.neoforge.server.permission.nodes.PermissionTypes; -import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.event.lifecycle.GeyserRegisterPermissionsEvent; +import org.geysermc.geyser.api.util.TriState; import java.lang.reflect.Constructor; @@ -57,22 +57,27 @@ public class GeyserNeoForgePermissionHandler { } public void onPermissionGather(PermissionGatherEvent.Nodes event) { - this.registerNode(Constants.UPDATE_PERMISSION, event); - - GeyserImpl.getInstance().eventBus().fire((GeyserRegisterPermissionsEvent) (permission, defaultValue) -> this.registerNode(permission, event)); + GeyserImpl.getInstance().eventBus().fire( + (GeyserRegisterPermissionsEvent) (permission, defaultValue) -> { + if (permission.isBlank()) { + return; + } + this.registerNode(permission, defaultValue, event); + } + ); } - private void registerNode(String node, PermissionGatherEvent.Nodes event) { - PermissionNode permissionNode = this.createNode(node); + private void registerNode(String node, TriState permissionDefault, PermissionGatherEvent.Nodes event) { + PermissionNode permissionNode = this.createNode(node, permissionDefault); // NeoForge likes to crash if you try and register a duplicate node - if (!event.getNodes().contains(permissionNode)) { + if (event.getNodes().stream().noneMatch(eventNode -> eventNode.getNodeName().equals(node))) { event.addNodes(permissionNode); } } @SuppressWarnings("unchecked") - private PermissionNode createNode(String node) { + private PermissionNode createNode(String node, TriState permissionDefault) { // The typical constructors in PermissionNode require a // mod id, which means our permission nodes end up becoming // geyser_neoforge. instead of just . We work around @@ -82,7 +87,16 @@ public class GeyserNeoForgePermissionHandler { return (PermissionNode) PERMISSION_NODE_CONSTRUCTOR.newInstance( node, PermissionTypes.BOOLEAN, - (PermissionNode.PermissionResolver) (player, playerUUID, context) -> false, + (PermissionNode.PermissionResolver) (player, playerUUID, context) -> switch (permissionDefault) { + case TRUE -> true; + case FALSE -> false; + case NOT_SET -> { + if (player != null) { + yield player.createCommandSourceStack().hasPermission(player.server.getOperatorUserPermissionLevel()); + } + yield false; + } + }, new PermissionDynamicContextKey[0] ); } catch (Exception e) { diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSource.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSource.java index 9ba89b4dd..3612cb8b7 100644 --- a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSource.java +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSource.java @@ -88,7 +88,7 @@ public class ModCommandSource implements GeyserCommandSource { // Unlike other bootstraps; we delegate to cloud here too: // On NeoForge; we'd have to keep track of all PermissionNodes - cloud already does that // For Fabric, we won't need to include the Fabric Permissions API anymore - cloud already does that too :p - return GeyserImpl.getInstance().commandRegistry().cloud().hasPermission(this, permission); + return GeyserImpl.getInstance().commandRegistry().hasPermission(this, permission); } @Override 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 7b4dec7cf..05baec501 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 @@ -41,7 +41,6 @@ import org.bukkit.permissions.PermissionDefault; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.adapters.spigot.SpigotAdapters; @@ -277,14 +276,13 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { geyserLogger.debug("Using default world manager."); } - // Register permissions so they appear in, for example, LuckPerms' UI // Re-registering permissions without removing it throws an error - PluginManager pluginManager = Bukkit.getPluginManager(); - - // todo: this can probably always be run regardless if geyser has been initialized once or not, since we are removing the permission geyser.eventBus().fire((GeyserRegisterPermissionsEvent) (permission, def) -> { + if (permission.isBlank()) { + return; + } PermissionDefault permissionDefault = switch (def) { case TRUE -> PermissionDefault.TRUE; case FALSE -> PermissionDefault.FALSE; @@ -302,9 +300,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { pluginManager.addPermission(new Permission(permission, permissionDefault)); }); - pluginManager.addPermission(new Permission(Constants.UPDATE_PERMISSION, - "Whether update notifications can be seen", PermissionDefault.OP)); - // Events cannot be unregistered - re-registering results in duplicate firings GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(geyser, this.geyserWorldManager); pluginManager.registerEvents(blockPlaceListener, this); diff --git a/core/src/main/java/org/geysermc/geyser/Constants.java b/core/src/main/java/org/geysermc/geyser/Constants.java index 5de8e6e6b..4c9c36e17 100644 --- a/core/src/main/java/org/geysermc/geyser/Constants.java +++ b/core/src/main/java/org/geysermc/geyser/Constants.java @@ -38,6 +38,8 @@ public final class Constants { public static final String GEYSER_DOWNLOAD_LOCATION = "https://geysermc.org/download"; public static final String UPDATE_PERMISSION = "geyser.update"; + public static final String SERVER_SETTINGS_PERMISSION = "geyser.settings.server"; + public static final String SETTINGS_GAMERULES_PERMISSION = "geyser.settings.gamerules"; static final String SAVED_REFRESH_TOKEN_FILE = "saved-refresh-tokens.json"; diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java index 1a60b746c..786433c0f 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java @@ -28,6 +28,7 @@ package org.geysermc.geyser.command; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import lombok.AllArgsConstructor; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.api.command.Command; @@ -112,7 +113,7 @@ public class CommandRegistry { new GeyserExceptionHandler<>(NoSuchCommandException.class, (src, e) -> src.sendLocaleString("geyser.command.not_found")), new GeyserExceptionHandler<>(ArgumentParseException.class, (src, e) -> src.sendLocaleString("geyser.command.invalid_argument", e.getCause().getMessage())), new GeyserExceptionHandler<>(CommandExecutionException.class, (src, e) -> handleUnexpectedThrowable(src, e.getCause())), - new GeyserExceptionHandler<>(RuntimeException.class, (src, e) -> handleUnexpectedThrowable(src, e.getCause())) + new GeyserExceptionHandler<>(Throwable.class, (src, e) -> handleUnexpectedThrowable(src, e.getCause())) ); for (GeyserExceptionHandler handler : exceptionHandlers) { handler.register(cloud); @@ -195,6 +196,10 @@ public class CommandRegistry { register(command, this.extensionCommands.computeIfAbsent(extension, e -> new HashMap<>())); } + public boolean hasPermission(GeyserCommandSource source, String permission) { + return cloud.hasPermission(source, permission); + } + private void register(GeyserCommand command, Map commands) { command.register(cloud); @@ -218,6 +223,11 @@ public class CommandRegistry { for (Map.Entry permission : permissionDefaults.entrySet()) { event.register(permission.getKey(), permission.getValue()); } + + // Register other various Geyser permissions + event.register(Constants.UPDATE_PERMISSION, TriState.NOT_SET); + event.register(Constants.SERVER_SETTINGS_PERMISSION, TriState.NOT_SET); + event.register(Constants.SETTINGS_GAMERULES_PERMISSION, TriState.NOT_SET); } /** @@ -277,7 +287,7 @@ public class CommandRegistry { } @AllArgsConstructor - private static class GeyserExceptionHandler implements ExceptionHandler { + private static class GeyserExceptionHandler implements ExceptionHandler { final Class type; final BiConsumer handler; diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java index 939edebe7..b419dee30 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserCommand.java @@ -89,6 +89,9 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C if (name.isBlank()) { throw new IllegalArgumentException("Command cannot be null or blank!"); } + if (permission.isBlank()) { + permissionDefault = null; + } this.name = name; this.description = description; diff --git a/core/src/main/java/org/geysermc/geyser/command/GeyserPermission.java b/core/src/main/java/org/geysermc/geyser/command/GeyserPermission.java index 7af32d570..2ba9b32e6 100644 --- a/core/src/main/java/org/geysermc/geyser/command/GeyserPermission.java +++ b/core/src/main/java/org/geysermc/geyser/command/GeyserPermission.java @@ -42,6 +42,9 @@ public class GeyserPermission implements PredicatePermission manager; public Result check(GeyserCommandSource source) { + if (permission.isBlank()) { + return Result.ALLOWED; + } if (bedrockOnly) { if (source.connection() == null) { return Result.NOT_BEDROCK; diff --git a/core/src/main/java/org/geysermc/geyser/command/standalone/StandaloneCloudCommandManager.java b/core/src/main/java/org/geysermc/geyser/command/standalone/StandaloneCloudCommandManager.java index 2788cb201..4a0e78a78 100644 --- a/core/src/main/java/org/geysermc/geyser/command/standalone/StandaloneCloudCommandManager.java +++ b/core/src/main/java/org/geysermc/geyser/command/standalone/StandaloneCloudCommandManager.java @@ -86,6 +86,9 @@ public class StandaloneCloudCommandManager extends CommandManager { + if (permission.isBlank()) { + return; + } if (def == TriState.TRUE) { basePermissions.add(permission); } diff --git a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java index 5613cba1b..f90045e9a 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/extension/command/GeyserExtensionCommand.java @@ -27,7 +27,6 @@ package org.geysermc.geyser.extension.command; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.api.command.CommandExecutor; import org.geysermc.geyser.api.command.CommandSource; @@ -111,12 +110,6 @@ public abstract class GeyserExtensionCommand extends GeyserCommand { @Override public Builder permission(@NonNull String permission) { this.permission = Objects.requireNonNull(permission, "command permission"); - if (!permission.contains(".") && !permission.isBlank()) { - String newPermission = extension.description().id() + "." + permission; - GeyserImpl.getInstance().getLogger().error("Extension " + extension.name() + " tried to register an invalid permission (" + permission + ")." + - "Changing it to " + newPermission + "!"); - this.permission = newPermission; - } return this; } @@ -124,12 +117,6 @@ public abstract class GeyserExtensionCommand extends GeyserCommand { public Builder permission(@NonNull String permission, @NonNull TriState defaultValue) { this.permission = Objects.requireNonNull(permission, "command permission"); this.permissionDefault = Objects.requireNonNull(defaultValue, "command permission defaultValue"); - if (!permission.contains(".") && !permission.isBlank()) { - String newPermission = extension.description().id() + "." + permission; - GeyserImpl.getInstance().getLogger().error("Extension " + extension.name() + " tried to register an invalid permission (" + permission + ")." + - "Changing it to " + newPermission + "!"); - this.permission = newPermission; - } return this; } diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 3eb2dc18c..fdff1cd19 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -65,8 +65,6 @@ import io.netty.channel.Channel; import io.netty.channel.EventLoop; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -1463,7 +1461,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { public boolean hasPermission(String permission) { // for Geyser-Standalone, standalone's permission system will handle it. // for server platforms, the session will be mapped to a server command sender, and the server's api will be used. - return geyser.commandRegistry().cloud().hasPermission(this, permission); + return geyser.commandRegistry().hasPermission(this, permission); } /** diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetDefaultGameTypeTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetDefaultGameTypeTranslator.java index df28f7ca7..38e9e152b 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetDefaultGameTypeTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetDefaultGameTypeTranslator.java @@ -28,6 +28,7 @@ package org.geysermc.geyser.translator.protocol.bedrock.entity.player; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import org.cloudburstmc.protocol.bedrock.packet.SetDefaultGameTypePacket; import org.cloudburstmc.protocol.bedrock.packet.SetPlayerGameTypePacket; +import org.geysermc.geyser.Constants; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -41,7 +42,7 @@ public class BedrockSetDefaultGameTypeTranslator extends PacketTranslator= 2 && session.hasPermission("geyser.settings.server")) { + if (session.getOpPermissionLevel() >= 2 && session.hasPermission(Constants.SERVER_SETTINGS_PERMISSION)) { session.getGeyser().getWorldManager().setDefaultGameMode(session, GameMode.byId(packet.getGamemode())); } // Stop the client from updating their own Gamemode without telling the server diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetDifficultyTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetDifficultyTranslator.java index b996a96b1..0f68cf2df 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetDifficultyTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetDifficultyTranslator.java @@ -27,6 +27,7 @@ package org.geysermc.geyser.translator.protocol.bedrock.entity.player; import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; import org.cloudburstmc.protocol.bedrock.packet.SetDifficultyPacket; +import org.geysermc.geyser.Constants; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -39,7 +40,7 @@ public class BedrockSetDifficultyTranslator extends PacketTranslator= 2 && session.hasPermission("geyser.settings.server")) { + if (session.getOpPermissionLevel() >= 2 && session.hasPermission(Constants.SERVER_SETTINGS_PERMISSION)) { if (packet.getDifficulty() != session.getWorldCache().getDifficulty().ordinal()) { session.getGeyser().getWorldManager().setDifficulty(session, Difficulty.from(packet.getDifficulty())); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetPlayerGameTypeTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetPlayerGameTypeTranslator.java index 2d8d420f8..185cf576a 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetPlayerGameTypeTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetPlayerGameTypeTranslator.java @@ -27,6 +27,7 @@ package org.geysermc.geyser.translator.protocol.bedrock.entity.player; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import org.cloudburstmc.protocol.bedrock.packet.SetPlayerGameTypePacket; +import org.geysermc.geyser.Constants; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -45,7 +46,7 @@ public class BedrockSetPlayerGameTypeTranslator extends PacketTranslator= 2 && session.hasPermission("geyser.settings.server")) { + if (session.getOpPermissionLevel() >= 2 && session.hasPermission(Constants.SERVER_SETTINGS_PERMISSION)) { if (packet.getGamemode() != session.getGameMode().ordinal()) { // Bedrock has more Gamemodes than Java, leading to cases 5 (for "default") and 6 (for "spectator") being sent // https://github.com/CloudburstMC/Protocol/blob/3.0/bedrock-codec/src/main/java/org/cloudburstmc/protocol/bedrock/data/GameType.java diff --git a/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java b/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java index ed97408b9..0311c4834 100644 --- a/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java @@ -27,6 +27,7 @@ package org.geysermc.geyser.util; import org.geysermc.cumulus.component.DropdownComponent; import org.geysermc.cumulus.form.CustomForm; +import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.level.GameRule; import org.geysermc.geyser.level.WorldManager; @@ -79,7 +80,7 @@ public class SettingsUtils { } } - boolean showGamerules = session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.gamerules"); + boolean showGamerules = session.getOpPermissionLevel() >= 2 || session.hasPermission(Constants.SETTINGS_GAMERULES_PERMISSION); if (showGamerules) { builder.label("geyser.settings.title.game_rules") .translator(MinecraftLocale::getLocaleString); // we need translate gamerules next