734 lines
23 KiB
JavaScript
734 lines
23 KiB
JavaScript
const assert = require('assert')
|
|
const { Vec3 } = require('vec3')
|
|
const { once } = require('events')
|
|
const { sleep, createDoneTask, createTask, withTimeout } = require('../promise_utils')
|
|
|
|
module.exports = inject
|
|
|
|
// ms to wait before clicking on a tool so the server can send the new
|
|
// damage information
|
|
const DIG_CLICK_TIMEOUT = 500
|
|
// The number of milliseconds to wait for the server to respond with consume completion.
|
|
// This number is larger than the eat time of 1.61 seconds to account for latency and low tps.
|
|
// The eat time comes from https://minecraft.fandom.com/wiki/Food#Usage
|
|
const CONSUME_TIMEOUT = 2500
|
|
// milliseconds to wait for the server to respond to a window click transaction
|
|
const WINDOW_TIMEOUT = 5000
|
|
|
|
const ALWAYS_CONSUMABLES = [
|
|
'potion',
|
|
'milk_bucket',
|
|
'enchanted_golden_apple',
|
|
'golden_apple'
|
|
]
|
|
|
|
function inject (bot, { hideErrors }) {
|
|
const Item = require('prismarine-item')(bot.registry)
|
|
const windows = require('prismarine-windows')(bot.version)
|
|
|
|
let eatingTask = createDoneTask()
|
|
|
|
let nextActionNumber = 0 // < 1.17
|
|
let stateId = -1
|
|
if (bot.supportFeature('stateIdUsed')) {
|
|
const listener = packet => { stateId = packet.stateId }
|
|
bot._client.on('window_items', listener)
|
|
bot._client.on('set_slot', listener)
|
|
}
|
|
const windowClickQueue = []
|
|
let windowItems
|
|
|
|
// 0-8, null = uninitialized
|
|
// which quick bar slot is selected
|
|
bot.quickBarSlot = null
|
|
bot.inventory = windows.createWindow(0, 'minecraft:inventory', 'Inventory')
|
|
bot.currentWindow = null
|
|
bot.heldItem = null
|
|
bot.usingHeldItem = false
|
|
|
|
bot._client.on('entity_status', (packet) => {
|
|
if (packet.entityId === bot.entity.id && packet.entityStatus === 9 && !eatingTask.done) {
|
|
eatingTask.finish()
|
|
}
|
|
bot.usingHeldItem = false
|
|
})
|
|
|
|
let previousHeldItem = null
|
|
bot.on('heldItemChanged', (heldItem) => {
|
|
// we only disable the item if the item type or count changes
|
|
if (
|
|
heldItem?.type === previousHeldItem?.type && heldItem?.count === previousHeldItem?.count
|
|
) {
|
|
previousHeldItem = heldItem
|
|
return
|
|
}
|
|
if (!eatingTask.done) {
|
|
eatingTask.finish()
|
|
}
|
|
bot.usingHeldItem = false
|
|
})
|
|
|
|
bot._client.on('set_cooldown', (packet) => {
|
|
if (bot.heldItem && bot.heldItem.type !== packet.itemID) return
|
|
if (!eatingTask.done) {
|
|
eatingTask.finish()
|
|
}
|
|
bot.usingHeldItem = false
|
|
})
|
|
|
|
async function consume () {
|
|
if (!eatingTask.done) {
|
|
eatingTask.cancel(new Error('Consuming cancelled due to calling bot.consume() again'))
|
|
}
|
|
|
|
if (bot.game.gameMode !== 'creative' && !ALWAYS_CONSUMABLES.includes(bot.heldItem.name) && bot.food === 20) {
|
|
throw new Error('Food is full')
|
|
}
|
|
|
|
eatingTask = createTask()
|
|
|
|
activateItem()
|
|
|
|
await withTimeout(eatingTask.promise, CONSUME_TIMEOUT)
|
|
}
|
|
|
|
function activateItem (offHand = false) {
|
|
bot.usingHeldItem = true
|
|
if (bot.supportFeature('useItemWithBlockPlace')) {
|
|
bot._client.write('block_place', {
|
|
location: new Vec3(-1, 255, -1),
|
|
direction: -1,
|
|
heldItem: Item.toNotch(bot.heldItem),
|
|
cursorX: -1,
|
|
cursorY: -1,
|
|
cursorZ: -1
|
|
})
|
|
} else if (bot.supportFeature('useItemWithOwnPacket')) {
|
|
bot._client.write('use_item', {
|
|
hand: offHand ? 1 : 0
|
|
})
|
|
}
|
|
}
|
|
|
|
function deactivateItem () {
|
|
bot._client.write('block_dig', {
|
|
status: 5,
|
|
location: new Vec3(0, 0, 0),
|
|
face: 5
|
|
})
|
|
bot.usingHeldItem = false
|
|
}
|
|
|
|
async function putSelectedItemRange (start, end, window, slot) {
|
|
// put the selected item back indow the slot range in window
|
|
|
|
// try to put it in an item that already exists and just increase
|
|
// the count.
|
|
|
|
while (window.selectedItem) {
|
|
const item = window.findItemRange(start, end, window.selectedItem.type, window.selectedItem.metadata, true, window.selectedItem.nbt)
|
|
|
|
if (item && item.stackSize !== item.count) { // something to join with
|
|
await clickWindow(item.slot, 0, 0)
|
|
} else { // nothing to join with
|
|
const emptySlot = window.firstEmptySlotRange(start, end)
|
|
if (emptySlot === null) { // no room left
|
|
if (slot === null) { // no room => drop it
|
|
await tossLeftover()
|
|
} else { // if there is still some leftover and slot is not null, click slot
|
|
await clickWindow(slot, 0, 0)
|
|
await tossLeftover()
|
|
}
|
|
} else {
|
|
await clickWindow(emptySlot, 0, 0)
|
|
}
|
|
}
|
|
}
|
|
|
|
async function tossLeftover () {
|
|
if (window.selectedItem) {
|
|
await clickWindow(-999, 0, 0)
|
|
}
|
|
}
|
|
}
|
|
|
|
async function activateBlock (block, direction, cursorPos) {
|
|
direction = direction ?? new Vec3(0, 1, 0)
|
|
const directionNum = vectorToDirection(direction) // The packet needs a number as the direction
|
|
cursorPos = cursorPos ?? new Vec3(0.5, 0.5, 0.5)
|
|
// TODO: tell the server that we are not sneaking while doing this
|
|
await bot.lookAt(block.position.offset(0.5, 0.5, 0.5), false)
|
|
// place block message
|
|
if (bot.supportFeature('blockPlaceHasHeldItem')) {
|
|
bot._client.write('block_place', {
|
|
location: block.position,
|
|
direction: directionNum,
|
|
heldItem: Item.toNotch(bot.heldItem),
|
|
cursorX: cursorPos.scaled(16).x,
|
|
cursorY: cursorPos.scaled(16).y,
|
|
cursorZ: cursorPos.scaled(16).z
|
|
})
|
|
} else if (bot.supportFeature('blockPlaceHasHandAndIntCursor')) {
|
|
bot._client.write('block_place', {
|
|
location: block.position,
|
|
direction: directionNum,
|
|
hand: 0,
|
|
cursorX: cursorPos.scaled(16).x,
|
|
cursorY: cursorPos.scaled(16).y,
|
|
cursorZ: cursorPos.scaled(16).z
|
|
})
|
|
} else if (bot.supportFeature('blockPlaceHasHandAndFloatCursor')) {
|
|
bot._client.write('block_place', {
|
|
location: block.position,
|
|
direction: directionNum,
|
|
hand: 0,
|
|
cursorX: cursorPos.x,
|
|
cursorY: cursorPos.y,
|
|
cursorZ: cursorPos.z
|
|
})
|
|
} else if (bot.supportFeature('blockPlaceHasInsideBlock')) {
|
|
bot._client.write('block_place', {
|
|
location: block.position,
|
|
direction: directionNum,
|
|
hand: 0,
|
|
cursorX: cursorPos.x,
|
|
cursorY: cursorPos.y,
|
|
cursorZ: cursorPos.z,
|
|
insideBlock: false
|
|
})
|
|
}
|
|
|
|
// swing arm animation
|
|
bot.swingArm()
|
|
}
|
|
|
|
async function activateEntity (entity) {
|
|
// TODO: tell the server that we are not sneaking while doing this
|
|
await bot.lookAt(entity.position.offset(0, 1, 0), false)
|
|
bot._client.write('use_entity', {
|
|
target: entity.id,
|
|
mouse: 0, // interact with entity
|
|
sneaking: false,
|
|
hand: 0 // interact with the main hand
|
|
})
|
|
}
|
|
|
|
async function activateEntityAt (entity, position) {
|
|
// TODO: tell the server that we are not sneaking while doing this
|
|
await bot.lookAt(position, false)
|
|
bot._client.write('use_entity', {
|
|
target: entity.id,
|
|
mouse: 2, // interact with entity at
|
|
sneaking: false,
|
|
hand: 0, // interact with the main hand
|
|
x: position.x - entity.position.x,
|
|
y: position.y - entity.position.y,
|
|
z: position.z - entity.position.z
|
|
})
|
|
}
|
|
|
|
async function transfer (options) {
|
|
const window = options.window || bot.currentWindow || bot.inventory
|
|
const itemType = options.itemType
|
|
const metadata = options.metadata
|
|
const nbt = options.nbt
|
|
let count = options.count === null ? 1 : options.count
|
|
let firstSourceSlot = null
|
|
|
|
// ranges
|
|
const sourceStart = options.sourceStart
|
|
const destStart = options.destStart
|
|
assert.notStrictEqual(sourceStart, null)
|
|
assert.notStrictEqual(destStart, null)
|
|
const sourceEnd = options.sourceEnd === null ? sourceStart + 1 : options.sourceEnd
|
|
const destEnd = options.destEnd === null ? destStart + 1 : options.destEnd
|
|
|
|
await transferOne()
|
|
|
|
async function transferOne () {
|
|
if (count === 0) {
|
|
await putSelectedItemRange(sourceStart, sourceEnd, window, firstSourceSlot)
|
|
return
|
|
}
|
|
if (!window.selectedItem || window.selectedItem.type !== itemType ||
|
|
(metadata != null && window.selectedItem.metadata !== metadata) ||
|
|
(nbt != null && window.selectedItem.nbt !== nbt)) {
|
|
// we are not holding the item we need. click it.
|
|
const sourceItem = window.findItemRange(sourceStart, sourceEnd, itemType, metadata, false, nbt)
|
|
const mcDataEntry = bot.registry.itemsArray.find(x => x.id === itemType)
|
|
assert(mcDataEntry, 'Invalid itemType')
|
|
if (!sourceItem) throw new Error(`Can't find ${mcDataEntry.name} in slots [${sourceStart} - ${sourceEnd}], (item id: ${itemType})`)
|
|
if (firstSourceSlot === null) firstSourceSlot = sourceItem.slot
|
|
// number of item that can be moved from that slot
|
|
await clickWindow(sourceItem.slot, 0, 0)
|
|
}
|
|
await clickDest()
|
|
|
|
async function clickDest () {
|
|
assert.notStrictEqual(window.selectedItem.type, null)
|
|
assert.notStrictEqual(window.selectedItem.metadata, null)
|
|
let destItem
|
|
let destSlot
|
|
// special case for tossing
|
|
if (destStart === -999) {
|
|
destSlot = -999
|
|
} else {
|
|
// find a non full item that we can drop into
|
|
destItem = window.findItemRange(destStart, destEnd,
|
|
window.selectedItem.type, window.selectedItem.metadata, true, nbt)
|
|
// if that didn't work find an empty slot to drop into
|
|
destSlot = destItem
|
|
? destItem.slot
|
|
: window.firstEmptySlotRange(destStart, destEnd)
|
|
// if that didn't work, give up
|
|
if (destSlot === null) {
|
|
throw new Error('destination full')
|
|
}
|
|
}
|
|
// move the maximum number of item that can be moved
|
|
const destSlotCount = destItem && destItem.count ? destItem.count : 0
|
|
const movedItems = Math.min(window.selectedItem.stackSize - destSlotCount, window.selectedItem.count)
|
|
// if the number of item the left click moves is less than the number of item we want to move
|
|
// several at the same time (left click)
|
|
if (movedItems <= count) {
|
|
await clickWindow(destSlot, 0, 0)
|
|
// update the number of item we want to move (count)
|
|
count -= movedItems
|
|
await transferOne()
|
|
} else {
|
|
// one by one (right click)
|
|
await clickWindow(destSlot, 1, 0)
|
|
count -= 1
|
|
await transferOne()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function extendWindow (window) {
|
|
window.close = () => {
|
|
closeWindow(window)
|
|
window.emit('close')
|
|
}
|
|
|
|
window.withdraw = async (itemType, metadata, count, nbt) => {
|
|
if (bot.inventory.emptySlotCount() === 0) {
|
|
throw new Error('Unable to withdraw, Bot inventory is full.')
|
|
}
|
|
const options = {
|
|
window,
|
|
itemType,
|
|
metadata,
|
|
count,
|
|
nbt,
|
|
sourceStart: 0,
|
|
sourceEnd: window.inventoryStart,
|
|
destStart: window.inventoryStart,
|
|
destEnd: window.inventoryEnd
|
|
}
|
|
await transfer(options)
|
|
}
|
|
window.deposit = async (itemType, metadata, count, nbt) => {
|
|
const options = {
|
|
window,
|
|
itemType,
|
|
metadata,
|
|
count,
|
|
nbt,
|
|
sourceStart: window.inventoryStart,
|
|
sourceEnd: window.inventoryEnd,
|
|
destStart: 0,
|
|
destEnd: window.inventoryStart
|
|
}
|
|
await transfer(options)
|
|
}
|
|
}
|
|
|
|
async function openBlock (block, direction, cursorPos) {
|
|
bot.activateBlock(block, direction, cursorPos)
|
|
const [window] = await once(bot, 'windowOpen')
|
|
extendWindow(window)
|
|
return window
|
|
}
|
|
|
|
async function openEntity (entity) {
|
|
bot.activateEntity(entity)
|
|
const [window] = await once(bot, 'windowOpen')
|
|
extendWindow(window)
|
|
return window
|
|
}
|
|
|
|
function createActionNumber () {
|
|
nextActionNumber = nextActionNumber === 32767 ? 1 : nextActionNumber + 1
|
|
return nextActionNumber
|
|
}
|
|
|
|
function updateHeldItem () {
|
|
bot.heldItem = bot.inventory.slots[bot.QUICK_BAR_START + bot.quickBarSlot]
|
|
bot.entity.heldItem = bot.heldItem
|
|
bot.emit('heldItemChanged', bot.entity.heldItem)
|
|
}
|
|
|
|
function closeWindow (window) {
|
|
bot._client.write('close_window', {
|
|
windowId: window.id
|
|
})
|
|
copyInventory(window)
|
|
bot.currentWindow = null
|
|
bot.emit('windowClose', window)
|
|
}
|
|
|
|
function copyInventory (window) {
|
|
const slotOffset = window.inventoryStart - bot.inventory.inventoryStart
|
|
for (let i = window.inventoryStart; i < window.inventoryEnd; i++) {
|
|
const item = window.slots[i]
|
|
const slot = i - slotOffset
|
|
if (item) {
|
|
item.slot = slot
|
|
}
|
|
if (!Item.equal(bot.inventory.slots[slot], item, true)) bot.inventory.updateSlot(slot, item)
|
|
}
|
|
}
|
|
|
|
function tradeMatch (limitItem, targetItem) {
|
|
return targetItem.type === limitItem.type && targetItem.count >= limitItem.count
|
|
}
|
|
|
|
function expectTradeUpdate (window) {
|
|
const trade = window.selectedTrade
|
|
const hasItem = !!window.slots[2]
|
|
|
|
if (hasItem !== tradeMatch(trade.inputItem1, window.slots[0])) {
|
|
if (trade.hasItems2) {
|
|
return hasItem !== tradeMatch(trade.inputItem2, window.slots[1])
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
async function waitForWindowUpdate (window, slot) {
|
|
if (window.type === 'minecraft:inventory') {
|
|
if (slot >= 1 && slot <= 4) {
|
|
await once(bot.inventory, 'updateSlot:0')
|
|
}
|
|
} else if (window.type === 'minecraft:crafting') {
|
|
if (slot >= 1 && slot <= 9) {
|
|
await once(bot.currentWindow, 'updateSlot:0')
|
|
}
|
|
} else if (window.type === 'minecraft:merchant') {
|
|
const toUpdate = []
|
|
if (slot <= 2 && !window.selectedTrade.tradeDisabled && expectTradeUpdate(window)) {
|
|
toUpdate.push(once(bot.currentWindow, 'updateSlot:2'))
|
|
}
|
|
if (slot === 2) {
|
|
for (const item of bot.currentWindow.containerItems()) {
|
|
toUpdate.push(once(bot.currentWindow, `updateSlot:${item.slot}`))
|
|
}
|
|
}
|
|
await Promise.all(toUpdate)
|
|
}
|
|
}
|
|
|
|
function confirmTransaction (windowId, actionId, accepted) {
|
|
// drop the queue entries for all the clicks that the server did not send
|
|
// transaction packets for.
|
|
// Also reject transactions that aren't sent from mineflayer
|
|
let click = windowClickQueue[0]
|
|
if (click === undefined || !windowClickQueue.some(clicks => clicks.id === actionId)) {
|
|
// mimic vanilla client and send a rejection for faulty transaction packets
|
|
bot._client.write('transaction', {
|
|
windowId,
|
|
action: actionId,
|
|
accepted: true
|
|
// bot.emit(`confirmTransaction${click.id}`, false)
|
|
})
|
|
return
|
|
}
|
|
// shift it later if packets are sent out of order
|
|
click = windowClickQueue.shift()
|
|
|
|
assert.ok(click.id <= actionId)
|
|
while (actionId > click.id) {
|
|
onAccepted()
|
|
click = windowClickQueue.shift()
|
|
}
|
|
assert.ok(click)
|
|
|
|
if (accepted) {
|
|
onAccepted()
|
|
} else {
|
|
onRejected()
|
|
}
|
|
updateHeldItem()
|
|
|
|
function onAccepted () {
|
|
const window = windowId === 0 ? bot.inventory : bot.currentWindow
|
|
if (!window || window.id !== click.windowId) return
|
|
window.acceptClick(click)
|
|
bot.emit(`confirmTransaction${click.id}`, true)
|
|
}
|
|
|
|
function onRejected () {
|
|
bot._client.write('transaction', {
|
|
windowId: click.windowId,
|
|
action: click.id,
|
|
accepted: true
|
|
})
|
|
bot.emit(`confirmTransaction${click.id}`, false)
|
|
}
|
|
}
|
|
|
|
function getChangedSlots (oldSlots, newSlots) {
|
|
assert.equal(oldSlots.length, newSlots.length)
|
|
|
|
const changedSlots = []
|
|
|
|
for (let i = 0; i < newSlots.length; i++) {
|
|
if (!Item.equal(oldSlots[i], newSlots[i])) {
|
|
changedSlots.push(i)
|
|
}
|
|
}
|
|
|
|
return changedSlots
|
|
}
|
|
|
|
async function clickWindow (slot, mouseButton, mode) {
|
|
// if you click on the quick bar and have dug recently,
|
|
// wait a bit
|
|
if (slot >= bot.QUICK_BAR_START && bot.lastDigTime != null) {
|
|
let timeSinceLastDig
|
|
while ((timeSinceLastDig = new Date() - bot.lastDigTime) < DIG_CLICK_TIMEOUT) {
|
|
await sleep(DIG_CLICK_TIMEOUT - timeSinceLastDig)
|
|
}
|
|
}
|
|
const window = bot.currentWindow || bot.inventory
|
|
|
|
assert.ok(mode >= 0 && mode <= 4)
|
|
const actionId = createActionNumber()
|
|
|
|
const click = {
|
|
slot,
|
|
mouseButton,
|
|
mode,
|
|
id: actionId,
|
|
windowId: window.id,
|
|
item: slot === -999 ? null : window.slots[slot]
|
|
}
|
|
|
|
let changedSlots
|
|
if (bot.supportFeature('transactionPacketExists')) {
|
|
windowClickQueue.push(click)
|
|
} else {
|
|
if (
|
|
// this array indicates the clicks that return changedSlots
|
|
[
|
|
0,
|
|
// 1,
|
|
// 2,
|
|
3,
|
|
4
|
|
// 5,
|
|
// 6
|
|
].includes(click.mode)) {
|
|
changedSlots = window.acceptClick(click)
|
|
} else {
|
|
// this is used as a fallback
|
|
const oldSlots = JSON.parse(JSON.stringify(window.slots))
|
|
|
|
window.acceptClick(click)
|
|
|
|
changedSlots = getChangedSlots(oldSlots, window.slots)
|
|
}
|
|
|
|
changedSlots = changedSlots.map(slot => {
|
|
return {
|
|
location: slot,
|
|
item: Item.toNotch(window.slots[slot])
|
|
}
|
|
})
|
|
}
|
|
|
|
// WHEN ADDING SUPPORT FOR OTHER CLICKS, MAKE SURE TO CHANGE changedSlots TO SUPPORT THEM
|
|
if (bot.supportFeature('stateIdUsed')) { // 1.17.1 +
|
|
bot._client.write('window_click', {
|
|
windowId: window.id,
|
|
stateId,
|
|
slot,
|
|
mouseButton,
|
|
mode,
|
|
changedSlots,
|
|
cursorItem: Item.toNotch(window.selectedItem)
|
|
})
|
|
} else if (bot.supportFeature('actionIdUsed')) { // <= 1.16.5
|
|
bot._client.write('window_click', {
|
|
windowId: window.id,
|
|
slot,
|
|
mouseButton,
|
|
action: actionId,
|
|
mode,
|
|
// protocol expects null even if there is an item at the slot in mode 2 and 4
|
|
item: Item.toNotch((mode === 2 || mode === 4) ? null : click.item)
|
|
})
|
|
} else { // 1.17
|
|
bot._client.write('window_click', {
|
|
windowId: window.id,
|
|
slot,
|
|
mouseButton,
|
|
mode,
|
|
changedSlots,
|
|
cursorItem: Item.toNotch(window.selectedItem)
|
|
})
|
|
}
|
|
|
|
if (bot.supportFeature('transactionPacketExists')) {
|
|
const response = once(bot, `confirmTransaction${actionId}`)
|
|
if (!window.transactionRequiresConfirmation(click)) {
|
|
confirmTransaction(window.id, actionId, true)
|
|
}
|
|
const [success] = await withTimeout(response, WINDOW_TIMEOUT)
|
|
.catch(() => {
|
|
throw new Error(`Server didn't respond to transaction for clicking on slot ${slot} on window with id ${window?.id}.`)
|
|
})
|
|
if (!success) {
|
|
throw new Error(`Server rejected transaction for clicking on slot ${slot}, on window with id ${window?.id}.`)
|
|
}
|
|
} else {
|
|
await waitForWindowUpdate(window, slot)
|
|
}
|
|
}
|
|
|
|
async function putAway (slot) {
|
|
const window = bot.currentWindow || bot.inventory
|
|
const promisePutAway = once(window, `updateSlot:${slot}`)
|
|
await clickWindow(slot, 0, 0)
|
|
const start = window.inventoryStart
|
|
const end = window.inventoryEnd
|
|
await putSelectedItemRange(start, end, window, null)
|
|
await promisePutAway
|
|
}
|
|
|
|
async function moveSlotItem (sourceSlot, destSlot) {
|
|
await clickWindow(sourceSlot, 0, 0)
|
|
await clickWindow(destSlot, 0, 0)
|
|
// if we're holding an item, put it back where the source item was.
|
|
// otherwise we're done.
|
|
updateHeldItem()
|
|
if (bot.inventory.selectedItem) {
|
|
await clickWindow(sourceSlot, 0, 0)
|
|
}
|
|
}
|
|
|
|
bot._client.on('transaction', (packet) => {
|
|
// confirm transaction
|
|
confirmTransaction(packet.windowId, packet.action, packet.accepted)
|
|
})
|
|
|
|
bot._client.on('held_item_slot', (packet) => {
|
|
// held item change
|
|
bot.setQuickBarSlot(packet.slot)
|
|
})
|
|
|
|
function prepareWindow (window) {
|
|
if (!windowItems || window.id !== windowItems.windowId) {
|
|
// don't emit windowOpen until we have the slot data
|
|
bot.once(`setWindowItems:${window.id}`, () => {
|
|
extendWindow(window)
|
|
bot.emit('windowOpen', window)
|
|
})
|
|
} else {
|
|
for (let i = 0; i < windowItems.items.length; ++i) {
|
|
const item = Item.fromNotch(windowItems.items[i])
|
|
window.updateSlot(i, item)
|
|
}
|
|
updateHeldItem()
|
|
extendWindow(window)
|
|
bot.emit('windowOpen', window)
|
|
}
|
|
}
|
|
|
|
bot._client.on('open_window', (packet) => {
|
|
// open window
|
|
bot.currentWindow = windows.createWindow(packet.windowId,
|
|
packet.inventoryType, packet.windowTitle, packet.slotCount)
|
|
prepareWindow(bot.currentWindow)
|
|
})
|
|
bot._client.on('open_horse_window', (packet) => {
|
|
// open window
|
|
bot.currentWindow = windows.createWindow(packet.windowId,
|
|
'HorseWindow', 'Horse', packet.nbSlots)
|
|
prepareWindow(bot.currentWindow)
|
|
})
|
|
bot._client.on('close_window', (packet) => {
|
|
// close window
|
|
const oldWindow = bot.currentWindow
|
|
bot.currentWindow = null
|
|
bot.emit('windowClose', oldWindow)
|
|
})
|
|
bot._client.on('set_slot', (packet) => {
|
|
// set slot
|
|
const window = packet.windowId === 0 ? bot.inventory : bot.currentWindow
|
|
if (!window || window.id !== packet.windowId) return
|
|
const newItem = Item.fromNotch(packet.item)
|
|
const oldItem = window.slots[packet.slot]
|
|
window.updateSlot(packet.slot, newItem)
|
|
updateHeldItem()
|
|
bot.emit(`setSlot:${window.id}`, oldItem, newItem)
|
|
})
|
|
bot._client.on('window_items', (packet) => {
|
|
const window = packet.windowId === 0 ? bot.inventory : bot.currentWindow
|
|
if (!window || window.id !== packet.windowId) {
|
|
windowItems = packet
|
|
return
|
|
}
|
|
|
|
// set window items
|
|
for (let i = 0; i < packet.items.length; ++i) {
|
|
const item = Item.fromNotch(packet.items[i])
|
|
window.updateSlot(i, item)
|
|
}
|
|
updateHeldItem()
|
|
bot.emit(`setWindowItems:${window.id}`)
|
|
})
|
|
|
|
/**
|
|
* Convert a vector direction to minecraft packet number direction
|
|
* @param {Vec3} v
|
|
* @returns {number}
|
|
*/
|
|
function vectorToDirection (v) {
|
|
if (v.y < 0) {
|
|
return 0
|
|
} else if (v.y > 0) {
|
|
return 1
|
|
} else if (v.z < 0) {
|
|
return 2
|
|
} else if (v.z > 0) {
|
|
return 3
|
|
} else if (v.x < 0) {
|
|
return 4
|
|
} else if (v.x > 0) {
|
|
return 5
|
|
}
|
|
assert.ok(false, `invalid direction vector ${v}`)
|
|
}
|
|
|
|
bot.activateBlock = activateBlock
|
|
bot.activateEntity = activateEntity
|
|
bot.activateEntityAt = activateEntityAt
|
|
bot.consume = consume
|
|
bot.activateItem = activateItem
|
|
bot.deactivateItem = deactivateItem
|
|
|
|
// not really in the public API
|
|
bot.clickWindow = clickWindow
|
|
bot.putSelectedItemRange = putSelectedItemRange
|
|
bot.putAway = putAway
|
|
bot.closeWindow = closeWindow
|
|
bot.transfer = transfer
|
|
bot.openBlock = openBlock
|
|
bot.openEntity = openEntity
|
|
bot.moveSlotItem = moveSlotItem
|
|
bot.updateHeldItem = updateHeldItem
|
|
}
|