合成與工作站
本頁說明配方、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。死亡交易者留在交易輸入槽中的物品會隨下一個交易窗口返還 (容器已不存在)。
- 價格: