Commands Guide
WaterdogPE commands are proxy commands: they run on the proxy, not on the downstream server. That makes them perfect for things like /server, /hub or /staffchat that need to work regardless of which server the player is on.
A command is a class that extends dev.waterdog.waterdogpe.command.Command. You create it, then register it with the proxy's command map.
A minimal command
package com.example.myplugin;
import dev.waterdog.waterdogpe.command.Command;
import dev.waterdog.waterdogpe.command.CommandSender;
import dev.waterdog.waterdogpe.command.CommandSettings;
import dev.waterdog.waterdogpe.player.ProxiedPlayer;
public class HubCommand extends Command {
public HubCommand() {
super("hub", CommandSettings.builder()
.setDescription("Teleport to the hub")
.setPermission("myplugin.command.hub")
.setAliases("lobby", "leave")
.build());
}
@Override
public boolean onExecute(CommandSender sender, String alias, String[] args) {
if (!sender.isPlayer()) {
sender.sendMessage("§cOnly players can use this command.");
return true;
}
ProxiedPlayer player = (ProxiedPlayer) sender;
var hub = player.getProxy().getServerInfo("lobby1");
if (hub == null) {
player.sendMessage("§cThe hub is not available right now.");
return true;
}
player.connect(hub);
return true;
}
}Register it in your plugin's onEnable():
@Override
public void onEnable() {
this.getProxy().getCommandMap().registerCommand(new HubCommand());
}That's a complete, working command. The rest of this page explains each part.
The constructor and CommandSettings
The Command constructor takes a name and (optionally) a CommandSettings object:
public Command(String name)
public Command(String name, CommandSettings settings)CommandSettings carries the description, usage message, required permission and aliases. Build it with the fluent builder:
CommandSettings.builder()
.setDescription("Teleport to the hub") // shown in /help
.setUsageMessage("/hub") // shown when onExecute returns false
.setPermission("myplugin.command.hub") // required permission
.setPermissionMessage("§cNo permission.") // shown if the sender lacks it
.setAliases("lobby", "leave") // alternative names
.build()| Builder method | Purpose |
|---|---|
setDescription(String) | Human-readable description (used in /help). |
setUsageMessage(String) | Usage hint shown when onExecute returns false. |
setPermission(String) | Permission the sender must have. |
setPermissionMessage(String) | Message shown when the permission check fails. |
setAliases(String...) | Alternative command names. |
The default proxy commands pass translation keys (like
"waterdog.command.server.description") to these methods so they can be localised vialang.ini. For your own plugin you can pass plain strings.
onExecute — handling the command
public abstract boolean onExecute(CommandSender sender, String alias, String[] args)sender— who ran the command (see Command senders).alias— the alias used, if the player triggered the command through one of its aliases (otherwise the base name).args— everything after the command name, split on spaces. For/warp spawn 1the array is{"spawn", "1"}(the command name itself is not included).
The return value controls the usage message:
- Return
trueif you handled the command (even if you showed your own error). - Return
falseto tell the proxy to show the command's usage message.
Arguments arrive as raw strings. You are responsible for validating them and converting types (e.g.
Integer.parseInt(args[0])), and for handling bad input gracefully.
Command senders
The sender is a CommandSender (dev.waterdog.waterdogpe.command.CommandSender), which can be a player or the console. Its key methods:
String getName();
boolean isPlayer();
boolean hasPermission(String permission);
void sendMessage(String message);
void sendMessage(TextContainer message);
ProxyServer getProxy();There are two built-in senders:
ProxiedPlayer— a player in-game.sendMessageshows in their chat,hasPermissionchecks their permissions,getNameis their username.ConsoleCommandSender— the proxy console. It has all permissions (hasPermissionalways returnstrue),getName()returns"CONSOLE", andsendMessagelogs at INFO level.
Always check isPlayer() before casting to ProxiedPlayer. If you cast a console sender to a player your command will throw when run from the console:
if (!sender.isPlayer()) {
sender.sendMessage("§cThis command can only be used by players.");
return true;
}
ProxiedPlayer player = (ProxiedPlayer) sender;Registering and unregistering
The command map (dev.waterdog.waterdogpe.command.CommandMap) is the registry for all proxy commands. Reach it with proxy.getCommandMap():
CommandMap commands = this.getProxy().getCommandMap();
commands.registerCommand(new HubCommand()); // register
commands.registerAlias("h", new HubCommand()); // extra alias
commands.unregisterCommand("hub"); // remove (e.g. in onDisable)
boolean exists = commands.isRegistered("hub");
Command cmd = commands.getCommand("hub");By default WaterdogPE uses a SimpleCommandMap with the / prefix and handles permission and success checking for you. DefaultCommandMap extends it and registers the proxy's built-in commands (/server, /transfer, etc.).
Client-side autocompletion (advanced)
Bedrock clients can show command argument hints and tab-completion. By default every command is given a single optional text argument. To customise the arguments shown to the client, override buildCommandOverloads().
Requirement: client-side command injection must be enabled in the proxy
config.ymlwithinject_proxy_commands: true, otherwise these hints are never sent.
The protocol types come from the CloudburstMC protocol library (org.cloudburstmc.protocol.bedrock.data.command.*). Here is the real /server command's definition, which shows a required server text argument and an optional player target argument:
import org.cloudburstmc.protocol.bedrock.data.command.CommandOverloadData;
import org.cloudburstmc.protocol.bedrock.data.command.CommandParam;
import org.cloudburstmc.protocol.bedrock.data.command.CommandParamData;
@Override
protected CommandOverloadData[] buildCommandOverloads() {
CommandParamData server = new CommandParamData();
server.setName("server");
server.setOptional(false);
server.setType(CommandParam.TEXT);
CommandParamData player = new CommandParamData();
player.setName("player");
player.setOptional(true);
player.setType(CommandParam.TARGET);
return new CommandOverloadData[]{
new CommandOverloadData(false, new CommandParamData[]{server, player})
};
}- Each
CommandOverloadDatais one overload (one valid way to call the command). Returning several lets the client suggest different argument shapes. CommandParamprovides the data types the client understands —TEXT,INT,TARGET(a player),FLOAT, and so on.- This only changes what the client displays and suggests. You still parse and validate
argsyourself inonExecute.
Migrating from WaterdogPE 1.x: the old
craftNetwork()method returning aCommandData(withcom.nukkitx.*imports) has been replaced bybuildCommandOverloads()returningCommandOverloadData[](withorg.cloudburstmc.*imports).
Reacting to commands from elsewhere
If you want to observe or block commands rather than define them, subscribe to DispatchCommandEvent, which is fired (and is cancellable) right before a player dispatches a proxy command. See the Events Guide.
