Checking if a player is holding the right tool - HeartyYF/fabric-carpet-Wiki-CN GitHub Wiki

检查玩家是否拿了正确的工具

为什么需要这个功能?

这个功能在很多地方都会用到。比如你打算更改一个工具的用途,或者是让玩家在手持一个工具时执行某个功能,那么你就需要检查他是不是拿着对应的那种工具。

这是怎么做到的?

简单起见,我们假设这种工具是锄,因为它的用途单一,在原版游戏中和大多数方块都不能交互。我们的目标是实现一个 private 函数 __holding_right_tool(player, tool),以检查玩家 player 是不是在主手拿着正确的工具 tool。首先来看一下实现它的原理:查阅 Scarpet API 文档可以发现,我们可以通过 query(entity, 'holds', slot?) 函数来获取与玩家手持的物体相关的信息,这个函数返回一个包含3个元素的列表 [id, Count, nbt]。在这里我们既不关心玩家手里拿了几个锄头(反正最多就一个),也不关心锄的 nbt,所以我们只看列表的第一个元素 id。例如,要查询玩家是否在主手拿着一把钻石锄,我们可以用 query(player, 'holds', 'mainhand'):0 == 'diamond_hoe' 实现:query(player, 'holds', 'mainhand') 返回 3 元列表而 :0 取出列表索引为 0 的(也就是第一个)元素,即我们关心的 id,然后用 == 'diamond_hoe' 就可以检查这个物体的 id 是不是 'diamond_hoe'

但实际上我们并不关心钻石锄,我们关心的是玩家是不是手持一把任何类型的锄。实现这一功能有很多种方法,最显而易见的一个就是检查所有的锄,看看有没有相等的:

__holding_hoe(player) -> (
    item_in_main_hand = query(player, 'holds', 'mainhand'):0;
    item_in_main_hand == 'wooden_hoe'
 || item_in_main_hand == 'stone_hoe'
 || item_in_main_hand == 'iron_hoe'
 || item_in_main_hand == 'gold_hoe'
 || item_in_main_hand == 'diamond_hoe'
 || item_in_main_hand == 'netherite_hoe'
)

这个方法管用,但是太笨、太丑。幸运的是 Scarpet 支持正则表达式匹配(如果你不知道什么是正则表达式,网上有很多教程可供参考),可以简化在 ID 中查找某个字符串的过程。在这个例子中,所有锄的 id 中都包含 _hoe ,所以我们只要用 运算符就可以在 id 中查找这个子串:

__holding_hoe(player) -> (
    item_in_main_hand = query(player, 'holds', 'mainhand'):0;
    item_in_main_hand ~ '_hoe'
)

现在看起来好多了,而且依旧有效。接下来我们可以进一步简化这个过程。根据文档entity ~ featurequery(entity, feature) 的别名,因为如果没有指定可选参数 slot?,它默认就是 mainhand,因此 query(player, 'holds', 'mainhand') <=>query(player, 'holds')<=>player ~ 'holds',要获得玩家主手中的物体,只需要写 (player ~ 'holds'):0 即可。最终版本的函数长这个样子:

__holding_hoe(player) -> (player ~ 'holds'):0 ~ '_hoe'

注意:虽然这里运算符出现了两次,但是两次有不同的含义。文档中给出了更详细的解释。

来到最后一步,我们不再只看是不是手持锄,而是任何一种指定的工具。如果要检查玩家是否手持一把镐,需要用到 (player ~ 'holds'):0 ~ '_pickaxe',因此只要修改用于匹配的正则表达式。最简单的方式就是把这个正则表达式作为参数传递,比如下面这样:

__holding_right_tool(player, tool) -> (player ~ 'holds'):0 ~ tool

