formation_action_type(阵法功能)
formation_action_type
阵法 的 actions 数组存放这些功能模块。每一项在 type 里写一个模块 ID 来选中它,并且只带自己需要的字段——结构、半径、成本、spare_friends 留在阵法顶层。数据包只能选用已有模块,不能新增;写了未知的 type 会让定义加载失败,不会退化成什么都不做。
同一个模块可以出现多次,列表不会合并:两段参数不同的攻击模块就是两段攻击。执行顺序就是列表顺序,没有第二种次序。
四个节拍
每个模块都会被问四种节拍,响应哪一个由它自己决定(不响应就是不写那个方法):
| 节拍 | 阵法自身 | 逐实体 |
|---|---|---|
activate | 立阵成功、写完索引之后一次 | 实体进入半径时(含立阵后第一拍就已经在里面的) |
tick | 每个维护周期一次 | 每个被覆盖的实体每维护周期一次 |
interval | 同上,但只在该模块的 interval_ticks 到期的那一拍 | 同上 |
inactivate | 阵法被拆除时一次 | 实体离开半径、或阵法被拆除时 |
tick 与 interval 的范围完全一样,区别只在节流:按维护周期跑就写 mxt:tick,要另一个节奏就写自带 interval_ticks 的模块。两条边都要自己认:只作用于实体的模块(mxt:attack / mxt:buff / mxt:send_message)在"阵法自身"那一趟什么都不做,只作用于阵法自身的模块(mxt:particle)在逐实体那一趟什么都不做。
interval_ticks 是全部四个会节流的模块共有的字段(attack / buff / send_message / particle),单位是 tick:必须是 10 的倍数(写别的数整个定义加载失败),最小 10、最大 24000。阵法每 10 tick 一拍,而维护费、mxt:tick 与实体进出的判定按 20 tick 的维护周期算——显示器可以比账单跑得细,这个字段就是为此存在的。相位取自世界时钟而不是模块自己的计数器,所以同一座阵法里的每个实体、以及同一份定义的多座阵法,都在同一拍一起跑——把间隔调大不会让一座阵法里的几个人轮流收到消息。
上下文
"阵法自身"的那几趟只有 Level 上下文,公式里只有 zero / random;逐实体那几趟按实体求值,并额外带 formation_radius / distance / formation_x / formation_y / formation_z,还能读到阵法携带者(mxt:formation_owner 与 mxt:formation_ally 消费它)。
{
"actions": [
{"type": "mxt:protection", "delegate_to_claims": true},
{"type": "mxt:aura", "aura_zone": "example:fireland"},
{"type": "mxt:particle", "shape": "mxt:five"}
]
}mxt:empty
空模块,什么都不做。它同时是这张分派表的默认项,写出来等价于不写。
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
| — | — | — | 没有字段,也不响应任何节拍。 |
{"type": "mxt:empty"}mxt:attack
攻击半径内的实体。
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
damage | NumberProvider | 0 | 每周期对每个受影响实体施加的伤害 |
damage_type | 伤害类型 id | 无 | 命中使用的伤害类型;不写就走原版「阵主打了他」的语义 |
attribute_to_owner | Boolean | true | 把第一位阵主(名单首位,即当初立阵的那位)记成加害者 |
effects | 数组 | [] | 每周期施加的状态效果,字段与 mxt:apply_effect 完全一致:effect / duration_ticks / amplifier |
target_condition | EntityCondition | 恒真 | 对目标实体的额外筛选(只打亡灵、只打玩家之类),在敌我判断之后求值 |
interval_ticks | Integer,10 的倍数 10..24000 | 20 | 每多少 tick 打一次(20 = 一个维护周期) |
{"type": "mxt:attack", "damage": 8, "damage_type": "minecraft:lightning_bolt"}这个模块只说明「打什么」,不说明「打谁」:它打的是阵法覆盖到的每一个实体,要不要放过阵主与好友由阵法顶层的 spare_friends 决定——不写就是连阵主一起打。认不出敌友时,声明了 spare_friends 的阵法停火(理由见阵法的「敌我判断」)。
attribute_to_owner 决定击杀归属、怪物仇恨,以及所有读取攻击者的条件;设成 false 才是无主的「环境伤害」。
每次伤害都过统一伤害管线:阵主(attribute_to_owner 为真时)作为攻击者参与第一层的元素克制,受击者按自己的元素适应做第二层减免——阵主是火灵根、目标是水灵根时打得更疼,目标适应火则扣得更少。这些倍率写在 mxt:element 定义里,不在这里。
伤害求值为非有限或 ≤ 0 时这一下不打,但 effects 照常施加——默认的 damage: 0 就是一座只上状态、不造成伤害的阵法。
mxt:buff
授予半径内的实体技能。
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
abilities | 技能 id 或 #标签的列表 | [] | 授予匹配实体的技能 |
target | Enum | all | 模块作用于哪些实体:all、allies 或 owner |
interval_ticks | Integer,10 的倍数 10..24000 | 20 | 每多少 tick 重新授予一次 |
{"type": "mxt:buff", "abilities": ["example:blessing"], "target": "allies"}授予的技能在实体离开半径、阵法拆除或不再被选中时自动撤销;source 自动取 mxt:formation/<命名空间>/<路径>。
三档 target 是同一条规则的不同宽度:all 不筛;allies 要好友判定给出 true——判定会问名单上每一位阵主,任一位认得你就算,阵主在好友判定里算自己的好友,所以 allies 包含整份名单;认不出就不给。owner 只认 UUID 名单,是同样效果的更窄写法。
属性加成不在这里找字段:被动属性是 mxt:modifier 技能自己的 modifiers 字段,授予一条 mxt:modifier 技能就等于授予它的属性修饰符;没有第二个入口,否则同一件事会有两套规则——包括「修饰符活过了授予它的那座阵法」这类最容易出错的部分。
阵地的灵气覆写也不在这里,那是独立的 mxt:aura。
interval_ticks 只让重新授予变稀疏,不影响撤销:授予本身是幂等的,已发出的技能在跳过的那些拍里照旧生效。离开半径与阵法拆除这两条撤销路径不看这个字段,立刻收回;只有「站在原地、但阵主忽然不再把你当好友」这一条要等到下一个到期的拍才收回,因为它是在重新授予那一趟里顺带判定的。
mxt:aura
替换阵地所在位置的灵气解析结果,并抬高它的上限。不响应任何节拍——它是一份声明,灵气查询自己来读。
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
aura_zone | 灵气区域 id | 必填 | 高优先级的运行时灵气覆写:替换阵地所在位置的灵气解析结果 |
max_bonus | 灵气 id 到 NumberProvider 的映射 | {} | 覆盖后选中的那个区域每种灵气的额外容量上限 |
upkeep_weight | NumberProvider | 1 | 阵法参与同区块灵气分配的权重 |
{"type": "mxt:aura", "aura_zone": "example:fireland", "max_bonus": {"mxt:common": 50}, "upkeep_weight": 3}aura_zone 的优先级高于群系 / 维度 / 人工区域,覆盖只在阵法在册期间生效;完整口径见阵法的「灵气覆写」。
max_bonus 只随着覆盖一起应用:没有覆盖就没有地方加,该灵气区域不提供的灵气也加不上(不会凭空创造灵气)。多条加成落在同一种灵气上时取最高。
upkeep_weight 在阵法和玩家抢同一份区块灵气时生效:阵法把自己覆盖范围内、每个区块的发射方块算作一次请求,与该区块里修炼的人按 aura_zone.distribution 分同一个池,权重就是它这一份。分到的量用来抵扣维护费,不够的部分才由阵法储备与主人补。realm_weighted 下它和玩家的权重同列比较。多个 mxt:aura 模块时取最大的那个。
aura_zone 因此是必填的:只写 max_bonus 的模块永远没有作用对象,会被当成写错直接拒收。
mxt:protection
拒绝半径内列出的各类干扰。
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
block_break | Boolean | true | 拦破坏 |
block_place | Boolean | true | 拦放置 |
block_interact | Boolean | true | 拦右键方块(箱子、门、工作台……),也拦「拿着物品对方块使用」 |
explosions | Boolean | true | 拦爆炸改地形;只剔掉方块,实体照样受伤 |
mob_griefing | Boolean | true | 拦生物破坏(末影人搬方块这类) |
entity_interact | Boolean | true | 拦右键实体(村民、马、盔甲架……) |
attack_entity | Boolean | true | 拦近战攻击实体 |
item_use | Boolean | true | 拦使用物品(对着空气或自己用桶、药水、拉弓……) |
spare_friends | Boolean | true | 阵主的好友豁免 |
delegate_to_claims | Boolean | false | 把守御交给领地插件(例如 FTB Chunks):在有生效的领地保护时,上面九个开关只作声明、不再执行 |
{"type": "mxt:protection", "spare_friends": true}九个开关全部默认 true:声明这个模块本身就是那句完整的话,只想放开某一项就显式写 false。其中三个的代价要大一些,不需要时值得关掉:block_interact 覆盖所有带界面的方块;explosions 与 mob_griefing 在爆炸逐方块结算、生物每次破坏这类高频路径上都要扫一遍在册阵法。
判定:动作的两端都算
只问一句:行为者,或者被作用的方块 / 实体,任意一个落在半径内,这条开关就生效。
- 站在阵外点阵内的箱子 → 拦(箱子在阵里)。
- 站在阵内点阵外的箱子 → 拦(人在阵里)。
item_use没有作用对象,只看行为者自己。- 爆炸与生物破坏没有行为者,只看位置,因此阵主自己的爆炸也会被拦——要留口子就写
"explosions": false。
豁免与其它规则
- 阵主永远豁免,且不受任何配置影响——把自己锁在自家门外是 bug 不是规则。
- 好友豁免要求
spare_friends为真且服务端配置「阵法 → 敌我识别」打开。 - 认不出就照常保护:声明了
spare_friends的阵法在认不出时停火,守御阵在认不出时继续拦。猜错的方向应当指向安全的那一侧。 - 只有在册阵法生效,拆除即失效;无主阵法不豁免任何人(否则一座被遗弃的阵法会永久锁住一块地)。
- 基座不自带任何领地保护:需要领地时由外部领地模组提供,这一层按下面的
delegate_to_claims让路。
交给领地插件(delegate_to_claims)
置真且当前确实有生效的领地保护时,上面九个开关一个都不执行:真正拦人的是领地插件自己的规则。以 FTB Chunks 为例,它本来就独立保护自己的领地,所以「交出去」不需要调用它的 API,只是让阵法不再叠加自己那一层。
判定「有没有生效的领地保护」会看一眼 FTB Chunks:装了,且它的全局 disable_protection 没被关。它看不见队伍级的设置——某个队伍把自己的领地四项隐私模式全设成 public 时,这里仍算作「有保护」(要把这层也重算,就等于把领地插件的判定抄第二份,而那正是「交出去」想避开的事)。
服务端配置「兼容 → 委派需领地保护」(默认开启)决定没有领地保护时怎么办:
| 配置 | 行为 |
|---|---|
| 开(默认) | 只有真的有领地保护时才交出;否则回退到上面九个开关,并在激活时按定义打一条警告日志。 |
| 关 | 字面读法:一律交出,没有领地保护时就真的什么都不保护。 |
无论哪一支,两条代价都属于设置者:
- 交出时阵法只保护被认领的地方,自己的
radius不再参与保护判定; - 回退分支里阵法保护的是它自己半径内的东西,与领地插件的判定无关。
与领地插件的关系:三种联动(服务端配置)
上面那个开关是内容包对单座阵法的声明;服务端另有一个配置(「兼容」标签页)决定所有保护阵法与领地插件怎么相处——服务端配置「兼容 → 领地联动」:
| 取值 | 行为 |
|---|---|
| 不联动(默认) | 不联动:阵法按自己的开关在任意位置生效;两边都在时是「与」关系——谁拒绝都算拒绝。 |
| 只在领地内 | 只允许在领地内立保护阵法:阵心所在区块必须已被认领(任何队伍),否则激活失败并提示(item.mxt.formation_plate.failed.not_claimed)。只作用于带守御模块的阵法。 |
| 领地优先 | 哪里都能立,但立在领地内时改用该领地规则:阵心所在区块一旦被认领,这座阵法的九个开关整个交出,由领地插件判定;不在领地内则仍按自己的开关。 |
三条边界:
- 判定「在不在领地内」看的是阵心所在区块,不是每一个受影响的位置——一座横跨领地边界的阵法要么整体归领地管,要么整体不归。
- 「只在领地内」不要求「你自己的领地」:站得进去就说明房主允许(否则他早就把你拦了),这条选项管的是「别在无主之地立法阵」。
- 服务器没装领地插件时两个模式都不起作用,而不是让阵法立不起来:没有领地可要求,也没有领地规则可让。「只在领地内」的这种情况会打一条警告(整局只报一次),因为「选项没生效」和「选项生效但没人来」在游戏里一模一样。
别人领地不能开保护阵法
服务端配置「兼容 → 立阵需许可」(默认开启,与上面那个枚举相互独立):
阵心落在别人认领的区块里时,只有三种人能立保护阵法。
- 有编辑权限的人——直接问领地插件「你会不会让他放这种方块」,而不是在这里重写一遍隐私模式规则:公开领地、队内职级、盟友、被许可的假玩家到底算不算,那是它的规则,抄第二份只会抄出个会漂移的副本。
- 地主的好友——走的是守御阵同一套好友系统(所以装了 FTB Teams 时,队友与盟友本来就被算作好友,自然会通过)。
- 无主之地——没被认领的地方是「没有地主」,谁都能立。
其余一律拒绝,包括解析不出的行为者(控制台 / 指令来源):没人能替地主答应。
守御阵是宣称管辖区,不只是盖房子:允许在别人地盘放方块,不等于允许在那里立法;结构本来就立着时(模板匹配的是自然地形,或者别人搭的),光靠方块保护拦不住任何人。
无领地插件时这条规则同样惰性(没有别人的领地可判),所以对纯 Mxt 服务器不改变任何行为。拒绝时的提示是
item.mxt.formation_plate.failed.foreign_claim,比「结构不匹配」更能说明问题。
记在别处:FTB Chunks 默认不拦物品使用(只有
ftbchunks:right_click_blacklist能拦),也不拦攻击生物。也就是说item_use与attack_entity这两项一旦交出去就没人管了。
已知不覆盖
弹射物。attack_entity 拦的是近战挥击(游戏在攻击者一侧派发的事件),从阵内射出的箭命中时不算「攻击者此刻在攻击」,因此不会被拦。
mxt:particle
用粒子把阵法的图形画出来:图形是 formation_shape 的一份定义,外环与星形都由它的笔画拼出来。
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
shape | 图形 id | 必填 | 画哪一份图形,指向 formation_shape。本体自带 mxt:five(环 + 五角星)、mxt:six(环 + 两个三角)、mxt:eight(环 + 两个方框)。 |
duration_ticks | Integer 0..24000 | 0 | 从立阵起用多少 tick 把图形画完;0 表示不画动画,第一次收到就是完整的 |
interval_ticks | Integer,10 的倍数 10..24000 | 10 | 每多少 tick 发一次心跳(默认就是每一拍)。它只决定服务端多久确认一次「还在渲染」、以及顺手对齐一次进度,不决定画面流畅度 |
display_distance | Double 1..4096 | 不写=跟视距 | 阵列半径之外最多再多远还发图形(格)。写小了就是把显示范围压窄,写大了不生效——超过视距的那部分地面客户端本来就没有区块 |
{"type": "mxt:particle", "shape": "mxt:five"}图形的坐标、起点、颜色、疏密与窗口全在图形那一页:坐标以阵列半径为单位、0 号方位在正东、每一笔按自己的总长(以及自己的 start / end 窗口)截断,颜色、spacing 与 size 都写在笔画上,模块这边一个都不管。这里只说这个模块怎么把它画出来:
duration_ticks 让图形从阵心"长"出来:进度是「立阵到现在的 tick ÷ duration_ticks」,到 1 就停住。描线的时钟在客户端:服务端每一拍只发一个"还在渲染"的包(带当前进度与剩余时长),客户端第一次收到时把已经该有的那一段直接铺上,剩下的用自己的计时器长完。所以画面顺着客户端的帧率走,interval_ticks 写多大都不影响顺不顺;中途走过来、中途登录、或者本地卡一下之后重新收到包,看到的都是正确的进度,不会重播。写 0 就是不画动画。重新立阵会重新计时;从更早的存档读出来的阵法按「已经画完」处理。
- 只发给看得见的玩家:半径 + 服务端视距(
view-distance× 16,最少 32 格)以内。原版的广播接口自己卡死在 32 格,所以这里按玩家逐个发送——否则半径超过 32 格的阵法会被剪掉一圈轮廓。一个玩家一拍只发一个包:包里只有图形的 id,客户端自己去读它的定义,所以图形多密都不花这一份带宽。这个余量也能在模块自己身上写小(display_distance),适合"只在近处看得见就够"的阵法。 - 客户端每 tick 只补上刚长出来的那一小段,每 10 tick 才把整幅重铺一次(尘埃活得比 10 tick 长,所以旧的那部分不会先灭):一次描线总共也就铺出一幅图的量,所以画得顺不额外花粒子。
- 心跳停了就不再画:阵法被拆、玩家走出范围都是这一条。窗口是
interval_ticks的两倍(下限 30 tick、上限 200 tick,客户端 tick 计),已经铺出去的尘埃自己慢慢消失,所以拆阵是"停止描线 + 淡出",不是瞬间消失。 - 画的是
minecraft:dust,不受 32 格距离与「粒子效果」设置的裁剪,远处的半个圈也画得出来。 - 画在阵心方块底面往上 0.1 格的高度上,贴着地面。线画得密不密、尘埃大不大,由图形里每一笔自己的
spacing与size说了算;半径很大时把它们一起放大。 - 它只作用于阵法自身:不作用于任何实体、不会让阵法变成敌对、不参与敌我判断;不写这个模块的阵法一点开销都没有。
- 它跑在逐实体那几趟之前,所以取消
TickEffects的那一拍连范围一起不画。 - 可以写多个,叠不同颜色。
mxt:send_message
给阵法覆盖到的玩家发消息。
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
message | 文本 | 必填 | 字符串按翻译键读,对象按完整文本组件读 |
channel | Enum | chat | chat 发到聊天栏,action_bar 发到物品栏上方的动作栏 |
recipients | EntityCondition | 恒真 | 对每个被覆盖的实体逐一判定,不成立就不发给他 |
interval_ticks | Integer,10 的倍数 10..24000 | 20 | 每多少 tick 发一次 |
{"type": "mxt:send_message", "message": "example.array.warning", "channel": "action_bar"}按维护周期跑(20 tick),对每一个被覆盖的实体各跑一次,所以每人各收一条自己的消息,而不是全场广播一条。它不是送给阵主的:谁站在半径里谁收到,包括阵主自己。
recipients 逐人求值,所以可以只提醒一部分人(只通知阵主的敌人、只通知好友、只通知某种生物)。非玩家实体收不到消息,条件对它们求值也没有意义。
只影响收件人,不参与敌我判断、不改变阵法是否敌对。
mxt:activate
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
block_action | 方块行为 | mxt:no_op | 立阵成功之后对阵法自身执行一次,上下文是 Level |
entity_action | 实体行为 | mxt:no_op | 实体进入半径时对该实体执行一次 |
{"type": "mxt:activate", "entity_action": {"type": "mxt:apply_effect", "effect": "minecraft:glowing", "duration_ticks": 200}}entity_action 对立阵时就已经在半径里的实体同样会跑(那时还没有「上一拍谁在里面」这份记录),所以它必须幂等:区块卸载再加载、服务器重启都会让它再来一次。
两个字段只写一个也行,都不写就等于 mxt:empty。
mxt:tick
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
block_action | 方块行为 | mxt:no_op | 每个周期对阵法自身执行一次,上下文是 Level |
entity_action | 实体行为 | mxt:no_op | 每个周期对半径内每个被覆盖的实体执行一次 |
{"type": "mxt:tick", "block_action": {"type": "mxt:no_op"}}这是每个维护周期那一档(20 tick),本身不节流;要另一个节奏,用 mxt:attack / mxt:buff / mxt:send_message / mxt:particle 这四个自带 interval_ticks 的模块。
mxt:inactivate
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
block_action | 方块行为 | mxt:no_op | 阵法被拆除时对阵法自身执行一次,上下文是 Level |
entity_action | 实体行为 | mxt:no_op | 实体离开半径、或阵法被拆除时对该实体执行一次 |
{"type": "mxt:inactivate", "block_action": {"type": "mxt:no_op"}}阵法被拆除时,被它跟踪的实体先各跑一遍(entity_action 与授予的撤销),然后才轮到 block_action——方块行为看不到实体,所以只能这样排。