物品欄與窗口
本頁說明 bedboo 如何在 Bedrock 服務端權威的物品欄之上保留 mineflayer 的 Java 佈局物品欄 API(bot.inventory、
bot.currentWindow、槽位編號、clickWindow、transfer、deposit / withdraw、事件),以及由此帶來的少數可見差異。
觀測環境為 BDS 1.26.51.1(協議 2193)。
clickWindow 由 item stack request 模擬
- 影響:
bot.clickWindow,以及所有基於它的操作(transfer、putAway、moveSlotItem、putSelectedItemRange、toss、tossStack、equip、unequip、simpleClick、窗口的deposit/withdraw) - Java: 客戶端發送
window_click(slot、button、mode);服務端執行點擊並同步窗口。 - Bedrock: 沒有點擊數據包。客戶端在明確的容器槽位(
hotbar、inventory、armor、offhand、cursor、container、barrel、shulker、熔爐 / 釀造臺 / 工作站的 ui 槽位)之間發送item_stack_request動作(take、place、swap、drop、destroy、craft_creative、…),服務端以item_stack_response應答(ok + 結果數量 / stack id,或錯誤碼)。請求之後服務端不會重發物品欄。光標移動、跨容器移動以及craft_creative只在打開某個界面時才被接受 (否則返回狀態 7 / 50)。 - bedboo:
lib/bedrock/click.js按 Java(原版 1.21doClick/quickMoveStack)的結果把每種點擊模式規劃為 stack request 步驟:- 模式 0(左鍵 / 右鍵,窗口外 = 丟棄)、模式 1(Shift,含 Java 按菜單區分的規則,包括盔甲 → 盔甲槽;工作站規則——熔爐的燃料與原料、 釀造臺的藥瓶 / 原料 / 燃料、附魔臺的青金石、織布機、製圖臺、鍛造臺、信標——來自窗口定義並使用物品名列表,因為這裡沒有熔煉 / 釀造配方數據)、模式 2(數字鍵,40 = 副手)、模式 3(創造模式複製)和模式 4(丟棄鍵)。
- 模型預測結果(每個變化的槽位觸發一次
updateSlot,與 Java 1.17+ 相同),每次點擊發送一個請求並等待應答,應答會更新數量和 stack id(5 s 超時之後才到達的應答仍會被應用)。點擊可以重疊:參見重疊的請求。 - 被拒絕的請求會回滾,
clickWindow拋出 mineflayer 自己的消息Server rejected transaction for clicking on slot <s>, on window with id <id>.(無應答時:Server didn't respond to transaction …)。 - 對
bot.inventory的點擊會打開玩家自己的物品欄界面(interact open_inventory),並在最後一次點擊後 100 ms、光標為空時關閉它。 - 模式 5(拖拽)和 6(雙擊)拋出 "not implemented",與 prismarine-windows 一致。點擊合成 / 交易的結果槽需要
bot.craft的合成請求(合成),因此會拋出錯誤。 - Java 服務端會靜默忽略無效的放置(例如把泥土放進盔甲槽);規劃器對盔甲槽和結果槽應用 Java 的
Slot.mayPlace,其餘被 BDS 拒絕的情況以拒絕錯誤的形式拋出。
重疊的 item stack request
- 影響: 調用重疊時的
bot.clickWindow及所有基於它的操作(未 await 的點擊、armor-manager / tool / auto-eat 的裝備操作與用戶代碼競爭、丟棄物品時的transfer),窗口的deposit/withdraw - Java: 客戶端立即把每次點擊應用到窗口(1.17+ 預測),服務端用
set_slot/window_items糾正錯誤的槽位,因此重疊的點擊會自愈。 - Bedrock: 每次點擊都是一個
item_stack_request;基於先前請求預測結果規劃的請求,會把尚未應答的請求 id 作為該槽位的 stack id;BDS 按發送順序應答;被拒絕的請求(例如狀態 55:把泥土放進副手,隨後引用它的請求得到 49)只收到錯誤應答——不會重發任何內容。 - bedboo: 物品欄模型按槽位記錄修改了該槽位、仍在進行中的請求。
- 一個請求結束後,把它在該槽位上的結果(服務端的數量和 stack id,若被拒絕則為它自己的起始狀態)交給該槽位上的下一個請求, 而不是覆蓋那個請求的預測;只有槽位上的最後一個請求才寫入槽位。
- 因此連鎖拒絕最終會回到第一個請求之前的狀態——或者回到這些請求進行期間服務端最後為該槽位發送的內容(拾取、
/give)。 基於這種服務端更新規劃的請求會把它作為基準;而服務端重發的恰好是這些請求規劃時所基於的狀態時,界面仍顯示它們的預測 (物品欄重新同步)。 - 遲到(超時)的應答按順序應用。期間已關閉的容器窗口的應答不再影響下一個窗口。
- 物品欄界面已打開時,點擊的預測會同步提交(否則在等待界面打開後重新規劃),因此不等待上一次點擊就發出的點擊會基於它進行, 與 Java 客戶端側點擊一致。
被服務端修改槽位搶先的點擊會重新規劃
- 影響: 在拾取掉落物、執行
/give、漏斗或其他玩家修改請求涉及的槽位時,bot.clickWindow及所有基於它的操作 (transfer、deposit/withdraw、moveSlotItem、equip、toss/tossStack、putAway、合成) - Java: 服務端把
window_click應用到自己當前的狀態(並重新同步);它永遠不會失敗。 - Bedrock: item stack request 以客戶端所知的 stack id 指明每個槽位;若服務端在此期間修改了該槽位(拾取:帶有新 stack id
的 normal 類型
inventory_transaction),BDS 會拒絕整個請求(狀態 49 stack id 不匹配 / 50)。實測:一個箱子 存入 / 取出循環,每 400 ms 執行一次控制台/give,88 次操作中有 6 次被拒絕("Server rejected transaction for clicking on slot 27 …")。 - bedboo: 若某個服務端數據包把進行中請求的某個槽位設為與該請求規劃時不同的物品堆,該請求被標記為 raced
(
lib/bedrock/inventory_model.js);被 BDS 拒絕的 raced 請求會基於服務端狀態重新規劃一次並再次發送(item stack request 是原子的:被拒絕的請求沒有改變任何東西)。鏈接在它之上(基於它的預測規劃)的請求以同樣方式重試。沒有此類變化的拒絕仍會拋出 mineflayer 的消息。
關閉窗口時放回光標上的物品和工作站輸入
- 影響:
bot.closeWindow、窗口的close() - Java: 關閉時服務端把光標上的物品(以及合成網格 / 鐵砧 / 附魔臺的輸入)放回玩家物品欄(
placeItemBackInInventory)。 - Bedrock: BDS 保留它們:光標物品留在
cursor(ui 槽位 0),工作站輸入留在各自的 ui 槽位(在container_close之後觀測到)。Java < 1.17 在close_window之後會發送一次空操作點擊;Bedrock 兩者都沒有。 - bedboo: 與上游一樣,
closeWindow立即清空bot.currentWindow並觸發windowClose。隨後按 Java 的規則用一個 stack request 移動這些物品(先放到選中的快捷欄槽位、副手和有空間的物品堆,再放到第一個空槽位,剩餘的丟出),併發送container_close { window_id, window_type, server: false }(沒有需要放回的物品時只發送container_close)。返回的 promise 在服務端回顯時 resolve,打開器會等待它再加 100 ms(2 tick)後才激活下一個容器:BDS 會靜默丟棄在上一個容器關閉的同一 tick 內到達的容器激活(沒有container_open,也沒有重新同步)。方塊實體(箱子、熔爐、釀造臺、…)像 Java 一樣保留其內容。bot.closeWindow(bot.inventory)會關閉由 bot 打開的物品欄界面。
在另一個窗口打開時打開窗口
- 影響:
bot.openBlock、bot.openEntity、bot.openContainer/openChest/openDispenser、工作站打開器 (openFurnace、openAnvil、openEnchantmentTable、openVillager、使用工作臺的craft),bot.activateBlock/bot.activateEntity/bot.activateEntityAt、windowClose - Java:
ServerPlayer.openMenu先關閉當前菜單(close_window→ mineflayer 觸發windowClose),再打開新菜單; 從不關閉箱子的代碼(mineflayer-collectblock 的emptyInventory)也能工作。 - Bedrock: 玩家已打開容器時,BDS 會忽略會打開另一個容器的激活——既沒有
container_open也沒有container_close—— 因此openBlock會等滿once()的 20 s 後拋出錯誤(已實測驗證:先箱子後木桶,以及同一個箱子打開兩次)。其他激活(拉桿) 在容器打開時照常處理,容器保持打開——與 Java 相同。 - bedboo:
openBlock/openEntity、工作站打開器,以及對容器或工作站方塊(箱子、木桶、潛影盒、熔爐、工作臺 / 附魔臺 / 鍛造臺、鐵砧、漏斗、發射器、投擲器、釀造臺、信標、砂輪、切石機、織布機、製圖臺、合成器、講臺)或交易 / 存儲實體(村民、 流浪商人、運輸礦車 / 漏斗礦車、運輸船)的activateBlock/activateEntity(At),都會先用bot.closeWindow關閉已打開的容器窗口(立即觸發windowClose,放回光標和工作站物品,container_close,服務端回顯),然後等待 BDS 的 100 ms 重新打開延遲再激活。其他激活不會關閉窗口。
容器的實體或方塊已不存在
- 影響:
windowClose、bot.currentWindow,交易者死亡或消失、方塊被破壞時的交易(bot.openVillager、bot.trade) 和工作站窗口(鐵砧、附魔臺、熔爐、…) - Java: 服務端關閉菜單(
stillValid失敗)並歸還輸入槽中的物品。 - Bedrock: BDS 只發送
remove_entity(或方塊更新):沒有container_close,也不歸還任何物品。交易 / 工作站輸入槽中的物品 位於玩家的ui容器中;BDS 拒絕把它們從這個已失效的界面中移出(狀態 49,無效的源槽位)並把它們留在那裡——下一個使用這些槽位的 窗口會顯示它們。 - bedboo: 物品欄插件把容器實體消失(窗口實體的
entityGone)或其方塊變為另一種方塊(燃燒的熔爐仍是熔爐,開裂的鐵砧仍是鐵砧) 視為服務端關閉:windowClose、currentWindow置為 null,然後發送container_close(不發送註定失敗的歸還請求;下一個打開器 等待其回顯)。離開交互範圍不會關閉窗口(BDS 什麼也不發送;未處理)。openVillager會立即歸還它在交易輸入槽中發現的物品 (新打開的 Java 交易窗口是空的),因此死亡交易者收下的付款會隨下一個交易窗口返還。update_trade.villager_unique_id是 zigzag varint,而 bedrock-protocol 3.60.1 按無符號讀取;它在inventory.js中解碼。
窗口類型:為 Bedrock 容器使用 Java 類型
- 影響:
windowOpen、bot.currentWindow、bot.openBlock、bot.openEntity、bot.openContainer(openChest、openDispenser)、工作站打開器 - Java:
open_window攜帶 Java 窗口類型;window_items攜帶槽位。 - Bedrock:
container_open { window_id, window_type, coordinates, runtime_entity_id }+ 該 window id 的inventory_content;所有類箱子方塊(箱子、陷阱箱 / 末影箱 / 銅箱子、木桶、潛影盒)都是window_type: 'container', 通過coordinates處的方塊區分;工作站輸入(合成網格、鐵砧、附魔臺、砂輪、切石機、織布機、製圖臺、鍛造臺、信標、交易) 位於ui容器中。Bedrock 在container_open之後立即以窗口自己的 window id 發送其內容,並把玩家物品欄作為獨立容器 (inventory)同步。 - bedboo: 註冊表
bot._bedrock.windows把每種容器類型映射到 Java 1.21 的窗口類型和槽位佈局 (minecraft:generic_9x3/9x6/3x3、hopper、shulker_box、crafting、furnace、blast_furnace、smoker、brewing_stand、enchantment、anvil、merchant、beacon、grindstone、stonecutter、loom、cartography、smithing(1.20+ 模板 / 基礎 / 附加 / 結果)、crafter_3x3、lectern、HorseWindow),因此openContainer和工作站打開器的類型檢查無需修改即可通過。窗口中的玩家部分鏡像bot.inventory。複用 window id 時提前到達的內容會被緩存並使用,關閉之後到達的更新總會到達bot.inventory。openContainer也接受 Bedrock 方塊名undyed_shulker_box和各種銅箱子。馬和講臺窗口尚未在 BDS 上驗證。在容器窗口打開時打開另一個窗口,會先關閉前者,與 Java 服務端一致(切換窗口)。
updateSlot 僅在變化時觸發
- 影響: 窗口的
updateSlot/updateSlot:<n>事件、heldItemChanged - Java: 每個
set_slot/window_items都會調用updateSlot(即使槽位沒有變化)。 - Bedrock: 沒有
set_slot。每條命令、遊戲模式變更和被拒絕的事務之後,BDS 都會重發所有物品欄窗口 (inventory_content× 4)。 - bedboo: 只改變 stack id 的重發會靜默替換 Item;只有類型、數量、metadata 或 NBT 變化時才觸發
updateSlot(進而觸發heldItemChanged)。手持槽位的inventory_slot/inventory_content會觸發heldItemChanged。
沒有 Java 那樣的窗口重新同步請求
- 影響:
bot._syncWindow(內部) - Java(1.17.1+): 不可能的點擊會讓服務端重發窗口。
- Bedrock: 被拒絕的 item stack request 之後 BDS 不會重發任何內容(已實測驗證:只到達錯誤應答),並且每個請求都會得到應答。
- bedboo:
_syncWindow立即 resolve;模型自行回滾被拒絕的預測(參見重疊的請求)。 發現過期的槽位通過物品欄重新同步修復。
物品欄重新同步(inventory_mismatch)
- 影響: 失步後的
bot.inventory、clickWindow(及所有基於它的操作)、Bedrock 擴展bot.bedrock.resyncInventory()(bot.bedrock) - Java: 只要客戶端視圖過期(state id),服務端就會重發窗口。
- Bedrock(在 BDS 1.26.51.1 上實測): 客戶端的
inventory_transaction { transaction_type: 'inventory_mismatch' }會讓 BDS 在一個 tick 內重發玩家的整個物品欄——物品欄、盔甲、ui(光標)和副手窗口的inventory_content,然後是player_hotbar——沒有其他副作用。其他方式都不行:(重新)打開物品欄界面不行,被拒絕的請求不行(對服務端沒有其 stack id 的槽位返回狀態 49,並且該槽位在之後的每個請求中都會被拒絕),/clear <player> <item> -1 0也不行。 - bedboo: 服務端數據包總是優先(
inventory_content、inventory_slot、服務端事務按到達順序應用)。- 當模型發現某個玩家槽位過期時,會請求服務端的副本(自動重新同步每秒最多一次——提前請求的會在這一秒結束時發出)。觸發條件:
- 一個以狀態 49 被拒絕、且沒有服務端數據包能解釋的請求(不是 raced,也不是基於另一個被拒絕的請求規劃的)。若重發的副本與
請求規劃時所基於的不同,已規劃的請求(
clickWindow、returnItems、合成)會基於它重新規劃一次,否則拒絕成立(無法重新 規劃的請求立即 reject,重新同步同時進行); - 一個服務端
inventory_transaction(拾取、/give),其old_item既不是本地物品也不是新物品,且沒有請求正在修改該槽位; - 一個應答在模型認為為空的槽位中統計到物品。
- 一個以狀態 49 被拒絕、且沒有服務端數據包能解釋的請求(不是 raced,也不是基於另一個被拒絕的請求規劃的)。若重發的副本與
請求規劃時所基於的不同,已規劃的請求(
- 重新同步以應答中的
player_hotbar { select_slot: true }結束(命令觸發的重發以select_slot: false結束)。bot.bedrock.resyncInventory()→Promise<boolean>顯式請求重新同步(應用後為 true,2 s 內無應答為 false);診斷信息在bot._bedrock.inventoryModel.resyncs中。 - 在請求進行中到達、且只重複服務端已知槽位副本(stack id、類型、數量、metadata、NBT)的重發,不會替換這些請求的預測:服務端會 把它們應用到該狀態上,因此期間規劃的點擊基於預測進行,應答會補全預測的物品(否則在這種重發之後才得到應答的 swap 會顯示舊物品和新數量)。
- 當模型發現某個玩家槽位過期時,會請求服務端的副本(自動重新同步每秒最多一次——提前請求的會在這一秒結束時發出)。觸發條件:
- 拾取: 拾取以
take_item_entity+ 服務端inventory_transaction(normal:容器槽位的舊物品和新物品,然後是一個world_interaction動作)到達,從不以inventory_slot到達。普通挖掘和拾取(合併到快捷欄和主物品欄的物品堆、物品欄已滿、 打開箱子時、mineflayer-collectblock)的物品數量都保持正確。曾出現的失步(約 40 輪中 1 次)源於一堆煤被重疊的moveSlotItem來回移動、同時拾取的物品合併進來:拾取落在該物品堆上時,一個從它取出的請求以及基於該取出規劃的點擊正在進行中; BDS 拒絕了兩者(49:物品堆已變化 / 它引用的請求被拒絕),回滾把拾取之前的物品堆寫了回去——bot.inventory顯示 28 個煤 (stack 236),而服務端有 29 個(stack 237),之後對該物品堆的每次點擊都被拒絕。按槽位的請求鏈 (重疊的請求)修復了這一問題:被拒絕的請求回到服務端最後為該槽位發送的內容,基於服務端更新規劃的請求 把它作為基準。上述重新同步會修復任何殘留的失步。
下界傳送門旅行後的物品欄界面
- 影響:
bot.equip(盔甲、副手)、bot.toss、使用物品欄網格合成、bot.moveSlotItem——所有需要玩家物品欄界面的請求 (lib/bedrock/inventory_model.js的screen.open()) - Java: 物品欄點擊不需要界面。
- Bedrock: 對玩家物品欄的 item stack request 需要物品欄界面處於打開狀態(
interact open_inventory→container_open window_type inventory)。在 BDS 1.26.51.1 上實測:玩家站在下界傳送門中時,以及走出到達傳送門後約 1–2 s 內,BDS 會靜默丟棄open_inventory(沒有container_open,也沒有其他任何東西)。命令傳送 (/execute in nether run tp)不會這樣。 - bedboo:
screen.open()每 1.5 s 重複一次open_inventory請求,直到界面打開(超時 8 s)。沒有這一處理時,傳送門旅行後的 第一次裝備會以 "the server did not open the inventory screen" 失敗。站在傳送門內仍然會失敗(原版行為):先走出到達傳送門。
手持物品選擇
- 影響:
bot.quickBarSlot、bot.setQuickBarSlot、heldItemChanged - Java: 雙向都使用
held_item_slot。 - Bedrock: 服務端用
player_hotbar { selected_slot, select_slot }選擇(select_slot: false僅作通知);客戶端用攜帶精確 手持物品的mob_equipment告知自己的選擇。 - bedboo: 如上所述;
bot._bedrock.heldNetworkItem()從物品欄模型返回手持槽位的精確線上物品(每個inventory_transaction都會用到)。
服務端的快捷欄選擇可能比 bot 自己的選擇更舊
- 影響: 剛調用
setQuickBarSlot之後的bot.quickBarSlot、bot.heldItem、heldItemChanged - Java: 服務端的
held_item_slot和客戶端的held_item_change在實踐中不會交錯。 - Bedrock: BDS 對控制台命令(
/clear、/replaceitem、/give)的應答是攜帶其所選槽位的player_hotbar { select_slot: true }。如果這樣的命令在 BDS 讀取 bot 最新的mob_equipment選擇之前執行,該選擇就是過期的: 跟隨它會讓 bot 切回之前的槽位,而 BDS 隨後又應用 bot 的選擇——客戶端與服務端保持不同步。 - bedboo: 與 bot 在不到 1 s 前發送的選擇相矛盾的服務端選擇(
player_hotbar或 bot 自己的mob_equipment)會被忽略, 並重新發送 bot 的選擇(重發不會延長這個時間窗口)。
副手:只能放副手物品
- 影響:
bot.equip(item, 'off-hand')、對槽位 45 的bot.clickWindow、inventory示例 - Java: 任何物品都可以放進副手槽。
- Bedrock: 副手只接受帶有副手組件的物品(盾牌、不死圖騰、箭、煙花火箭、地圖、鸚鵡螺殼、…);BDS 拒絕其他物品(狀態 50)。
在線上,stack request 中的副手槽是
offhand槽位 1(槽位 0 會被拒絕),而inventory_content/inventory_slot使用索引 0。 - bedboo: 槽位 45 映射到
offhand1;裝備非副手物品會以點擊錯誤 reject。因此在examples/inventory.js中,equip off-hand dirt會被拒絕;equip off-hand arrow可以工作(物品列表隨後以arrow x 16結尾,因為槽位 45 最後列出)。
創造模式物品欄:用 craft_creative 代替 set_creative_slot
- 影響:
bot.creative.setInventorySlot、bot.creative.clearSlot、bot.creative.clearInventory - Java:
set_creative_slot可以把任意物品(帶任意 NBT)寫入玩家槽位。 - Bedrock: 創造模式玩家在物品欄界面中用 item stack request 創建物品:
craft_creative { item_id: <creative_content entry id> }(+ 從creative_output槽位 50place,以請求 id 作為 stack id);destroy刪除物品。只能創建服務端創造模式物品欄(creative_content:所有基礎物品加上列出的 NBT 變體,例如附魔書) 中的物品——任意 NBT(自定義名稱、其他附魔、損耗值)都不行;舊式的創造模式inventory_transaction會被 BDS 拒絕(它會重發 物品欄)。容器打開時craft_creative會被拒絕(狀態 7)。 - bedboo: 簽名和檢查與上游相同(相同物品 → 無操作,對同一槽位的第二個併發調用 →
Setting slot <n> cancelled …)。 替換已佔用的槽位是在一個請求中執行destroy+craft_creative+place;也接受槽位 45(副手);不在創造模式物品欄中的物品、 槽位 0(合成結果)或打開的窗口會以說明性錯誤 reject。waitTimeout未使用(服務端總會應答)。創造模式索引是bot._bedrock.creativeItems。創造模式條目全都帶 NBT 的普通物品:參見創造模式默認變體。
從創造模式物品欄獲取普通物品
- 影響: 對不帶 NBT 的
firework_rocket、firework_star或banner調用bot.creative.setInventorySlot - Java: 任意物品,任意 NBT。
- Bedrock: 創造模式物品欄只列出這些物品帶 NBT 的版本(煙花火箭
Fireworks {Flight: 1, Explosions: []},旗幟Type: 0,煙火之星按顏色區分)。 - bedboo: 沒有精確條目的無 NBT 物品,在 NBT 標籤最少的條目恰好只有一個時(默認變體)獲得該條目;槽位隨後持有該 NBT。 附魔書(所有變體都一樣)仍被拒絕。
使用物品、進食和盾牌
- 影響:
bot.activateItem、bot.deactivateItem、bot.consume、bot.usingHeldItem - Java:
use_item(hand、sequence、rotation)/block_digstatus 5;進食以entity_status9 結束。 - Bedrock:
inventory_transaction item_use click_air(手持物品 = 服務端的精確副本;沒有 rotation 字段——方向取自同一 tick 的player_auth_input的 yaw / pitch),item_release release;一次 click_air 即可進食:32 tick 後服務端發送completed_using_item { use_method: 'eat' }和減少後的物品堆。盾牌沒有使用動作:Bedrock 玩家在任一隻手持盾並潛行時格擋。 和平難度下飢餓值從不下降(飢餓效果只會清空飽和度)。 - bedboo:
activateItem(offHand)發送 click_air(offHand時為hand: off_hand+ 副手物品);持盾時改為保持潛行 (deactivateItem鬆開)。這個潛行屬於盾牌本身:clearControlStates()或鬆開潛行控制——mineflayer-pathfinder 在 mineflayer-pvp 下兩者都會做——不會解除它,正如它們在 Java 上不會放下舉起的盾牌;getControlState('sneak')會報告它。completed_using_item(或手持物品堆變化,與上游相同)結束consume()並清除usingHeldItem。Java 的set_cooldown處理沒有對應物:Bedrock 的物品冷卻在客戶端(協議 2193 中沒有服務端數據包)。
桶可以堆疊
- 影響: 桶的處理(裝岩漿 / 水)、用桶調用
bot.activateBlock之後的bot.heldItem - Java: 桶的堆疊上限為 1;對液體源使用空桶會把手持物品變為裝滿的桶。
- Bedrock: 空桶可以堆疊(16)。手持 3 個桶調用
bot.activateBlock(<lava source>),會在手中留下 2 個空桶,並把lava_bucket放進另一個槽位。 - bedboo: 庫中無需改動(鏡像服務端的物品欄更新);請統計
lava_bucket/water_bucket物品,而不是檢查手持物品。