合成与工作站
本页说明配方、bot.craft 以及工作站 API(bot.openFurnace、bot.openEnchantmentTable、bot.openAnvil、
bot.openVillager、bot.trade)在 Bedrock 上如何工作。工作站 API 保留 mineflayer 的签名、窗口成员、事件和错误消息;
在底层,每个合成和工作站操作都是服务端权威的 item stack request(lib/bedrock/stations.js,参见
clickWindow)。观测环境为 BDS 1.26.51.1(协议 2193,bedrock-protocol 3.60.1)。
配方来自服务端的 crafting_data
- 影响:
bot.recipesFor、bot.recipesAll、bot.registry.recipes、require('prismarine-recipe')(bot.registry) - Java: mineflayer 通过 prismarine-recipe 的
Recipe.find读取 minecraft-data 的 Java 配方(registry.recipes[resultId]={ result, inShape | ingredients, outShape? },标签已展开为每个物品一个配方)。 - Bedrock: minecraft-data 的 Bedrock
recipes结构不同(按索引组织,使用 Bedrock 名称),而且并不是服务端实际使用的配方。 BDS 在加入时通过crafting_data发送配方(1.26.51:分类型的数组shaped_recipes、shapeless_recipes、shulker_box_recipes、…):每个配方带有recipe_id、network_id(合成请求引用的就是它)、block标签、priority、 原料描述符(name+ metadata,32767 = 任意;item_tag)以及一个或多个产物(network-id 物品)。- 所有合成网格配方——包括 2×2——都是
block: 'crafting_table';是否适合 2×2 网格取决于其尺寸。 deprecated配方是隐藏的旧版重复项(BDS 仍然接受)。- 熔炉、烟熏炉、高炉、营火、切石机、制图台、锻造台和药水配方都不是合成网格配方;
multi_recipes(地图复制、烟花、旗帜 / 书的复制、…) 没有固定的输入或输出。 - 标签原料只给出标签名;参见物品标签。
- 所有合成网格配方——包括 2×2——都是
- bedboo:
lib/bedrock/recipes.js把crafting_table的有序 / 无序 / 潜影盒配方转换为 prismarine-recipe 的Recipe, 字段相同(result {id, count, metadata}、inShape、outShape、ingredients、delta、requiresTable),另加 Bedrock 的networkId。- 标签原料按 minecraft-data Java 配方的方式展开:同一标签的所有格子使用同一个成员(13 个木棍配方,每种木板一个);一个配方中的
两个标签取笛卡尔积(13 种木板 × 13 种木台阶 = 169 个木桶配方);结构相同的展开结果只保留一个(Bedrock
priority较低者优先: 通用的minecraft:stick优先于stick_from_crimson_planks;BDS 接受任一 id,其他 id 保留用于限制合成)。与 Java 的展开配方一样,一个配方从不混用同一标签的不同成员(2 块橡木木板 + 2 块白桦木板不对应任何工作台配方,尽管 BDS 会接受这样的网格) ——这是上游的行为,予以保留。BDS 1.26.51 的crafting_data由 4128 个 Bedrock 配方得到 4874 个配方,没有跳过任何一个。 bot.registry.recipes按 bot 替换为同样这些配方的 minecraft-data Java 结构(每次收到crafting_data时原地填充),因此require('prismarine-recipe')(bot.registry).Recipe.find(id)的用法与 Java 相同。这些 Recipe 没有networkId(prismarine-recipe 会丢弃未知字段),所以bot.craft按结果和原料把它们匹配到服务端配方(手动构造的 Recipe 也一样)。 来自旧crafting_data的 Recipe 也以同样方式重新匹配。recipesFor/recipesAll是上游代码作用于这些配方,只有一处修正:metadata参数按结果的 metadata 过滤(上游的Recipe.find查找的是meta键,从不过滤)。这在 Bedrock 上很重要,例如床的颜色就是bed物品的 metadata(白色 0,红色 14)。- 开启游戏规则
doLimitedCrafting时,只返回服务端已解锁的配方(unlocked_recipes)(只要某个网格的任一 Bedrock 配方已解锁, 该网格即可用,并用那个配方合成)。 - 下界合金装备是锻造台升级,不属于合成(并且没有对应的 mineflayer API)。
- 标签原料按 minecraft-data Java 配方的方式展开:同一标签的所有格子使用同一个成员(13 个木棍配方,每种木板一个);一个配方中的
两个标签取笛卡尔积(13 种木板 × 13 种木台阶 = 169 个木桶配方);结构相同的展开结果只保留一个(Bedrock
每批合成是一个 item stack request
-
影响:
bot.craft -
Java: mineflayer 把原料逐个点击放入网格(每次点击一个
window_click),从槽位 0 取出结果并收起,每次合成重复一遍。 -
Bedrock: 没有点击数据包;服务端根据
item_stack_request合成,并且只从合成网格中取料:直接从快捷栏消耗会被拒绝 (craft_recipe:状态 35 "mismatched recipe for grid items";craft_recipe_auto:状态 27)。2×2 网格是crafting_inputui 28-31,需要玩家物品栏界面(否则状态 7);工作台的网格是 ui 32-40(按行优先;形状可以放在任意位置, 也接受混用同一标签的不同成员)。已验证的请求结构(一个请求,任意次数的合成):place× n(物品栏 → 网格格子),craft_recipe { recipe_network_id, times_crafted }(旧版 id 12),results_deprecated { result_items: [{ type: 'name', legacy_type: 1, name, metadata, count (per craft), block_runtime_id, extra }], times_crafted }(19),- 对每个网格格子
consume(5)(count = 次数), - 对每个结果,从
creative_output50(stack id = 请求 id)place(1)到物品栏——或take到光标。
每个请求的产物最多一组物品堆(16 个原木 → 一个请求得到 64 块木板;20 个原木 → 状态 14),每个结果都必须取走(否则状态 17), 结果也可以从产物中直接丢出(
drop)。批量合成即times_crafted> 1。 -
bedboo:
bot.craft(recipe, count, craftingTable)保留上游的检查、错误和窗口处理 (Recipe requires craftingTable, but one was not supplied: …、crafting: non craftingTable used as craftingTable, 错误以new Error(err)重新抛出,因此缺少原料时显示为Error: missing ingredient;工作台窗口只打开一次,在结束或出错时关闭)。- 每个请求在物品栏、每格一组物品堆以及每种结果一组物品堆允许的范围内,尽可能多地完成
count次合成(count= 25 次木棍 → 16 和 9 两个请求);若完成部分合成后原料用完,会像上游的下一次craftOnce一样抛出missing ingredient。 - 原料按上游
findInventoryItem的顺序取用(先主物品栏,再快捷栏;每格一种物品堆);结果放到上游putSelectedItemRange会放的位置(先有空间的物品堆,再第一个空槽位,主物品栏优先)。 - 请求经过物品栏模型(预测,被拒绝时回滚);在网格中发现的物品会先被归还。
- 在 2×2 网格中合成时若有容器窗口打开,或在工作台合成时有其他窗口打开,会先关闭它:2×2 网格只存在于玩家物品栏界面中(上游会点进
打开的容器),并且另一个窗口打开时 BDS 会忽略容器激活(切换窗口)。已在同一方块上打开的工作台窗口
会被复用,并且像上游
activateBlock本会在其位置打开的窗口一样,在结束时关闭。
- 每个请求在物品栏、每格一组物品堆以及每种结果一组物品堆允许的范围内,尽可能多地完成
多个结果:桶和瓶子进入物品栏
- 影响: 蛋糕、蜂蜜块以及蜂蜜瓶 → 糖 的
bot.craft/recipe.delta/recipe.outShape - Java: 空桶 / 空瓶留在合成网格中(minecraft-data 旧的
outShape),mineflayer 在合成后把它们收起。 - Bedrock: 配方把它们列为额外产物(蛋糕:
output: [cake, bucket × 3]);客户端用create { result_slot_id: k }(旧版 id 6)选择每一个,并像主结果一样从产物中取走(这是原版客户端的请求,已在 BDS 上确认:不发create时取走会以状态 49 失败)。 - bedboo: 额外产物被加入
recipe.delta(正数);由于网格中没有剩余物品,outShape保持 null。有多个结果时,bot.craft在取走每个结果之前发送create。
放不下的结果先拿在光标上,再丢出
- 影响: 物品栏已满时的
bot.craft - Java: 上游的
putSelectedItemRange会丢出放不下的物品(tossLeftover)。 - Bedrock: 结果必须在合成请求本身中取走。
- bedboo: 放不下的部分在同一请求中取到光标上,随后
returnCursor把它放回(Java 的规则,与关闭窗口 相同)并丢出剩余部分——结果与上游相同(一个itemDrop)。光标只能拿一组物品堆,因此结果溢出超过一组的批次会减少合成次数(剩余的放到下一个 请求);即便如此仍放不下结果的单次合成以destination full(上游对已满范围的消息)失败。
服务端不提供熔炉配方
- 影响: 任何需要知道什么能熔炼成什么的代码
- Java: minecraft-data / 客户端的配方书包含熔炉配方。
- Bedrock: BDS 1.26.51 的
crafting_data不含熔炉配方(参见配方);minecraft-data 的 Bedrock 配方并不是 服务端使用的配方。 - bedboo: 库中没有熔炼数据。agent 包(
packages/agent)内置了一份经过核对的熔炉表,来自 CloudburstMC/Data(Apache-2.0)的recipes.json@73bda57f9afa(1.21.40,最后一份包含熔炉配方的导出):packages/agent/src/data/smelting.json,由packages/agent/scripts/gen-smelting.js重新生成,并已对照 1.26.51 注册表检查。
熔炉属性来自 container_set_data
- 影响:
Furnace的.fuel、.progress、.totalFuel、.fuelSeconds、.totalProgress、.progressSeconds,Furnace的update事件,bot.openFurnace(熔炉、高炉、烟熏炉) - Java:
craft_progress_bar(窗口属性)携带属性 0(剩余燃料)、1(燃料燃烧时长)、2(烧炼进度)和 3(总烧炼时间)。 - Bedrock:
container_set_data { window_id, property, value }。属性 0 是 tick 计数(进度),1 是燃烧剩余时间(剩余燃料), 2 是燃烧时长(燃料燃烧时长)。3(储存的经验)和 4(燃料 aux)没有 Java 对应项。没有总烧炼时间:它是方块的常量,熔炉为 200 tick,高炉或烟熏炉为 100 tick(高炉或烟熏炉烧一块煤也只有 800 tick,而不是 1600)。BDS 在该方块的container_open之前发送其状态,之后在方块燃烧期间每 tick 发送一次。熔炼产物以该窗口的inventory_slot到达。Bedrock 既没有/data也没有/blockdata,因此无法用命令缩短烧炼时间。 - bedboo: Bedrock 属性映射到 Java 属性(0→2、1→0、2→1),并完全按上游的方式归一化。窗口打开时根据方块类型设置
totalProgress。窗口打开之前到达的值在打开时应用。窗口的每个container_set_data都会触发update,正如上游对每个窗口属性 都触发它。
对刚放置的熔炉的点击偶尔会被拒绝
- 影响: 熔炉刚放置并打开后的
Furnace.putInput/putFuel/takeOutput - Java: 没有这种拒绝。
- Bedrock: 大约每 10 次熔炼中有 1 次,对刚放置的熔炉的第一次转移以
Server rejected transaction for clicking on slot 1, on window with id N.失败(BDS 对该 item stack request 应答了错误); 几百毫秒后同样的转移成功。单独测试时无法复现(放置已久的熔炉不受影响)。 - bedboo: 库中未修复。请重试被拒绝的熔炉转移(agent 在
packages/agent/src/skills/smelt.js中会在 600 ms 后重试两次, 并重新计算仍缺少的物品)。
附魔:选项、种子和附魔请求
- 影响:
EnchantmentTable的.enchantments、.xpseed、.enchant(choice),EnchantmentTable的ready事件 - Java: 窗口属性 0–9 给出三个等级要求、附魔种子,以及每个选项一个提示附魔(id + 等级)。
enchant_item { windowId, enchantment }执行附魔,服务端发送新物品。 - Bedrock:
player_enchant_options { options: [{ cost, slot_flags, equip_enchants, held_enchants, self_enchants, name, option_id }] }在附魔台的container_open之前发送(为空),并在物品槽每次变化后再次发送。cost是等级要求。三个列表是该选项 施加的全部附魔,而不只是一个提示。种子从不发送给客户端。附魔是一个 item stack request:craft_recipe { recipe_network_id: zigzag32(option_id) },然后results_deprecated,然后consumechoice + 1 个青金石 (创造模式下不消耗),然后consume物品,最后把产物从creative_output50place回物品槽。BDS 应用自己的结果(错误的results_deprecated会被忽略)并扣除 choice + 1 级。应答中不携带物品。 - bedboo:
enchantments[i] = { level: cost, expected: { enchant, level } },其中expected是该选项的第一个附魔 (Bedrock id,与bot.registry.enchantments中一致)。ready在与上游相同的状态转换时触发,xpseed保持 −1。enchant(choice)根据选项预测附魔后的物品:依次为 equip、held 和 self 附魔,enchNBT 与 BDS 相同(书会变成enchanted_book)。该预测已针对剑、镐、胸甲和书的选项 0–2 与服务端副本逐字节比对一致。服务端接受请求后,它以槽位 0 中的物品 resolve。缺少青金石或等级时会以说明性错误 reject,而不是无限等待。
没有附魔的附魔选项
- 影响:
EnchantmentTable.enchant、enchantments[i].expected - Java: 每个提供的选项至少施加一个附魔。
- Bedrock: BDS 可能提供附魔列表全为空的选项(在没有书架的附魔台上附魔钓鱼竿或胡萝卜钓竿);它照样扣除等级和青金石,物品保持不变。
- bedboo: 这样的选项为
expected: { enchant: -1, level: -1 };enchant()预测物品不变(不会生成空的ench列表), 因此 bot 的副本与服务端一致。
铁砧:名称随请求发送,客户端预测结果
- 影响:
Anvil的.combine(itemOne, itemTwo[, name])、Anvil的.rename(item[, name])、bot.openAnvil - Java: 名称通过
name_item(或MC|ItemName)输入,服务端在槽位 2 显示结果,putAway(2)取走它。上游检查的花费来自 prismarine-item 的Item.anvil。 - Bedrock: 没有命名数据包。整个操作是一个 item stack request:
optional { recipe_network_id: 0, filtered_string_index: 0 (rename) | -1 },然后consume目标物品和牺牲物品,最后从creative_output50place产物。新名称放在请求的custom_names中(causeanvil_text)。BDS 在槽位 2 中不显示任何东西, 也从不回传结果。- Bedrock 的规则与 Java 不同:附魔的花费是倍率 × 它增加的等级(锋利 IV + IV → V 花费 1,而不是 5),不兼容的附魔被免费丢弃,
穿刺的倍率为 2/1,仅重命名会保留之前的累积惩罚(新重命名的物品
RepairCost为 0),并且重命名也可能"过于昂贵"(Java 将其 上限设为 39)。 - 名称最多 30 个字符,自定义名称是普通字符串,而不是 JSON 文本组件。
- prismarine-item 没有 Bedrock 铁砧(
Item.anvil为 undefined)。
- Bedrock 的规则与 Java 不同:附魔的花费是倍率 × 它增加的等级(锋利 IV + IV → V 花费 1,而不是 5),不兼容的附魔被免费丢弃,
穿刺的倍率为 2/1,仅重命名会保留之前的累积惩罚(新重命名的物品
- bedboo:
lib/bedrock/anvil.js(改编自 prismarine-item 的anvil.js)在检查("Not anvil-able…"、"not enough xp"、 输入顺序)和预测结果时应用 Bedrock 规则。该结果采用 BDS 的 NBT 布局:RepairCost、display.Name、ench、Damage。两者都已 针对合并、合并 + 重命名、重命名、材料修复、整组重命名和附魔升级与服务端副本核对。- 结果按上游
putAway(2)的方式放入物品栏:先放到有空间的相同物品堆,否则放到第一个空槽位。 - 经验检查使用实际采用方向的花费(上游的
smallest在某个方向不可行时总为 0,因此从不拒绝;而 BDS 会拒绝请求并把物品留在铁砧中)。 - 与原版一样,没有任何效果的牺牲物品即使带名称也不产生结果;每次操作前会清空槽位中的残留物品(例如未用完的修复材料)。
- 创造模式下
rename不等待经验变化(上游在创造模式下也会等待,尽管永远等不到)。 - 超过 30 个字符的名称以上游的 "Name is too long." 拒绝(上游上限:35)。
- 结果按上游
村民交易:update_trade 打开窗口,交易项为 NBT
- 影响:
bot.openVillager、bot.trade、Villager的.trades、.selectedTrade、villager.trade(tradeIndex, [times])、Villager的.close()、Villager的ready事件 - Java:
open_window(merchant)加上trade_list(只含已解锁的交易项,带specialPrice、demand和priceMultiplier)。select_trade让服务端移动输入物品,putAway(2)取走产物。打开器只接受villager实体。 - Bedrock: 与村民交互会发送
update_trade { window_id, window_type: 'trading', trade_tier, villager_unique_id, offers: NBT { Recipes: [{ buyA, buyCountA, buyB, buyCountB, sell, uses, maxUses, tier, traderExp, demand, priceMultiplierA/B, netId }] } },并且没有container_open:正是这个数据包打开界面。- 交易项列出所有等级(包括未解锁的)。
buyA.Count是服务端实际收取的价格,已计入村庄英雄等折扣(已验证),buyCountA是基础价格。Damage为 32767 表示任意数据值。 - 没有
select_trade。一次交易是一个 item stack request:craft_recipe { recipe_network_id: netId },然后从trade2_ingredient1/trade2_ingredient2(ui 槽位 4 / 5,已验证)consume,最后从creative_output50place产物。 服务端计入使用次数(下一个update_trade中的uses)。 - 村民实体是
villager_v2;wandering_trader打开同样的窗口。
- 交易项列出所有等级(包括未解锁的)。
- bedboo: 村民插件把
update_trade作为trading容器交给物品栏插件(minecraft:merchant,参见 窗口类型;在另一个窗口打开时或对正在关闭的窗口的刷新不会打开任何东西)。trades列出已解锁的交易项 (tier ≤trade_tier),与 Java 一致。- 价格:
inputItem1.count是基础价格,realPrice是服务端价格。specialPrice是两者之差减去 Java 的 demand 项,因此上游的公式 仍得到realPrice。xp是traderExp。Damage为 32767 的输入在计数和放入时匹配任意数据值。配方 network id 是不可枚举的trade.bedrock.netId。 trade()保留上游的检查和消息,每次使用发送一个请求(putRequirements像上游一样放入输入物品)。由于 Bedrock 没有select_trade,它会先清空存放其他物品的原料槽。产物根据交易项的sell物品预测(包括 NBT,例如附魔书),并像putAway(2)一样放置。已用完的交易会以 "trade blocked" reject。openVillager接受villager_v2、villager和wandering_trader。死亡交易者留在交易输入槽中的物品会随下一个交易窗口返还 (容器已不存在)。
- 价格: