mineflayer 3.10.0 → 3.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/.github/FUNDING.yml +0 -2
  2. package/.github/workflows/ci.yml +20 -16
  3. package/README.md +16 -15
  4. package/docs/README.md +16 -15
  5. package/docs/api.md +50 -27
  6. package/docs/es/README_ES.md +1 -1
  7. package/docs/es/api_es.md +34 -18
  8. package/docs/fr/README_FR.md +1 -1
  9. package/docs/history.md +28 -0
  10. package/docs/ru/README_RU.md +1 -1
  11. package/docs/ru/api_ru.md +1 -1
  12. package/docs/tr/README_TR.md +1 -1
  13. package/docs/zh/README_ZH_CN.md +1 -1
  14. package/examples/chatterbox.js +1 -1
  15. package/examples/discord.js +15 -5
  16. package/examples/plugins/afk.js +50 -37
  17. package/examples/python/basic.py +2 -2
  18. package/examples/python/chatterbox.py +1 -1
  19. package/examples/screenshot-with-node-canvas-webgl/screenshot.js +2 -2
  20. package/index.d.ts +108 -134
  21. package/lib/conversions.js +1 -1
  22. package/lib/features.json +57 -27
  23. package/lib/loader.js +5 -4
  24. package/lib/plugins/bed.js +1 -1
  25. package/lib/plugins/block_actions.js +19 -7
  26. package/lib/plugins/blocks.js +17 -4
  27. package/lib/plugins/chat.js +5 -6
  28. package/lib/plugins/command_block.js +1 -1
  29. package/lib/plugins/craft.js +10 -18
  30. package/lib/plugins/creative.js +2 -2
  31. package/lib/plugins/digging.js +15 -6
  32. package/lib/plugins/enchantment_table.js +4 -2
  33. package/lib/plugins/entities.js +13 -8
  34. package/lib/plugins/explosion.js +3 -1
  35. package/lib/plugins/fishing.js +2 -2
  36. package/lib/plugins/game.js +55 -50
  37. package/lib/plugins/inventory.js +164 -40
  38. package/lib/plugins/physics.js +16 -5
  39. package/lib/plugins/place_block.js +7 -6
  40. package/lib/plugins/ray_trace.js +10 -0
  41. package/lib/plugins/simple_inventory.js +1 -1
  42. package/lib/plugins/sound.js +1 -1
  43. package/lib/plugins/spawn_point.js +1 -1
  44. package/lib/plugins/villager.js +57 -42
  45. package/lib/version.js +3 -3
  46. package/package.json +23 -22
@@ -1,4 +1,4 @@
1
- const Vec3 = require('vec3').Vec3
1
+ const { Vec3 } = require('vec3')
2
2
  const assert = require('assert')
3
3
  const Painting = require('../painting')
4
4
  const { onceWithCleanup, callbackify } = require('../promise_utils')
@@ -89,7 +89,12 @@ function inject (bot, { version, storageBuilder }) {
89
89
  }
90
90
  let column = bot.world.getColumn(args.x, args.z)
91
91
  if (!column) {
92
- column = new Chunk()
92
+ // Allocates new chunk object while taking world's custom min/max height into account
93
+ if (bot.supportFeature('dimensionDataIsAvailable')) {
94
+ column = new Chunk({ minY: bot.game.dimensionData.min_y, worldHeight: bot.game.dimensionData.height })
95
+ } else {
96
+ column = new Chunk()
97
+ }
93
98
  }
94
99
 
