celestriode/constructure-target-selector

针对目标选择结构体的Constructure实现。

v2.0.3 2022-01-13 11:00 UTC

This package is auto-updated.

Last update: 2024-09-13 16:50:20 UTC


README

这是Minecraft目标选择格式的Constructure实现。

Minecraft中的目标选择格式定义不明确,甚至依赖于输入本身。这使得很难定义一个通用的结构。因此,解析输入和定义预期结构比预期的要复杂。

入门

不是从constructure对象开始,而是需要从目标选择解析器本身开始。可以自定义解析器的选项,并且你会发现更改解析器的选项对于准确解析Minecraft特定的输入是必要的。

$parser = new TargetSelectorParser();

解析器

目标选择的一般和默认格式为

# @a[name1=value1,name2=!value2]

TARGETER:               @
TARGET:                 a
DELIMITER_OPEN:         [
parameter name (1):     name1
DESIGNATOR:             =
NEGATOR:                !
parameter value (1):    value1
SEPARATOR:              ,
parameter name (2):     name2
DESIGNATOR:             =
NEGATOR:                !
parameter value (2):    value2
DELIMITER_CLOSE:        ]

标记

所有标记都可以直接修改,除了目标。以下重复设置标记为默认值,但可以根据特定解析器实例的必要进行更改。

$parser->targeter = '@';
$parser->delimiterOpen = '[';
$parser->delimiterClose = ']';
$parser->nestedDelimiterOpen = '{';
$parser->nestedDelimiterClose = '}';
$parser->designator = '=';
$parser->separator = ',';
$parser->negator = '!';

目标类型(p, e, a, r, s)

目标将使用来自Dynamic Minecraft Registries存储库中的SelectorTargets动态注册表中的值。请注意,所有这些存储库默认为空,以便您可以根据Minecraft的版本和版本有特定的值。

如果您不需要动态填充,可以直接将标准目标类型添加到注册表中。必须在验证之前完成此操作。如果您不是通过constructure进行验证输入,则不需要此操作。

SelectorTargets::get()->addValues('p', 'e', 'a', 'r', 's');

参数覆盖

解析器将尝试根据不明确的选择格式,在没有上下文的情况下自动确定输入值的类型。因此,它可能会做出错误的假设。

为了解决解析器缺少的情况,可以强制特定名称的参数遵守特定类型。addOverride()方法接受参数名称(接受点分语法以针对嵌套参数,如root.child),以及一个将返回参数值的函数。

例如,如果输入中包含nbt={},解析器将默认使用“嵌套参数”类型而不是“SNBT”类型,这是不正确的。可以为这种情况创建一个覆盖,以确保始终强制为SNBT值。

以下覆盖对于Java版1.17很有用。

$parser->addOverride('nbt', function(TargetSelectorParser $parser, StringReader $reader, Parameter $parameter) {

    return $parser::parseValueAsSnbt($parser, $reader, $parameter);
});

$parser->addOverride('type', function(TargetSelectorParser $parser, StringReader $reader, Parameter $parameter) {

    return $parser::parseValueAsResourceLocation($parser, $reader, $parameter);
});

Constructure

现在,解析器已经就绪,可以创建constructure对象。

$constructure = new TargetSelectorConstructure($parser, new EventHandler());

全局审计

默认情况下,不执行类型匹配。将类型匹配用作全局审计可能很有用,但请注意,全局审计适用于树中的所有 constructure结构。要针对其类型的目标值,可以使用StructureIsValue审计作为TypesMatch审计的谓词。

同时,默认情况下不会执行对值有效否定性的检查,而是依赖于Negation审计。与TypesMatch一样,您希望使用StructureIsValue作为谓词,以确保仅针对值,而不是树中的其他结构。

$constructure->addGlobalAudit(TypesMatch::get()->addPredicate(StructureIsValue::get()));
$constructure->addGlobalAudit(Negatable::get()->addPredicate(StructureIsValue::get()));

解析

现在可以解析输入以生成可遍历的树。

