formation(阵法)
文件位置:data/<namespace>/mxt/formation/<path>.json
formation 定义一座阵法:结构、半径、资源消耗,以及它做什么(actions 里的功能模块)。激活、每个周期与拆除这些时刻也都是模块;运行时的灵气覆写与灵气上限加成同样是一个模块(mxt:aura)。
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
quality | 品质 id | 无 | 可选。携带这座阵法的阵盘起始的档位。 |
structure_template | 结构模板 ID | 见下 | 原版结构模板,阵心即模板原点。与 structure 二选一(structure_check: "structure" 时必须恰好写一个)。 |
structure_check | structure / always | structure | 立阵时是否校验结构。always 表示这座阵法哪里都能立,此时 structure_template 与 structure 都不会被读取,写了会被静默忽略(与未知键同一口径)。 |
structure | 数组,每项 { "offset": [...], "state": ... } | [] | 内联结构,阵心即偏移原点。与 structure_template 二选一。 |
radius | NumberProvider | 必填 | 阵法影响半径。 |
activation_costs | 数组,条目见 Cost | [] | 激活时消耗,从激活者自己的账户扣,整份数组全有或全无。 |
maintenance_costs | 数组,条目见 Cost | [] | 每个维护周期消耗;阵法内方块供的灵气与它自己的存量先依次抵扣,缺口再由阵主支付,失败时失效——缺口为 0 时阵主不在线也照常维持。mxt:item 与 mxt:js 在这里永远付不出。 |
storage | 存量声明 | 无(不启用) | 阵法自己的存量:capacity(灵气 ID 到数值的映射,必填)声明每种灵气最多存多少。存下自家地脉供应的盈余,付账顺序变成 地脉 → 存量 → 阵主。通用字段,任何阵法都能写。 |
actions | 数组 | [] | 这座阵法做什么:功能模块列表,见阵法功能。 |
spare_friends | boolean | false | 敌我识别开关:逐实体节拍是给覆盖范围内所有人,还是先过一道敌我判断(好友与认不出身份的都不受影响)。它只决定"全部 / 识别",不代表这座阵法是攻击型——那是 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资源,偏移量直接写在定义里。json"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共用同一批文件)。适合大型或复杂布局。代价有两层:
- 每次校验都要重新序列化并解析一遍模板,另加每方块一次方块状态注册表查询——20 tick 一次、每座阵法都来一遍,所以别拿它描述几个坐标。
- 模板里所有方块都必须逐个对上,除了空气。
空气不参与校验。 用结构方块保存时,它会记录整个包围盒,空位也作为空气条目写进
.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 对应的 mxt: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) | 地脉给 | 存量 | 存量支付 | 阵主支付 | 存量入账 |
|---|---|---|---|---|---|
| 地脉足够 | 50 | 0 | 0 | 0 | 40 |
| 地脉断了 | 0 | 30 | 10 | 0 | 0 |
| 三方分摊 | 4 | 3 | 3 | 3 | 0 |
| 库快满了 | 50 | 490 | 0 | 0 | 10(只进到 500) |
| 容量里没列它 | 50 | 0 | 0 | 0 | 0 |
按资源各算各的:一库火灵气付不了灵力账单,不管它是本周期收的还是十个周期前收的。一条规则对应一条账:某个资源的账单可以由地脉付、另一个由存量付、第三个落到阵主头上,同周期互不影响。
几条边界:
- 容量是每种资源各自的上限,不是总量:
{"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 组件有两个独立字段:
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
allowed | ID 或 #标签的列表 | [] | 这块阵盘允许激活哪些阵法。属性属于物品本身,同一 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 始终完整)。