参数解析

命令生成器(Command Builder)的 API 附带了一个非常强大的参数解析器。它把字符串输入转换为 Java 的基本类型(整数、布尔值、字符串)或游戏中的对象(玩家、世界、方块类型等)。这一解析器支持可选参数和标志,它同时也可以处理使用 TAB 的参数自动补全。

解析过的参数存储在 CommandContext 对象中。如果解析器返回了单个对象,可以通过 CommandContext#getOne(String) 获取。可选参数和弱参数可能会返回 Optional.empty()

很多解析器可能会返回多个对象(例如匹配用户名的多个玩家)。在这种情况下,你必须使用 CommandContext#getAll(String) 以获取所有可能的匹配项对应的 Collection否则上下文对象会抛出异常

当创建命令时,应考虑某个参数是否可以有多个返回值,比如原本应该有玩家参数的地方是否可以用选择器来支持多个玩家。若如此做,玩家在使用这个命令的时候就可以只输入一条命令,并且语法看起来也会很简洁,比如:/tell @a Who took the cookies?

小技巧

你可以使用 GenericArguments#onlyOne(CommandElement) 元素来限制参数的返回数量,这样你可以安全使用 args.<T>getOne(String)。但用户仍然会在尝试选择多个值的时候收到相应的提示。

若要创建一个新的 CommandElement (参数),请使用 GenericArguments 这一工厂类(Factory Class)。许多 CommandElement 都需要一段短文本用于显示帮助或错误信息。

使用 CommandSpec.Builder#arguments(CommandElement…)CommandElement 应用到命令生成器上。这一方法可以传入多个 CommandElement ,这样就可以解析多个参数(例如 /msg <player> <msg> )。传入多个参数等价于使用 GenericArguments#seq(CommandElement…) 把多个参数包装起来。

示例:使用命令生成器生成多个参数的命令

import org.spongepowered.api.Sponge;
import org.spongepowered.api.text.Text;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.command.CommandException;
import org.spongepowered.api.command.CommandResult;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.command.args.CommandContext;
import org.spongepowered.api.command.args.GenericArguments;
import org.spongepowered.api.command.spec.CommandExecutor;
import org.spongepowered.api.command.spec.CommandSpec;

PluginContainer plugin = ...;

CommandSpec myCommandSpec = CommandSpec.builder()
        .description(Text.of("Send a message to a player"))
        .permission("myplugin.command.message")

        .arguments(
                GenericArguments.onlyOne(GenericArguments.player(Text.of("player"))),
                GenericArguments.remainingJoinedStrings(Text.of("message")))

        .executor((CommandSource src, CommandContext args) -> {

            Player player = args.<Player>getOne("player").get();
            String message = args.<String>getOne("message").get();

            player.sendMessage(Text.of(message));

            return CommandResult.success();
        })
        .build();

Sponge.getCommandManager().register(plugin, myCommandSpec, "message", "msg", "m");

对于 GenericArguments 命令元素的概述

命令元素

说明

值类型和数量

none

期望没有参数,这也是 CommandSpec 的默认行为。

Java 基础类型

string

期望一个表示字符串的参数。

一个 String

remainingJoinedStrings

把所有剩余的参数使用空格连接起来(对于消息命令来说十分有用)。

一个 String

bool

期望一个表示布尔值的参数。

一个 Boolean

integer

期望一个表示整数的参数。

一个 Integer

doubleNum

期望一个表示浮点数的参数。

一个 Double

游戏对象

player

期望一个表示在线玩家的参数,并 可能返回多个玩家

多个 Player 的实例

playerOrSource

player 类似,但在没有找到匹配的玩家时返回命令的执行者。

多个 Player 的实例

userOrSource

playerOrSource 类似,只不过返回一个用户( User )而不是玩家。

多个 User 的实例

world

期望一个表示世界的参数(包括尚未加载的世界)。

多个 WorldProperties

dimension

期望一个表示维度的参数(如 ENDNETHER 、和 OVERWORLD )。

多个 DimensionType 的实例

location

期望一个表示坐标( Location )的参数。

一个 Location

vector3d

期望一个表示坐标( Vector3d )的参数。

一个 Vector3d

catalogedElement

期望一个表示特定 CatalogType 成员的参数。

多个匹配特定索引类型(Catalog Type)的实例

匹配器

choices

返回一个允许从一组特定的值中选定的参数。

一个特定的值

literal

期望一个表示特定文本序列的参数(例如 "i", "luv", "u" 表示 /cmd i luv u ),如果不匹配则抛出一个异常。

一个特定的值

enumValue

期望一个表示给定枚举类型的值的参数。

多个匹配给定枚举的元素

辅助元素

用于把若干命令元素包装起来。其值类型是从被包装元素继承的。

seq

生成一串命令元素的序列(例如 /cmd <arg1> <arg2> <arg3> )。

继承

repeated

期望一个表示重复数次的命令元素。

多个继承

allOf

期望所有剩余的参数匹配给定命令元素。

多个继承

optional

使给定的命令元素可选。如果参数的格式无效或并没有更多的参数,将抛出一个异常。

继承

optionalWeak

使给定的命令元素可选。如果参数的格式无效或并没有更多的参数,将不会抛出一个异常。

