diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/BungeeCommandSender.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/BungeeCommandSender.java index 1f3a875b2..4cda47af7 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/BungeeCommandSender.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/BungeeCommandSender.java @@ -63,4 +63,9 @@ public class BungeeCommandSender implements CommandSender { } return LanguageUtils.getDefaultLocale(); } + + @Override + public boolean hasPermission(String permission) { + return handle.hasPermission(permission); + } } diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java index b391d7b1c..07a1fd897 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/command/GeyserBungeeCommandExecutor.java @@ -35,8 +35,8 @@ import org.geysermc.connector.command.GeyserCommand; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.LanguageUtils; -import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; public class GeyserBungeeCommandExecutor extends Command implements TabExecutor { @@ -82,8 +82,9 @@ public class GeyserBungeeCommandExecutor extends Command implements TabExecutor @Override public Iterable onTabComplete(CommandSender sender, String[] args) { if (args.length == 1) { - return connector.getCommandManager().getCommandNames(); + return commandExecutor.tabComplete(new BungeeCommandSender(sender)); + } else { + return Collections.emptyList(); } - return new ArrayList<>(); } } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java index 6cdcdae67..384fcf13a 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/GeyserSpigotCommandExecutor.java @@ -35,8 +35,8 @@ import org.geysermc.connector.command.GeyserCommand; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.LanguageUtils; -import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; public class GeyserSpigotCommandExecutor extends CommandExecutor implements TabExecutor { @@ -78,8 +78,8 @@ public class GeyserSpigotCommandExecutor extends CommandExecutor implements TabE @Override public List onTabComplete(CommandSender sender, Command command, String label, String[] args) { if (args.length == 1) { - return connector.getCommandManager().getCommandNames(); + return tabComplete(new SpigotCommandSender(sender)); } - return new ArrayList<>(); + return Collections.emptyList(); } } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/SpigotCommandSender.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/SpigotCommandSender.java index 16533623a..9cd6d672f 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/SpigotCommandSender.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/command/SpigotCommandSender.java @@ -73,6 +73,11 @@ public class SpigotCommandSender implements CommandSender { return locale; } + @Override + public boolean hasPermission(String permission) { + return handle.hasPermission(permission); + } + /** * Set if we are on pre-1.12, and therefore {@code player.getLocale()} doesn't exist and we have to get * {@code player.spigot().getLocale()}. diff --git a/bootstrap/spigot/src/main/resources/plugin.yml b/bootstrap/spigot/src/main/resources/plugin.yml index 0171abd70..7edd14520 100644 --- a/bootstrap/spigot/src/main/resources/plugin.yml +++ b/bootstrap/spigot/src/main/resources/plugin.yml @@ -8,21 +8,32 @@ api-version: 1.13 commands: geyser: description: The main command for Geyser. - usage: /geyser help + usage: /geyser permissions: geyser.command.help: description: Shows help for all registered commands. - geyser.command.advancement: - description: Shows the advancements of the player on the server. - geyser.command.dump: - description: Dumps Geyser debug information for bug reports. - geyser.command.list: - description: List all players connected through Geyser. + default: true geyser.command.offhand: description: Puts an items in your offhand. - geyser.command.reload: - description: Reloads the Geyser configurations. Kicks all players when used! + default: true + geyser.command.advancements: + description: Shows the advancements of the player on the server. + default: true geyser.command.statistics: description: Shows the statistics of the player on the server. + default: true + geyser.command.settings: + description: Modify user settings + default: true + geyser.command.list: + description: List all players connected through Geyser. + default: op + geyser.command.dump: + description: Dumps Geyser debug information for bug reports. + default: op + geyser.command.reload: + description: Reloads the Geyser configurations. Kicks all players when used! + default: false geyser.command.version: - description: Shows the current Geyser version and checks for updates. \ No newline at end of file + description: Shows the current Geyser version and checks for updates. + default: op \ No newline at end of file diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java index 0e46413cc..cff6d24d7 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/GeyserSpongeCommandExecutor.java @@ -40,8 +40,8 @@ import org.spongepowered.api.world.Location; import org.spongepowered.api.world.World; import javax.annotation.Nullable; -import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Optional; @@ -82,9 +82,9 @@ public class GeyserSpongeCommandExecutor extends CommandExecutor implements Comm @Override public List getSuggestions(CommandSource source, String arguments, @Nullable Location targetPosition) { if (arguments.split(" ").length == 1) { - return connector.getCommandManager().getCommandNames(); + return tabComplete(new SpongeCommandSender(source)); } - return new ArrayList<>(); + return Collections.emptyList(); } @Override diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/SpongeCommandSender.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/SpongeCommandSender.java index 4616e400b..d2167d80b 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/SpongeCommandSender.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/command/SpongeCommandSender.java @@ -51,4 +51,9 @@ public class SpongeCommandSender implements CommandSender { public boolean isConsole() { return handle instanceof ConsoleSource; } + + @Override + public boolean hasPermission(String permission) { + return handle.hasPermission(permission); + } } diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java index 5255dc16f..94d5857db 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneLogger.java @@ -110,4 +110,9 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements Gey public boolean isConsole() { return true; } + + @Override + public boolean hasPermission(String permission) { + return true; + } } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java index c8998d8fe..6f47b8791 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/GeyserVelocityCommandExecutor.java @@ -34,8 +34,8 @@ import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.LanguageUtils; -import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; public class GeyserVelocityCommandExecutor extends CommandExecutor implements SimpleCommand { @@ -71,9 +71,10 @@ public class GeyserVelocityCommandExecutor extends CommandExecutor implements Si @Override public List suggest(Invocation invocation) { - if (invocation.arguments().length == 0) { - return connector.getCommandManager().getCommandNames(); + // 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 VelocityCommandSender(invocation.source())); } - return new ArrayList<>(); + return Collections.emptyList(); } } diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/VelocityCommandSender.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/VelocityCommandSender.java index 21069acbb..140711a7c 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/VelocityCommandSender.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/command/VelocityCommandSender.java @@ -72,4 +72,9 @@ public class VelocityCommandSender implements CommandSender { } return LanguageUtils.getDefaultLocale(); } + + @Override + public boolean hasPermission(String permission) { + return handle.hasPermission(permission); + } } diff --git a/connector/src/main/java/org/geysermc/connector/command/CommandExecutor.java b/connector/src/main/java/org/geysermc/connector/command/CommandExecutor.java index 751f51260..913c39c5e 100644 --- a/connector/src/main/java/org/geysermc/connector/command/CommandExecutor.java +++ b/connector/src/main/java/org/geysermc/connector/command/CommandExecutor.java @@ -29,6 +29,11 @@ import lombok.AllArgsConstructor; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + /** * Represents helper functions for listening to {@code /geyser} commands. */ @@ -53,4 +58,39 @@ public class CommandExecutor { } return null; } + + /** + * Determine which subcommands to suggest in the tab complete for the main /geyser command by a given command sender. + * + * @param sender The command sender to receive the tab complete suggestions. + * If the command sender is a bedrock player, an empty list will be returned as bedrock players do not get command argument suggestions. + * If the command sender is not a bedrock player, bedrock commands will not be shown. + * If the command sender does not have the permission for a given command, the command will not be shown. + * @return A list of command names to include in the tab complete + */ + public List tabComplete(CommandSender sender) { + if (getGeyserSession(sender) != null) { + // Bedrock doesn't get tab completions or argument suggestions + return Collections.emptyList(); + } + + List availableCommands = new ArrayList<>(); + Map commands = connector.getCommandManager().getCommands(); + + // Only show commands they have permission to use + for (String name : commands.keySet()) { + GeyserCommand geyserCommand = commands.get(name); + if (sender.hasPermission(geyserCommand.getPermission())) { + + if (geyserCommand.isBedrockOnly()) { + // Don't show commands the JE player can't run + continue; + } + + availableCommands.add(name); + } + } + + return availableCommands; + } } diff --git a/connector/src/main/java/org/geysermc/connector/command/CommandSender.java b/connector/src/main/java/org/geysermc/connector/command/CommandSender.java index 78d12aadb..9fa8673f4 100644 --- a/connector/src/main/java/org/geysermc/connector/command/CommandSender.java +++ b/connector/src/main/java/org/geysermc/connector/command/CommandSender.java @@ -56,4 +56,12 @@ public interface CommandSender { default String getLocale() { return LanguageUtils.getDefaultLocale(); } + + /** + * Checks if the CommandSender has a permission + * + * @param permission The permission node to check + * @return true if the CommandSender has the requested permission, false if not + */ + boolean hasPermission(String permission); } diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/HelpCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/HelpCommand.java index 53968e40b..c180a473d 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/HelpCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/HelpCommand.java @@ -33,9 +33,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.LanguageUtils; import java.util.Collections; -import java.util.List; import java.util.Map; -import java.util.stream.Collectors; public class HelpCommand extends GeyserCommand { private final GeyserConnector connector; @@ -47,16 +45,33 @@ public class HelpCommand extends GeyserCommand { this.setAliases(Collections.singletonList("?")); } + /** + * Sends the help menu to a command sender. Will not show certain commands depending on the command sender and session. + * + * @param session The Geyser session of the command sender, if it is a bedrock player. If null, bedrock-only commands will be hidden. + * @param sender The CommandSender to send the help message to. + * @param args Not used. + */ @Override public void execute(GeyserSession session, CommandSender sender, String[] args) { int page = 1; int maxPage = 1; String header = LanguageUtils.getPlayerLocaleString("geyser.commands.help.header", sender.getLocale(), page, maxPage); - sender.sendMessage(header); + Map cmds = connector.getCommandManager().getCommands(); - List commands = connector.getCommandManager().getCommands().keySet().stream().sorted().collect(Collectors.toList()); - commands.forEach(cmd -> sender.sendMessage(ChatColor.YELLOW + "/geyser " + cmd + ChatColor.WHITE + ": " + - LanguageUtils.getPlayerLocaleString(cmds.get(cmd).getDescription(), sender.getLocale()))); + for (String cmdName : cmds.keySet()) { + GeyserCommand cmd = cmds.get(cmdName); + + if (sender.hasPermission(cmd.getPermission())) { + // Only list commands the player can actually run + if (cmd.isBedrockOnly() && session == null) { + continue; + } + + sender.sendMessage(ChatColor.YELLOW + "/geyser " + cmdName + ChatColor.WHITE + ": " + + LanguageUtils.getPlayerLocaleString(cmd.getDescription(), sender.getLocale())); + } + } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index e5009938d..db4d1fd8f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -1381,7 +1381,8 @@ public class GeyserSession implements CommandSender { * @param permission The permission node to check * @return true if the player has the requested permission, false if not */ - public Boolean hasPermission(String permission) { + @Override + public boolean hasPermission(String permission) { return connector.getWorldManager().hasPermission(this, permission); }