95
100
  try {
@@ -294,11 +299,19 @@ function inject (bot, { version, storageBuilder }) {
294
299
  bot._client.on('update_light', (packet) => {
295
300
  let column = bot.world.getColumn(packet.chunkX, packet.chunkZ)
296
301
  if (!column) {
297
- column = new Chunk()
302
+ if (bot.supportFeature('dimensionDataIsAvailable')) {
303
+ column = new Chunk({ minY: bot.game.dimensionData.min_y, worldHeight: bot.game.dimensionData.height })
304
+ } else {
305
+ column = new Chunk()
306
+ }
298
307
  bot.world.setColumn(packet.chunkX, packet.chunkZ, column)
299
308
  }
300
309
 
301
- column.loadLight(packet.data, packet.skyLightMask, packet.blockLightMask, packet.emptySkyLightMask, packet.emptyBlockLightMask)
310
+ if (bot.supportFeature('dimensionDataIsAvailable')) {
311
+ column.loadParsedLight(packet.skyLight, packet.blockLight, packet.skyLightMask, packet.blockLightMask, packet.emptySkyLightMask, packet.emptyBlockLightMask)
312
+ } else {
313
+ column.loadLight(packet.data, packet.skyLightMask, packet.blockLightMask, packet.emptySkyLightMask, packet.emptyBlockLightMask)
314
+ }
302
315
  })
303
316
 
304
317
  bot._client.on('map_chunk', (packet) => {
@@ -4,6 +4,7 @@ module.exports = inject
4
4
 
5
5
  function inject (bot, options) {
6
6
  const CHAT_LENGTH_LIMIT = options.chatLengthLimit ?? (bot.supportFeature('lessCharsInChat') ? 100 : 256)
7
+ const defaultChatPatterns = options.defaultChatPatterns ?? true
7
8
 
8
9
  const ChatMessage = require('prismarine-chat')(bot.version)
9
10
  // chat.pattern.type will emit an event for bot.on() of the same type, eg chatType = whisper will trigger bot.on('whisper')
@@ -15,6 +16,7 @@ function inject (bot, options) {
15
16
  }
16
17
 
17
18
  bot.addChatPatternSet = (name, patterns, opts = {}) => {
19
+ if (!patterns.every(p => p instanceof RegExp)) throw new Error('Pattern parameter should be of type RegExp')
18
20
  const { repeat = true, parse = false } = opts
19
21
  _patterns[_length++] = {
20
22
  name,
@@ -29,6 +31,7 @@ function inject (bot, options) {
29
31
  }
30
32
 
31
33
  bot.addChatPattern = (name, pattern, opts = {}) => {
34
+ if (!(pattern instanceof RegExp)) throw new Error('Pattern parameter should be of type RegExp')
32
35
  const { repeat = true, deprecated = false, parse = false } = opts
33
36
  _patterns[_length++] = {
34
37
  name,
@@ -103,12 +106,7 @@ function inject (bot, options) {
103
106
  addDefaultPatterns()
104
107
 
105
108
  bot._client.on('chat', (packet) => {
106
- let msg
107
- try {
108
- msg = new ChatMessage(JSON.parse(packet.message))
109
- } catch (e) {
110
- msg = new ChatMessage(packet.message)
111
- }
109
+ const msg = ChatMessage.fromNotch(packet.message)
112
110
 
113
111
  const ChatPositions = {
114
112
  0: 'chat',
@@ -174,6 +172,7 @@ function inject (bot, options) {
174
172
  bot.tabComplete = callbackify(tabComplete)
175
173
 
176
174
  function addDefaultPatterns () {
175
+ if (!defaultChatPatterns) return
177
176
  const USERNAME_REGEX = '(?:\\(.+\\)|\\[.+\\]|.)*?(\\w+)'
178
177
  bot.addChatPattern('whisper', new RegExp(`^${USERNAME_REGEX} whispers(?: to you)?:? (.*)$`), { deprecated: true })
179
178
  bot.addChatPattern('whisper', new RegExp(`^\\[${USERNAME_REGEX} -> \\w+\\s?\\] (.*)$`), { deprecated: true })
@@ -1,5 +1,5 @@
1
1
  const assert = require('assert')
2
- const ProtoDef = require('protodef').ProtoDef
2
+ const { ProtoDef } = require('protodef')
3
3
 
4
4
  module.exports = inject
5
5
 
@@ -114,10 +114,10 @@ function inject (bot, { version }) {
114
114
 
115
115
  async function grabResult () {
116
116
  assert.strictEqual(window.selectedItem, null)
117
+ // Causes a double-emit on 1.12+ --nickelpro
117
118
  // put the recipe result in the output
118
119
  const item = new Item(recipe.result.id, recipe.result.count, recipe.result.metadata)
119
120
  window.updateSlot(0, item)
120
- // move the result to inventory
121
121
  await bot.putAway(0)
122
122
  await updateOutShape()
123
123
  }
@@ -131,28 +131,20 @@ function inject (bot, { version }) {
131
131
  return
132
132
  }
133
133
  const slotsToClick = []
134
- let x
135
- let y
136
- let row
137
- let item
138
- let theSlot
139
- for (y = 0; y < recipe.outShape.length; ++y) {
140
- row = recipe.outShape[y]
141
- for (x = 0; x < row.length; ++x) {
142
- theSlot = slot(x, y)
134
+ for (let y = 0; y < recipe.outShape.length; ++y) {
135
+ const row = recipe.outShape[y]
136
+ for (let x = 0; x < row.length; ++x) {
137
+ const _slot = slot(x, y)
138
+ let item = null
143
139
  if (row[x].id !== -1) {
144
140
  item = new Item(row[x].id, row[x].count, row[x].metadata || null)
145
- slotsToClick.push(theSlot)
146
- } else {
147
- item = null
141
+ slotsToClick.push(_slot)
148
142
  }
149
- window.updateSlot(theSlot, item)
143
+ window.updateSlot(_slot, item)
150
144
  }
151
145
  }
152
- theSlot = slotsToClick.pop()
153
- while (theSlot) {
154
- await bot.putAway(theSlot)
155
- theSlot = slotsToClick.pop()
146
+ for (const _slot of slotsToClick) {
147
+ await bot.putAway(_slot)
156
148
  }
157
149
  closeTheWindow()
158
150
  }
@@ -1,5 +1,5 @@
1
1
  const assert = require('assert')
2
- const Vec3 = require('vec3').Vec3
2
+ const { Vec3 } = require('vec3')
3
3
  const { callbackify, sleep } = require('../promise_utils')
4
4
  const { once } = require('events')
5
5
 
@@ -23,7 +23,7 @@ function inject (bot, { version }) {
23
23
  }
24
24
  })
25
25
 
26
- // WARN: This method should not be called twice on the same slot before first callback exceeds
26
+ // WARN: This method should not be called twice on the same slot before first callback succeeds
27
27
  async function setInventorySlot (slot, item) {
28
28
  assert(slot >= 0 && slot <= 44)
29
29
 
@@ -1,4 +1,3 @@
1
- const nbt = require('prismarine-nbt')
2
1
  const { performance } = require('perf_hooks')
3
2
  const { createDoneTask, createTask } = require('../promise_utils')
4
3
  const BlockFaces = require('prismarine-world').iterators.BlockFace
@@ -179,12 +178,22 @@ function inject (bot) {
179
178
  function digTime (block) {
180
179
  let type = null
181
180
  let enchantments = []
182
- if (bot.heldItem) {
183
- type = bot.heldItem.type
184
- if (bot.heldItem.nbt) {
185
- enchantments = nbt.simplify(bot.heldItem.nbt).Enchantments || nbt.simplify(bot.heldItem.nbt).ench
186
- }
181
+
182
+ // Retrieve currently held item ID and active enchantments from heldItem
183
+ const currentlyHeldItem = bot.heldItem
184
+ if (currentlyHeldItem) {
185
+ type = currentlyHeldItem.type
186
+ enchantments = currentlyHeldItem.enchants
187
187
  }
188
+
189
+ // Append helmet enchantments (because Aqua Affinity actually affects dig speed)
190
+ const headEquipmentSlot = bot.getEquipmentDestSlot('head')
191
+ const headEquippedItem = bot.inventory.slots[headEquipmentSlot]
192
+ if (headEquippedItem) {
193
+ const helmetEnchantments = headEquippedItem.enchants
194
+ enchantments = enchantments.concat(helmetEnchantments)
195
+ }
196
+
188
197
  const creative = bot.game.gameMode === 'creative'
189
198
  return block.digTime(type, creative, bot.entity.isInWater, !bot.entity.onGround, enchantments, bot.entity.effects)
190
199
  }
@@ -48,8 +48,10 @@ function inject (bot) {
48
48
  }
49
49
 
50
50
  if (slots[0].level >= 0 && slots[1].level >= 0 && slots[2].level >= 0) {
51
- if (!ready) enchantmentTable.emit('ready')
52
- ready = true
51
+ if (!ready) {
52
+ ready = true
53
+ enchantmentTable.emit('ready')
54
+ }
53
55
  } else {
54
56
  ready = false
55
57
  }
@@ -1,4 +1,4 @@
1
- const Vec3 = require('vec3').Vec3
1
+ const { Vec3 } = require('vec3')
2
2
  const Entity = require('prismarine-entity')
3
3
  const conv = require('../conversions')
4
4
  const NAMED_ENTITY_HEIGHT = 1.62
@@ -31,6 +31,11 @@ function inject (bot, { version }) {
31
31
  const entitiesArray = require('minecraft-data')(version).entitiesArray
32
32
  const Item = require('prismarine-item')(version)
33
33
  const ChatMessage = require('prismarine-chat')(version)
34
+ // ONLY 1.17 has this destroy_entity packet which is the same thing as entity_destroy packet except the entity is singular
35
+ // 1.17.1 reverted this change so this is just a simpler fix
36
+ bot._client.on('destroy_entity', (packet) => {
37
+ bot._client.emit('entity_destroy', { entityIds: [packet.entityId] })
38
+ })
34
39
 
35
40
  bot.findPlayer = bot.findPlayers = (filter) => {
36
41
  const filterFn = (entity) => {
@@ -478,15 +483,15 @@ function inject (bot, { version }) {
478
483
  } else if (packet.action === 3 && item.displayName) {
479
484
  player.displayName = new ChatMessage(JSON.parse(item.displayName))
480
485
  } else if (packet.action === 4) {
481
- if (player.entity === bot.entity) return
486
+ if (player.entity === bot.entity) continue
482
487
 
483
488
  player.entity = null
484
489
  delete bot.players[player.username]
485
490
  delete bot.uuidToUsername[item.UUID]
486
491
  bot.emit('playerLeft', player)
487
- return
492
+ continue
488
493
  } else {
489
- return
494
+ continue
490
495
  }
491
496
 
492
497
  bot.emit('playerUpdated', player)
@@ -526,8 +531,8 @@ function inject (bot, { version }) {
526
531
  bot.useOn = useOn
527
532
  bot.moveVehicle = moveVehicle
528
533
 
529
- function swingArm (arm = 'left', showHand = true) {
530
- const hand = arm === 'left' ? 0 : 1
534
+ function swingArm (arm = 'right', showHand = true) {
535
+ const hand = arm === 'right' ? 0 : 1
531
536
  const packet = {}
532
537
  if (showHand) packet.hand = hand
533
538
  bot._client.write('arm_animation', packet)
@@ -539,11 +544,11 @@ function inject (bot, { version }) {
539
544
  }
540
545
 
541
546
  function attack (target, swing = true) {
542
- useEntity(target, 1)
543
-
547
+ // arm animation comes before the use_entity packet
544
548
  if (swing) {
545
549
  swingArm()
546
550
  }
551
+ useEntity(target, 1)
547
552
  }
548
553
 
549
554
  function mount (target) {
@@ -77,7 +77,7 @@ function inject (bot) {
77
77
  // The following modifiers are constant for the input targetEntity and doesnt depend
78
78
  // on the source position, so if the goal is to compare between positions they can be
79
79
  // ignored to save computations
80
- if (!rawDamages) {
80
+ if (!rawDamages && targetEntity.attributes['generic.armor']) {
81
81
  const armor = getAttributeValue(targetEntity.attributes['generic.armor'])
82
82
  const armorToughness = getAttributeValue(targetEntity.attributes[armorThoughnessKey])
83
83
  damages = getDamageAfterAbsorb(damages, armor, armorToughness)
@@ -85,6 +85,8 @@ function inject (bot) {
85
85
  // TODO: protection enchantment and resistance effects
86
86
 
87
87
  if (targetEntity.type === 'player') damages *= difficultyValues[bot.game.difficulty] * 0.5
88
+ } else if (!rawDamages && !targetEntity.attributes['generic.armor']) {
89
+ return null
88
90
  }
89
91
  return Math.floor(damages)
90
92
  }
@@ -27,13 +27,13 @@ function inject (bot) {
27
27
  if (!lastBobber || fishingTask.done) return
28
28
 
29
29
  const pos = lastBobber.position
30
- if (packet.particleId === 4 && packet.particles === 6 && pos.distanceTo(new Vec3(packet.x, pos.y, packet.z)) <= 1.23) {
30
+ const parts = mcData.particlesByName
31
+ if (packet.particleId === (parts?.fishing ?? parts.bubble).id && packet.particles === 6 && pos.distanceTo(new Vec3(packet.x, pos.y, packet.z)) <= 1.23) {
31
32
  bot.activateItem()
32
33
  lastBobber = undefined
33
34
  fishingTask.finish()
34
35
  }
35
36
  })
36
-
37
37
  bot._client.on('entity_destroy', (packet) => {
38
38
  if (!lastBobber) return
39
39
  if (packet.entityIds.some(id => id === lastBobber.id)) {
@@ -1,78 +1,82 @@
1
+ const nbt = require('prismarine-nbt')
1
2
  module.exports = inject
2
3
 
4
+ const difficultyNames = ['peaceful', 'easy', 'normal', 'hard']
5
+ const gameModes = ['survival', 'creative', 'adventure']
6
+
3
7
  const dimensionNames = {
4
8
  '-1': 'minecraft:nether',
5
9
  0: 'minecraft:overworld',
6
10
  1: 'minecraft:end'
7
11
  }
8
12
 
9
- const difficultyNames = ['peaceful', 'easy', 'normal', 'hard']
10
-
11
- function inject (bot) {
12
- bot.game = {}
13
-
14
- bot._client.on('custom_payload', (packet) => {
15
- if (packet.channel === 'minecraft:brand') {
16
- bot.game.serverBrand = String.fromCharCode.apply(null, packet.data)
17
- bot.emit('game')
18
- }
19
- })
13
+ const parseGameMode = gameModeBits => gameModes[(gameModeBits & 0b11)] // lower two bits
20
14
 
21
- bot._client.on('game_state_change', (packet) => {
22
- if (packet?.reason === 4 && packet?.gameMode === 1) {
23
- bot._client.write('client_command', { action: 0 })
15
+ function inject (bot, options) {
16
+ function getBrandCustomChannelName () {
17
+ if (bot.supportFeature('customChannelMCPrefixed')) {
18
+ return 'MC|Brand'
19
+ } else if (bot.supportFeature('customChannelIdentifier')) {
20
+ return 'minecraft:brand'
24
21
  }
25
- })
22
+ throw new Error('Unsupported brand channel name')
23
+ }
26
24
 
27
- bot._client.on('login', (packet) => {
28
- bot.game.levelType = (packet.levelType ? packet.levelType : (packet.isFlat ? 'flat' : 'default'))
25
+ function handleRespawnPacketData (packet) {
26
+ bot.game.levelType = packet.levelType ?? (packet.isFlat ? 'flat' : 'default')
27
+ bot.game.hardcore = packet.isHardcore ?? Boolean(packet.gameMode & 0b100)
29
28
  bot.game.gameMode = parseGameMode(packet.gameMode)
30
- bot.game.hardcore = parseHardcore(packet.gameMode)
31
29
  if (bot.supportFeature('dimensionIsAnInt')) {
32
30
  bot.game.dimension = dimensionNames[packet.dimension]
33
31
  } else if (bot.supportFeature('dimensionIsAString')) {
34
32
  bot.game.dimension = packet.dimension
35
33
  } else if (bot.supportFeature('dimensionIsAWorld')) {
36
34
  bot.game.dimension = packet.worldName
35
+ } else {
36
+ throw new Error('Unsupported dimension type in login packet')
37
+ }
38
+
39
+ if (bot.supportFeature('dimensionDataIsAvailable')) {
40
+ bot.game.dimensionData = nbt.simplify(packet.dimension)
41
+ }
42
+ if (packet.difficulty) {
43
+ bot.game.difficulty = difficultyNames[packet.difficulty]
44
+ }
45
+ }
46
+
47
+ bot.game = {}
48
+
49
+ const brandChannel = getBrandCustomChannelName()
50
+ bot._client.registerChannel(brandChannel, ['string', []])
51
+
52
+ bot._client.on('login', (packet) => {
53
+ handleRespawnPacketData(packet)
54
+
55
+ bot.game.maxPlayers = packet.maxPlayers
56
+ if (packet.enableRespawnScreen) {
57
+ bot.game.enableRespawnScreen = packet.enableRespawnScreen
37
58
  }
38
59
  if (packet.viewDistance) {
39
60
  bot.game.serverViewDistance = packet.viewDistance
40
61
  }
41
- bot.game.difficulty = packet.difficulty ? difficultyNames[packet.difficulty] : bot.game.difficulty
42
- bot.game.maxPlayers = packet.maxPlayers
62
+
43
63
  bot.emit('login')
44
64
  bot.emit('game')
45
65
  bot._client.write('held_item_slot', { slotId: 0 })
46
- let channel
47
- if (bot.supportFeature('customChannelMCPrefixed')) {
48
- channel = 'MC|Brand'
49
- } else if (bot.supportFeature('customChannelNotPrefixed')) {
50
- channel = 'brand'
51
- }
52
- bot._client.write('custom_payload', {
53
- channel,
54
- data: Buffer.from('\x07vanilla')
55
- }) // varint length-prefixed string TODO: encode varint, see GH-253
56
66
 
57
- // autoRespawn(bot);
67
+ // varint length-prefixed string as data
68
+ bot._client.writeChannel(brandChannel, options.brand)
58
69
  })
59
70
 
60
71
  bot._client.on('respawn', (packet) => {
61
- bot.game.levelType = packet.levelType
62
- if (bot.supportFeature('dimensionIsAnInt')) {
63
- bot.game.dimension = dimensionNames[packet.dimension]
64
- } else if (bot.supportFeature('dimensionIsAString')) {
65
- bot.game.dimension = packet.dimension
66
- } else if (bot.supportFeature('dimensionIsAWorld')) {
67
- bot.game.dimension = packet.worldName
68
- }
69
- bot.game.difficulty = difficultyNames[packet.difficulty]
70
- bot.game.gameMode = parseGameMode(packet.gamemode)
71
- bot.game.hardcore = parseHardcore(packet.gamemode)
72
+ handleRespawnPacketData(packet)
72
73
  bot.emit('game')
73
74
  })
74
75
 
75
76
  bot._client.on('game_state_change', (packet) => {
77
+ if (packet?.reason === 4 && packet?.gameMode === 1) {
78
+ bot._client.write('client_command', { action: 0 })
79
+ }
76
80
  if (packet.reason === 3) {
77
81
  bot.game.gameMode = parseGameMode(packet.gameMode)
78
82
  bot.emit('game')
@@ -82,14 +86,15 @@ function inject (bot) {
82
86
  bot._client.on('difficulty', (packet) => {
83
87
  bot.game.difficulty = difficultyNames[packet.difficulty]
84
88
  })
85
- }
86
-
87
- const gameModes = ['survival', 'creative', 'adventure']
88
89
 
89
- function parseGameMode (gameModeBits) {
90
- return gameModes[(gameModeBits & 0x03)]
91
- }
90
+ bot._client.on(brandChannel, (serverBrand) => {
91
+ bot.game.serverBrand = serverBrand
92
+ })
92
93
 
93
- function parseHardcore (gameModeBits) {
94
- return !!(gameModeBits & 0x04)
94
+ // mimic the vanilla 1.17 client to prevent anticheat kicks
95
+ bot._client.on('ping', (data) => {
96
+ bot._client.write('pong', {
97
+ id: data.id
98
+ })
99
+ })
95
100
  }