继承

firstParsing

返回给定命令元素中第一个可解析的元素(对像是 /settime <day|night|<number> 这种命令重载的情况有用)。

继承

onlyOne

限制给定命令元素,使其只允许在给定上下文插入仅仅一个。

继承

flags

返回一个标志(Flag)生成器(例如 /cmd [-a] [-b <value>] )。

请参阅 命令标志

短标志(Short Flag):一个 Boolean

长标志(Long Flag):一个 String

值标志(Value Flag):继承

requiringPermission

要求命令执行者拥有使用给定命令参数的特定权限

继承

requiringPermissionWeak

要求命令执行者拥有使用给定命令参数的特定权限,但在命令执行者没有权限时不会报错。

继承

小技巧

详见 GenericArguments 以获取更详细的信息。

警告

不要认为 CommandElement 只会有一个返回值,事实上他们当中很多都支持多重返回值,甚至有些还支持正则表达式或命令选择器。这样做是为了让命令系统更容易使用。例子:/tell @a BanditPlayer has the cookies!。如果你希望某个参数永远只有一个返回值,请使用 GenericArguments#onlyOne(CommandElement)

自定义命令元素

可以通过继承 CommandElement 类创建自定义命令元素(如解析 URL 或者一个 Vector2i 元素)。

CommandElement#parseValue(CommandSource, CommandArgs) 方法需要通过 CommandArgs#next() 方法获取没有处理过的字符串并将其转换为一个对象(Object),如果转换出错,应抛出一个 ArgumentParseException 异常。

CommandElement#complete(CommandSource, CommandArgs, CommandContext) 方法应使用 CommandArgs#peek() 方法读取下一个未处理过的参数。其返回一个 TAB 自动补全的列表。

示例: Vector2i 命令元素的定义

在这一示例中,解析器读取两个传入的参数并将其转换为一个 Vector2i

import com.flowpowered.math.vector.Vector2i;
import org.spongepowered.api.command.args.ArgumentParseException;
import org.spongepowered.api.command.args.CommandArgs;
import org.spongepowered.api.text.Text;
import org.spongepowered.api.command.args.CommandElement;

import java.util.Collections;
import java.util.List;

public class Vector2iCommandElement extends CommandElement {

    protected Vector2iCommandElement(Text key) {
        super(key);
    }

    @Override
    protected Object parseValue(CommandSource source, CommandArgs args) throws ArgumentParseException {

        String xInput = args.next();
        int x = parseInt(xInput, args);

        String yInput = args.next();
        int y = parseInt(yInput, args);

        return new Vector2i(x, y);
    }

    private int parseInt(String input, CommandArgs args) throws ArgumentParseException {
        try {
            return Integer.parseInt(input);
        } catch(NumberFormatException e) {
            throw args.createError(Text.of("'" + input + "' is not a valid number!"));
        }
    }

    @Override
    public List<String> complete(CommandSource src, CommandArgs args, CommandContext context) {
        return Collections.emptyList();
    }

    @Override
    public Text getUsage(CommandSource src) {
        return Text.of("<x> <y>");
    }
}

示例: Vector2i 命令元素的用法

// /plottp <x> <y>
CommandSpec myCommandSpec = CommandSpec.builder()
        .description(Text.of("Teleport to a plot"))
        .permission("myplugin.command.plot.tp")

        .arguments(new Vector2iCommandElement(Text.of("coordinates")))

        .executor(new MyCommandExecutor())
        .build();

小技巧

请参阅源代码以获取更多关于 GenericArguments 类的用法示例。

在自定义命令元素中使用选择器

Sponge 提供了解析选择器的支持,所以你可以在自定义元素中使用选择器。使用选择器需要走两步:解析(从字符串中构建出 Selector)和解释(根据选择器获得一个包含所有被选中的 Entity 的集合)。

解析选择器字符串需用到 Selector#parse(String) 方法,为此需要传入整个选择器字符串(包含 @)。该方法会返回可用于查询和解释的 Selector 对象。注意,如果传入的字符串不是合法的选择器字符串,该方法会抛出一个 IllegalArgumentException 异常。

请使用 Selector#resolve(CommandSource)解释该选择器,它将返回一个包含有该选择器匹配中的所有 Entity 的集合。

下面的 CommandElement 类的 parseValue 方法会尝试解析一个选择器,并根据 CommandSource 的位置返回一个实体的集合。若传入的字符串开头不是 @,会抛出一个异常告知目标字符串不是选择器。

@Override
protected Object parseValue(CommandSource source, CommandArgs args) throws ArgumentParseException {
    String nextArg = args.next();
    if (nextArg.startsWith("@")) {
        Set<Entity> selectedEntities;
        try {
            selectedEntities = Selector.parse(nextArg).resolve(source);
        } catch (IllegalArgumentException e) {
            throw args.createError(Text.of("Could not parse selector."));
        }

        if (selectedEntities.isEmpty()) {
            throw args.createError(Text.of("No entities selected."));
        }

        return selectedEntities;
    }

    throw args.createError(Text.of("Not a selector."));
}

小技巧

关于标准 Sponge 中的 CommandElements 中选择器解析的具体过程,可参阅 SelectorCommandElement 类的源码