This commit is contained in:
Konicai 2023-09-14 13:24:42 -04:00
parent c40cfd6806
commit 2aa2621adc
18 changed files with 87 additions and 57 deletions

View File

@ -61,7 +61,7 @@ public interface Command {
* Gets the permission node associated with
* this command.
*
* @return the permission node for this command
* @return the permission node for this command if defined, otherwise an empty string
*/
@NonNull
String permission();
@ -78,6 +78,7 @@ public interface Command {
* Gets if this command is designed to be used only by server operators.
*
* @return if this command is designated to be used only by server operators.
* @deprecated this method is not guaranteed to provide meaningful or expected results.
*/
@Deprecated(forRemoval = true)
default boolean isSuggestedOpOnly() {
@ -99,11 +100,7 @@ public interface Command {
boolean isBedrockOnly();
/**
* Gets the subcommands associated with this
* command. Mainly used within the Geyser Standalone
* GUI to know what subcommands are supported.
*
* @return the subcommands associated with this command
* @deprecated this method will always return an empty list
*/
@Deprecated(forRemoval = true)
@NonNull
@ -133,7 +130,7 @@ public interface Command {
* is an instance of this source.
*
* @param sourceType the source type
* @return the builder
* @return this builder
*/
Builder<T> source(@NonNull Class<? extends T> sourceType);
@ -141,7 +138,7 @@ public interface Command {
* Sets the command name.
*
* @param name the command name
* @return the builder
* @return this builder
*/
Builder<T> name(@NonNull String name);
@ -149,26 +146,27 @@ public interface Command {
* Sets the command description.
*
* @param description the command description
* @return the builder
* @return this builder
*/
Builder<T> description(@Nullable String description);
/**
* Sets the permission node.
* Sets the permission node required to run this command. <br>
* It will not be registered anywhere, unlike {@link #permission(String, TriState)}.
*
* @param permission the permission node
* @return the builder
* @return this builder
*/
Builder<T> permission(@Nullable String permission);
/**
* Sets the permission node, and its default value. The usage of the default value is platform dependant
* and may or may not be used. Extensions may instead listen for {@link GeyserRegisterPermissionsEvent}
* to register permissions.
* Sets the permission node and its default value. The usage of the default value is platform dependant
* and may or may not be used. <br>
* Extensions may instead listen for {@link GeyserRegisterPermissionsEvent} to register permissions.
*
* @param permission the permission node
* @param defaultValue the node's default value
* @return the builder
* @return this builder
*/
Builder<T> permission(@Nullable String permission, @NonNull TriState defaultValue);
@ -176,7 +174,7 @@ public interface Command {
* Sets the aliases.
*
* @param aliases the aliases
* @return the builder
* @return this builder
*/
Builder<T> aliases(@Nullable List<String> aliases);
@ -184,7 +182,8 @@ public interface Command {
* Sets if this command is designed to be used only by server operators.
*
* @param suggestedOpOnly if this command is designed to be used only by server operators
* @return the builder
* @return this builder
* @deprecated this method is not guaranteed to produce meaningful or expected results
*/
@Deprecated(forRemoval = true)
Builder<T> suggestedOpOnly(boolean suggestedOpOnly);
@ -193,7 +192,7 @@ public interface Command {
* Sets if this command is executable on console.
*
* @param executableOnConsole if this command is executable on console
* @return the builder
* @return this builder
*/
Builder<T> executableOnConsole(boolean executableOnConsole);
@ -201,7 +200,7 @@ public interface Command {
* Sets if this command is bedrock only.
*
* @param bedrockOnly if this command is bedrock only
* @return the builder
* @return this builder
*/
Builder<T> bedrockOnly(boolean bedrockOnly);
@ -209,7 +208,8 @@ public interface Command {
* Sets the subcommands.
*
* @param subCommands the subcommands
* @return the builder
* @return this builder
* @deprecated this method has no effect
*/
@Deprecated(forRemoval = true)
default Builder<T> subCommands(@NonNull List<String> subCommands) {
@ -220,14 +220,14 @@ public interface Command {
* Sets the {@link CommandExecutor} for this command.
*
* @param executor the command executor
* @return the builder
* @return this builder
*/
Builder<T> executor(@NonNull CommandExecutor<T> executor);
/**
* Builds the command.
*
* @return the command
* @return a new command from this builder
*/
@NonNull
Command build();

View File

@ -69,10 +69,14 @@ public interface CommandSource {
boolean isConsole();
/**
* todo: commands
* @return an Optional containing a Java UUID if this source represents any player, otherwise empty
*/
Optional<UUID> playerUuid();
/**
* @return an Optional with a present Connection if this source represents a Bedrock player that is connected
* to this Geyser instance. Otherwise, returns an empty optional.
*/
Optional<? extends Connection> connection();
/**

View File

@ -50,7 +50,7 @@ public interface GeyserDefineCommandsEvent extends Event {
/**
* Gets all the registered built-in {@link Command}s.
*
* @return all the registered built-in commands as an unmodifiable map.
* @return all the registered built-in commands as an unmodifiable map
*/
@NonNull
Map<String, Command> commands();

View File

@ -31,7 +31,7 @@ import org.geysermc.geyser.api.permission.PermissionChecker;
/**
* Fired by any permission manager implementations that wish to add support for custom permission checking.
* This event is not guaranteed to be fired.
* This event is not guaranteed to be fired - it is currently only fired on Geyser-Standalone.
* <p>
* Subscribing to this event with an earlier {@link PostOrder} and registering a {@link PermissionChecker}
* will result in that checker having a higher priority than others.

View File

@ -29,12 +29,13 @@ import org.geysermc.event.Event;
import org.geysermc.geyser.api.util.TriState;
/**
* Fired by any implementations that want to gather permission nodes and defaults
* Fired by anything that wishes to gather permission nodes and defaults.
* This event is not guaranteed to be fired, as certain Geyser platforms do not have a native permission system.
*/
public interface GeyserRegisterPermissionsEvent extends Event {
/**
* Registers a permission node with the permission system being used by Geyser's command manager.
* Registers a permission node and its default value with the firer.
*
* @param permission the permission node to register
* @param defaultValue the default value of the node

View File

@ -108,7 +108,8 @@ public interface Extension extends EventRegistrar {
}
/**
* todo: commands
* @return the root command that all of this extension's commands will stem from.
* By default, this is the extension's id.
*/
@NonNull
default String rootCommand() {

View File

@ -29,9 +29,21 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.command.CommandSource;
import org.geysermc.geyser.api.util.TriState;
/**
* Something capable of checking if a {@link CommandSource} has a permission
*/
@FunctionalInterface
public interface PermissionChecker {
/**
* Checks if the given source has a permission
*
* @param source the {@link CommandSource} whose permissions should be queried
* @param permission the permission node to check
* @return a {@link TriState} as the value of the node. {@link TriState#NOT_SET} generally means that the permission
* node itself was not found, and the source does not have such permission.
* {@link TriState#TRUE} and {@link TriState#FALSE} represent explicitly set values.
*/
@NonNull
TriState hasPermission(@NonNull CommandSource source, @NonNull String permission);
}

View File

@ -28,7 +28,6 @@ package org.geysermc.geyser.platform.fabric;
import cloud.commandframework.CommandManager;
import cloud.commandframework.execution.CommandExecutionCoordinator;
import cloud.commandframework.fabric.FabricServerCommandManager;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
@ -39,6 +38,8 @@ import net.minecraft.commands.CommandSourceStack;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.entity.player.Player;
import org.apache.logging.log4j.LogManager;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger;
@ -55,8 +56,6 @@ import org.geysermc.geyser.platform.fabric.command.FabricCommandSource;
import org.geysermc.geyser.platform.fabric.world.GeyserFabricWorldManager;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.FileUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
@ -208,7 +207,7 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
}
@SuppressWarnings("ConstantConditions") // Certain IDEA installations think that ip cannot be null
@NotNull
@NonNull
@Override
public String getServerBindAddress() {
String ip = this.server.getLocalIp();

View File

@ -49,11 +49,21 @@ import java.util.Set;
public class GeyserStandaloneCommandManager extends CommandManager<GeyserCommandSource> {
private final GeyserImpl geyser;
/**
* The checkers we use to test if a command source has a permission
*/
private final List<PermissionChecker> permissionCheckers = new ArrayList<>();
/**
* Any permissions that all connections have
*/
private final Set<String> basePermissions = new ObjectOpenHashSet<>();
public GeyserStandaloneCommandManager(GeyserImpl geyser) {
super(CommandExecutionCoordinator.simpleCoordinator(), CommandRegistrationHandler.nullCommandRegistrationHandler());
// simpleCoordinator: execute commands immediately on the calling thread.
// nullCommandRegistrationHandler: cloud is not responsible for handling our CommandRegistry, which is fairly decoupled.
this.geyser = geyser;
// allow any extensions to customize permissions
@ -66,8 +76,7 @@ public class GeyserStandaloneCommandManager extends CommandManager<GeyserCommand
PermissionConfiguration config = FileUtils.loadConfig(permissionsFile, PermissionConfiguration.class);
basePermissions.addAll(config.getDefaultPermissions());
} catch (Exception e) {
geyser.getLogger().warning("Failed to load permissions.yml");
e.printStackTrace();
geyser.getLogger().error("Failed to load permissions.yml - proceeding without it", e);
}
}
@ -79,8 +88,6 @@ public class GeyserStandaloneCommandManager extends CommandManager<GeyserCommand
geyser.getEventBus().fire((GeyserRegisterPermissionsEvent) (permission, def) -> {
if (def == TriState.TRUE) {
basePermissions.add(permission);
} else if (def == TriState.FALSE) {
basePermissions.remove(permission); // todo: maybe remove this case?
}
});
}
@ -101,7 +108,10 @@ public class GeyserStandaloneCommandManager extends CommandManager<GeyserCommand
if (result != null) {
return result;
}
// undefined - try the next checker to see if it has a defined value
}
// fallback to our list of default permissions
// note that a PermissionChecker may in fact override any values set here by return FALSE
return basePermissions.contains(permission);
}

View File

@ -44,6 +44,8 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements Gey
@Override
protected void runCommand(String line) {
// seems like terminal console appender invokes this method async off the terminal,
// so we can probably invoke the command directly on the current thread
GeyserImpl.getInstance().commandRegistry().runCommand(this, line);
}

View File

@ -270,15 +270,14 @@ public class GeyserStandaloneGUI {
}
/**
* Enable the command input box.
* Enables the command input box.
*
* @param executor the executor for running commands off the GUI thread
* @param registry the command registry to delegate commands to
* @param executor the executor that commands will be run on
* @param registry the command registry containing all current commands
*/
public void enableCommands(ScheduledExecutorService executor, CommandRegistry registry) {
// we don't want to block the GUI thread with the command execution
// todo: maybe use a AsynchronousCommandExecutionCoordinator (cloud thing)
commandListener.handler = cmd -> executor.execute(() -> registry.runCommand(logger, cmd));
commandListener.dispatcher = cmd -> executor.execute(() -> registry.runCommand(logger, cmd));
commandInput.setEnabled(true);
commandInput.requestFocusInWindow();
}
@ -343,14 +342,14 @@ public class GeyserStandaloneGUI {
private class CommandListener implements ActionListener {
private Consumer<String> handler;
private Consumer<String> dispatcher;
@Override
public void actionPerformed(ActionEvent e) {
// the headless variant of Standalone strips trailing whitespace for us - we need to manually
String command = commandInput.getText().stripTrailing();
appendConsole(command + "\n"); // show what was run in the console
handler.accept(command); // run the command
dispatcher.accept(command); // run the command
commandInput.setText(""); // clear the input
}
}

View File

@ -1,9 +1,9 @@
# Add any permissions here that all players should have.
# Add any permissions here that all connections should have.
# Permissions for builtin geyser commands do not have to be listed here.
# If an extension/plugin hooks into the permission system of Geyser-Standalone, entries here may be ignored/overridden.
# If extensions don't register their permissions, they must be added here manually.
# If an extension/plugin registers their permissions with default values, entries here are typically unnecessary.
# If extensions don't register their permissions, permissions that everyone should have must be added here manually.
default-permissions:
- geyser.command.help # this is unnecessary

View File

@ -94,9 +94,9 @@ public class CommandRegistry {
private final Map<String, Extension> extensionRootCommands = new Object2ObjectOpenHashMap<>(0);
/**
* Map of permission nodes to their default values
* Map containing only permissions that have been registered with a default value
*/
private final Map<String, TriState> permissions = new Object2ObjectOpenHashMap<>(13);
private final Map<String, TriState> permissionDefaults = new Object2ObjectOpenHashMap<>(13);
/**
* The order and behaviour of these exception handlers is designed to mirror the typical cloud implementations.
@ -194,7 +194,7 @@ public class CommandRegistry {
public void clear() {
this.commands.clear();
this.extensionCommands.clear();
this.permissions.clear();
this.permissionDefaults.clear();
}
/**
@ -219,12 +219,12 @@ public class CommandRegistry {
}
if (!command.permission().isBlank() && command.permissionDefault() != null) {
permissions.put(command.permission(), command.permissionDefault());
permissionDefaults.put(command.permission(), command.permissionDefault());
}
}
private void onRegisterPermissions(GeyserRegisterPermissionsEvent event) {
for (Map.Entry<String, TriState> permission : permissions.entrySet()) {
for (Map.Entry<String, TriState> permission : permissionDefaults.entrySet()) {
event.register(permission.getKey(), permission.getValue());
}
}

View File

@ -56,6 +56,7 @@ public record CommandSourceConverter<S>(Class<S> senderType,
}
if (!(source instanceof GeyserSession)) {
// todo cloud remove this
GeyserImpl.getInstance().getLogger().warning("Falling back to UUID for command sender lookup for a command source that is not a GeyserSession: " + source);
Thread.dumpStack();
}
@ -68,7 +69,7 @@ public record CommandSourceConverter<S>(Class<S> senderType,
/**
* Creates a new CommandSourceConverter for a server platform
* in which the player type is not the command sender type, and must be mapped.
* in which the player type is not a command sender type, and must be mapped.
*
* @param senderType class of the command sender type
* @param playerLookup function for looking up a player by UUID

View File

@ -215,4 +215,4 @@ public abstract class GeyserCommand implements org.geysermc.geyser.api.command.C
* @param context the context with which this command should be executed
*/
public abstract void execute(CommandContext<GeyserCommandSource> context);
}
}

View File

@ -65,7 +65,8 @@ public interface GeyserCommandSource extends CommandSource {
}
/**
* todo: commands
* @return the underlying platform handle that this source represents.
* If such handle doesn't exist, this itself is returned.
*/
default Object handle() {
return this;

View File

@ -202,7 +202,7 @@ public abstract class GeyserExtensionCommand extends GeyserCommand {
// currently, the only subclass of CommandSource exposed in the api is GeyserConnection.
// when this command was registered, we enabled bedrockOnly if the sourceType was a GeyserConnection.
// as a result, SenderTypeProcessor should handle that case and this method shouldn't even be reached.
// as a result, the permission checker should handle that case and this method shouldn't even be reached.
source.sendMessage("You must be a " + sourceType.getSimpleName() + " to run this command.");
}

View File

@ -49,11 +49,11 @@ public class BedrockCommandRequestTranslator extends PacketTranslator<CommandReq
if (args.length > 0) {
String root = args[0];
// todo: do we want to pass the command to the server
// todo cloud do we want to pass the command to the server
// if cloud gives a NoSuchCommandException? might be more accurate.
CommandRegistry registry = GeyserImpl.getInstance().commandRegistry();
if (registry.cloud().rootCommands().contains(root)) {
// todo: cloud might not like the trailing whitespace either
// todo cloud might not like the trailing whitespace either
registry.runCommand(session, strippedCommand);
return; // don't pass the command to the java server
}