Refactor command abstraction and implement `/geyser` as help

This commit is contained in:
Konicai 2023-09-04 21:02:40 -04:00
parent 065abb0550
commit acd53a4519
No known key found for this signature in database
GPG Key ID: 710D09287708C823
15 changed files with 136 additions and 125 deletions

View File

@ -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<GeyserCommandSource> builder(CommandManager<GeyserCommandSource> manager) {
public final Command.Builder<GeyserCommandSource> baseBuilder(CommandManager<GeyserCommandSource> 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<GeyserCommandSource> 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<GeyserCommandSource> 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.
* <br><br>
* 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<GeyserCommandSource> 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<GeyserCommandSource> context);
}

View File

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

View File

@ -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<GeyserCommandSource> builder(CommandManager<GeyserCommandSource> manager) {
return super.builder(manager)
.handler(context ->
context.getSender().connection().ifPresent(session ->
session.getAdvancementsCache().buildAndShowMenuForm()));
public void execute(CommandContext<GeyserCommandSource> context) {
GeyserSession session = context.getSender().connection().orElseThrow();
session.getAdvancementsCache().buildAndShowMenuForm();
}
}

View File

@ -53,17 +53,18 @@ public class ConnectionTestCommand extends GeyserCommand {
}
@Override
public Command.Builder<GeyserCommandSource> builder(CommandManager<GeyserCommandSource> manager) {
return super.builder(manager)
public void register(CommandManager<GeyserCommandSource> manager) {
manager.command(baseBuilder(manager)
.argument(StringArgument.of(ADDRESS))
.argument(IntegerArgument.<GeyserCommandSource>builder(PORT)
.asOptionalWithDefault(19132)
.withMax(65535).withMin(0)
.build())
.handler(this::execute);
.handler(this::execute));
}
private void execute(CommandContext<GeyserCommandSource> context) {
@Override
public void execute(CommandContext<GeyserCommandSource> context) {
GeyserCommandSource source = context.getSender();
String address = context.get(ADDRESS);
int port = context.get(PORT);

View File

@ -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<GeyserCommandSource> builder(CommandManager<GeyserCommandSource> manager) {
return super.builder(manager)
public void register(CommandManager<GeyserCommandSource> manager) {
manager.command(baseBuilder(manager)
.argument(createArgument())
.handler(this::execute);
.handler(this::execute));
}
private StringArrayArgument<GeyserCommandSource> createArgument() {
@ -74,6 +73,7 @@ public class DumpCommand extends GeyserCommand {
return StringArrayArgument.optional(ARGUMENTS, (context, input) -> SUGGESTIONS);
}
@Override
public void execute(CommandContext<GeyserCommandSource> context) {
GeyserCommandSource source = context.getSender();
String[] args = context.getOrDefault(ARGUMENTS, new String[0]);

View File

@ -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<GeyserCommandSource> builder(CommandManager<GeyserCommandSource> manager) {
return super.builder(manager)
.handler(this::execute);
}
public void execute(CommandContext<GeyserCommandSource> context) {
GeyserCommandSource source = context.getSender();

View File

@ -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<GeyserCommandSource> builder(CommandManager<GeyserCommandSource> manager) {
return super.builder(manager)
.handler(this::execute);
public void register(CommandManager<GeyserCommandSource> manager) {
super.register(manager);
// Also register just `/geyser`
manager.command(manager.commandBuilder(rootCommand())
.permission(permission())
.apply(meta())
.handler(this::execute));
}
private void execute(CommandContext<GeyserCommandSource> context) {
@Override
public void execute(CommandContext<GeyserCommandSource> context) {
GeyserCommandSource source = context.getSender();
boolean bedrockPlayer = source.connection().isPresent();

View File

@ -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<GeyserCommandSource> builder(CommandManager<GeyserCommandSource> manager) {
return super.builder(manager)
.handler(context -> {
GeyserCommandSource source = context.getSender();
public void execute(CommandContext<GeyserCommandSource> 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);
}
}

View File

@ -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<GeyserCommandSource> builder(CommandManager<GeyserCommandSource> manager) {
return super.builder(manager)
.handler(context -> context.getSender().connection().ifPresent(GeyserSession::requestOffhandSwap));
public void execute(CommandContext<GeyserCommandSource> context) {
GeyserSession session = context.getSender().connection().orElseThrow();
session.requestOffhandSwap();
}
}

View File

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

View File

@ -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<GeyserCommandSource> builder(CommandManager<GeyserCommandSource> manager) {
return super.builder(manager)
.handler(context ->
context.getSender().connection().ifPresent(session ->
session.sendForm(SettingsUtils.buildForm(session))));
public void execute(CommandContext<GeyserCommandSource> context) {
GeyserSession session = context.getSender().connection().orElseThrow();
session.sendForm(SettingsUtils.buildForm(session));
}
}

View File

@ -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<GeyserCommandSource> builder(CommandManager<GeyserCommandSource> 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<GeyserCommandSource> context) {
GeyserSession session = context.getSender().connection().orElseThrow();
session.setWaitingForStatistics(true);
ServerboundClientCommandPacket packet = new ServerboundClientCommandPacket(ClientCommand.STATS);
session.sendDownstreamPacket(packet);
}
}

View File

@ -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<GeyserCommandSource> builder(CommandManager<GeyserCommandSource> manager) {
return super.builder(manager)
.handler(context -> geyser.getBootstrap().onDisable());
public void execute(CommandContext<GeyserCommandSource> context) {
geyser.getBootstrap().onDisable();
}
}

View File

@ -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<GeyserCommandSource> builder(CommandManager<GeyserCommandSource> manager) {
return super.builder(manager)
.handler(this::execute);
}
public void execute(CommandContext<GeyserCommandSource> context) {
GeyserCommandSource source = context.getSender();

View File

@ -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<GeyserCommandSource> 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<GeyserCommandSource> builder(CommandManager<GeyserCommandSource> 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<GeyserCommandSource> 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