-
Notifications
You must be signed in to change notification settings - Fork 2
Checking if a player is holding the right tool
这个功能在很多地方都会用到。比如你打算更改一个工具的用途,或者是让玩家在手持一个工具时执行某个功能,那么你就需要检查他是不是拿着对应的那种工具。
简单起见,我们假设这种工具是锄,因为它的用途单一,在原版游戏中和大多数方块都不能交互。我们的目标是实现一个 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 ~ feature
是 query(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()
返回的三个值对应赋给 what
,count
和 nbt
三个变量,我们刚刚关心的物体 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
函数的例子,所以就不写得太仔细咯;)
本篇由 @茨月 翻译。