Initial start on cloud

This commit is contained in:
Konicai 2023-06-01 00:28:38 -04:00
parent 246ebddc78
commit db5ccff9e5
No known key found for this signature in database
GPG Key ID: 710D09287708C823
41 changed files with 366 additions and 1020 deletions

View File

@ -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<UUID> playerUuid();
Optional<? extends Connection> connection();
/**
* Returns the locale of the command source.
*

View File

@ -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
*

View File

@ -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)

View File

@ -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<GeyserCommandSource> 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<Extension, Map<String, Command>> entry : this.geyserCommandManager.extensionCommands().entrySet()) {
Map<String, Command> 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 {

View File

@ -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<UUID> 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;
}
}

View File

@ -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<String, org.geysermc.geyser.api.command.Command> 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<String> onTabComplete(CommandSender sender, String[] args) {
if (args.length == 1) {
return commandExecutor.tabComplete(new BungeeCommandSource(sender));
} else {
return Collections.emptyList();
}
}
}

View File

@ -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"))

View File

@ -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<GeyserCommandSource> 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<CommandSourceStack> builder = Commands.literal("geyser").executes(helpExecutor);
// Register all subcommands as valid
for (Map.Entry<String, Command> 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() {

View File

@ -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()));
}
}

View File

@ -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<UUID> 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;
}
}

View File

@ -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<CommandSourceStack> {
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;
}
}

View File

@ -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)

View File

@ -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<GeyserCommandSource> 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<PluginCommand> 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<Extension, Map<String, Command>> entry : this.geyserCommandManager.extensionCommands().entrySet()) {
Map<String, Command> 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);

View File

@ -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() {
}
}

View File

@ -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<String, Command> commands = GeyserImpl.getInstance().commandManager().getCommands();
Iterator<? extends CommandNode<?>> 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;
}
}

View File

@ -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<String, org.geysermc.geyser.api.command.Command> 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<String> onTabComplete(CommandSender sender, Command command, String label, String[] args) {
if (args.length == 1) {
return tabComplete(new SpigotCommandSource(sender));
}
return Collections.emptyList();
}
}

View File

@ -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;
}
}

View File

@ -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<UUID> playerUuid() {
if (handle instanceof Player player) {
return Optional.of(player.getUniqueId());
}
return Optional.empty();
}
@Override
public String locale() {
if (this.handle instanceof Player player) {

View File

@ -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<org.spongepowered.api.command.Command.Raw> event) {
if (enabled) {
event.register(this.pluginContainer, new GeyserSpongeCommandExecutor(geyser, geyserCommandManager.getCommands()), "geyser");
for (Map.Entry<Extension, Map<String, Command>> entry : this.geyserCommandManager.extensionCommands().entrySet()) {
Map<String, Command> 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
}
/**

View File

@ -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<String, Command> 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<CommandCompletion> 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<Component> shortDescription(CommandCause cause) {
return Optional.of(Component.text("The main command for Geyser."));
}
@Override
public Optional<Component> extendedDescription(CommandCause cause) {
return shortDescription(cause);
}
@Override
public Optional<Component> help(@NotNull CommandCause cause) {
return Optional.of(Component.text("/geyser help"));
}
@Override
public Component usage(CommandCause cause) {
return Component.text("/geyser help");
}
}

View File

@ -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<Component> description(CommandMapping mapping) {
return mapping.registrar().shortDescription(CommandCause.create(), mapping);
}
}

View File

@ -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<UUID> 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;
}
}

View File

@ -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) {

View File

@ -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<GeyserCommandSource> {
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();
}
}

View File

@ -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

View File

@ -260,7 +260,7 @@ public class GeyserStandaloneGUI {
commandsMenu.removeAll();
optionsMenu.removeAll();
for (Map.Entry<String, Command> entry : geyserCommandManager.getCommands().entrySet()) {
for (Map.Entry<String, Command> 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;

View File

@ -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.*:*")

View File

@ -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<GeyserCommandSource> 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<Extension, Map<String, Command>> entry : this.geyserCommandManager.extensionCommands().entrySet()) {
Map<String, Command> 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 {

View File

@ -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<String, Command> 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<String> 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();
}
}

View File

@ -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<UUID> 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;
}
}

View File

@ -47,6 +47,9 @@ dependencies {
// Adventure text serialization
api(libs.bundles.adventure)
// command library
api(libs.cloud.core)
api(libs.erosion.common) {
isTransitive = false
}

View File

@ -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();

View File

@ -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<UUID> playerUuid() {
return Optional.empty();
}
@Override
default boolean hasPermission(String permission) {
return true;

View File

@ -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<GeyserCommandSource> 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(" "));
})
);
}
}

View File

@ -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<String, Command> 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<String> tabComplete(GeyserCommandSource sender) {
if (getGeyserSession(sender) != null) {
// Bedrock doesn't get tab completions or argument suggestions
return Collections.emptyList();
}
List<String> availableCommands = new ArrayList<>();
// Only show commands they have permission to use
for (Map.Entry<String, Command> 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;
}
}

View File

@ -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<String, Command> commands = new Object2ObjectOpenHashMap<>(12);
private final Map<String, Command> commands = new Object2ObjectOpenHashMap<>(13);
private final Map<Extension, Map<String, Command>> extensionCommands = new Object2ObjectOpenHashMap<>(0);
private final GeyserImpl geyser;
private final CommandManager<GeyserCommandSource> cloud;
public GeyserCommandManager(GeyserImpl geyser, CommandManager<GeyserCommandSource> 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<String, Command> commands) {
private void register(GeyserCommand command, Map<String, Command> 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<String, Command> 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<GeyserCommandSource> cloud() {
return cloud;
}
/**
@ -324,6 +292,11 @@ public class GeyserCommandManager {
public boolean isExecutableOnConsole() {
return CommandBuilder.this.executableOnConsole;
}
@Override
public String rootCommand() {
return extension().rootCommand();
}
};
}
}

View File

@ -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<GeyserSession> connection() {
return playerUuid().map(id -> GeyserImpl.getInstance().connectionByUuid(id));
}
/**
* todo: commands
*/
default Object handle() {
return this;
}
}

View File

@ -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();

View File

@ -1407,6 +1407,11 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
return false;
}
@Override
public Optional<UUID> playerUuid() {
return Optional.of(playerEntity.getUuid());
}
@Override
public String locale() {
return clientData.getLanguageCode();

View File

@ -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<CommandReq
@Override
public void translate(GeyserSession session, CommandRequestPacket packet) {
String command = MessageTranslator.convertToPlainText(packet.getCommand());
if (!(session.getGeyser().getPlatformType() == PlatformType.STANDALONE
&& GeyserImpl.getInstance().commandManager().runCommand(session, command.substring(1)))) {
if (MessageTranslator.isTooLong(command, session)) {
return;
}
String strippedCommand = command.substring(1);
session.sendCommand(command.substring(1));
if (session.getGeyser().getPlatformType() == PlatformType.STANDALONE) {
// try to handle the command within the standalone command manager
String[] args = strippedCommand.split(" ");
if (args.length > 0) {
String root = args[0];
CommandManager<GeyserCommandSource> manager = GeyserImpl.getInstance().commandManager().cloud();
if (manager.rootCommands().contains(root)) {
manager.executeCommand(session, strippedCommand);
return;
}
}
}
if (MessageTranslator.isTooLong(command, session)) {
return;
}
session.sendCommand(strippedCommand);
}
}

View File

@ -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" }