mineflayer 4.23.0 → 4.24.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.
@@ -22,6 +22,22 @@ Mineflayer has two kind of tests :
22
22
 
23
23
  The objective of these tests is to know automatically what works and what doesn't in mineflayer, so it's easier to make mineflayer work.
24
24
 
25
+
26
+ ## Running tests
27
+ You can run tests for Different Minecraft versions using the `-g` flag with npm run mocha_test. For example:
28
+
29
+ ```bash
30
+ # Run all tests in all supported versions
31
+ npm run test
32
+
33
+ # Run a specific test in Minecraft 1.20.4
34
+ npm run mocha_test -- -g "mineflayer_external 1.20.4v.*exampleBee"
35
+
36
+ # Run all tests in just version 1.20.4
37
+ npm run mocha_test -- -g "mineflayer_external 1.20.4v"
38
+ ```
39
+
40
+
25
41
  ### Creating an external test
26
42
 
27
43
  In order to add an external test now you only need to create a file in [test/externalTests](test/externalTests)
package/docs/FAQ.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  This Frequently Asked Question document is meant to help people for the most common things.
4
4
 
5
+ ### I get an error (ie. protocol/data) when bot is trying to connect to minecraft server
6
+
7
+ Make sure the Minecraft server version is supported (cf. root readme), else you should retry using one of the [mineflayer tested versions](../lib/version.js).
8
+
5
9
  ### I get an error when trying to login with a microsoft account.
6
10
 
7
11
  Make sure the email you entered into the username option in createBot can be used to login to `minecraft.net` using the 'Login with Microsoft' button.