$raw = "@e[tag=test]";

$input = $constructure->toStructure($raw);

如果无法解析输入,将抛出ConversionFailure

验证

如果需要验证输入,则需要一个预期的结构。这必须从一个根 Selector 对象开始。根对象将包含在验证上下文中支持的各种目标选择器。

$selector = Selector::root();

库已经提供了一系列目标选择器类型。它们包括:

  • PlayerSelector (Selector::name()) - 根据玩家名称选择目标。
  • UuidSelector (Selector::uuid()) - 根据UUID选择目标。UUID的格式宽松(例如,“1-2-3-4-5”是有效的),因为这是一个Minecraft特有的结构。
  • DynamicSelector (Selector::dynamic()) - 以TARGETER标记(@a[tag=test])开始的复杂选择器。

这些选择器在需要时可以添加到根对象中。结构的几乎每个部分都可以附加一个审计。以下要求玩家名称长度在1到16个字符之间。

$selector->addAcceptedSelector(Selector::uuid());
$selector->addAcceptedSelector(
    Selector::name()->addAudit(new StringLength(new MinMaxBounds(1, 16)))
);

动态选择器有许多构建选项。首先创建构建器。它接受一个可接受的目标类型注册表,您之前已经填充了它。

$dynamicSelector = Selector::dynamic(SelectorTargets::get());

然后添加各种可接受参数。添加值时,需要提供参数名称和期望的值类型。值也有几个选项,它们独立于审计进行验证。选项包括 negatable()(例如,tag=!test)和 supportsMultiple()(例如,tag=a,tag=b)。

如果参数名称是 NULL,则输入可以具有任何名称,只要值匹配即可。

$dynamicSelector->getParameters()->addValue('tag', Selector::string()->negatable()->supportMultiple());
$dynamicSelector->getParameters()->addValue('scores', Selector::nested()
    ->addValue(null, Selector::string()->addAudit(Numeric::get()))
);

$selector->addAcceptedSelector($dynamicSelector);

最后,运行验证器。

$result = $constructure->validate($input, $selector);

整合所有内容

$raw = "@e[tag=test]";

// Prepare the parser.

$parser = new TargetSelectorParser();

$parser->addOverride('nbt', TargetSelectorParser::forceSnbt());
$parser->addOverride('type', TargetSelectorParser::forceValueUntil(
    $parser->separator,
    $parser->delimiterClose,
    $parser->nestedDelimiterClose
));

// Prepare the input.

$constructure = new TargetSelectorConstructure($parser, new EventHandler());
$constructure->addGlobalAudit(TypesMatch::get()->addPredicate(StructureIsValue::get()));
$constructure->addGlobalAudit(Negatable::get()->addPredicate(StructureIsValue::get()));

$input = $constructure->toStructure($raw);

// Prepare the validator.

SelectorTargets::get()->addValues('p', 'e', 'a', 'r', 's');

$expected = Selector::root(Selector::name());
$dynamicSelector = Selector::dynamic(SelectorTargets::get());

$dynamicSelector->getParameters()
    ->addValue('tag', Selector::string()->negatable())
    ->addValue('x', Selector::string())
    ->addValue('something', Selector::string()->addAudit(Numeric::get()));

$expected->addAcceptedSelector($dynamicSelector);

// Validate.

$result = $constructure->validate($input, $expected);

var_dump($result);

以下输入将产生所述结果

$a = 'Bob';                         // true
$b = '1-2-3-4-5';                   // false (UUID not allowed)
$c = '1g-2-3-4-5';                  // false (UUID not allowed)
$d = '@a';                          // true
$e = '@a[]';                        // true
$f = '@a[tag=test]';                // true
$g = '@a[tag=!test]';               // true 
$h = '@a[blargh=test]';             // false ("blargh" is not a valid key)
$i = '@a[x=!test]';                 // false ("x" is not negatable)
$j = '@a[tag=test,something=4]';    // true
$k = '@a[tag=test,something=test]'; // false ("something" must be numeric)