跳到主要内容

formation(阵法)

文件位置:data/<namespace>/mxt/formation/<path>.json

formation 定义一座阵法:结构、半径、资源消耗,以及它做什么(actions 里的功能模块)。激活、每个周期与拆除这些时刻也都是模块;运行时的灵气覆写与灵气上限加成同样是一个模块(mxt:aura)。

字段类型默认说明
quality品质 id无可选。携带这座阵法的阵盘起始的档位。
structure_template结构模板 ID见下原版结构模板,阵心即模板原点。与 structure 二选一(structure_check: "structure" 时必须恰好写一个)。
structure_checkstructure / alwaysstructure立阵时是否校验结构。always 表示这座阵法哪里都能立,此时 structure_template 与 structure 都不会被读取,写了会被静默忽略(与未知键同一口径)。
structure数组,每项 { "offset": [...], "state": ... }[]内联结构,阵心即偏移原点。与 structure_template 二选一。
radiusNumberProvider必填阵法影响半径。
activation_costs数组,条目见 Cost[]激活时消耗,从激活者自己的账户扣,整份数组全有或全无。
maintenance_costs数组,条目见 Cost[]每个维护周期消耗;阵法内方块供的灵气与它自己的存量先依次抵扣,缺口再由阵主支付,失败时失效——缺口为 0 时阵主不在线也照常维持。mxt:item 与 mxt:js 在这里永远付不出。
storage存量声明无(不启用)阵法自己的存量:capacity(灵气 ID 到数值的映射,必填)声明每种灵气最多存多少。存下自家地脉供应的盈余,付账顺序变成 地脉 → 存量 → 阵主。通用字段,任何阵法都能写。
actions数组[]这座阵法做什么:功能模块列表,见阵法功能。
spare_friendsbooleanfalse敌我识别开关:逐实体节拍是给覆盖范围内所有人,还是先过一道敌我判断(好友与认不出身份的都不受影响)。它只决定"全部 / 识别",不代表这座阵法是攻击型——那是 actions 的事,见「敌我判断」。

quality 是可选的:阵盘是所有阵法共用的一件物品,物品本身说不清是哪一档,只有堆上携带的这份阵法报得出——携带这座阵法的阵盘起始就在这一档。档位读的是阵盘上存着的那座阵法自己报的档,与这块阵盘的 allowed 无关:落在这块阵盘允许列表之外、激活会被拒的阵法,照样报得出它的档位。堆上写了自己的 mxt:quality 组件时以组件为准;这份阵法没写 quality 时这一层不作答,继续落到注册表 default_quality。

结构尺寸不受结构方块的 48×48×48 限制。 原版结构方块那两个轴上限常量只用于它读取自身 NBT 时的 clamp,是编辑器限制,不是格式或阵法限制:底层结构模板格式读 size 不设上限,结构方块 LOAD 一个更大的模板时会直接采用模板尺寸,/place template 也没有尺寸检查。因此手工用结构方块圈选做不了超过 48³ 的模板,但外部工具生成的 .nbt 可以被阵法正常引用。阵法的作用范围由 radius 决定,与结构包围盒大小无关。

阵法功能(actions)​

actions 回答"这座阵法做什么"。它是一个模块列表,每一项用 type 选中一种功能,并且只带自己需要的字段——结构、半径、成本、spare_friends 留在顶层共用;激活、每个周期与拆除这些时刻也写在这里(mxt:activate / mxt:tick / mxt:inactivate,各带一个方块行为与一个实体行为)。

{
"structure": [ { "offset": [0, 0, 0], "state": "minecraft:gold_block" } ],
"radius": 12,
"activation_costs": [ { "id": "mxt:spirit_power", "amount": 100 } ],
"actions": [
{ "type": "mxt:attack", "damage": 8, "damage_type": "minecraft:lightning_bolt" },
{ "type": "mxt:protection", "spare_friends": true }
]
}

