celestriode / constructure-target-selector
针对目标选择结构体的Constructure实现。
Requires
- php: ^8.0
- ext-mbstring: *
- celestriode/captain: v0.0.10-beta
- celestriode/constructure: ^0.6.5
- celestriode/dynamic-minecraft-registries: ^2.0
- celestriode/mattock: v0.1.0-beta
Requires (Dev)
- phpunit/phpunit: ^9.5
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)