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