规则:

  • 同一类型可以出现多次,列表不会合并——两段数值不同的攻击模块是两段攻击。
  • 执行顺序就是列表顺序,没有第二种次序:想读另一段的结果就写在它后面。
  • 逐实体那几趟只跑在阵法覆盖的实体上,"谁被覆盖"由顶层 spare_friends 决定:写着它,逐实体节拍只作用于非好友;不写就是覆盖范围内所有人。作用于阵法自身的节拍不受这个开关影响。
  • type 写错会让定义加载失败,不会退化成"什么都不做"——模块的字段几乎全是可选的,静默退化会把一个拼写错误变成一座站着不动的阵法。
  • 内置 mxt:attack、mxt:buff、mxt:aura、mxt:protection、mxt:particle、mxt:send_message、mxt:empty(空模块,同时是分派表的默认项),以及三个通用节拍 mxt:activate、mxt:tick、mxt:inactivate。

模块响应哪些节拍、各自读的字段、默认值与判定边界见阵法功能模块类型,那里一个模块一节。这一页只留 actions 这个字段本身与阵法整体的语义。

结构:二选一​

structure_check 决定立阵时要不要校验结构:

  • structure(默认):structure_template 与 structure 必须且只能给一个,两个都给或都不给都会让定义加载失败。
  • always:这座阵法哪里都能立,此时 structure_template 与 structure 不会被读取——写了会被静默忽略(与未知键同一口径)。用来表达"不靠结构、只靠别的手段立起来"的阵法(例如由脚本或命令直接放下)。

优先用 structure。 阵法要表达的通常是「阵基 + 几根阵旗」这种少量固定位置,内联写法已经够用,而且它在数据包加载时就把期望值解析完并固定下来——每次校验只是每个方块一次方块状态查询。structure_template 只在布局真的复杂到无法逐格列出时才值得用。

  • structure(内联,首选):无需 .nbt 资源,偏移量直接写在定义里。

    "structure": [
    { "offset": [0, 0, 0], "state": "minecraft:gold_block" },
    { "offset": [0, 0, -8], "state": "mypack:wood_flag" }
    ]

    offset 相对阵心。state 可以直接写方块 ID(取默认状态);需要指定属性时才用原版的 { "Name": "...", "Properties": { ... } } 写法——能用一个 ID 表达的状态在导出时也会写回 ID。

  • structure_template:引用原版结构模板(data/<命名空间>/structure/<路径>.nbt,与结构方块、/place template 共用同一批文件)。适合大型或复杂布局。

    代价有两层:

    1. 每次校验都要重新序列化并解析一遍模板,另加每方块一次方块状态注册表查询——20 tick 一次、每座阵法都来一遍,所以别拿它描述几个坐标。
    2. 模板里所有方块都必须逐个对上,除了空气。

    空气不参与校验。 用结构方块保存时,它会记录整个包围盒,空位也作为空气条目写进 .nbt;如果要求这些格子保持空气,旁边落一根火把或者掉一个方块就会把阵法废掉。所以模板里的空气条目表示"这里不关心",不是"这里必须是空气"。反过来说,模板只能约束"什么必须在",不能约束"什么必须不在"——需要"此处必须空着"的判定只能靠条件自行实现。

    注意这与原版放置模板的行为正好相反:放置会把空气覆盖到世界上,因为结构复现的是整个体积;阵法只断言自己的阵旗还立着,是个更弱的问题。

    48×48×48 是结构方块自己的限制,不是阵法的限制。 那两个轴上限常量都是 48,并且只用在结构方块读取自身 NBT 时做 clamp;底层的结构模板格式(从 NBT 读 size)没有任何尺寸上限,结构方块在 LOAD 模式下加载更大的模板时会直接采用模板尺寸而不是夹到 48。所以用结构方块手工圈选做不了超过 48³ 的模板,但外部工具直接生成 .nbt、再用 /place template(同样没有尺寸限制)验证过的文件,阵法照常可以引用——问题只在"怎么把文件做出来",不在加载。

逐实体节拍的上下文​

模块在逐实体那一趟(activate / tick / interval / inactivate)对每个实体各执行一次,可以读到:

