mineflayer 4.7.0 → 4.8.1

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.
@@ -33,6 +33,8 @@ jobs:
33
33
  mcVersion: '1.19'
34
34
  - javaVersion: 17
35
35
  mcVersion: '1.19.2'
36
+ - javaVersion: 17
37
+ mcVersion: '1.19.3'
36
38
  fail-fast: false
37
39
 
38
40
  steps:
package/README.md CHANGED
@@ -123,7 +123,7 @@ And you'll get a *live* view looking like this:
123
123
  |[guard](https://github.com/PrismarineJS/mineflayer/blob/master/examples/guard.js) | Make a bot guard a defined area from nearby mobs |
124
124
  |[multiple-from-file](https://github.com/PrismarineJS/mineflayer/blob/master/examples/multiple_from_file.js) | Add a text file with accounts and have them all login |
125
125
 
126
- And many mores in the [examples](https://github.com/PrismarineJS/mineflayer/tree/master/examples) folder
126
+ And many more in the [examples](https://github.com/PrismarineJS/mineflayer/tree/master/examples) folder.
127
127
 
128
128
  ### Modules
129
129
 
package/docs/README.md CHANGED
@@ -123,7 +123,7 @@ And you'll get a *live* view looking like this:
123
123
  |[guard](https://github.com/PrismarineJS/mineflayer/blob/master/examples/guard.js) | Make a bot guard a defined area from nearby mobs |
124
124
  |[multiple-from-file](https://github.com/PrismarineJS/mineflayer/blob/master/examples/multiple_from_file.js) | Add a text file with accounts and have them all login |
125
125
 
126
- And many mores in the [examples](https://github.com/PrismarineJS/mineflayer/tree/master/examples) folder
126
+ And many more in the [examples](https://github.com/PrismarineJS/mineflayer/tree/master/examples) folder.
127
127
 
128
128
  ### Modules
129
129
 
package/docs/history.md CHANGED
@@ -1,3 +1,18 @@
1
+ ## 4.8.1
2
+
3
+ * Fix client crashing when player_remove contains unknown player (@frej4189)
4
+ * Improve look and fix bug slow craft (@sefirosweb)
5
+ * Fix player entity being unset when player is updated (@frej4189)
6
+ * Fix type (@sefirosweb)
7
+ * Improve crafting stacks (@sefirosweb)
8
+ * add example for using the node:readline module (@Jovan-04)
9
+
10
+ ## 4.8.0
11
+
12
+ * Update chat parsing (@frej4189)
13
+ * Fix message event not including chat position (@frej4189)
14
+ * 1.19.3 (@frej4189)
15
+
1
16
  ## 4.7.0
2
17
 
3
18
  * 1.19.2 support (@frej4189)
@@ -12,9 +12,9 @@
12
12
  | <sub>EN</sub> [English](../README.md) | <sub>RU</sub> [русский](../ru/README_RU.md) | <sub>ES</sub> [Español](../es/README_ES.md) | <sub>FR</sub> [Français](../fr/README_FR.md) | <sub>TR</sub> [Türkçe](../tr/README_TR.md) | <sub>ZH</sub> [中文](../zh/README_ZH_CN.md) |
13
13
  |-------------------------|----------------------------|----------------------------|----------------------------|----------------------------|----------------------------|
14
14
 
15
- Создавайте ботов Minecraft с помощью мощного, стабильного и высокоуровневого JavaScript [API](api.md).
15
+ Создавайте ботов Minecraft с помощью мощного, стабильного и высокоуровневого JavaScript [API](api.md), также можете использовать Python
16
16
 
17
- Первый раз используете Node.js? Начните с [этого](tutorial.md).
17
+ Первый раз используете Node.js? Начните с [этого](tutorial.md). Любите гонять змею? Зацените [Python примеры](https://github.com/PrismarineJS/mineflayer/tree/master/examples/python) и попробуйте колабнуться в [Mineflayer Google коллабе](https://colab.research.google.com/github/PrismarineJS/mineflayer/blob/master/docs/mineflayer.ipynb).
18
18
 
19
19
  ## Возможности
20
20
 
@@ -0,0 +1,50 @@
1
+ /*
2
+ * This example is an easy way to connect mineflayer to the node:readline module
3
+ * See: https://nodejs.org/api/readline.html
4
+ * Using this, we can make a simple terminal-to-ingame-chat interface
5
+ *
6
+ * Made by Jovan04 01/24/2023
7
+ */
8
+
9
+ if (process.argv.length < 4 || process.argv.length > 6) {
10
+ console.log('Usage : node readline.js <host> <port> [<name>] [<auth>]')
11
+ process.exit(1)
12
+ }
13
+
14
+ const mineflayer = require('mineflayer') // load mineflayer library
15
+ const readline = require('node:readline') // load the node.js readline module
16
+
17
+ // bot options
18
+ const options = {
19
+ host: process.argv[2],
20
+ port: parseInt(process.argv[3]),
21
+ username: process.argv[4] || 'readline',
22
+ auth: process.argv[5] || 'offline'
23
+ }
24
+
25
+ const bot = mineflayer.createBot(options) // join the minecraft server
26
+
27
+ const rl = readline.createInterface({ // creates our readline interface with our console as input and output
28
+ input: process.stdin,
29
+ output: process.stdout
30
+ })
31
+
32
+ bot.once('spawn', () => {
33
+ console.log(`Bot joined the game with username ${bot.username}.`)
34
+ rl.setPrompt('> '); rl.prompt() // gives us a little arrow at the bottom for the input line
35
+ })
36
+
37
+ bot.on('message', (message) => {
38
+ readline.moveCursor(process.stdout, -2, 0) // we move the cursor to the left two places because our cursor is already two positions in (because of the input arrow)
39
+ console.log(message.toAnsi()) // convert our message to ansi to preserve chat formatting
40
+ rl.prompt() // regenerate our little arrow on the input line
41
+ })
42
+
43
+ rl.on('line', (line) => {
44
+ readline.moveCursor(process.stdout, 0, -1) // move cursor up one line
45
+ readline.clearScreenDown(process.stdout) // clear all the lines below the cursor (i.e. the last line we entered)
46
+ bot.chat(line.toString()) // sends the line we entered to ingame chat
47
+ })
48
+
49
+ bot.on('kicked', console.log)
50
+ bot.on('error', console.log)
package/index.d.ts CHANGED
@@ -122,7 +122,7 @@ interface BotEvents {
122
122
  ) => Promise<void> | void
123
123
  noteHeard: (block: Block, instrument: Instrument, pitch: number) => Promise<void> | void
124
124
  pistonMove: (block: Block, isPulling: number, direction: number) => Promise<void> | void
125
- chestLidMove: (block: Block, isOpen: number) => Promise<void> | void
125
+ chestLidMove: (block: Block, isOpen: number, block2: Block | null) => Promise<void> | void
126
126
  blockBreakProgressObserved: (block: Block, destroyStage: number) => Promise<void> | void
127
127
  blockBreakProgressEnd: (block: Block) => Promise<void> | void
128
128
  diggingCompleted: (block: Block) => Promise<void> | void
@@ -567,6 +567,7 @@ export interface TransferOptions {
567
567
  window: Window
568
568
  itemType: number
569
569
  metadata: number | null
570
+ count?: number,
570
571
  sourceStart: number
571
572
  sourceEnd: number
572
573
  destStart: number
@@ -113,30 +113,18 @@ function inject (bot, options) {
113
113
  const verified = data.verified
114
114
  let msg
115
115
  if (bot.supportFeature('clientsideChatFormatting')) {
116
- let sender
117
-
118
- try {
119
- sender = JSON.parse(data.senderName)
120
- } catch {
121
- sender = {
122
- insertion: data.senderName,
123
- clickEvent: { action: 'suggest_command', value: `/tell ${data.senderName} ` },
124
- hoverEvent: { action: 'show_entity', contents: { id: bot.findPlayer(data.senderName).id, name: data.senderName } },
125
- text: data.senderName
126
- }
127
- }
128
-
129
116
  const parameters = {
130
- sender,
117
+ sender: data.senderName ? JSON.parse(data.senderName) : undefined,
118
+ target: data.targetName ? JSON.parse(data.targetName) : undefined,
131
119
  content: message ? JSON.parse(message) : { text: data.plainMessage }
132
120
  }
133
121
  msg = ChatMessage.fromNetwork(data.type, parameters)
134
122
 
135
123
  if (data.unsignedContent) {
136
- msg.unsigned = ChatMessage.fromNetwork(data.type, { sender, content: JSON.parse(data.unsignedContent) })
124
+ msg.unsigned = ChatMessage.fromNetwork(data.type, { sender: parameters.sender, target: parameters.target, content: JSON.parse(data.unsignedContent) })
137
125
  }
138
126
  } else {
139
- msg = ChatMessage.fromNotch(data.formattedMessage)
127
+ msg = ChatMessage.fromNotch(message)
140
128
  }
141
129
  bot.emit('message', msg, 'chat', data.sender, verified)
142
130
  bot.emit('messagestr', msg.toString(), 'chat', msg, data.sender, verified)
@@ -148,9 +136,9 @@ function inject (bot, options) {
148
136
  1: 'system',
149
137
  2: 'game_info'
150
138
  }
151
- bot.emit('message', msg, chatPositions[data.positionid], null)
152
- bot.emit('messagestr', msg.toString(), chatPositions[data.positionid], msg, null)
153
- if (data.positionid === 2) bot.emit('actionBar', msg, null)
139
+ bot.emit('message', msg, chatPositions[data.positionId], null)
140
+ bot.emit('messagestr', msg.toString(), chatPositions[data.positionId], msg, null)
141
+ if (data.positionId === 2) bot.emit('actionBar', msg, null)
154
142
  })
155
143
 
156
144
  function chatWithHeader (header, message) {
@@ -170,6 +158,8 @@ function inject (bot, options) {
170
158
  salt: 0n,
171
159
  argumentSignatures: [],
172
160
  signedPreview: false,
161
+ messageCount: 0,
162
+ acknowledged: Buffer.alloc(3),
173
163
  // 1.19.2 Chat Command packet also includes an array of last seen messages
174
164
  previousMessages: []
175
165
  })
@@ -6,6 +6,7 @@ module.exports = inject
6
6
  function inject (bot) {
7
7
  const Item = require('prismarine-item')(bot.registry)
8
8
  const Recipe = require('prismarine-recipe')(bot.registry).Recipe
9
+ let windowCraftingTable
9
10
 
10
11
  async function craft (recipe, count, craftingTable) {
11
12
  assert.ok(recipe)
@@ -13,19 +14,36 @@ function inject (bot) {
13
14
  if (recipe.requiresTable && !craftingTable) {
14
15
  throw new Error('recipe requires craftingTable')
15
16
  }
16
- for (let i = 0; i < count; i++) {
17
- await craftOnce(recipe, craftingTable)
17
+
18
+ try {
19
+ for (let i = 0; i < count; i++) {
20
+ await craftOnce(recipe, craftingTable)
21
+ }
22
+
23
+ if (windowCraftingTable) {
24
+ bot.closeWindow(windowCraftingTable)
25
+ windowCraftingTable = undefined
26
+ }
27
+ } catch (err) {
28
+ if (windowCraftingTable) {
29
+ bot.closeWindow(windowCraftingTable)
30
+ windowCraftingTable = undefined
31
+ }
32
+ throw new Error(err)
18
33
  }
19
34
  }
20
35
 
21
36
  async function craftOnce (recipe, craftingTable) {
22
37
  if (craftingTable) {
23
- bot.activateBlock(craftingTable)
24
- const [window] = await once(bot, 'windowOpen')
25
- if (!window.type.startsWith('minecraft:crafting')) {
38
+ if (!windowCraftingTable) {
39
+ bot.activateBlock(craftingTable)
40
+ const [window] = await once(bot, 'windowOpen')
41
+ windowCraftingTable = window
42
+ }
43
+ if (!windowCraftingTable.type.startsWith('minecraft:crafting')) {
26
44
  throw new Error('crafting: non craftingTable used as craftingTable')
27
45
  }
28
- await startClicking(window, 3, 3)
46
+ await startClicking(windowCraftingTable, 3, 3)
29
47
  } else {
30
48
  await startClicking(bot.inventory, 2, 2)
31
49
  }
@@ -73,7 +91,7 @@ function inject (bot) {
73
91
  if (ingredient.id === -1) return nextShapeClick()
74
92
  if (!window.selectedItem || window.selectedItem.type !== ingredient.id ||
75
93
  (ingredient.metadata != null &&
76
- window.selectedItem.metadata !== ingredient.metadata)) {
94
+ window.selectedItem.metadata !== ingredient.metadata)) {
77
95
  // we are not holding the item we need. click it.
78
96
  const sourceItem = window.findInventoryItem(ingredient.id, ingredient.metadata)
79
97
  if (!sourceItem) throw new Error('missing ingredient')
@@ -89,7 +107,7 @@ function inject (bot) {
89
107
  const destSlot = extraSlots.pop()
90
108
  if (!window.selectedItem || window.selectedItem.type !== ingredient.id ||
91
109
  (ingredient.metadata != null &&
92
- window.selectedItem.metadata !== ingredient.metadata)) {
110
+ window.selectedItem.metadata !== ingredient.metadata)) {
93
111
  // we are not holding the item we need. click it.
94
112
  const sourceItem = window.findInventoryItem(ingredient.id, ingredient.metadata)
95
113
  if (!sourceItem) throw new Error('missing ingredient')
@@ -126,7 +144,6 @@ function inject (bot) {
126
144
  for (let i = 1; i <= w * h; i++) {
127
145
  window.updateSlot(i, null)
128
146
  }
129
- closeTheWindow()
130
147
  return
131
148
  }
132
149
  const slotsToClick = []
@@ -145,11 +162,6 @@ function inject (bot) {
145
162
  for (const _slot of slotsToClick) {
146
163
  await bot.putAway(_slot)
147
164
  }
148
- closeTheWindow()
149
- }
150
-
151
- function closeTheWindow () {
152
- bot.closeWindow(window)
153
165
  }
154
166
 
155
167
  function slot (x, y) {
@@ -361,13 +361,14 @@ function inject (bot) {
361
361
  entity.metadata = metadata
362
362
  bot.emit('entityUpdate', entity)
363
363
 
364
- const typeSlot = bot.supportFeature('itemsAreAlsoBlocks') ? 5 : 6
364
+ const typeSlot = (bot.supportFeature('itemsAreAlsoBlocks') ? 5 : 6) + (bot.supportFeature('entityMetadataHasLong') ? 1 : 0)
365
365
  const slot = packet.metadata.find(e => e.type === typeSlot)
366
366
  if (entity.name && (entity.name.toLowerCase() === 'item' || entity.name === 'item_stack') && slot) {
367
367
  bot.emit('itemDrop', entity)
368
368
  }
369
369
 
370
- const pose = packet.metadata.find(e => e.type === 18)
370
+ const typePose = bot.supportFeature('entityMetadataHasLong') ? 19 : 18
371
+ const pose = packet.metadata.find(e => e.type === typePose)
371
372
  if (pose && pose.value === 2) {
372
373
  bot.emit('entitySleep', entity)
373
374
  }
@@ -444,81 +445,149 @@ function inject (bot) {
444
445
 
445
446
  bot._client.on('player_info', (packet) => {
446
447
  // player list item(s)
447
- for (const item of packet.data) {
448
- let player = bot.uuidToUsername[item.UUID] ? bot.players[bot.uuidToUsername[item.UUID]] : null
449
- if (packet.action === 0) {
448
+
449
+ if (bot.supportFeature('playerInfoActionIsBitfield')) {
450
+ for (const item of packet.data) {
451
+ let player = bot.uuidToUsername[item.uuid] ? bot.players[bot.uuidToUsername[item.uuid]] : null
450
452
  let newPlayer = false
451
453
 
452
- // New Player
453
- if (!player) {
454
- player = bot.players[item.name] = {
455
- username: item.name,
456
- ping: item.ping,
457
- uuid: item.UUID,
458
- displayName: new ChatMessage({ text: '', extra: [{ text: item.name }] }),
459
- profileKeys: item.crypto
460
- ? {
461
- publicKey: item.crypto.publicKey, // DER-encoded public key
462
- signature: item.crypto.signature // Signature
463
- }
464
- : null
465
- }
454
+ const obj = {
455
+ uuid: item.uuid
456
+ }
466
457
 
467
- bot.uuidToUsername[item.UUID] = item.name
468
- bot.emit('playerJoined', player)
469
- newPlayer = true
470
- } else {
471
- // Just an Update
472
- player.gamemode = item.gamemode
473
- player.ping = item.ping
474
- if (item.crypto) {
475
- player.profileKeys = {
476
- publicKey: item.crypto.publicKey,
477
- signature: item.crypto.signature
478
- }
479
- }
458
+ if (!player) newPlayer = true
459
+
460
+ player = player || obj
461
+
462
+ if (packet.action & 1) {
463
+ obj.username = item.player.name
464
+ obj.displayName = player.displayName || new ChatMessage({ text: '', extra: [{ text: item.player.name }] })
465
+ }
466
+
467
+ if (packet.action & 4) {
468
+ obj.gamemode = item.gamemode
469
+ }
470
+
471
+ if (packet.action & 16) {
472
+ obj.ping = item.latency
480
473
  }
481
474
 
482
475
  if (item.displayName) {
483
- player.displayName = new ChatMessage(JSON.parse(item.displayName))
476
+ obj.displayName = new ChatMessage(JSON.parse(item.displayName))
477
+ } else if (packet.action & 32) obj.displayName = new ChatMessage({ text: '', extra: [{ text: player.username || obj.username }] })
478
+
479
+ if (newPlayer) {
480
+ if (!obj.username) continue // Should be unreachable
481
+ player = bot.players[obj.username] = obj
482
+ bot.uuidToUsername[obj.uuid] = obj.username
483
+ } else {
484
+ Object.assign(player, obj)
484
485
  }
485
486
 
486
- const playerEntity = Object.values(bot.entities).find(e => e.type === 'player' && e.username === item.name)
487
+ const playerEntity = Object.values(bot.entities).find(e => e.type === 'player' && e.username === player.username)
487
488
  player.entity = playerEntity
488
489
 
489
490
  if (playerEntity === bot.entity) {
490
491
  bot.player = player
491
492
  }
492
493
 
493
- if (!newPlayer) {
494
- bot.emit('playerUpdated', player)
495
- }
496
- } else if (player) {
497
- if (packet.action === 1) {
498
- player.gamemode = item.gamemode
499
- } else if (packet.action === 2) {
500
- player.ping = item.ping
501
- } else if (packet.action === 3 && !item.displayName) {
502
- player.displayName = new ChatMessage({ text: '', extra: [{ text: player.username }] })
503
- } else if (packet.action === 3 && item.displayName) {
504
- player.displayName = new ChatMessage(JSON.parse(item.displayName))
505
- } else if (packet.action === 4) {
506
- if (player.entity === bot.entity) continue
507
-
508
- player.entity = null
509
- delete bot.players[player.username]
510
- delete bot.uuidToUsername[item.UUID]
511
- bot.emit('playerLeft', player)
512
- continue
494
+ if (newPlayer) {
495
+ bot.emit('playerJoined', player)
513
496
  } else {
514
- continue
497
+ bot.emit('playerUpdated', player)
515
498
  }
499
+ }
500
+ } else {
501
+ for (const item of packet.data) {
502
+ let player = bot.uuidToUsername[item.UUID] ? bot.players[bot.uuidToUsername[item.UUID]] : null
503
+ if (packet.action === 0) {
504
+ let newPlayer = false
505
+
506
+ // New Player
507
+ if (!player) {
508
+ player = bot.players[item.name] = {
509
+ username: item.name,
510
+ ping: item.ping,
511
+ uuid: item.UUID,
512
+ displayName: new ChatMessage({ text: '', extra: [{ text: item.name }] }),
513
+ profileKeys: item.crypto
514
+ ? {
515
+ publicKey: item.crypto.publicKey, // DER-encoded public key
516
+ signature: item.crypto.signature // Signature
517
+ }
518
+ : null
519
+ }
520
+
521
+ bot.uuidToUsername[item.UUID] = item.name
522
+ bot.emit('playerJoined', player)
523
+ newPlayer = true
524
+ } else {
525
+ // Just an Update
526
+ player.gamemode = item.gamemode
527
+ player.ping = item.ping
528
+ if (item.crypto) {
529
+ player.profileKeys = {
530
+ publicKey: item.crypto.publicKey,
531
+ signature: item.crypto.signature
532
+ }
533
+ }
534
+ }
535
+
536
+ if (item.displayName) {
537
+ player.displayName = new ChatMessage(JSON.parse(item.displayName))
538
+ }
539
+
540
+ const playerEntity = Object.values(bot.entities).find(e => e.type === 'player' && e.username === item.name)
541
+ player.entity = playerEntity
542
+
543
+ if (playerEntity === bot.entity) {
544
+ bot.player = player
545
+ }
516
546
 
517
- bot.emit('playerUpdated', player)
547
+ if (!newPlayer) {
548
+ bot.emit('playerUpdated', player)
549
+ }
550
+ } else if (player) {
551
+ if (packet.action === 1) {
552
+ player.gamemode = item.gamemode
553
+ } else if (packet.action === 2) {
554
+ player.ping = item.ping
555
+ } else if (packet.action === 3 && !item.displayName) {
556
+ player.displayName = new ChatMessage({ text: '', extra: [{ text: player.username }] })
557
+ } else if (packet.action === 3 && item.displayName) {
558
+ player.displayName = new ChatMessage(JSON.parse(item.displayName))
559
+ } else if (packet.action === 4) {
560
+ if (player.entity === bot.entity) continue
561
+
562
+ player.entity = null
563
+ delete bot.players[player.username]
564
+ delete bot.uuidToUsername[item.UUID]
565
+ bot.emit('playerLeft', player)
566
+ continue
567
+ } else {
568
+ continue
569
+ }
570
+
571
+ bot.emit('playerUpdated', player)
572
+ }
518
573
  }
519
574
  }
520
575
  })
521
576
 
577
+ // (1.19.3) player(s) leave the game
578
+ bot._client.on('player_remove', (packet) => {
579
+ for (const uuid of packet.players) {
580
+ const player = bot.uuidToUsername[uuid] ? bot.players[bot.uuidToUsername[uuid]] : null
581
+
582
+ if (!player || player.entity === bot.entity) continue
583
+
584
+ player.entity = null
585
+ delete bot.players[player.username]
586
+ delete bot.uuidToUsername[uuid]
587
+ bot.emit('playerLeft', player)
588
+ }
589
+ })
590
+
522
591
  // attaching to a vehicle
523
592
  bot._client.on('attach_entity', (packet) => {
524
593
  if (packet.entityId !== bot.entity.id) return
@@ -235,6 +235,10 @@ function inject (bot, { physicsEnabled }) {
235
235
  const yawChange = Math.round((yaw - bot.entity.yaw) / sensitivity) * sensitivity
236
236
  const pitchChange = Math.round((pitch - bot.entity.pitch) / sensitivity) * sensitivity
237
237
 
238
+ if (yawChange === 0 && pitchChange === 0) {
239
+ return
240
+ }
241
+
238
242
  bot.entity.yaw += yawChange
239
243
  bot.entity.pitch += pitchChange
240
244
 
package/lib/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  module.exports = {
2
2
  supportedVersions: ['1.8', '1.9', '1.10', '1.11', '1.12', '1.13', '1.14', '1.15', '1.16', '1.17', '1.18', '1.19'],
3
- 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']
3
+ 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']
4
4
  } // when updating testedVersions, make sure to update CI.yml
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mineflayer",
3
- "version": "4.7.0",
3
+ "version": "4.8.1",
4
4
  "description": "create minecraft bots with a stable, high level API",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -21,8 +21,8 @@
21
21
  },
22
22
  "license": "MIT",
23
23
  "dependencies": {
24
- "minecraft-data": "^3.15.2",
25
- "minecraft-protocol": "^1.38.1",
24
+ "minecraft-data": "^3.26.0",
25
+ "minecraft-protocol": "^1.40.3",
26
26
  "prismarine-biome": "^1.1.1",
27
27
  "prismarine-block": "^1.13.1",
28
28
  "prismarine-chat": "^1.7.1",