package/docs/api.md CHANGED
@@ -169,7 +169,7 @@
169
169
  - ["respawn"](#respawn)
170
170
  - ["game"](#game)
171
171
  - ["resourcePack" (url, hash)](#resourcepack-url-hash)
172
- - ["title"](#title)
172
+ - ["title" (title, type)](#title-title-type)
173
173
  - ["rain"](#rain)
174
174
  - ["weatherUpdate"](#weatherupdate)
175
175
  - ["time"](#time)
@@ -1237,11 +1237,12 @@ Emitted when the server changes any of the game properties.
1237
1237
 
1238
1238
  Emitted when the server sends a resource pack.
1239
1239
 
1240
- #### "title"
1240
+ #### "title" (title, type)
1241
1241
 
1242
1242
  Emitted when the server sends a title
1243
1243
 
1244
- * `text` - title's text
1244
+ * `title` - title's text
1245
+ * `type` - title's type "subtitle" or "title"
1245
1246
 
1246
1247
  #### "rain"
1247
1248
 
@@ -1771,7 +1772,7 @@ Checks if the given plugin is loaded (or scheduled to be loaded) on this bot.
1771
1772
 
1772
1773
  This function returns a `Promise`, with `void` as its argument upon completion.
1773
1774
 
1774
- Sleep in a bed. `bedBlock` should be a `Block` instance which is a bed.
1775
+ Sleep in a bed. `bedBlock` should be a `Block` instance which is a bed.
1775
1776
 
1776
1777
  #### bot.isABed(bedBlock)
1777
1778
 
@@ -1781,7 +1782,7 @@ Return true if `bedBlock` is a bed
1781
1782
 
1782
1783
  This function returns a `Promise`, with `void` as its argument upon completion.
1783
1784
 
1784
- Get out of bed.
1785
+ Get out of bed.
1785
1786
 
1786
1787
  #### bot.setControlState(control, state)
1787
1788
 
@@ -2120,7 +2121,7 @@ These are lower level methods for the inventory, they can be useful sometimes bu
2120
2121
  #### bot.clickWindow(slot, mouseButton, mode)
2121
2122
 
2122
2123
  This function returns a `Promise`, with `void` as its argument upon completion.
2123
-
2124
+
2124
2125
  The only valid mode option at the moment is 0. Shift clicking or mouse dragging is not implemented.
2125
2126
 
2126
2127
  Click on the current window. See details at https://wiki.vg/Protocol#Click_Container
package/docs/br/api_br.md CHANGED
@@ -141,7 +141,7 @@
141
141
  - ["respawn"](#respawn)
142
142
  - ["game"](#game)
143
143
  - ["resourcePack" (url, hash)](#resourcepack-url-hash)
144
- - ["title"](#title)
144
+ - ["title" (title, type)](#title-title-type)
145
145
  - ["rain"](#rain)
146
146
  - ["weatherUpdate"](#weatherupdate)
147
147
  - ["time"](#time)
@@ -1060,11 +1060,12 @@ Este evento é emitido quando o arquivo index é carregado. Você pode carregar
1060
1060
 
1061
1061
  É emitido quando o servidor envia um pacote de recursos.
1062
1062
 
1063
- #### "title"
1063
+ #### "title" (title, type)
1064
1064
 
1065
1065
  É emitido quando o servidor exibe um título.
1066
1066
 
1067
- * `text` - texto do título
1067
+ * `title` - texto do título
1068
+ * `type` - tipo do título "subtitle" ou "title"
1068
1069
 
1069
1070
  #### "rain"
1070
1071
 
@@ -2035,4 +2036,4 @@ Observação: enquanto você voa, `bot.entity.velocity` não é preciso.
2035
2036
 
2036
2037
  #### bot.creative.stopFlying()
2037
2038
 
2038
- Restaura `bot.physics.gravity` ao seu valor original.
2039
+ Restaura `bot.physics.gravity` ao seu valor original.
package/docs/es/api_es.md CHANGED
@@ -141,7 +141,7 @@
141
141
  - ["respawn"](#respawn)
142
142
  - ["game"](#game)
143
143
  - ["resourcePack" (url, hash)](#resourcepack-url-hash)
144
- - ["title"](#title)
144
+ - ["title" (title, type)](#title-title-type)
145
145
  - ["rain"](#rain)
146
146
  - ["weatherUpdate"](#weatherupdate)
147
147
  - ["time"](#time)
@@ -1071,11 +1071,12 @@ Se emite cuando el servidor cambia cualquiera de sus propiedades
1071
1071
 
1072
1072
  Se emite cuando el servidor manda un paquete de recursos
1073
1073
 
1074
- #### "title"
1074
+ #### "title" (title, type)
1075
1075
 
1076
1076
  Se emite cuando el servidor manda/muestra un título
1077
1077
 
1078
- * `text` - texto del título
1078
+ * `title` - texto del título
1079
+ * `type` - tipo del título "subtitle" o "title"
1079
1080
 
1080
1081
  #### "rain"
1081
1082
 
package/docs/history.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## 4.24.0
2
+ * [Support 1.21.3. (#3489)](https://github.com/PrismarineJS/mineflayer/commit/58ae9e5b5abf75139f4ba93fe4f34ef7ed3936e8) (thanks @rom1504)
3
+ * [Fix out of bounds access leading to crash at spawn (#3535)](https://github.com/PrismarineJS/mineflayer/commit/3187368397e880ba8b32bb03affa18203cbcbb42) (thanks @wAIfu-DEV)
4
+ * [fix: use dimension data on 1.16.2 (#3397)](https://github.com/PrismarineJS/mineflayer/commit/f6187f66c16dd122165287be7864c78b2fe7c32c) (thanks @zardoy)
5
+ * [update contribution docs to show test running commands (#3511)](https://github.com/PrismarineJS/mineflayer/commit/71a3a262681a173db86b8911aec82402a6993d21) (thanks @Madlykeanu)
6
+ * [Update inventory.js (#3507)](https://github.com/PrismarineJS/mineflayer/commit/a0e92cad5887181bf7e235f69378c8ede14a350c) (thanks @Pix3lPirat3)
7
+ * [Update FAQ.md - add mineflayer tested versions : lib/version.js (#3517)](https://github.com/PrismarineJS/mineflayer/commit/f2dd3a37505b374bf63119633659e35ec2ce3542) (thanks @boly38)
8
+ * [Bump protodef from 1.17.0 to 1.18.0 (#3523)](https://github.com/PrismarineJS/mineflayer/commit/06faa36c2da3da399bd5370869700aea6c65c9b0) (thanks @dependabot[bot])
9
+ * [Bump mocha from 10.8.2 to 11.0.1 (#3516)](https://github.com/PrismarineJS/mineflayer/commit/166971d317db3ec68cf3eebeda37f509152628fd) (thanks @dependabot[bot])
10
+ * [Fix chatterbox example (#3506)](https://github.com/PrismarineJS/mineflayer/commit/386200759556aa261fa212f26c43992a66cfa8ac) (thanks @ShiftSad)
11
+ * [Proper title event (#3498)](https://github.com/PrismarineJS/mineflayer/commit/3829a25150eec782bc045a222476865af7b0ac72) (thanks @SMEDjs)
12
+ * [Set `sequence` field correctly in activateItem (#3445)](https://github.com/PrismarineJS/mineflayer/commit/fdba03737ecdeaaf419e3175b9be33291db4e085) (thanks @GenerelSchwerz)
13
+ * [increase timeout in external test common](https://github.com/PrismarineJS/mineflayer/commit/3d6e2344751c38428701dc52e9f29dda73f7f782) (thanks @rom1504)
14
+
1
15
  ## 4.23.0
2
16
  * [1.21 (#3480)](https://github.com/PrismarineJS/mineflayer/commit/4aa10fb45431940504c7809f078f1f410e7fa7a3) (thanks @Madlykeanu)
3
17
  * [Adding mindcraft to mineflayer readme](https://github.com/PrismarineJS/mineflayer/commit/dd00db42ba20682418d8fbd5629e1033dfb0ff20) (thanks @rom1504)
package/docs/ru/api_ru.md CHANGED
@@ -169,7 +169,7 @@
169
169
  - ["respawn"](#respawn)
170
170
  - ["game"](#game)
171
171
  - ["resourcePack" (url, hash)](#resourcepack-url-hash)
172
- - ["title"](#title)
172
+ - ["title" (title, type)](#title-title-type)
173
173
  - ["rain"](#rain)
174
174
  - ["weatherUpdate"](#weatherupdate)
175
175
  - ["time"](#time)
@@ -1264,11 +1264,12 @@ UUID существа, который определяется боссом.
1264
1264
 
1265
1265
  Срабатывает, когда сервер отправляет ресурспак.
1266
1266
 
1267
- #### "title"
1267
+ #### "title" (title, type)
1268
1268
 
1269
1269
  Срабатывает, когда сервер отправляет текст по центру экрана.
1270
1270
 
1271
- * `text` - Текст на экране.
1271
+ * `title` - Текст на экране.
1272
+ * `type` - Тип текста "subtitle" или "title"
1272
1273
 
1273
1274
  #### "rain"
1274
1275
 
@@ -1634,7 +1635,7 @@ UUID существа, который определяется боссом.
1634
1635
 
1635
1636
  #### bot.recipesFor(itemType, metadata, minResultCount, craftingTable)
1636
1637
 
1637
- Возвращает список рецептов(`Recipe`), которые вы можете использовать для крафта
1638
+ Возвращает список рецептов(`Recipe`), которые вы можете использовать для крафта
1638
1639
  предмета(`itemType`) с мета-данными(`metadata`).
1639
1640
 
1640
1641
  * `itemType` - Числовой ID предмета, который вы хотите создать.
@@ -2116,7 +2117,7 @@ bot.once('login', () => {
2116
2117
  #### bot.clickWindow(slot, mouseButton, mode)
2117
2118
 
2118
2119
  Эта функция возвращает `Promise` с `void` в качестве аргумента при завершении.
2119
-
2120
+
2120
2121
  Единственное действительное значение для `mode` - 0. Нажатие с шифтом или перемещение через мышь не реализовано.
2121
2122
 
2122
2123
  Нажимает на текущее окно. Подробнее - https://wiki.vg/Protocol#Click_Container
package/docs/zh/api.md CHANGED
@@ -153,7 +153,7 @@
153
153
  - ["respawn"](#respawn)
154
154
  - ["game"](#game)
155
155
  - ["resourcePack" (url, hash)](#resourcepack-url-hash)
156
- - ["title"](#title)
156
+ - ["title" (title, type)](#title-title-type)
157
157
  - ["rain"](#rain)
158
158
  - ["weatherUpdate"](#weatherupdate)
159
159
  - ["time"](#time)
@@ -1127,11 +1127,12 @@ Emitted for every server message, including chats.
1127
1127
 
1128
1128
  当服务器发送资源包时触发
1129
1129
 
1130
- #### "title"
1130
+ #### "title" (title, type)
1131
1131
 
1132
1132
  当服务器发送标题时触发
1133
1133
 
1134
- * `text` - 标题文本
1134
+ * `title` - 标题文本
1135
+ * `type` - 标题类型 "subtitle" 或 "title"
1135
1136
 
1136
1137
  #### "rain"
1137
1138
 
@@ -1150,11 +1151,11 @@ If you join a server where it is already raining, this event will fire.
1150
1151
 
1151
1152
  当bot从服务器被踢出时触发
1152
1153
 
1153
- `reason`是一条解释你被踢的原因的聊天信息.
1154
+ `reason`是一条解释你被踢的原因的聊天信息.
1154
1155
 
1155
1156
  `loggedIn`
1156
1157
  如果客户端在成功登录后被踢出则为`true`
1157
- 如果kick发生在登录阶段则为 `false`
1158
+ 如果kick发生在登录阶段则为 `false`
1158
1159
 
1159
1160
  #### "end" (reason)
1160
1161
 
@@ -30,8 +30,7 @@ username = sys.argv[3] if len(sys.argv) > 3 else "boat"
30
30
  bot = mineflayer.createBot({
31
31
  "host": host,
32
32
  "port": port,
33
- "username": username,
34
- "port": port
33
+ "username": username
35
34
  })
36
35
 
37
36
  Item = require("prismarine-item")(bot.registry)
@@ -300,4 +299,4 @@ def entityEffect(this, entity, effect):
300
299
 
301
300
  @On(bot, "entityEffectEnd")
302
301
  def entityEffectEnd(this, entity, effect):
303
- print("entityEffectEnd", entity, effect)
302
+ print("entityEffectEnd", entity, effect)
@@ -0,0 +1,22 @@
1
+ /*
2
+ * An example of how to handle title events from the server.
3
+ */
4
+ const mineflayer = require('mineflayer')
5
+
6
+ if (process.argv.length < 4 || process.argv.length > 6) {
7
+ console.log('Usage : node titles.js <host> <port> [<name>] [<password>]')
8
+ process.exit(1)
9
+ }
10
+
11
+ const bot = mineflayer.createBot({
12
+ host: process.argv[2],
13
+ port: parseInt(process.argv[3]),
14
+ username: process.argv[4] ? process.argv[4] : 'titles',
15
+ password: process.argv[5]
16
+ })
17
+
18
+ // This event is triggered when the server sends a title to the client.
19
+ bot.on('title', (text, type) => {
20
+ // type is either "title" or "subtitle"
21
+ console.log(`Received ${type}: ${text}`)
22
+ })
package/index.d.ts CHANGED
@@ -71,7 +71,7 @@ export interface BotEvents {
71
71
  spawn: () => Promise<void> | void
72
72
  respawn: () => Promise<void> | void
73
73
  game: () => Promise<void> | void
74
- title: (text: string) => Promise<void> | void
74
+ title: (text: string, type: "subtitle" | "title") => Promise<void> | void
75
75
  rain: () => Promise<void> | void
76
76
  time: () => Promise<void> | void
77
77
  kicked: (reason: string, loggedIn: boolean) => Promise<void> | void
@@ -261,7 +261,7 @@ function inject (bot, { version, storageBuilder, hideErrors }) {
261
261
  bot.world.setColumn(packet.chunkX, packet.chunkZ, column)
262
262
  }
263
263
 
264
- if (bot.supportFeature('dimensionDataIsAvailable')) {
264
+ if (bot.supportFeature('newLightingDataFormat')) {
265
265
  column.loadParsedLight(packet.skyLight, packet.blockLight, packet.skyLightMask, packet.blockLightMask, packet.emptySkyLightMask, packet.emptyBlockLightMask)
266
266
  } else {
267
267
  column.loadLight(packet.data, packet.skyLightMask, packet.blockLightMask, packet.emptySkyLightMask, packet.emptyBlockLightMask)
@@ -395,10 +395,13 @@ function inject (bot, { version, storageBuilder, hideErrors }) {
395
395
  bot._client.on('explosion', (packet) => {
396
396
  // explosion
397
397
  const p = new Vec3(packet.x, packet.y, packet.z)
398
- packet.affectedBlockOffsets.forEach((offset) => {
399
- const pt = p.offset(offset.x, offset.y, offset.z)
400
- updateBlockState(pt, 0)
401
- })
398
+ if (packet.affectedBlockOffsets) {
399
+ // TODO: server no longer sends in 1.21.3. Is client supposed to compute this or is it sent via normal block updates?
400
+ packet.affectedBlockOffsets.forEach((offset) => {
401
+ const pt = p.offset(offset.x, offset.y, offset.z)
402
+ updateBlockState(pt, 0)
403
+ })
404
+ }
402
405
  })
403
406
 
404
407
  bot._client.on('spawn_entity_painting', (packet) => {
@@ -11,7 +11,7 @@ function inject (bot) {
11
11
  if (bot.entity.id !== packet.entityId) return
12
12
  for (const metadata of packet.metadata) {
13
13
  if (metadata.key === 1) {
14
- bot.oxygenLevel = Math.round(packet.metadata[1].value / 15)
14
+ bot.oxygenLevel = Math.round(metadata.value / 15)
15
15
  bot.emit('breath')
16
16
  }
17
17
  }
@@ -21,7 +21,7 @@ function inject (bot) {
21
21
  const creativeSlotsUpdates = []
22
22
 
23
23
  // WARN: This method should not be called twice on the same slot before first promise succeeds
24
- async function setInventorySlot (slot, item) {
24
+ async function setInventorySlot (slot, item, waitTimeout = 400) {
25
25
  assert(slot >= 0 && slot <= 44)
26
26
 
27
27
  if (Item.equal(bot.inventory.slots[slot], item, true)) return
@@ -29,12 +29,32 @@ function inject (bot) {
29
29
  throw new Error(`Setting slot ${slot} cancelled due to calling bot.creative.setInventorySlot(${slot}, ...) again`)
30
30
  }
31
31
  creativeSlotsUpdates[slot] = true
32
-
33
32
  bot._client.write('set_creative_slot', {
34
33
  slot,
35
34
  item: Item.toNotch(item)
36
35
  })
37
36
 
37
+ if (bot.supportFeature('noAckOnCreateSetSlotPacket')) {
38
+ // No ack
39
+ bot._setSlot(slot, item)
40
+ if (waitTimeout === 0) return // no wait
41
+ // allow some time to see if server rejects
42
+ return new Promise((resolve, reject) => {
43
+ function updateSlot (oldItem, newItem) {
44
+ if (newItem.itemId !== item.itemId) {
45
+ creativeSlotsUpdates[slot] = false
46
+ reject(Error('Server rejected'))
47
+ }
48
+ }
49
+ bot.inventory.once(`updateSlot:${slot}`, updateSlot)
50
+ setTimeout(() => {
51
+ bot.inventory.off(`updateSlot:${slot}`, updateSlot)
52
+ creativeSlotsUpdates[slot] = false
53
+ resolve()
54
+ }, waitTimeout)
55
+ })
56
+ }
57
+
38
58
  await onceWithCleanup(bot.inventory, `updateSlot:${slot}`, {
39
59
  timeout: 5000,
40
60
  checkCondition: (oldItem, newItem) => item === null ? newItem === null : newItem?.name === item.name && newItem?.count === item.count && newItem?.metadata === item.metadata
@@ -341,6 +341,16 @@ function inject (bot) {
341
341
  bot.emit('entityMoved', entity)
342
342
  })
343
343
 
344
+ // 1.21.3 - merges the packets above
345
+ bot._client.on('sync_entity_position', (packet) => {
346
+ const entity = fetchEntity(packet.entityId)
347
+ entity.position.set(packet.x, packet.y, packet.z)
348
+ entity.velocity.update(packet.dx, packet.dy, packet.dz)
349
+ entity.yaw = packet.yaw
350
+ entity.pitch = packet.pitch
351
+ bot.emit('entityMoved', entity)
352
+ })
353
+
344
354
  bot._client.on('entity_head_rotation', (packet) => {
345
355
  // entity head look
346
356
  const entity = fetchEntity(packet.entityId)
@@ -361,6 +371,11 @@ function inject (bot) {
361
371
  if (eventName) bot.emit(eventName, entity)
362
372
  })
363
373
 
374
+ bot._client.on('damage_event', (packet) => { // 1.20+
375
+ const entity = bot.entities[packet.entityId]
376
+ bot.emit('entityHurt', entity)
377
+ })
378
+
364
379
  bot._client.on('attach_entity', (packet) => {
365
380
  // attach entity
366
381
  const entity = fetchEntity(packet.entityId)
@@ -585,6 +600,62 @@ function inject (bot) {
585
600
  bot._client.on('player_info', (packet) => {
586
601
  // player list item(s)
587
602
 
603
+ if (typeof packet.action !== 'number') {
604
+ // the features checks below this will be un-needed with https://github.com/PrismarineJS/minecraft-data/pull/948
605
+ for (const update of packet.data) {
606
+ let player = bot.uuidToUsername[update.uuid] ? bot.players[bot.uuidToUsername[update.uuid]] : null
607
+ let newPlayer = false
608
+
609
+ const obj = {
610
+ uuid: update.uuid
611
+ }
612
+
613
+ if (!player) newPlayer = true
614
+
615
+ player ||= obj
616
+
617
+ if (packet.action.add_player) {
618
+ obj.username = update.player.name
619
+ obj.displayName = player.displayName || new ChatMessage({ text: '', extra: [{ text: update.player.name }] })
620
+ obj.skinData = extractSkinInformation(update.player.properties)
621
+ }
622
+
623
+ if (packet.action.update_game_mode) {
624
+ obj.gamemode = update.gamemode
625
+ }
626
+
627
+ if (packet.action.update_latency) {
628
+ obj.ping = update.latency
629
+ }
630
+
631
+ if (update.displayName) {
632
+ obj.displayName = ChatMessage.fromNotch(update.displayName)
633
+ }
634
+
635
+ if (newPlayer) {
636
+ if (!obj.username) continue // Should be unreachable
637
+ player = bot.players[obj.username] = obj
638
+ bot.uuidToUsername[obj.uuid] = obj.username
639
+ } else {
640
+ Object.assign(player, obj)
641
+ }
642
+
643
+ const playerEntity = Object.values(bot.entities).find(e => e.type === 'player' && e.username === player.username)
644
+ player.entity = playerEntity
645
+
646
+ if (playerEntity === bot.entity) {
647
+ bot.player = player
648
+ }
649
+
650
+ if (newPlayer) {
651
+ bot.emit('playerJoined', player)
652
+ } else {
653
+ bot.emit('playerUpdated', player)
654
+ }
655
+ }
656
+ return
657
+ }
658
+
588
659
  if (bot.supportFeature('playerInfoActionIsBitfield')) {
589
660
  for (const item of packet.data) {
590
661
  let player = bot.uuidToUsername[item.uuid] ? bot.players[bot.uuidToUsername[item.uuid]] : null
@@ -795,20 +866,42 @@ function inject (bot) {
795
866
  }
796
867
 
797
868
  function moveVehicle (left, forward) {
798
- bot._client.write('steer_vehicle', {
799
- sideways: left,
800
- forward,
801
- jump: 0x01
802
- })
869
+ if (bot.supportFeature('newPlayerInputPacket')) {
870
+ // docs:
871
+ // * left can take -1 or 1 : -1 means right, 1 means left
872
+ // * forward can take -1 or 1 : -1 means backward, 1 means forward
873
+ bot._client.write('player_input', {
874
+ inputs: {
875
+ forward: forward > 0,
876
+ backward: forward < 0,
877
+ left: left > 0,
878
+ right: left < 0
879
+ }
880
+ })
881
+ } else {
882
+ bot._client.write('steer_vehicle', {
883
+ sideways: left,
884
+ forward,
885
+ jump: 0x01
886
+ })
887
+ }
803
888
  }
804
889
 
805
890
  function dismount () {
806
891
  if (bot.vehicle) {
807
- bot._client.write('steer_vehicle', {
808
- sideways: 0.0,
809
- forward: 0.0,
810
- jump: 0x02
811
- })
892
+ if (bot.supportFeature('newPlayerInputPacket')) {
893
+ bot._client.write('player_input', {
894
+ inputs: {
895
+ jump: true
896
+ }
897
+ })
898
+ } else {
899
+ bot._client.write('steer_vehicle', {
900
+ sideways: 0.0,
901
+ forward: 0.0,
902
+ jump: 0x02
903
+ })
904
+ }
812
905
  } else {
813
906
  bot.emit('error', new Error('dismount: not mounted'))
814
907
  }
@@ -10,7 +10,12 @@ const dimensionNames = {
10
10
  1: 'the_end'
11
11
  }
12
12
 
13
- const parseGameMode = gameModeBits => gameModes[(gameModeBits & 0b11)] // lower two bits
13
+ const parseGameMode = gameModeBits => {
14
+ if (gameModeBits < 0 || gameModeBits > 0b11) {
15
+ return 'survival'
16
+ }
17
+ return gameModes[(gameModeBits & 0b11)] // lower two bits
18
+ }
14
19
 
15
20
  function inject (bot, options) {
16
21
  function getBrandCustomChannelName () {
@@ -25,7 +30,12 @@ function inject (bot, options) {
25
30
  function handleRespawnPacketData (packet) {
26
31
  bot.game.levelType = packet.levelType ?? (packet.isFlat ? 'flat' : 'default')
27
32
  bot.game.hardcore = packet.isHardcore ?? Boolean(packet.gameMode & 0b100)
28
- bot.game.gameMode = packet.gamemode || parseGameMode(packet.gameMode)
33
+ // Either a respawn packet or a login packet. Depending on the packet it can be "gamemode" or "gameMode"
34
+ if (bot.supportFeature('spawnRespawnWorldDataField')) { // 1.20.5
35
+ bot.game.gameMode = packet.gamemode
36
+ } else {
37
+ bot.game.gameMode = parseGameMode(packet.gamemode ?? packet.gameMode)
38
+ }
29
39
  if (bot.supportFeature('segmentedRegistryCodecData')) { // 1.20.5
30
40
  if (typeof packet.dimension === 'number') {
31
41
  bot.game.dimension = bot.registry.dimensionsArray[packet.dimension]?.name?.replace('minecraft:', '')
@@ -60,7 +70,7 @@ function inject (bot, options) {
60
70
  bot.game.minY = dimData.minY
61
71
  bot.game.height = dimData.height
62
72
  }
63
- } else if (bot.supportFeature('dimensionDataIsAvailable')) { // 1.18
73
+ } else if (bot.supportFeature('dimensionDataIsAvailable')) { // 1.16.2+
64
74
  const dimensionData = nbt.simplify(packet.dimension)
65
75
  bot.game.minY = dimensionData.min_y
66
76
  bot.game.height = dimensionData.height
@@ -79,7 +79,9 @@ function inject (bot) {
79
79
  cursorX: dx,
80
80
  cursorY: dy,
81
81
  cursorZ: dz,
82
- insideBlock: false
82
+ insideBlock: false,
83
+ sequence: 0, // 1.19.0
84
+ worldBorderHit: false // 1.21.3
83
85
  })
84
86
  }
85
87
 
@@ -26,6 +26,7 @@ function inject (bot, { hideErrors }) {
26
26
  const windows = require('prismarine-windows')(bot.version)
27
27
 
28
28
  let eatingTask = createDoneTask()
29
+ let sequence = 0
29
30
 
30
31
  let nextActionNumber = 0 // < 1.17
31
32
  let stateId = -1
@@ -43,6 +44,7 @@ function inject (bot, { hideErrors }) {
43
44
  bot.inventory = windows.createWindow(0, 'minecraft:inventory', 'Inventory')
44
45
  bot.currentWindow = null
45
46
  bot.usingHeldItem = false
47
+
46
48
  Object.defineProperty(bot, 'heldItem', {
47
49
  get: function () {
48
50
  return bot.inventory.slots[bot.QUICK_BAR_START + bot.quickBarSlot]
@@ -111,6 +113,8 @@ function inject (bot, { hideErrors }) {
111
113
 
112
114
  function activateItem (offHand = false) {
113
115
  bot.usingHeldItem = true
116
+ sequence++
117
+
114
118
  if (bot.supportFeature('useItemWithBlockPlace')) {
115
119
  bot._client.write('block_place', {
116
120
  location: new Vec3(-1, 255, -1),
@@ -123,17 +127,26 @@ function inject (bot, { hideErrors }) {
123
127
  } else if (bot.supportFeature('useItemWithOwnPacket')) {
124
128
  bot._client.write('use_item', {
125
129
  hand: offHand ? 1 : 0,
130
+ sequence,
126
131
  rotation: { x: 0, y: 0 }
127
132
  })
128
133
  }
129
134
  }
130
135
 
131
136
  function deactivateItem () {
132
- bot._client.write('block_dig', {
137
+ const body = {
133
138
  status: 5,
134
139
  location: new Vec3(0, 0, 0),
135
140
  face: 5
136
- })
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
+
137
150
  bot.usingHeldItem = false
138
151
  }
139
152
 
@@ -177,6 +190,7 @@ function inject (bot, { hideErrors }) {
177
190
  // TODO: tell the server that we are not sneaking while doing this
178
191
  await bot.lookAt(block.position.offset(0.5, 0.5, 0.5), false)
179
192
  // place block message
193
+ // TODO: logic below can likely be simplified
180
194
  if (bot.supportFeature('blockPlaceHasHeldItem')) {
181
195
  bot._client.write('block_place', {
182
196
  location: block.position,
@@ -212,7 +226,9 @@ function inject (bot, { hideErrors }) {
212
226
  cursorX: cursorPos.x,
213
227
  cursorY: cursorPos.y,
214
228
  cursorZ: cursorPos.z,
215
- insideBlock: false
229
+ insideBlock: false,
230
+ sequence: 0, // 1.19.0+
231
+ worldBorderHit: false // 1.21.3+
216
232
  })
217
233
  }
218
234
 
@@ -250,7 +266,7 @@ function inject (bot, { hideErrors }) {
250
266
  const itemType = options.itemType
251
267
  const metadata = options.metadata
252
268
  const nbt = options.nbt
253
- let count = options.count === null ? 1 : options.count
269
+ let count = (options.count === undefined || options.count === null) ? 1 : options.count
254
270
  let firstSourceSlot = null
255
271
 
256
272
  // ranges
@@ -699,15 +715,18 @@ function inject (bot, { hideErrors }) {
699
715
  bot.currentWindow = null
700
716
  bot.emit('windowClose', oldWindow)
701
717
  })
702
- bot._client.on('set_slot', (packet) => {
718
+ bot._setSlot = (slotId, newItem, window = bot.inventory) => {
703
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) => {
704
726
  const window = packet.windowId === 0 ? bot.inventory : bot.currentWindow
705
727
  if (!window || window.id !== packet.windowId) return
706
728
  const newItem = Item.fromNotch(packet.item)
707
- const oldItem = window.slots[packet.slot]
708
- window.updateSlot(packet.slot, newItem)
709
- updateHeldItem()
710
- bot.emit(`setSlot:${window.id}`, oldItem, newItem)
729
+ bot._setSlot(packet.slot, newItem, window)
711
730
  })
712
731
  bot._client.on('window_items', (packet) => {
713
732
  const window = packet.windowId === 0 ? bot.inventory : bot.currentWindow
@@ -48,7 +48,8 @@ function inject (bot, { physicsEnabled, maxCatchupTicks }) {
48
48
  yaw: 0,
49
49
  pitch: 0,
50
50
  onGround: false,
51
- time: 0
51
+ time: 0,
52
+ flags: { onGround: false, hasHorizontalCollision: false }
52
53
  }
53
54
 
54
55
  // This function should be executed each tick (every 0.05 seconds)
@@ -103,6 +104,7 @@ function inject (bot, { physicsEnabled, maxCatchupTicks }) {
103
104
  lastSent.y = position.y
104
105
  lastSent.z = position.z
105
106
  lastSent.onGround = onGround
107
+ lastSent.flags = { onGround, hasHorizontalCollision: undefined } // 1.21.3+
106
108
  bot._client.write('position', lastSent)
107
109
  bot.emit('move', oldPos)
108
110
  }
@@ -113,6 +115,7 @@ function inject (bot, { physicsEnabled, maxCatchupTicks }) {
113
115
  lastSent.yaw = yaw
114
116
  lastSent.pitch = pitch
115
117
  lastSent.onGround = onGround
118
+ lastSent.flags = { onGround, hasHorizontalCollision: undefined } // 1.21.3+
116
119
  bot._client.write('look', lastSent)
117
120
  bot.emit('move', oldPos)
118
121
  }
@@ -126,6 +129,7 @@ function inject (bot, { physicsEnabled, maxCatchupTicks }) {
126
129
  lastSent.yaw = yaw
127
130
  lastSent.pitch = pitch
128
131
  lastSent.onGround = onGround
132
+ lastSent.flags = { onGround, hasHorizontalCollision: undefined } // 1.21.3+
129
133
  bot._client.write('position_look', lastSent)
130
134
  bot.emit('move', oldPos)
131
135
  }
@@ -167,9 +171,9 @@ function inject (bot, { physicsEnabled, maxCatchupTicks }) {
167
171
 
168
172
  // Only send a position update if necessary, select the appropriate packet
169
173
  const positionUpdated = lastSent.x !== position.x || lastSent.y !== position.y || lastSent.z !== position.z ||
170
- // Send a position update every second, even if no other update was made
171
- // This function rounds to the nearest 50ms (or PHYSICS_INTERVAL_MS) and checks if a second has passed.
172
- (Math.round((now - lastSent.time) / PHYSICS_INTERVAL_MS) * PHYSICS_INTERVAL_MS) >= 1000
174
+ // Send a position update every second, even if no other update was made
175
+ // This function rounds to the nearest 50ms (or PHYSICS_INTERVAL_MS) and checks if a second has passed.
176
+ (Math.round((now - lastSent.time) / PHYSICS_INTERVAL_MS) * PHYSICS_INTERVAL_MS) >= 1000
173
177
  const lookUpdated = lastSent.yaw !== yaw || lastSent.pitch !== pitch
174
178
 
175
179
  if (positionUpdated && lookUpdated) {
@@ -184,7 +188,10 @@ function inject (bot, { physicsEnabled, maxCatchupTicks }) {
184
188
  // For versions < 1.12, one player packet should be sent every tick
185
189
  // for the server to update health correctly
186
190
  // For versions >= 1.12, onGround !== lastSent.onGround should be used, but it doesn't ever trigger outside of login
187
- bot._client.write('flying', { onGround: bot.entity.onGround })
191
+ bot._client.write('flying', {
192
+ onGround: bot.entity.onGround,
193
+ flags: { onGround: bot.entity.onGround, hasHorizontalCollision: undefined } // 1.21.3+
194
+ })
188
195
  }
189
196
 
190
197
  lastSent.onGround = bot.entity.onGround // onGround is always set
@@ -288,9 +295,14 @@ function inject (bot, { physicsEnabled, maxCatchupTicks }) {
288
295
  bot._client.on('explosion', explosion => {
289
296
  // TODO: emit an explosion event with more info
290
297
  if (bot.physicsEnabled && bot.game.gameMode !== 'creative') {
291
- bot.entity.velocity.x += explosion.playerMotionX
292
- bot.entity.velocity.y += explosion.playerMotionY
293
- bot.entity.velocity.z += explosion.playerMotionZ
298
+ if (explosion.playerKnockback) { // 1.21.3+
299
+ bot.entity.velocity.add(explosion.playerMotionX, explosion.playerMotionY, explosion.playerMotionZ)
300
+ }
301
+ if ('playerMotionX' in explosion) {
302
+ bot.entity.velocity.x += explosion.playerMotionX
303
+ bot.entity.velocity.y += explosion.playerMotionY
304
+ bot.entity.velocity.z += explosion.playerMotionZ
305
+ }
294
306
  }
295
307
  })
296
308
 
@@ -330,30 +342,59 @@ function inject (bot, { physicsEnabled, maxCatchupTicks }) {
330
342
  await bot.look(yaw, pitch, force)
331
343
  }
332
344
 
345
+ // 1.21.3+
346
+ bot._client.on('player_rotation', (packet) => {
347
+ bot.entity.yaw = conv.fromNotchianYaw(packet.yaw)
348
+ bot.entity.pitch = conv.fromNotchianPitch(packet.pitch)
349
+ })
350
+
333
351
  // player position and look (clientbound)
334
352
  bot._client.on('position', (packet) => {
335
353
  // Is this necessary? Feels like it might wrongly overwrite hitbox size sometimes
336
354
  // e.g. when crouching/crawling/swimming. Can someone confirm?
337
355
  bot.entity.height = 1.8
338
356
 
339
- // Velocity is only set to 0 if the flag is not set, otherwise keep current velocity
357
+ // Velocity is reset if the x, y, z flags are not set
340
358
  const vel = bot.entity.velocity
341
- vel.set(
342
- packet.flags & 1 ? vel.x : 0,
343
- packet.flags & 2 ? vel.y : 0,
344
- packet.flags & 4 ? vel.z : 0
345
- )
346
-
347
- // If flag is set, then the corresponding value is relative, else it is absolute
359
+ // If the x, y, z flags are not set, the position is absolute
348
360
  const pos = bot.entity.position
349
- pos.set(
350
- packet.flags & 1 ? (pos.x + packet.x) : packet.x,
351
- packet.flags & 2 ? (pos.y + packet.y) : packet.y,
352
- packet.flags & 4 ? (pos.z + packet.z) : packet.z
353
- )
354
-
355
- const newYaw = (packet.flags & 8 ? conv.toNotchianYaw(bot.entity.yaw) : 0) + packet.yaw
356
- const newPitch = (packet.flags & 16 ? conv.toNotchianPitch(bot.entity.pitch) : 0) + packet.pitch
361
+
362
+ // TODO: this current mineflayer logic maybe incorrect. New (maybe even older) versions of minecraft have flag values for
363
+ // dx/dy/dz but mineflayer is assuming that 0b111 applies for both velocity and position.
364
+
365
+ let newYaw, newPitch
366
+
367
+ if (bot.registry.version['>=']('1.21.3')) {
368
+ // flags is now an object with keys
369
+ // "flags": ["x", "y", "z", "yaw", "pitch", "dx", "dy", "dz", "yawDelta"]
370
+ const flags = packet.flags
371
+ vel.set(
372
+ flags.x ? vel.x : 0,
373
+ flags.y ? vel.y : 0,
374
+ flags.z ? vel.z : 0
375
+ )
376
+ pos.set(
377
+ flags.x ? (pos.x + packet.x) : packet.x,
378
+ flags.y ? (pos.y + packet.y) : packet.y,
379
+ flags.z ? (pos.z + packet.z) : packet.z
380
+ )
381
+ newYaw = (flags.yaw ? conv.toNotchianYaw(bot.entity.yaw) : 0) + packet.yaw
382
+ newPitch = (flags.pitch ? conv.toNotchianPitch(bot.entity.pitch) : 0) + packet.pitch
383
+ } else {
384
+ vel.set(
385
+ packet.flags & 1 ? vel.x : 0,
386
+ packet.flags & 2 ? vel.y : 0,
387
+ packet.flags & 4 ? vel.z : 0
388
+ )
389
+ pos.set(
390
+ packet.flags & 1 ? (pos.x + packet.x) : packet.x,
391
+ packet.flags & 2 ? (pos.y + packet.y) : packet.y,
392
+ packet.flags & 4 ? (pos.z + packet.z) : packet.z
393
+ )
394
+ newYaw = (packet.flags & 8 ? conv.toNotchianYaw(bot.entity.yaw) : 0) + packet.yaw
395
+ newPitch = (packet.flags & 16 ? conv.toNotchianPitch(bot.entity.pitch) : 0) + packet.pitch
396
+ }
397
+
357
398
  bot.entity.yaw = conv.fromNotchianYaw(newYaw)
358
399
  bot.entity.pitch = conv.fromNotchianPitch(newPitch)
359
400
  bot.entity.onGround = false
@@ -87,7 +87,8 @@ function inject (bot, options) {
87
87
  : options.skinParts,
88
88
  mainHand: options.mainHand || 'right',
89
89
  enableTextFiltering: options.enableTextFiltering || false,
90
- enableServerListing: options.enableServerListing || true
90
+ enableServerListing: options.enableServerListing || true,
91
+ particleStatus: 'all'
91
92
  }
92
93
 
93
94
  bot._client.on('login', () => {
@@ -1,9 +1,14 @@
1
1
  module.exports = inject
2
-
3
2
  function inject (bot) {
4
3
  bot._client.on('title', (packet) => {
5
4
  if (packet.action === 0 || packet.action === 1) {
6
- bot.emit('title', packet.text)
5
+ bot.emit('title', packet.text, 'title')
7
6
  }
8
7
  })
8
+ bot._client.on('set_title_text', packet => {
9
+ bot.emit('title', packet.text, 'title')
10
+ })
11
+ bot._client.on('set_title_subtitle', packet => {
12
+ bot.emit('title', packet.text, 'subtitle')
13
+ })
9
14
  }
package/lib/version.js CHANGED
@@ -1,4 +1,4 @@
1
- const testedVersions = ['1.8.8', '1.9.4', '1.10.2', '1.11.2', '1.12.2', '1.13.2', '1.14.4', '1.15.2', '1.16.5', '1.17.1', '1.18.2', '1.19', '1.19.2', '1.19.3', '1.19.4', '1.20.1', '1.20.2', '1.20.4', '1.20.6', '1.21.1']
1
+ const testedVersions = ['1.8.8', '1.9.4', '1.10.2', '1.11.2', '1.12.2', '1.13.2', '1.14.4', '1.15.2', '1.16.5', '1.17.1', '1.18.2', '1.19', '1.19.2', '1.19.3', '1.19.4', '1.20.1', '1.20.2', '1.20.4', '1.20.6', '1.21.1', '1.21.3']
2
2
  module.exports = {
3
3
 
4
4
  testedVersions,
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "mineflayer",
3
- "version": "4.23.0",
3
+ "version": "4.24.0",
4
4
  "description": "create minecraft bots with a stable, high level API",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
7
7
  "scripts": {
8
- "mocha_test": "mocha --reporter spec --exit --bail",
8
+ "mocha_test": "mocha --reporter spec --exit",
9
9
  "test": "npm run mocha_test",
10
10
  "pretest": "npm run lint",
11
11
  "lint": "standard && standard-markdown",
@@ -22,7 +22,7 @@
22
22
  "license": "MIT",
23
23
  "dependencies": {
24
24
  "minecraft-data": "^3.76.0",
25
- "minecraft-protocol": "^1.50.0",
25
+ "minecraft-protocol": "^1.51.0",
26
26
  "prismarine-biome": "^1.1.1",
27
27
  "prismarine-block": "^1.17.0",
28
28
  "prismarine-chat": "^1.7.1",
@@ -35,7 +35,7 @@
35
35
  "prismarine-registry": "^1.10.0",
36
36
  "prismarine-windows": "^2.9.0",
37
37
  "prismarine-world": "^3.6.0",
38
- "protodef": "1.17.0",
38
+ "protodef": "^1.18.0",
39
39
  "typed-emitter": "^1.0.0",
40
40
  "vec3": "^0.1.7"
41
41
  },
@@ -44,7 +44,7 @@
44
44
  "doctoc": "^2.0.1",
45
45
  "minecraft-wrap": "^1.3.0",
46
46
  "mineflayer": "file:",
47
- "mocha": "^10.0.0",
47
+ "mocha": "^11.0.1",
48
48
  "protodef-yaml": "^1.5.3",
49
49
  "standard": "^17.0.0",
50
50
  "standard-markdown": "^7.1.0",