名称位置含义
formation_radius公式显式值阵法半径。
distance公式显式值该实体到阵心的距离。
formation_x / formation_y / formation_z公式显式值阵心坐标,可直接喂给 mxt:teleport。
阵法携带者上下文数据记录"是哪座阵法、阵主名单、阵心在哪",由 mxt:formation_owner 与 mxt:formation_ally 消费。

阵法自身那一趟的上下文是 Level,只有 zero / random 两个变量——不要在它里面写依赖实体状态的公式。三个时刻(activate / tick / inactivate)的方块行为都走这一条规则,没有例外。

阵法所有者​

  • mxt:formation_owner——该实体是当前正在评估的这座阵法的所有者之一。阵法之外恒为 false。
  • mxt:formation_member——该实体拥有本维度任意一座在册阵法(归属名单上有它就算)。与上一条是不同的问题,不要混用。

归属是一个集合:一座阵法记的是一组 UUID,名单上的每一位都算阵主——mxt:formation_owner、拆除权限、功能模块的"给阵主 / 给队友"都按这一组判定,好友系统也会逐个问名单上的每一位(任一位认得你就算队友)。放置者在阵盘激活时写入名单;几个人共养一座阵法时用 /mxt formation owners <pos> [add|remove <player>] 增删阵主(增删需要 gamemaster 权限,见命令),/mxt formation list 会把整组逗号分隔列出来。

付费者取名单上的第一位(单一归属时就是唯一的阵主,也是当初立阵的那位),所以有维持消耗的阵法在第一位阵主离线、取不到付费者时会被拆除——除非有脚本取消 UpkeepFailed 让它撑过去,或者它的 storage 存量还付得起这一期(见「存量」)。

mxt:formation_owner 只排除阵主本人(名单上的任何一位都算)。队友保护是它旁边的另一个判定,不会改动它的语义——数据包因此可以分别表达「只排除阵主」和「排除所有友方」。

敌我判断(队友保护)​

顶层 spare_friends 是开关,只回答一件事:这座阵法的逐实体节拍是给覆盖范围内的所有人,还是先过一道敌我判断(好友与认不出身份的都不受影响)。它不代表这座阵法是攻击型——一座阵法是进攻还是辅助,看的是它的 actions:带 mxt:attack 就是会打人的阵法,带 mxt:buff 就是给增益的阵法。开关与性质各管一头,互不推断。

  • 不写(默认)=对所有人生效,阵主和好友也不例外。想让攻击阵法放过自己人,必须显式写 "spare_friends": true;忘了写就是一拳打在阵主脸上,这是刻意的——运行时不会替数据包猜意图。

  • 写了之后:好友与阵主不受任何逐实体节拍影响,陌生人照常受影响,认不出身份的也放过。

  • 谁算队友由好友系统回答:先发一次好友关系事件(其他模组可以表态,且事件带的是阵主 UUID,所以数据在服务端的来源——例如装了 FTB Teams 时的队伍成员与盟友——在阵主离线时也能作答),没人表态时才查阵主自己的好友名单。名单上有几位阵主就逐个问几位:任一位认得你就算队友(有一位说不是、其余没人表态,仍然算不是)。阵主本人也算——好友判定里"自己算自己的好友",所以声明了开关的阵法也不会伤到立阵的人,不必再写 mxt:formation_owner 条件。

  • 阵主无法解析时(离线、未加载)仍然先按 id 问一遍(名单上每一位都问):事件带的是阵主 UUID,所以装了 FTB Teams 之类的来源照样能答;内置好友系统也有离线镜像缓存,阵主只要登录过一次就仍有答案可用。只有名单上没有任何一位能识别这个实体时(没有阵主记录,或既没有实体、镜像里也没见过这个玩家、又没有来源认识这对关系)声明了开关的阵法才停火:对所有实体都不生效,而不是"对所有人生效"。名单挂在玩家实体上,"打所有人"恰好会打到这个开关本来要保护的人,而"挑一批人放过"只能靠猜——认不出敌我就不开火。停火同样意味着它授予的能力会被释放(被豁免的实体不进在场集合,见下一条)。想做"阵主不在也照打"的阵法,有两条路:不写 spare_friends、改用 mxt:formation_ally 自己写判断(它在阵法之外 / 没有阵主时恒为 false,于是走"打"的分支),或者把服务端配置关掉。

  • 被豁免的实体不会进入阵法的在场集合,因此也拿不到 mxt:activate / mxt:inactivate 的逐实体那一半。好处是阵法授予的能力会在豁免生效时被释放;代价是好友关系变化会产生一次"离开再进入",所以进场那一半依然必须幂等。

  • 服务端配置「阵法 → 敌我识别」(默认开启)是总开关:关掉之后 spare_friends 形同不存在,阵法一律对所有人生效(包括阵主自己)。默认开启不改变任何数据包——spare_friends 默认为 false。

