宠物行为模块
宠物行为模块是一个告诉宠物该做什么的 Lua 文件:跟随主人、漂移、绕着营火打转、蜇刺。每个宠物物种文件会列出其宠物运行的模块,并为它们提供设置。本页面向想要修改某个模块或编写新模块的生物作者。若要使用和调整自带模块,参见宠物。
模块使用与 EliteMobs 行为程序相同的 Lua 方言:ai.module、ai.behavior、ai.sensor、ai.controls、带类型的记忆,以及 c.memory、c.perception、c.actuator 和 c.entity 上下文。那一页涵盖共享的规则。本页介绍宠物增加的内容:模块文件、c.pet、c.settings,以及宠物运行时受到的限制。
文件
- 模块位于
plugins/FreeMinecraftModels/pet_behaviors/,可在该文件夹或其任意子文件夹中。那里的每个.lua文件都是一个模块。 - 模块的名称是其去掉
.lua的文件名,使用小写。只能使用字母、数字、.、_和-。宠物文件通过这个名称列出模块。 - 模块的
id必须是fmm:pet/加上它的名称,所以pet_behaviors/mine/slow_drift.lua声明id = 'fmm:pet/slow_drift'。id 与文件名不匹配的模块会被跳过并给出警告。 - 当两个文件同名时,路径排序靠前的那个会加载,控制台会针对另一个给出
Duplicate content filename警告。字节完全相同的副本会被静默跳过。 - 链接到
pet_behaviors/之外的文件或文件夹会被跳过。 - 对于自带的 15 个模块,只要文件夹中任何位置都没有同名文件,FreeMinecraftModels 就会写入该模块。被删除的模块会在下次加载时以当前自带版本回来。已经存在的文件永远不会被覆盖,无论是否编辑过,即使你把它移到子文件夹中也是如此,因此它保留你的更改,同时也错过自带模块之后的更新。
/fmm reload会重新读取每个模块和宠物文件,把宠物收起,再让它们用新模块回来。它们的记忆从空开始。编辑模块后无需提高其revision。
从一个副本开始
自带模块是最好的示例:drift.lua 移动飞行者,wander.lua 行走,night_glow.lua 是单个传感器,sting.lua 有两个。请从最接近你想要的那个开始。
- 用新名称复制它,例如从
drift.lua复制出pet_behaviors/mine/slow_drift.lua。 - 把它的
id改为fmm:pet/slow_drift。 - 重命名它的记忆、行为和传感器,使它们以新名称开头:
drift_to变成slow_drift_to,在memories中和每一个c.memory调用中都要改,行为的id = 'fmm:pet/drift'变成fmm:pet/slow_drift。同一个生物上的两个模块不能共用记忆名称、行为 id 或传感器 id;列表中靠后的那个会被排除并给出警告。重命名可以让副本与原件并排运行。 - 在宠物文件中用它替换原件:
- slow_drift,或带设置:- slow_drift: { speed: 0.03 }。 - 运行
/fmm reload并检查控制台。
直接就地编辑自带模块也可以,但这会改变列出它的每个物种。
模块文件
这个模块很小,但很完整。夜间,宠物不再漂移,而是停在其锚点附近的一个位置上,并且从选定该位置的那一刻起,每 10 秒播放一次动画。
-- At night: settles on one spot near its anchor (its owner, or home when wild) and plays an animation now and then.
return ai.module {
id = 'fmm:pet/night_rest', revision = 1,
-- radius: blocks around the anchor. speed: blocks per tick for a flyer, at species speed 1.
settings = { radius = 3, speed = 0.04, animation = 'interact' },
memories = { night_rest_to = 'position', night_rest_retry = 'integer' },
behaviors = {
ai.behavior {
id = 'fmm:pet/night_rest', priority = 12, probe_interval = 10,
controls = { ai.controls.move, ai.controls.look },
can_start = function(c)
return c.pet.night and not c.pet.staying and c.pet.anchor ~= nil
and c.tick >= (c.memory:get('night_rest_retry') or 0)
end,
can_continue = function(c)
return c.pet.night and not c.pet.staying and c.memory:contains('night_rest_to')
end,
start = function(c)
local anchor = c.pet.anchor
local to = anchor and c.pet:rest_point(anchor, c.settings.radius)
if to ~= nil then
c.memory:set('night_rest_to', to)
else
-- Nothing fits here: look again in 5 seconds instead of every tick.
c.memory:set('night_rest_retry', c.tick + 100)
end
end,
tick = function(c)
local to = c.memory:get('night_rest_to')
if to ~= nil then c.pet:steer_to(to, c.settings.speed * c.pet.species.speed) end
end,
stop = function(c)
c.memory:forget('night_rest_to')
c.pet:stop()
end
}
},
sensors = {
ai.sensor {
id = 'fmm:pet/night_rest_animation', interval = 200,
sense = function(c)
if c.memory:contains('night_rest_to') then c.pet:play_animation(c.settings.animation) end
end
}
}
}
随后宠物文件把它列为 - night_rest,或 - night_rest: { radius: 5, animation: idle }。
| 字段 | 说明 |
|---|---|
id | 必填。fmm:pet/ 加文件名 |
revision | 必填。任何正整数 |
settings | 可选。宠物文件可以更改的默认值。每个默认值的类型就是宠物文件必须给出的类型:数字、true/false、文本,或文本列表。参见设置值 |
memories | 可选。模块为每只宠物记住的内容,按名称和类型声明:integer、number、boolean、string、uuid 或 position。请让每个名称都以模块的名称开头。参见记忆 |
dependencies | 可选。此模块所需的 pet_behaviors/ 中其他模块的 id。它们会在每个列出此模块的生物上运行,使用默认设置,除非宠物文件也列出了它们 |
behaviors | 可选。ai.behavior { ... } 条目,用于移动或转动宠物 |
sensors | 可选。ai.sensor { ... } 条目,从不移动宠物 |
允许在 return 之上编写代码,例如局部函数和常量;wander.lua 和 idle_look.lua 中就有一些。
回调能得到什么
| 名称 | 含义 |
|---|---|
c.pet | 宠物,只读。参见 c.pet |
c.settings | 此模块的设置:它的默认值,加上宠物文件和变体的值叠加其上,如 c.settings.radius。每只宠物有自己的副本,因此写入只会改变那只宠物 |
c.memory | 针对 memories 中名称的 get(name)、set(name, value)、forget(name) 和 contains(name)。每只宠物有自己的记忆 |
c.tick | 当前游戏 tick,用于计时器 |
c.perception、c.actuator、c.entity | MagmaCore 的 Mind 上下文,与 EliteMobs 行为程序中相同。c.entity 是隐藏的身体,而不是模型。参见 Mind 程序上下文。idle_look.lua 使用 c.perception:nearest_player(range) 和 c.actuator:look_at(point) |
c.pet
点是一个带有 x、y 和 z,并可选带有 world 的表,如 c.pet.position。没有 world 的点位于宠物所在的世界。位于另一个世界的点视为无:方法返回 nil、false 或 0,而 distance_to 返回 math.huge。c.pet 返回的点总是在宠物所在的世界中,本应在别处的点会读作 nil。
- 方法用冒号调用:
c.pet:distance_to(point)。 - 数字参数必须是有限数。半径、深度、高度、等级、持续时间和其他范围会按下文所列设置上限。没有任何方法会加载区块。
c.pet和c.pet.species是只读的。对它们赋值会抛出错误;请把每只宠物的值保存在c.memory中。- 属性和
route_target()是快照:每个宠物 tick 读取一次,并在该 tick 的所有回调中保持不变。c.pet:teleport_near_owner和c.pet:stop会刷新它们。以其他方式进行的移动(例如c.entity:teleport)会在下一个 tick 显示出来。speed是例外,始终是新读取的。 - 每次读取位置都会得到一个新的表,模块可以修改它。
属性
| 名称 | 类型 | 值 |
|---|---|---|
wild | bool | 野生宠物为 true,玩家的宠物为 false |
staying | bool | 被命令停留 |
settled | bool | 没有停留,并且是野生的或距主人在 follow.distance 以内 |
baby | bool | 是幼崽 |
migrating | bool | 正在飞行夜间迁徙路线 |
model | string | 其变体的模型。穿着 babyModel 的幼崽仍读取其变体 |
position | point | 其身体所在位置 |
home | point/nil | 野生宠物居住的地方;玩家的宠物为 nil |
anchor | point/nil | 它围绕游荡的对象:它的主人,或者对野生宠物而言是它的家,没有家时则是它所在的位置。主人离线或在另一个世界时为 nil |
stay_anchor | point/nil | 它被命令停留的位置;不处于停留时为 nil |
owner_location | point/nil | 野生宠物,或主人离线、死亡或在另一个世界时为 nil |
owner_distance | number | 距主人的方块数;本世界没有主人时为 math.huge |
night、day | bool | day 仅在主世界类型的世界白天为真;黄昏、黎明、下界和末地都算作 night |
raining | bool | 雨淋在宠物身上,最多每 10 个 tick 检查一次 |
speed | number | 其身体当前的速度,单位为方块每 tick |
route_cruise | number | 迁徙时其迁徙群的巡航速度,否则为 0 |
species | table | 宠物文件中的数值,见下文 |
c.pet.species 包含:
| 字段 | 宠物文件键 |
|---|---|
id | species |
speed | follow.speed |
hover_height | float.hoverHeight |
hover_variance | float.hoverVariance |
roam_radius | follow.roamRadius |
follow_distance | follow.distance |
leash_distance | follow.leashDistance |
teleport_distance | follow.teleportDistance |
scale | scale |
baby_scale | babyScale |
移动
只能在行为的 start、tick 或 stop 中调用这些方法,并且该行为的 controls 要列出它们所需的控制权。在 can_start 或传感器中调用时,steer_to 和 stop 会抛出一个说明它们应该在哪里调用的错误。
| 方法 | 返回值 | 作用 |
|---|---|---|
steer_to(point, cruise) | bool | 朝该点前进一个 tick,所以每个 tick 都要调用。需要 move 和 look。飞行者以每 tick cruise 个方块(0 到 4)飞行,临近时减速。行走者或游泳者以其身体类型移动速度的 4 × cruise 倍原生寻路前往那里,且绝不低于该移动速度的四分之一;cruise 为 0 时它会停下并返回 false。无法前往那里时返回 false |
stop() | nil | 停止移动。需要 move |
teleport_near_owner(radius) | bool | 把身体放到主人身边 radius 个方块以内(最多 64),在其自己的介质中;游泳者寻找水的范围至少为 6 个方块。行走者或游泳者会落在距主人至少一个方块处。当主人不在、在另一个世界、没有合适的位置,或该位置没有比宠物现在的位置至少近一个方块地靠近主人时,返回 false。它不需要任何控制权,但请从持有 move 的行为中调用,就像 follow_owner 那样,这样它就不会把宠物从另一个行为中拉走 |
位置与距离
这些方法在任何回调中都可使用。返回点的方法,在没有任何位置合适或区块未加载时返回 nil。半径最大为 64。
| 方法 | 返回值 | 作用 |
|---|---|---|
hover_point(center, radius) | point/nil | radius 范围内其悬停带中的一个点 |
rest_point(center, radius) | point/nil | 在其自身介质中休息的位置:飞行者是悬停带,行走者或固定身体是可站立的地面,游泳者是水(center 在水中时位于 center 的高度,否则位于水面下 0.5 到 2 个方块),两栖身体则是地面,没有地面时为水 |
ground_point(center, radius) | point/nil | 一个可站立的位置,范围从 center 下方约 5 个方块到上方 4 个方块。位于液体或长距离落差之上时为 nil |
water_point(center, radius, min_depth, max_depth) | point/nil | 水面下两个深度之间的游泳位置(0 到 16) |
ground_below(point, depth) | number/nil | 向下 depth 个方块(1 到 64)范围内地面顶部的高度。水面或岩浆表面算作地面,除非物种是 AQUATIC。范围内没有地面时,为该点的高度减去 depth |
trail_point(back, height) | point/nil | 主人所面朝方向身后 back 个方块处;FLYING 物种高出主人脚部 height,其他物种与主人脚部同高。主人不在或在另一个世界时为 nil |
distance_to(point) | number | 距其身体的方块数 |
route_target() | point/nil | 其迁徙路线在这个 tick 想让它去的位置。不在迁徙,或路线已结束时为 nil |
find_perch(height, radius) | point/nil | 位于它下方最近的树叶之上 height 个方块(0 到 8)处,向侧面最多 radius 个方块(0 到 4),或位于紧邻其下方的地面之上。玩家的宠物只会得到距主人 follow.distance 以内的栖息处 |
nearest_campfire(radius, keep_ticks, blocks, soul_variants, height) | point/nil | radius(1 到 32)范围内它可以使用的最近的点燃的火堆方块。blocks 列出它接受的火堆种类,CAMPFIRE、SOUL_CAMPFIRE 或两者,两者都没提到的列表则两者都接受;soul_variants 列出只接受灵魂营火的模型,此时其他所有模型只接受普通营火。玩家的宠物只会选择这样的火堆:其上方 height 处的位置距主人在 follow.distance 减 1 以内。每个区块的搜索结果保留 keep_ticks(20 到 1200),并由所有宠物共享 |
campfire_usable(fire, blocks, soul_variants, height) | bool | 之前找到的火堆是否仍然点燃并且仍然合适 |
效果
这些方法在任何回调中都可使用;传感器是常见的使用位置。
| 方法 | 返回值 | 作用 |
|---|---|---|
set_glow(light, lantern_bone, animation) | nil | 以 light(0 到 15)点亮模型的显示骨骼,仅限 Java 版玩家。带有名为 lantern_bone 的骨骼的模型还会循环播放 animation。后两个参数是可选的 |
clear_glow() | nil | 恢复为世界光照,并结束该动画 |
scale_toward(target, span, ticks, elapsed) | nil | 让身体大小平滑地趋向其基础大小的 target 倍。从 1 到 span 的完整变化需要 ticks(至少 2);elapsed 是自上次调用以来的 tick 数。倍数 0.1 到 16 |
sting_contacts(on, effect, duration, amplifier, damage, cooldown) | number | 蜇在最近一秒内触碰到野生宠物模型的玩家,从不蜇创造或旁观模式的玩家,并返回被蜇的数量。即使 on 为 false,它也会清除这些触碰记录。玩家的宠物不记录触碰 |
sting_enemies(reach, effect, duration, amplifier, damage, cooldown) | number | 蜇模型碰撞箱 reach 个方块(0 到 4)范围内的敌对生物,从不蜇玩家或宠物,并返回被蜇的数量 |
drizzle(depth, chance) | bool | 当宠物静止悬停在 depth 个方块(1 到 8)范围内的作物上方时,产生水粒子,并以 chance(0 到 1)的几率让该作物生长一个阶段。位于作物上方时返回 true |
owner_effect(effect, duration, amplifier, range) | bool | 当主人处于 range 个方块以内时,给予主人持续 duration 个 tick(1 到 6000)的药水效果,且不带粒子。主人已经拥有的、强度不低于它且持续时间不短于它的效果会被保留不动。主人不在、在另一个世界或超出范围,或者该效果不存在时返回 false |
play_animation(name) | bool | 播放模型的某个动画一次。模型没有它时返回 false |
对于这两种蜇,effect 是药水效果名称或 NONE,duration 以 tick 为单位(1 到 6000),amplifier 为 0 是 I 级(最高 255),damage 以半颗心为单位,cooldown 是这只宠物能再次蜇同一个受害者之前的 tick 数(至少 1)。蜇刺会显示火花并播放蜜蜂蜇刺的声音,成年宠物会播放它的 attack 动画。不存在的药水效果名称,会对每个物种和名称各警告一次,并且不施加任何效果。
行为与传感器
- 行为会移动或转动宠物。它列出自己所需的控制权,
c.pet的移动调用需要ai.controls.move和ai.controls.look,并且同一时间只有一个行为持有某项控制权。行为处于停止状态时,MagmaCore 会询问它的can_start。当它返回true且控制权空闲时,它就会取得控制权并运行start,然后在can_continue返回true期间每个 tick 运行tick(没有can_continue时由can_start决定),最后运行stop。参见生命周期。 priority数字较小的先行。行为会从数字较大的、正在运行的行为手中夺取控制权,后者的stop随后运行。示例中的12使它排在跟随和停留之后,位于漂移和悬停之前,但也在campfire之前,所以同时列出两者的宠物在夜间永远不会去看营火。大于15的数字会让营火胜出。- 行为只争夺它列出的控制权。
idle_look只取look,所以它在没有行为持有视线时运行;每个自带的移动行为也都取look,所以移动总是优先于它。 - 宠物文件无法更改优先级。若要重新排序,请复制该模块并更改数字。
probe_interval是可选的,以 tick 为单位:行为处于停止状态时,只每隔那么多个 tick 询问一次can_start,且每只宠物有各自的相位。当场上宠物很多时,它能节省服务器时间,代价是行为最多晚那么多个 tick 启动。它默认为1,大于200时按200计。正在运行的行为仍然每个 tick 运行。- 传感器从不移动宠物。无论行为在做什么,它都会在宠物的身体第一次被 tick 的那个 tick 运行
sense,然后在宠物自己的相位上每隔interval个 tick 运行一次。发光、膨胀、蜇刺、细雨和主人效果都是传感器。
自带模块使用:
| 模块 | 优先级 | probe_interval | 传感器 interval |
|---|---|---|---|
migrate | 1 | 10 | |
stay | 5 | 4 | |
follow_owner | 10 | 4 | |
campfire | 15 | 10 | |
day_perch | 18 | 10 | |
rain_bloom | 19 | 10 | 4(膨胀) |
drift、wander、swim | 20 | 10 | |
hover | 30 | 4 | |
idle_look | 30 | 10 | |
night_glow | 40 | ||
sting | 4(玩家),10(生物) | ||
crop_drizzle | 20 | ||
owner_effect | 20 |
规则
- 只能在行为的
start、tick或stop中调用steer_to和stop,并且该行为要列出它们所需的控制权。 - 把每只宠物的状态保存在
c.memory中,在memories下声明,名称以模块的名称开头。 - 不要写入
c.pet;对它赋值会抛出错误。 - 保持回调简短。每只宠物都在这些固定限制下运行,模块和宠物文件都无法更改:
| 限制 | 值 |
|---|---|
| 单个回调 | 1.5 毫秒 |
| 一只宠物在一个 tick 内的回调 | 3 毫秒,最多 32 个回调 |
| 所有宠物在一个 tick 内的回调 | 10 毫秒 |
| 单个回调的 CPU 时间,硬性限制 | 20 毫秒 |
| 单个回调的 Lua 指令数,硬性限制 | 20,000 |
当宠物达到前三个限制之一时,它剩余的回调会等到下一个 tick。超过硬性限制的回调会被停止,并像错误一样失败。参见预算。
出问题时
这里的任何问题都不会使服务器或其他生物停止。在控制台中搜索 pet behavior、pet_behaviors 和 .pet.yml,可以找到这些警告。
- 模块无法加载。 无法编译、id 与文件名不匹配,或文件名含有其他字符的模块,会被跳过并给出指明文件的警告,对于 Lua 错误还会给出行号。其他所有模块仍会加载。
- 宠物文件指定了一个不存在的模块。 警告会指出宠物文件,并且在某个模块只差一两个字母时建议最接近的模块名称。该生物仍会加载,只是没有那个模块。当模块存在但加载失败时,警告会指向有关其文件的那条警告。
- 某个设置有误。 模块没有的设置,或类型错误的值,会连同宠物文件的名称一起报告,并且保留默认值。参见设置值。
- 两个模块冲突。 当一个生物上的两个模块声明了相同的记忆名称、行为 id 或传感器 id 时,该生物保留列表中靠前的那个,并在没有另一个的情况下运行,同时给出警告。
- 宠物运行期间回调失败。 在下次重载之前,每个程序、回调和失败类型只记录一次。程序被命名为
fmm:pet/creature/<species>/<variant>,回调以其行为或传感器 id 命名,例如fmm:pet/drift.tick。失败的行为会被停止或不启动,并从下一个 tick 起重试;在它持续失败期间,优先级数字更大的、移动或转动宠物的行为可能不会运行。失败的传感器会在下一个间隔再次运行。 c.pet调用在 FreeMinecraftModels 内部失败。 原因会连同堆栈跟踪,对每个物种和调用只记录一次,并且回调以c.pet:<method> failed; the server log has the cause失败。- 药水效果名称不存在。 对每个物种和名称只警告一次,并且该调用不施加任何效果。
修复之后,运行 /fmm reload。