Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PrismarineJS
GitHub Repository: PrismarineJS/mineflayer
Path: blob/master/lib/plugins/inventory.js
1467 views
1
const assert = require('assert')
2
const { Vec3 } = require('vec3')
3
const { once, sleep, createDoneTask, createTask, withTimeout } = require('../promise_utils')
4
5
module.exports = inject
6
7
// ms to wait before clicking on a tool so the server can send the new
8
// damage information
9
const DIG_CLICK_TIMEOUT = 500
10
// The number of milliseconds to wait for the server to respond with consume completion.
11
// This number is larger than the eat time of 1.61 seconds to account for latency and low tps.
12
// The eat time comes from https://minecraft.wiki/w/Food#Usage
13
const CONSUME_TIMEOUT = 2500
14
// milliseconds to wait for the server to respond to a window click transaction
15
const WINDOW_TIMEOUT = 5000
16
17
const ALWAYS_CONSUMABLES = [
18
'potion',
19
'milk_bucket',
20
'enchanted_golden_apple',
21
'golden_apple'
22
]
23
24
function inject (bot, { hideErrors }) {
25
const Item = require('prismarine-item')(bot.registry)
26
const windows = require('prismarine-windows')(bot.version)
27
28
let eatingTask = createDoneTask()
29
let sequence = 0
30
31
let nextActionNumber = 0 // < 1.17
32
let stateId = -1
33
if (bot.supportFeature('stateIdUsed')) {
34
const listener = packet => { stateId = packet.stateId }
35
bot._client.on('window_items', listener)
36
bot._client.on('set_slot', listener)
37
}
38
const windowClickQueue = []
39
let windowItems
40
41
// 0-8, null = uninitialized
42
// which quick bar slot is selected
43
bot.quickBarSlot = null
44
bot.inventory = windows.createWindow(0, 'minecraft:inventory', 'Inventory')
45
bot.currentWindow = null
46
bot.usingHeldItem = false
47
48
Object.defineProperty(bot, 'heldItem', {
49
get: function () {
50
return bot.inventory.slots[bot.QUICK_BAR_START + bot.quickBarSlot]
51
}
52
})
53
54
bot.on('spawn', () => {
55
Object.defineProperty(bot.entity, 'equipment', {
56
get: bot.supportFeature('doesntHaveOffHandSlot')
57
? function () {
58
return [bot.heldItem, bot.inventory.slots[8], bot.inventory.slots[7],
59
bot.inventory.slots[6], bot.inventory.slots[5]]
60
}
61
: function () {
62
return [bot.heldItem, bot.inventory.slots[45], bot.inventory.slots[8],
63
bot.inventory.slots[7], bot.inventory.slots[6], bot.inventory.slots[5]]
64
}
65
})
66
})
67
68
bot._client.on('entity_status', (packet) => {
69
if (packet.entityId === bot.entity.id && packet.entityStatus === 9 && !eatingTask.done) {
70
eatingTask.finish()
71
}
72
bot.usingHeldItem = false
73
})
74
75
let previousHeldItem = null
76
bot.on('heldItemChanged', (heldItem) => {
77
// we only disable the item if the item type or count changes
78
if (
79
heldItem?.type === previousHeldItem?.type && heldItem?.count === previousHeldItem?.count
80
) {
81
previousHeldItem = heldItem
82
return
83
}
84
if (!eatingTask.done) {
85
eatingTask.finish()
86
}
87
bot.usingHeldItem = false
88
})
89
90
bot._client.on('set_cooldown', (packet) => {
91
if (bot.heldItem && bot.heldItem.type !== packet.itemID) return
92
if (!eatingTask.done) {
93
eatingTask.finish()
94
}
95
bot.usingHeldItem = false
96
})
97
98
async function consume () {
99
if (!eatingTask.done) {
100
eatingTask.cancel(new Error('Consuming cancelled due to calling bot.consume() again'))
101
}
102
103
if (bot.game.gameMode !== 'creative' && !ALWAYS_CONSUMABLES.includes(bot.heldItem.name) && bot.food === 20) {
104
throw new Error('Food is full')
105
}
106
107
eatingTask = createTask()
108
109
activateItem()
110
111
await withTimeout(eatingTask.promise, CONSUME_TIMEOUT)
112
}
113
114
function activateItem (offHand = false) {
115
bot.usingHeldItem = true
116
sequence++
117
118
if (bot.supportFeature('useItemWithBlockPlace')) {
119
bot._client.write('block_place', {
120
location: new Vec3(-1, 255, -1),
121
direction: -1,
122
heldItem: Item.toNotch(bot.heldItem),
123
cursorX: -1,
124
cursorY: -1,
125
cursorZ: -1
126
})
127
} else if (bot.supportFeature('useItemWithOwnPacket')) {
128
bot._client.write('use_item', {
129
hand: offHand ? 1 : 0,
130
sequence,
131
rotation: { x: 0, y: 0 }
132
})
133
}
134
}
135
136
function deactivateItem () {
137
const body = {
138
status: 5,
139
location: new Vec3(0, 0, 0),
140
face: 5
141
}
142
143
if (bot.supportFeature('useItemWithOwnPacket')) {
144
body.face = 0
145
body.sequence = 0
146
}
147
148
bot._client.write('block_dig', body)
149
150
bot.usingHeldItem = false
151
}
152
153
async function putSelectedItemRange (start, end, window, slot) {
154
// put the selected item back indow the slot range in window
155
156
// try to put it in an item that already exists and just increase
157
// the count.
158
159
while (window.selectedItem) {
160
const item = window.findItemRange(start, end, window.selectedItem.type, window.selectedItem.metadata, true, window.selectedItem.nbt)
161
162
if (item && item.stackSize !== item.count) { // something to join with
163
await clickWindow(item.slot, 0, 0)
164
} else { // nothing to join with
165
const emptySlot = window.firstEmptySlotRange(start, end)
166
if (emptySlot === null) { // no room left
167
if (slot === null) { // no room => drop it
168
await tossLeftover()
169
} else { // if there is still some leftover and slot is not null, click slot
170
await clickWindow(slot, 0, 0)
171
await tossLeftover()
172
}
173
} else {
174
await clickWindow(emptySlot, 0, 0)
175
}
176
}
177
}
178
179
async function tossLeftover () {
180
if (window.selectedItem) {
181
await clickWindow(-999, 0, 0)
182
}
183
}
184
}
185
186
async function activateBlock (block, direction, cursorPos) {
187
direction = direction ?? new Vec3(0, 1, 0)
188
const directionNum = vectorToDirection(direction) // The packet needs a number as the direction
189
cursorPos = cursorPos ?? new Vec3(0.5, 0.5, 0.5)
190
// TODO: tell the server that we are not sneaking while doing this
191
await bot.lookAt(block.position.offset(0.5, 0.5, 0.5), false)
192
// place block message
193
// TODO: logic below can likely be simplified
194
if (bot.supportFeature('blockPlaceHasHeldItem')) {
195
bot._client.write('block_place', {
196
location: block.position,
197
direction: directionNum,
198
heldItem: Item.toNotch(bot.heldItem),
199
cursorX: cursorPos.scaled(16).x,
200
cursorY: cursorPos.scaled(16).y,
201
cursorZ: cursorPos.scaled(16).z
202
})
203
} else if (bot.supportFeature('blockPlaceHasHandAndIntCursor')) {
204
bot._client.write('block_place', {
205
location: block.position,
206
direction: directionNum,
207
hand: 0,
208
cursorX: cursorPos.scaled(16).x,
209
cursorY: cursorPos.scaled(16).y,
210
cursorZ: cursorPos.scaled(16).z
211
})
212
} else if (bot.supportFeature('blockPlaceHasHandAndFloatCursor')) {
213
bot._client.write('block_place', {
214
location: block.position,
215
direction: directionNum,
216
hand: 0,
217
cursorX: cursorPos.x,
218
cursorY: cursorPos.y,
219
cursorZ: cursorPos.z
220
})
221
} else if (bot.supportFeature('blockPlaceHasInsideBlock')) {
222
bot._client.write('block_place', {
223
location: block.position,
224
direction: directionNum,
225
hand: 0,
226
cursorX: cursorPos.x,
227
cursorY: cursorPos.y,
228
cursorZ: cursorPos.z,
229
insideBlock: false,
230
sequence: 0, // 1.19.0+
231
worldBorderHit: false // 1.21.3+
232
})
233
}
234
235
// swing arm animation
236
bot.swingArm()
237
}
238
239
async function activateEntity (entity) {
240
// TODO: tell the server that we are not sneaking while doing this
241
await bot.lookAt(entity.position.offset(0, 1, 0), false)
242
bot._client.write('use_entity', {
243
target: entity.id,
244
mouse: 0, // interact with entity
245
sneaking: false,
246
hand: 0 // interact with the main hand
247
})
248
}
249
250
async function activateEntityAt (entity, position) {
251
// TODO: tell the server that we are not sneaking while doing this
252
await bot.lookAt(position, false)
253
bot._client.write('use_entity', {
254
target: entity.id,
255
mouse: 2, // interact with entity at
256
sneaking: false,
257
hand: 0, // interact with the main hand
258
x: position.x - entity.position.x,
259
y: position.y - entity.position.y,
260
z: position.z - entity.position.z
261
})
262
}
263
264
async function transfer (options) {
265
const window = options.window || bot.currentWindow || bot.inventory
266
const itemType = options.itemType
267
const metadata = options.metadata
268
const nbt = options.nbt
269
let count = (options.count === undefined || options.count === null) ? 1 : options.count
270
let firstSourceSlot = null
271
272
// ranges
273
const sourceStart = options.sourceStart
274
const destStart = options.destStart
275
assert.notStrictEqual(sourceStart, null)
276
assert.notStrictEqual(destStart, null)
277
const sourceEnd = options.sourceEnd === null ? sourceStart + 1 : options.sourceEnd
278
const destEnd = options.destEnd === null ? destStart + 1 : options.destEnd
279
280
await transferOne()
281
282
async function transferOne () {
283
if (count === 0) {
284
await putSelectedItemRange(sourceStart, sourceEnd, window, firstSourceSlot)
285
return
286
}
287
if (!window.selectedItem || window.selectedItem.type !== itemType ||
288
(metadata != null && window.selectedItem.metadata !== metadata) ||
289
(nbt != null && window.selectedItem.nbt !== nbt)) {
290
// we are not holding the item we need. click it.
291
const sourceItem = window.findItemRange(sourceStart, sourceEnd, itemType, metadata, false, nbt)
292
const mcDataEntry = bot.registry.itemsArray.find(x => x.id === itemType)
293
assert(mcDataEntry, 'Invalid itemType')
294
if (!sourceItem) throw new Error(`Can't find ${mcDataEntry.name} in slots [${sourceStart} - ${sourceEnd}], (item id: ${itemType})`)
295
if (firstSourceSlot === null) firstSourceSlot = sourceItem.slot
296
// number of item that can be moved from that slot
297
await clickWindow(sourceItem.slot, 0, 0)
298
}
299
await clickDest()
300
301
async function clickDest () {
302
assert.notStrictEqual(window.selectedItem.type, null)
303
assert.notStrictEqual(window.selectedItem.metadata, null)
304
let destItem
305
let destSlot
306
// special case for tossing
307
if (destStart === -999) {
308
destSlot = -999
309
} else {
310
// find a non full item that we can drop into
311
destItem = window.findItemRange(destStart, destEnd,
312
window.selectedItem.type, window.selectedItem.metadata, true, nbt)
313
// if that didn't work find an empty slot to drop into
314
destSlot = destItem
315
? destItem.slot
316
: window.firstEmptySlotRange(destStart, destEnd)
317
// if that didn't work, give up
318
if (destSlot === null) {
319
throw new Error('destination full')
320
}
321
}
322
// move the maximum number of item that can be moved
323
const destSlotCount = destItem && destItem.count ? destItem.count : 0
324
const movedItems = Math.min(window.selectedItem.stackSize - destSlotCount, window.selectedItem.count)
325
// if the number of item the left click moves is less than the number of item we want to move
326
// several at the same time (left click)
327
if (movedItems <= count) {
328
await clickWindow(destSlot, 0, 0)
329
// update the number of item we want to move (count)
330
count -= movedItems
331
await transferOne()
332
} else {
333
// one by one (right click)
334
await clickWindow(destSlot, 1, 0)
335
count -= 1
336
await transferOne()
337
}
338
}
339
}
340
}
341
342
function extendWindow (window) {
343
window.close = () => {
344
closeWindow(window)
345
window.emit('close')
346
}
347
348
window.withdraw = async (itemType, metadata, count, nbt) => {
349
if (bot.inventory.emptySlotCount() === 0) {
350
throw new Error('Unable to withdraw, Bot inventory is full.')
351
}
352
const options = {
353
window,
354
itemType,
355
metadata,
356
count,
357
nbt,
358
sourceStart: 0,
359
sourceEnd: window.inventoryStart,
360
destStart: window.inventoryStart,
361
destEnd: window.inventoryEnd
362
}
363
await transfer(options)
364
}
365
window.deposit = async (itemType, metadata, count, nbt) => {
366
const options = {
367
window,
368
itemType,
369
metadata,
370
count,
371
nbt,
372
sourceStart: window.inventoryStart,
373
sourceEnd: window.inventoryEnd,
374
destStart: 0,
375
destEnd: window.inventoryStart
376
}
377
await transfer(options)
378
}
379
}
380
381
async function openBlock (block, direction, cursorPos) {
382
bot.activateBlock(block, direction, cursorPos)
383
const [window] = await once(bot, 'windowOpen')
384
extendWindow(window)
385
return window
386
}
387
388
async function openEntity (entity) {
389
bot.activateEntity(entity)
390
const [window] = await once(bot, 'windowOpen')
391
extendWindow(window)
392
return window
393
}
394
395
function createActionNumber () {
396
nextActionNumber = nextActionNumber === 32767 ? 1 : nextActionNumber + 1
397
return nextActionNumber
398
}
399
400
function updateHeldItem () {
401
bot.emit('heldItemChanged', bot.heldItem)
402
}
403
404
function closeWindow (window) {
405
bot._client.write('close_window', {
406
windowId: window.id
407
})
408
copyInventory(window)
409
bot.currentWindow = null
410
bot.emit('windowClose', window)
411
}
412
413
function copyInventory (window) {
414
const slotOffset = window.inventoryStart - bot.inventory.inventoryStart
415
for (let i = window.inventoryStart; i < window.inventoryEnd; i++) {
416
const item = window.slots[i]
417
const slot = i - slotOffset
418
if (item) {
419
item.slot = slot
420
}
421
if (!Item.equal(bot.inventory.slots[slot], item, true)) bot.inventory.updateSlot(slot, item)
422
}
423
}
424
425
function tradeMatch (limitItem, targetItem) {
426
return (
427
targetItem !== null &&
428
limitItem !== null &&
429
targetItem.type === limitItem.type &&
430
targetItem.count >= limitItem.count
431
)
432
}
433
434
function expectTradeUpdate (window) {
435
const trade = window.selectedTrade
436
const hasItem = !!window.slots[2]
437
438
if (hasItem !== tradeMatch(trade.inputItem1, window.slots[0])) {
439
if (trade.hasItem2) {
440
return hasItem !== tradeMatch(trade.inputItem2, window.slots[1])
441
}
442
return true
443
}
444
return false
445
}
446
447
async function waitForWindowUpdate (window, slot) {
448
if (window.type === 'minecraft:inventory') {
449
if (slot >= 1 && slot <= 4) {
450
await once(bot.inventory, 'updateSlot:0')
451
}
452
} else if (window.type === 'minecraft:crafting') {
453
if (slot >= 1 && slot <= 9) {
454
await once(bot.currentWindow, 'updateSlot:0')
455
}
456
} else if (window.type === 'minecraft:merchant') {
457
const toUpdate = []
458
if (slot <= 1 && !window.selectedTrade.tradeDisabled && expectTradeUpdate(window)) {
459
toUpdate.push(once(bot.currentWindow, 'updateSlot:2'))
460
}
461
if (slot === 2) {
462
for (const item of bot.currentWindow.containerItems()) {
463
toUpdate.push(once(bot.currentWindow, `updateSlot:${item.slot}`))
464
}
465
}
466
await Promise.all(toUpdate)
467
468
if (slot === 2 && !window.selectedTrade.tradeDisabled && expectTradeUpdate(window)) {
469
// After the trade goes through, if the inputs are still satisfied,
470
// expect another update in slot 2
471
await once(bot.currentWindow, 'updateSlot:2')
472
}
473
}
474
}
475
476
function confirmTransaction (windowId, actionId, accepted) {
477
// drop the queue entries for all the clicks that the server did not send
478
// transaction packets for.
479
// Also reject transactions that aren't sent from mineflayer
480
let click = windowClickQueue[0]
481
if (click === undefined || !windowClickQueue.some(clicks => clicks.id === actionId)) {
482
// mimic vanilla client and send a rejection for faulty transaction packets
483
bot._client.write('transaction', {
484
windowId,
485
action: actionId,
486
accepted: true
487
// bot.emit(`confirmTransaction${click.id}`, false)
488
})
489
return
490
}
491
// shift it later if packets are sent out of order
492
click = windowClickQueue.shift()
493
494
assert.ok(click.id <= actionId)
495
while (actionId > click.id) {
496
onAccepted()
497
click = windowClickQueue.shift()
498
}
499
assert.ok(click)
500
501
if (accepted) {
502
onAccepted()
503
} else {
504
onRejected()
505
}
506
updateHeldItem()
507
508
function onAccepted () {
509
const window = windowId === 0 ? bot.inventory : bot.currentWindow
510
if (!window || window.id !== click.windowId) return
511
window.acceptClick(click)
512
bot.emit(`confirmTransaction${click.id}`, true)
513
}
514
515
function onRejected () {
516
bot._client.write('transaction', {
517
windowId: click.windowId,
518
action: click.id,
519
accepted: true
520
})
521
bot.emit(`confirmTransaction${click.id}`, false)
522
}
523
}
524
525
function getChangedSlots (oldSlots, newSlots) {
526
assert.equal(oldSlots.length, newSlots.length)
527
528
const changedSlots = []
529
530
for (let i = 0; i < newSlots.length; i++) {
531
if (!Item.equal(oldSlots[i], newSlots[i])) {
532
changedSlots.push(i)
533
}
534
}
535
536
return changedSlots
537
}
538
539
async function clickWindow (slot, mouseButton, mode) {
540
// if you click on the quick bar and have dug recently,
541
// wait a bit
542
if (slot >= bot.QUICK_BAR_START && bot.lastDigTime != null) {
543
let timeSinceLastDig
544
while ((timeSinceLastDig = new Date() - bot.lastDigTime) < DIG_CLICK_TIMEOUT) {
545
await sleep(DIG_CLICK_TIMEOUT - timeSinceLastDig)
546
}
547
}
548
const window = bot.currentWindow || bot.inventory
549
550
assert.ok(mode >= 0 && mode <= 4)
551
const actionId = createActionNumber()
552
553
const click = {
554
slot,
555
mouseButton,
556
mode,
557
id: actionId,
558
windowId: window.id,
559
item: slot === -999 ? null : window.slots[slot]
560
}
561
562
let changedSlots
563
if (bot.supportFeature('transactionPacketExists')) {
564
windowClickQueue.push(click)
565
} else {
566
if (
567
// this array indicates the clicks that return changedSlots
568
[
569
0,
570
// 1,
571
// 2,
572
3,
573
4
574
// 5,
575
// 6
576
].includes(click.mode)) {
577
changedSlots = window.acceptClick(click)
578
} else {
579
// this is used as a fallback
580
const oldSlots = JSON.parse(JSON.stringify(window.slots))
581
582
window.acceptClick(click)
583
584
changedSlots = getChangedSlots(oldSlots, window.slots)
585
}
586
587
changedSlots = changedSlots.map(slot => {
588
return {
589
location: slot,
590
item: Item.toNotch(window.slots[slot])
591
}
592
})
593
}
594
595
// WHEN ADDING SUPPORT FOR OTHER CLICKS, MAKE SURE TO CHANGE changedSlots TO SUPPORT THEM
596
if (bot.supportFeature('stateIdUsed')) { // 1.17.1 +
597
bot._client.write('window_click', {
598
windowId: window.id,
599
stateId,
600
slot,
601
mouseButton,
602
mode,
603
changedSlots,
604
cursorItem: Item.toNotch(window.selectedItem)
605
})
606
} else if (bot.supportFeature('actionIdUsed')) { // <= 1.16.5
607
bot._client.write('window_click', {
608
windowId: window.id,
609
slot,
610
mouseButton,
611
action: actionId,
612
mode,
613
// protocol expects null even if there is an item at the slot in mode 2 and 4
614
item: Item.toNotch((mode === 2 || mode === 4) ? null : click.item)
615
})
616
} else { // 1.17
617
bot._client.write('window_click', {
618
windowId: window.id,
619
slot,
620
mouseButton,
621
mode,
622
changedSlots,
623
cursorItem: Item.toNotch(window.selectedItem)
624
})
625
}
626
627
if (bot.supportFeature('transactionPacketExists')) {
628
const response = once(bot, `confirmTransaction${actionId}`)
629
if (!window.transactionRequiresConfirmation(click)) {
630
confirmTransaction(window.id, actionId, true)
631
}
632
const [success] = await withTimeout(response, WINDOW_TIMEOUT)
633
.catch(() => {
634
throw new Error(`Server didn't respond to transaction for clicking on slot ${slot} on window with id ${window?.id}.`)
635
})
636
if (!success) {
637
throw new Error(`Server rejected transaction for clicking on slot ${slot}, on window with id ${window?.id}.`)
638
}
639
} else {
640
await waitForWindowUpdate(window, slot)
641
}
642
}
643
644
async function putAway (slot) {
645
const window = bot.currentWindow || bot.inventory
646
const promisePutAway = once(window, `updateSlot:${slot}`)
647
await clickWindow(slot, 0, 0)
648
const start = window.inventoryStart
649
const end = window.inventoryEnd
650
await putSelectedItemRange(start, end, window, null)
651
await promisePutAway
652
}
653
654
async function moveSlotItem (sourceSlot, destSlot) {
655
await clickWindow(sourceSlot, 0, 0)
656
await clickWindow(destSlot, 0, 0)
657
// if we're holding an item, put it back where the source item was.
658
// otherwise we're done.
659
updateHeldItem()
660
if (bot.inventory.selectedItem) {
661
await clickWindow(sourceSlot, 0, 0)
662
}
663
}
664
665
bot._client.on('transaction', (packet) => {
666
// confirm transaction
667
confirmTransaction(packet.windowId, packet.action, packet.accepted)
668
})
669
670
bot._client.on('held_item_slot', (packet) => {
671
// held item change
672
bot.setQuickBarSlot(packet.slot)
673
})
674
675
function prepareWindow (window) {
676
if (!windowItems || window.id !== windowItems.windowId) {
677
// don't emit windowOpen until we have the slot data
678
bot.once(`setWindowItems:${window.id}`, () => {
679
extendWindow(window)
680
bot.emit('windowOpen', window)
681
})
682
} else {
683
for (let i = 0; i < windowItems.items.length; ++i) {
684
const item = Item.fromNotch(windowItems.items[i])
685
window.updateSlot(i, item)
686
}
687
updateHeldItem()
688
extendWindow(window)
689
bot.emit('windowOpen', window)
690
}
691
}
692
693
bot._client.on('open_window', (packet) => {
694
// open window
695
bot.currentWindow = windows.createWindow(packet.windowId,
696
packet.inventoryType, packet.windowTitle, packet.slotCount)
697
prepareWindow(bot.currentWindow)
698
})
699
bot._client.on('open_horse_window', (packet) => {
700
// open window
701
bot.currentWindow = windows.createWindow(packet.windowId,
702
'HorseWindow', 'Horse', packet.nbSlots)
703
prepareWindow(bot.currentWindow)
704
})
705
bot._client.on('close_window', (packet) => {
706
// close window
707
const oldWindow = bot.currentWindow
708
bot.currentWindow = null
709
bot.emit('windowClose', oldWindow)
710
})
711
bot._client.on('login', () => {
712
// close window when switch subserver
713
const oldWindow = bot.currentWindow
714
if (!oldWindow) return
715
bot.currentWindow = null
716
bot.emit('windowClose', oldWindow)
717
})
718
bot._setSlot = (slotId, newItem, window = bot.inventory) => {
719
// set slot
720
const oldItem = window.slots[slotId]
721
window.updateSlot(slotId, newItem)
722
updateHeldItem()
723
bot.emit(`setSlot:${window.id}`, oldItem, newItem)
724
}
725
bot._client.on('set_slot', (packet) => {
726
const window = packet.windowId === 0 ? bot.inventory : bot.currentWindow
727
if (!window || window.id !== packet.windowId) return
728
const newItem = Item.fromNotch(packet.item)
729
bot._setSlot(packet.slot, newItem, window)
730
})
731
bot._client.on('window_items', (packet) => {
732
const window = packet.windowId === 0 ? bot.inventory : bot.currentWindow
733
if (!window || window.id !== packet.windowId) {
734
windowItems = packet
735
return
736
}
737
738
// set window items
739
for (let i = 0; i < packet.items.length; ++i) {
740
const item = Item.fromNotch(packet.items[i])
741
window.updateSlot(i, item)
742
}
743
updateHeldItem()
744
bot.emit(`setWindowItems:${window.id}`)
745
})
746
747
/**
748
* Convert a vector direction to minecraft packet number direction
749
* @param {Vec3} v
750
* @returns {number}
751
*/
752
function vectorToDirection (v) {
753
if (v.y < 0) {
754
return 0
755
} else if (v.y > 0) {
756
return 1
757
} else if (v.z < 0) {
758
return 2
759
} else if (v.z > 0) {
760
return 3
761
} else if (v.x < 0) {
762
return 4
763
} else if (v.x > 0) {
764
return 5
765
}
766
assert.ok(false, `invalid direction vector ${v}`)
767
}
768
769
bot.activateBlock = activateBlock
770
bot.activateEntity = activateEntity
771
bot.activateEntityAt = activateEntityAt
772
bot.consume = consume
773
bot.activateItem = activateItem
774
bot.deactivateItem = deactivateItem
775
776
// not really in the public API
777
bot.clickWindow = clickWindow
778
bot.putSelectedItemRange = putSelectedItemRange
779
bot.putAway = putAway
780
bot.closeWindow = closeWindow
781
bot.transfer = transfer
782
bot.openBlock = openBlock
783
bot.openEntity = openEntity
784
bot.moveSlotItem = moveSlotItem
785
bot.updateHeldItem = updateHeldItem
786
}
787
788