数据包也可以自己判断,不必依赖自动过滤。mxt:formation_ally 是实体条件,在阵法上下文里回答"该实体是不是阵主的好友"——任一位阵主认得它就算(名单上没人能回答时恒为 false;阵主只是离线则仍会按 UUID 问事件,和其它判断走同一条流水线)。逐条行为各判一次,就能表达单一开关表达不了的东西——例如同一座阵法伤敌而治疗友军:

"actions": [
{
"type": "mxt:tick",
"entity_action": {
"type": "mxt:if_else",
"condition": { "type": "mxt:formation_ally" },
"if_action": { "type": "mxt:heal", "amount": 1 },
"else_action": { "type": "mxt:damage", "amount": 2 }
}
}
]

两条路的取舍:

spare_friends(顶层开关)mxt:formation_ally(实体条件)
粒度整座阵法逐条行为
由谁决定是否执行服务端配置「阵法 → 敌我识别」数据包自己,不受配置影响
认不出敌我时停火(不打任何人)走 false 分支(照打)
适用「这座阵法的效果只给外人和认不出的人以外的人」「同一拍里对敌人和友军做不同的事」

两者不要同时用在同一座阵法上。 spare_friends: true 会先由运行时把好友整个剔除,逐实体节拍根本不会跑在他们身上,上面那个"治疗友军"的分支也就永远走不到。要做分而治之,就别声明这个开关。

mxt:friend 是同一判断的双实体条件版本(actor 把 target 当自己人时为真),用在有双实体条件槽的地方,例如技能的 target_condition:{"type": "mxt:not", "condition": {"type": "mxt:friend"}} 表示"不作用于好友"。

周期与三个事件​

一切以 20 tick 为周期,一个周期会依次发三个事件(KubeJS 里同在 formation 组,用 isCancellable() / getPhase() 区分):

事件可取消含义
Tick❌维持费已经结算完。观察点:需要「每个付过费的周期都发生点什么」就挂这里。
TickEffects✅只挡周期这一拍(tick 与 interval 两个节拍,以及逐实体那一趟)。activate / inactivate 不受它影响。取消不退费——阵法还立着,也还在被维持。
UpkeepFailed✅付不出维持费。取消 = 这一周期不付也不做事,但阵法保留;不取消才拆除。

拆分的原因:付钱与取消必须是两件事。Tick 触发时钱已经花了,若它可取消,「取消」在一句话里就同时意味着"什么都不发生"和"照扣钱"——一个无条件取消的监听器能零效果地掏空付费者。

范围与时间粒度​

  • 范围是球,不是方盒:方盒角落处不会生效。
  • 一切以 20 tick 为周期,这也是进出判定的粒度:20 tick 内穿过阵法不会被观察到。
  • 区块卸载再加载会产生一次假的"离开再进入";服务器重启后阵内实体一律视为新进入。因此 mxt:activate 的逐实体那一半必须是幂等的。

释放阵法授予的能力​

mxt:grant_ability 的 source 建议取该 formation 自己的 id 对应的 saturn:formation/<命名空间>/<路径>。基座会在实体离开、阵法拆除、以及玩家脱离阵法时对账这个 source 并撤销它的全部授予——否则能力会永久留在实体身上(能力记录是持久附件,而 mxt:inactivate 的方块行为看不到实体)。不遵守这个约定不会出错,只是失去自动清理。

这一份 source 属于定义,不属于某一座实例:撤销前会先问"同一定义还有没有别的实例覆盖着他",所以同定义的两座阵法不会互相撤掉对方的授予;只有所有实例都够不到他(或者它们都拆了)才真的撤销。跨维度不算覆盖。

阵法吞噬地脉(维持费的第一个来源)​

阵法半径内的方块灵气发射器不再向环境供气,改为全部供给阵法。 阵法用这些灵气支付自己的 maintenance_costs,缺口才由阵主出;如果阵法自己供的灵气就够,阵主一分钱都不用出(甚至不需要持有那种资源)。

规则要点:

  • 判定范围就是阵法自己声明的 radius 球,没有第二套定义。
  • 归属按实例算,不按区块:每一格发射器记的是"它落在哪几座阵法里",所以一座阵法只吃自己半径内的那些格子,同区块另一座阵法的发射器不算它的供给;两座重叠的阵法都会把同时落在两边的那一格算进自己的供给(重叠即共用,不做分摊、也不按距离打折)。发射器到控制器按三维距离算,上下层不互相算错。
  • 被吞的方块会从环境灵气里彻底消失——它们不再参与"这里有多少灵气"的解析,也不会被其他查询共享。所以阵法是个真正的灵气黑洞,建在灵脉上会把那片地脉抽干(地脉本身仍按 regen_per_tick 恢复)。
  • 按资源类型抵扣,不通用:阵法里全是火属性灵气,抵不掉一笔灵力账单。不匹配的灵气既不给环境、也不给阵法,就是被吞掉了。
  • 不设存量:本周期吞到的灵气直接抵本期维护费,超出的部分不返还、不留到下期。要留下来就给这座阵法写一个 storage 字段(见「存量」)——这是唯一的例外,而且开得很窄。
  • 只有阵主能作为付费者。阵主离线时,阵法仍会因为"付不出缺口"而被拆除——除非缺口被自家灵气完全覆盖,那时根本不需要付费者。storage 的存量同样算付费者:阵主不在时它照常付账,付不动才拆。
  • 可选:连地皮的自然灵气也一起抽。 服务端配置「阵法 → 抽取环境灵气」(默认关闭)打开后,阵法所在位置解析出的环境灵气也会参与抵扣。注意两项:
    • 只算自然环境(群系 / 维度 / 人工区域以及该处的场地灵气),阵法内发射器贡献的那一份会被扣掉,不会重复计入;
    • 它让阵法比"站在什么之上"更便宜——灵气浓郁处即使不摆发射器也可能白跑,这是另一套平衡。

配套的引擎要求:阵法激活或拆除时会让半径覆盖到的区块重算方块灵气(否则被吞的方块会继续供环境,阵法那一份就等于白拿)。重算是排队执行的,在下一个服务端配置「灵气 → 方块灵气周期」的边界发生。

想让阵法完全靠自己运转,就在半径内摆足够多的发射器(例如 mxt:spirit_stone_block)。

存量(storage)​

storage 是通用字段:每座阵法都有这个属性,只是默认不启用——不写它就是不存。

字段类型默认说明
capacity灵气 id 到数值的映射必填每种灵气最多存多少;没列出的灵气不存。
{
"structure": [ { "offset": [0, 0, 0], "state": "minecraft:gold_block" } ],
"radius": 12,
"maintenance_costs": [ { "id": "mxt:common", "amount": 10 } ],
"storage": { "capacity": { "mxt:common": 500 } }
}

不写 storage 的阵法是穿堂而过的:自家地脉供的灵气只抵本期维护费,多出来的当场散逸。写上它,这座阵法才开始把盈余留下来——而且刻意留得很窄:存量来自地脉、只付这座阵法自己的账、随阵法一起消失。

付账顺序:本期地脉供应 → 存量 → 阵主。 先花当期的是有意的:地脉这份是本周期新给的,不花就没了;存量是能留到下一周期的那一份,用它先付反而等于让阵主为本周期站在脚下的灵气买单。

