跳到正文
本頁目錄

合成與工作站

本頁說明配方、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(地圖複製、煙花、旗幟 / 書的複製、…) 沒有固定的輸入或輸出。
    • 標籤原料只給出標籤名;參見物品標籤。
  • 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)。

每批合成是一個 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_input ui 28-31,需要玩家物品欄界面(否則狀態 7);工作臺的網格是 ui 32-40(按行優先;形狀可以放在任意位置, 也接受混用同一標籤的不同成員)。已驗證的請求結構(一個請求,任意次數的合成):

    1. place × n(物品欄 → 網格格子),
    2. craft_recipe { recipe_network_id, times_crafted }(舊版 id 12),
    3. results_deprecated { result_items: [{ type: 'name', legacy_type: 1, name, metadata, count (per craft), block_runtime_id, extra }], times_crafted }(19),
    4. 對每個網格格子 consume(5)(count = 次數),
    5. 對每個結果,從 creative_output 50(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,然後 consume choice + 1 個青金石 (創造模式下不消耗),然後 consume 物品,最後把產物從 creative_output 50 place 回物品槽。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 附魔,ench NBT 與 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_output 50 place 產物。新名稱放在請求的 custom_names 中(cause anvil_text)。BDS 在槽位 2 中不顯示任何東西, 也從不回傳結果。
    • Bedrock 的規則與 Java 不同:附魔的花費是倍率 × 它增加的等級(鋒利 IV + IV → V 花費 1,而不是 5),不兼容的附魔被免費丟棄, 穿刺的倍率為 2/1,僅重命名會保留之前的累積懲罰(新重命名的物品 RepairCost 為 0),並且重命名也可能"過於昂貴"(Java 將其 上限設為 39)。
    • 名稱最多 30 個字符,自定義名稱是普通字符串,而不是 JSON 文本組件。
    • prismarine-item 沒有 Bedrock 鐵砧(Item.anvil 為 undefined)。
  • 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_output 50 place 產物。 服務端計入使用次數(下一個 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。死亡交易者留在交易輸入槽中的物品會隨下一個交易窗口返還 (容器已不存在)。