diff --git a/docs/api.md b/docs/api.md index b154409ed..e885e9214 100644 --- a/docs/api.md +++ b/docs/api.md @@ -38,6 +38,15 @@ - [furnace.outputItem()](#furnaceoutputitem) - [furnace.fuel](#furnacefuel) - [furnace.progress](#furnaceprogress) + - [mineflayer.CartographyTable](#mineflayercartographytable) + - [cartographyTable.takeMap()](#cartographytabletakemap) + - [cartographyTable.takeModifier()](#cartographytabletakemodifier) + - [cartographyTable.takeOutput()](#cartographytabletakeoutput) + - [cartographyTable.putMap(itemType, metadata, count)](#cartographytableputmapitemtype-metadata-count) + - [cartographyTable.putModifier(itemType, metadata, count)](#cartographytableputmodifieritemtype-metadata-count) + - [cartographyTable.mapItem()](#cartographytablemapitem) + - [cartographyTable.modifierItem()](#cartographytablemodifieritem) + - [cartographyTable.outputItem()](#cartographytableoutputitem) - [mineflayer.EnchantmentTable](#mineflayerenchantmenttable) - [enchantmentTable "ready"](#enchantmenttable-ready) - [enchantmentTable.targetItem()](#enchantmenttabletargetitem) @@ -325,6 +334,7 @@ - [bot.openContainer(containerBlock or containerEntity, direction?, cursorPos?)](#botopencontainercontainerblock-or-containerentity-direction-cursorpos) - [bot.openChest(chestBlock or minecartchestEntity, direction?, cursorPos?)](#botopenchestchestblock-or-minecartchestentity-direction-cursorpos) - [bot.openFurnace(furnaceBlock)](#botopenfurnacefurnaceblock) + - [bot.openCartographyTable(cartographyTableBlock)](#botopencartographytablecartographytableblock) - [bot.openDispenser(dispenserBlock)](#botopendispenserdispenserblock) - [bot.openEnchantmentTable(enchantmentTableBlock)](#botopenenchantmenttableenchantmenttableblock) - [bot.openAnvil(anvilBlock)](#botopenanvilanvilblock) @@ -543,6 +553,47 @@ How much fuel is left between 0 and 1. How much cooked the input is between 0 and 1. +### mineflayer.CartographyTable + +Extends windows.Window for cartography table +See `bot.openCartographyTable(cartographyTableBlock)`. + +#### cartographyTable.takeMap() + +This function returns a `Promise`, with `item` as its argument upon completion. + + +#### cartographyTable.takeModifier() + +This function returns a `Promise`, with `item` as its argument upon completion. + + +#### cartographyTable.takeOutput() + +This function returns a `Promise`, with `item` as its argument upon completion. + + +#### cartographyTable.putMap(itemType, metadata, count) + +This function returns a `Promise`, with `void` as its argument upon completion. + +#### cartographyTable.putModifier(itemType, metadata, count) + +This function returns a `Promise`, with `void` as its argument upon completion. + +#### cartographyTable.mapItem() + +Returns `Item` instance which is the filled map being affected. + +#### cartographyTable.modifierItem() + +Returns `Item` instance which is the item being used to effect the filled map, e.g. Glass Pane, paper, empty map, etc. + +#### cartographyTable.outputItem() + +Returns `Item` instance which is the output (aka the modified map). + + ### mineflayer.EnchantmentTable Extends windows.Window for enchantment tables @@ -2083,6 +2134,10 @@ Deprecated. Same as `openContainer` Returns a promise on a `Furnace` instance which represents the furnace you are opening. +#### bot.openCartographyTable(cartographyTableBlock) + +Returns a promise on a `CartographyTable` instance which represents the cartography table you are opening. + #### bot.openDispenser(dispenserBlock) Deprecated. Same as `openContainer` diff --git a/lib/loader.js b/lib/loader.js index 9d83b7311..42d089cbd 100644 --- a/lib/loader.js +++ b/lib/loader.js @@ -21,6 +21,7 @@ const plugins = { explosion: require('./plugins/explosion'), fishing: require('./plugins/fishing'), furnace: require('./plugins/furnace'), + cartography_table: require('./plugins/cartography_table'), game: require('./plugins/game'), health: require('./plugins/health'), inventory: require('./plugins/inventory'), diff --git a/lib/plugins/cartography_table.js b/lib/plugins/cartography_table.js new file mode 100644 index 000000000..a74efaebb --- /dev/null +++ b/lib/plugins/cartography_table.js @@ -0,0 +1,82 @@ +const assert = require('assert') + +module.exports = inject + +function inject (bot) { + const allowedWindowTypes = ['minecraft:cartography', 'minecraft:generic', 'minecraft:generic_9'] + + function matchWindowType (window) { + for (const type of allowedWindowTypes) { + if (window.type.startsWith(type)) return true + } + return false + } + + async function openCartographyTable (cartographyTableBlock) { + const cartographyTable = await bot.openBlock(cartographyTableBlock) + if (!matchWindowType(cartographyTable)) { + throw new Error('This is not a cartographyTable-like window') + } + + const mapSlot = 0 + const modifierSlot = 1 + const outputSlot = 2 + + cartographyTable.takeMap = takeMap + cartographyTable.takeModifier = takeModifier + cartographyTable.takeOutput = takeOutput + cartographyTable.putMap = putMap + cartographyTable.putModifier = putModifier + cartographyTable.mapItem = function () { return this.slots[mapSlot] } + cartographyTable.modifierItem = function () { return this.slots[modifierSlot] } + cartographyTable.outputItem = function () { return this.slots[outputSlot] } + + return cartographyTable + + async function takeSomething (item) { + assert.ok(item) + await bot.putAway(item.slot) + return item + } + + async function takeMap () { + return takeSomething(cartographyTable.mapItem()) + } + + async function takeModifier () { + return takeSomething(cartographyTable.modifierItem()) + } + + async function takeOutput () { + return takeSomething(cartographyTable.outputItem()) + } + + async function putMap (itemType, metadata, count) { + await bot.transfer({ + window: cartographyTable, + itemType, + metadata, + count, + sourceStart: cartographyTable.inventoryStart, + sourceEnd: cartographyTable.inventoryEnd, + destStart: mapSlot, + destEnd: mapSlot + 1 + }) + } + + async function putModifier (itemType, metadata, count) { + await bot.transfer({ + window: cartographyTable, + itemType, + metadata, + count, + sourceStart: cartographyTable.inventoryStart, + sourceEnd: cartographyTable.inventoryEnd, + destStart: modifierSlot, + destEnd: modifierSlot + 1 + }) + } + } + + bot.openCartographyTable = openCartographyTable +} diff --git a/lib/plugins/inventory.js b/lib/plugins/inventory.js index 0636faab7..c6f5cae7e 100644 --- a/lib/plugins/inventory.js +++ b/lib/plugins/inventory.js @@ -129,10 +129,7 @@ function inject (bot, { hideErrors }) { bot._client.write('use_item', { hand: offHand ? 1 : 0, sequence, - rotation: { - x: toNotchianYaw(bot.entity.yaw), - y: toNotchianPitch(bot.entity.pitch) - } + rotation: { x: toNotchianYaw(bot.entity.yaw), y: toNotchianPitch(bot.entity.pitch) } }) } } @@ -551,7 +548,7 @@ function inject (bot, { hideErrors }) { } const window = bot.currentWindow || bot.inventory - assert.ok(mode >= 0 && mode <= 6) + assert.ok(mode >= 0 && mode <= 4) const actionId = createActionNumber() const click = { @@ -732,17 +729,6 @@ function inject (bot, { hideErrors }) { const newItem = Item.fromNotch(packet.item) bot._setSlot(packet.slot, newItem, window) }) - // 1.21.9+ uses set_player_inventory for server-initiated inventory changes - // (e.g. console /give) instead of set_slot - bot._client.on('set_player_inventory', (packet) => { - const newItem = Item.fromNotch(packet.contents) - bot._setSlot(packet.slotId, newItem) - }) - bot.inventory.on('updateSlot', (index) => { - if (index === bot.quickBarSlot + bot.inventory.hotbarStart) { - bot.emit('heldItemChanged', bot.heldItem) - } - }) bot._client.on('window_items', (packet) => { const window = packet.windowId === 0 ? bot.inventory : bot.currentWindow if (!window || window.id !== packet.windowId) { diff --git a/test/externalTests/cartographyTable.js b/test/externalTests/cartographyTable.js new file mode 100644 index 000000000..e482cdc2a --- /dev/null +++ b/test/externalTests/cartographyTable.js @@ -0,0 +1,81 @@ +const assert = require('assert') + +const sleep = (ms) => new Promise((resolve) => { setTimeout(resolve, ms) }) + +const waitFor = async (check, maxTicks = 20) => { + for (let i = 0; i < maxTicks; i++) { + await sleep(10) + if (check()) return true + } + return false +} + +module.exports = () => async (bot) => { + // Only test on 1.16+ due to transaction issues on older versions + if (bot.registry.isOlderThan('1.16')) return + + const Item = require('prismarine-item')(bot.registry) + + const cartographyTablePos = bot.entity.position.offset(2, 0, 0).floored() + const mapId = bot.registry.itemsByName.map.id + const paperId = bot.registry.itemsByName.paper.id + + const cartographyTableBlockId = bot.registry.blocksByName.cartography_table.id + + let blockItemsByName + if (bot.supportFeature('itemsAreNotBlocks')) { + blockItemsByName = 'itemsByName' + } else { + blockItemsByName = 'blocksByName' + } + + // Test setup + await bot.test.setInventorySlot(36, new Item(bot.registry[blockItemsByName].cartography_table.id, 1)) + await bot.test.placeBlock(36, cartographyTablePos) + await bot.test.setInventorySlot(37, new Item(mapId, 1)) + await bot.test.setInventorySlot(38, new Item(paperId, 1)) + + assert.strictEqual(bot.blockAt(cartographyTablePos).type, cartographyTableBlockId) + + // Open cartography table + const cartographyTable = await bot.openCartographyTable(bot.blockAt(cartographyTablePos)) + await waitFor(() => cartographyTable.mapItem() !== undefined) + + // Check initial state + assert.strictEqual(cartographyTable.mapItem(), cartographyTable.slots[0]) + assert.strictEqual(cartographyTable.modifierItem(), cartographyTable.slots[1]) + assert.strictEqual(cartographyTable.outputItem(), cartographyTable.slots[2]) + assert.strictEqual(cartographyTable.mapItem(), null) + assert.strictEqual(cartographyTable.modifierItem(), null) + assert.strictEqual(cartographyTable.outputItem(), null) + + // Put map in slot 0 + await cartographyTable.putMap(mapId, null, 1) + await waitFor(() => cartographyTable.mapItem()?.type === mapId) + assert.strictEqual(cartographyTable.mapItem().type, mapId) + assert.strictEqual(cartographyTable.mapItem().count, 1) + + // Put paper in slot 1 (modifier) + await cartographyTable.putModifier(paperId, null, 1) + await waitFor(() => cartographyTable.modifierItem()?.type === paperId) + assert.strictEqual(cartographyTable.modifierItem().type, paperId) + assert.strictEqual(cartographyTable.modifierItem().count, 1) + assert.strictEqual(cartographyTable.mapItem().type, mapId) + assert.strictEqual(cartographyTable.mapItem().count, 1) + + // Take items back + await cartographyTable.takeModifier() + await cartographyTable.takeMap() + await bot.test.wait(500) + assert.strictEqual(cartographyTable.mapItem(), null) + assert.strictEqual(cartographyTable.modifierItem(), null) + + cartographyTable.close() + await waitFor(() => bot.currentWindow === null) + + // Check inventory - items should be back + const mapCount = bot.inventory.count(mapId) + const paperCount = bot.inventory.count(paperId) + assert.strictEqual(mapCount, 1) + assert.strictEqual(paperCount, 1) +} diff --git a/test/externalTests/plugins/testCommon.js b/test/externalTests/plugins/testCommon.js index e623d609b..d3359106a 100644 --- a/test/externalTests/plugins/testCommon.js +++ b/test/externalTests/plugins/testCommon.js @@ -123,19 +123,20 @@ function inject (bot, wrap) { } async function clearInventory () { - // Use bot.chat for /give (server console /give doesn't send inventory - // update packets on 1.21.9+). Use server console for /clear. - bot.chat('/give @a stone 1') - await onceWithCleanup(bot.inventory, 'updateSlot', { + const giveStone = onceWithCleanup(bot.inventory, 'updateSlot', { timeout: 10000, checkCondition: (slot, oldItem, newItem) => newItem?.name === 'stone' }) - const clearMsg = onceWithCleanup(bot, 'message', { - timeout: 10000, + await bot.test.wait(500) + bot.chat('/give @a stone 1') + await giveStone + + const clearInv = onceWithCleanup(bot, 'message', { + timeout, checkCondition: msg => msg.translate === 'commands.clear.success.single' || msg.translate === 'commands.clear.success' }) bot.chat('/clear') - await clearMsg + await clearInv } // you need to be in creative mode for this to work diff --git a/test/internalTest.js b/test/internalTest.js index afd42eaf9..d9a4dbb82 100644 --- a/test/internalTest.js +++ b/test/internalTest.js @@ -22,13 +22,20 @@ for (const supportedVersion of mineflayer.testedVersions) { : JSON.stringify({ text }) } - function generateChunkPacket (chunk) { + function generateChunkPacket (chunk, version) { const lights = chunk.dumpLight() + const biomes = chunk.dumpBiomes?.() + let bitMap = chunk.getMask() + if (bitMap === undefined && chunk.numSections) { + for (let i = 0; i < chunk.numSections; i++) { + bitMap |= (1 << i) + } + } return { x: 0, z: 0, groundUp: true, - biomes: chunk.dumpBiomes !== undefined ? chunk.dumpBiomes() : undefined, + biomes: biomes !== undefined ? biomes : undefined, heightmaps: { type: 'compound', name: '', @@ -36,7 +43,7 @@ for (const supportedVersion of mineflayer.testedVersions) { MOTION_BLOCKING: { type: 'longArray', value: new Array(36).fill([0, 0]) } } }, // send fake heightmap - bitMap: chunk.getMask(), + bitMap, chunkData: chunk.dump(), blockEntities: [], trustEdges: false, @@ -1242,9 +1249,8 @@ for (const supportedVersion of mineflayer.testedVersions) { assert.strictEqual(newItem.type, stoneId) done() }) - // Directly call updateSlot on the inventory to simulate - // the set_player_inventory code path - bot.inventory.updateSlot( + // Use _setSlot to simulate the set_player_inventory code path + bot._setSlot( QUICK_BAR_SLOT + bot.inventory.hotbarStart, stoneItem )