场景(账单 10,容量 500)地脉给存量存量支付阵主支付存量入账
地脉足够5000040
地脉断了0301000
三方分摊43330
库快满了504900010(只进到 500)
容量里没列它500000

按资源各算各的:一库火灵气付不了灵力账单,不管它是本周期收的还是十个周期前收的。一条规则对应一条账:某个资源的账单可以由地脉付、另一个由存量付、第三个落到阵主头上,同周期互不影响。

几条边界:

  • 容量是每种资源各自的上限,不是总量:{"mxt:common": 500} 说的是 mxt:common 最多 500。
  • 写了 storage 就必须写 capacity:一个什么都不存的存量声明是拼写错误,不是配置(要关掉整个存量就别写 storage)。
  • 没有 maintenance_costs 的阵法什么都不存——没有账单要抵,存了也没有出口,所以连扫描都省了。
  • 拆除即散逸:存量属于这座阵法,拆掉就没了。归还阵主会把地脉灵气接成一条通向玩家背包的管道,那是另一套平衡。
  • 阵主离线时存量照常付款,缺口被存量覆盖就撑过去,覆盖不了才拆。代价是这时账单按 Level 上下文求值:常量费用不受影响,写依赖玩家状态的公式则读不到玩家。
  • 存量随存档持久化,/mxt formation info 会额外显示非空的存量。
  • 容量是公式,求值结果非有限或 ≤ 0 就表示这种资源不存(不夹取、不猜)。

灵气覆写​

mxt:aura 模块的 aura_zone 让这座阵法替换阵地所在位置的灵气解析结果(优先级高于群系 / 维度 / 人工区域):

  • 覆盖通过一个可取消的灵气覆写事件发布——取消它就退回静态结果,阵法仍然立着。
  • 覆盖只在阵法在册期间生效,拆除后立刻退回静态解析。
  • 灵气答案是按 tick 记忆化的,所以同一个 tick 内激活的阵法要到下一个 tick 才影响灵气查询。
  • 重叠时取最近一座声明了 aura_zone 的阵法;一座没有声明 zone 的阵法(例如地形保护阵)不会挡住它旁边那座修炼阵法的覆写。

同一个模块的 max_bonus 给灵气池加上限,但有一个必须知道的耦合:它只随着 aura_zone 的覆盖一起应用。上限是加在「覆盖后选中的那个 zone」所提供的资源上的,因此:

  • 没有 aura_zone → 没有覆盖 → max_bonus 无处可加。
  • 有 aura_zone 但该 zone 不提供某个灵气 → 对这个灵气加不上(不会凭空创造灵气)。

同一个模块的 upkeep_weight 决定阵法和玩家抢同一份灵气时的份额:灵气库存按子区块(垂直 16 格)分开存,阵法把自己覆盖范围内每个子区块的发射方块算作一次请求,与该子区块里正在修炼的人按 aura_zone.distribution 分同一个池;分到的量先抵维护费,不够才动阵法储备与主人。被阵法覆盖的地面不再从环境里扣除,所以站在阵里查灵气看到的仍是全部发射方块——阵法付账扣的是同一份库存,不会双计。

激活与拆除​

阵盘(mxt:formation_plate)是唯一能把阵法带进世界的物品:它的 mxt:formation_plate 组件里存着一个 formation 引用,右键方块时结算。同一块阵盘就是开关——对着阵心右键是激活,对着已激活的阵心右键是拆除(需为阵主或管理员;打开服务端配置「阵法 → 队友可拆除」后,阵主的好友也能拆)。

绑定阵法:用 /mxt formation bind <formation> 写入主手的阵盘(需要 gamemaster 权限),或在物品组件里直接指定:

give @s mxt:formation_plate[mxt:formation_plate={formation:"mypack:green_shade_array"}]

未绑定的阵盘会自动识别​

没有绑定阵法的阵盘,右键时会认出脚下这座阵法并激活它。 识别用的就是阵心容错那 3×3×3,候选是这块阵盘允许激活的全部阵法(allowed,空列表按服务端配置解释),按 ID 排序逐座比对结构,离点击位置最近的一座胜出;点击位置本身能匹配时永远优先。成功后的提示带上阵法名——玩家从来没说过要立哪一座,"有座阵法出现了"不算回答。

  • 绑定优先:绑了阵法的阵盘只立那一座,既不会顺手认出别的,也不做这次扫描。白名单回答"这块盘能立哪些",绑定回答"这块盘立哪一座",两者不能互相替代。
  • /mxt formation bind 因此不是"能玩"的前提,而是一种限制手段:内容包发出去的阵盘可以绑好,也可以留空。
  • 关掉服务端配置「阵法 → 阵盘自动识别」(默认开启)后,未绑定的阵盘提示「阵盘尚未绑定阵法」。
  • 代价是一次点击最多 27 × 白名单内阵法数次结构检查。内联 structure 是每格一次方块查询,而 structure_template 每次检查都要重新序列化并解析模板——数据包很大、又不想让玩家点一下卡一下,就把常用阵盘绑好(绑了就不扫描),或者直接关掉这个选项。
  • 认不出任何结构时提示 item.mxt.formation_plate.no_structure,而不是「尚未绑定阵法」:这两句话对应的下一步不一样("换地方 / 先把阵基摆出来" 对 "先用命令绑定")。

激活失败时会说明原因(结构不匹配 / 资源不足 / 位置已占用 / 领地不允许),不再只回一个内部枚举名,并且任何失败都不消耗东西。

阵盘白名单​

mxt:formation_plate 组件有两个独立字段:

字段类型默认说明
allowedID 或 #标签的列表[]这块阵盘允许激活哪些阵法。属性属于物品本身,同一 ID 的每一份都一样。
formation阵法 ID无这一份当前选中哪一座;必须属于 allowed 才可用。
# 只允许两座具体阵法
give @s mxt:formation_plate[mxt:formation_plate={allowed:["mypack:thunder_array","mypack:ice_array"],formation:"mypack:thunder_array"}]

# 用一个标签把同组阵法交给多块阵盘(推荐)
give @s mxt:formation_plate[mxt:formation_plate={allowed:["#mypack:wood_arrays"],formation:"mypack:green_shade_array"}]

标签文件放在 data/<命名空间>/tags/mxt/formation/<路径>.json,写法与物品标签一致:

{ "values": ["mypack:green_shade_array", "mypack:spirit_gathering_array"] }

allowed 为空是歧义情况,所以做成配置项:默认「不限制」,把服务端配置「阵法 → 空白名单放行」关掉后,空 allowed 表示什么都不允许,必须显式列出。/mxt formation bind 的 Tab 补全列出注册表里的全部阵法;白名单不收窄补全,只在写入前把关。

白名单只在写入前和激活前生效,都先于任何资源消耗:bind 拒绝不在名单里的阵法且不改动物品;若某块阵盘的 formation 不在自己的 allowed 里(手改存档、或配置改动导致),右键会提示「阵盘不允许激活它所绑定的阵法」而不是照常激活——这是一种显式配置,不是损坏的物品。

阵心容错:点击位置周围 3×3×3 内会寻找最近一个满足结构的阵心。点歪一格不会失败;点击位置本身有效时永远优先取它,所以贴着一个可当阵心的方块点永远不会把激活挪到别处。拆除走同一次查找。

注意这个窗口只有 3 格宽,所以它只对紧凑布局真正有用:一个位置要能当阵心,必须从它开始就能满足整份结构,因此跨度超过 1 格时只有原阵心能命中。这正是「点方块偏一格」这个真实约束对应的范围。

诊断​

/mxt formation list 列出当前维度所有已激活阵法;/mxt formation info 列出覆盖玩家所在位置的阵法;/mxt formation bind 是这棵子树里唯一的写操作。三者都有顶层别名 /formation ...(可用服务端配置「命令别名 → /formation」关掉别名,/mxt formation 始终完整)。