看起来不错!现在如果我们要检查 gnembon 是不是主手拿着一把铲子,我们只需要写 __holding_right_tool(player('gnembon'), '_shovel'),这个函数会帮我们完成任务。如果你不想把它写在 scarpet 脚本(.sc 文件)里,而是直接在游戏中执行,首先你需要 /script run __holding_right_tool(player, tool) -> (player ~ 'holds'):0 ~ tool 来定义这个函数,然后用 /script run __holding_right_tool(player('gnembon'), '_pickaxe') 来执行它。

它有什么用处呢?

Scarpet repo 里有很多用简单函数实现复杂功能的例子,看一看总是没有坏处的。一个和我们刚刚的 __holding_right_tool(player, tool) 很相似的例子是神圣手榴弹脚本(译注:出处为《巨蟒与圣杯》,非常好笑)中的 __holds(entity, item_regex, enchantment),不同之处是这个函数不仅匹配给定的正则表达式,还会检查这个物体的附魔:

__holds(entity, item_regex, enchantment) -> 
(
    if (entity~'gamemode_id'==3, return(0));
    for(l('mainhand','offhand'),
        holds = query(entity, 'holds', _);
        if( holds,
            l(what, count, nbt) = holds;
            if ((what ~ item_regex) && (enchs = nbt:'Enchantments[]'),
                if (type(enchs)!='list', enchs = l(enchs));
                for (enchs, 
                    if ( _:'id' == 'minecraft:'+enchantment,
                        lvl = max(lvl, _:'lvl')
                    )
                )	
            )
        )
    );
    lvl
);

在上面这段代码中,holds = query(entity, 'holds', _) 会检查两只手(不像我们的函数只检查主手),l(what, count, nbt)=holds 则将 query() 返回的三个值对应赋给 whatcountnbt 三个变量,我们刚刚关心的物体 id 在这个函数中就存放在 what 里。从开头到 if (what ~ item_regex) 的部分实现了我们刚刚那个函数的所有功能,后面的代码都是与物体附魔相关的了。不像刚刚我们返回一个 bool 来指示正则表达式能否匹配,这个函数在满足匹配条件的时候会直接执行下文的代码,它获取物体已有的最大附魔等级以供后面的代码使用。在神圣手榴弹脚本中,每当玩家触发 __on_player_uses_item(player, item_tuple, hand) 事件的时候,都会调用上文的函数来检查玩家是不是拿了正确的物体。

__on_player_uses_item(player, item_tuple, hand) -> 
(
    if (hand != 'mainhand', return());
    power_level = __holds(player, 'fire_charge', 'power');
    if (power_level == 0, return());
    __deploy_missile(player, power_level)
);

在这个例子中,当事件被触发时,我们用 __holds 来检查玩家手持的物品,如果物体的 id 能匹配正则表达式 'fire_charge'(事实上只有火焰弹可以匹配上,也就意味着玩家手里拿着火焰弹),__holds 函数会进一步获取火焰弹拥有的力量附魔的最高等级 power_level。等级越高,爆炸威力越大。随后 __on_player_uses_item 函数会调用 __deploy_missile 函数把手榴弹丢出去,这个函数接受 power_level 作为参数,决定投掷物与方块碰撞时的爆炸半径大小。

还有别的例子吗?

当然有。仿照某些 mod 中的“细化”功能(比如把圆石变成砂砾、砂砾变成沙子),我们给锄设计一个功能,让它作用于某个方块的时候把方块“细化”。

__holding_right_tool(player, tool) -> (player ~ 'holds'):0 ~ tool;

__on_player_right_clicks_block(player, item_tuple, hand, block, face, hitvec) -> (
    if (!__holding_right_tool(player, '_hoe'), return());
    if (block(block) == block('cobblestone'),
        set(pos(block), block('gravel'))
    ,   block(block) == block('gravel'),
        set(pos(block), block('sand'))
    )
)

这个实现非常粗糙,我们甚至没有让锄的耐久度减少。不过这只是一个展示 __holding_right_tool 函数的例子,所以就不写得太仔细咯;)

本篇由 @茨月 翻译。