CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PrismarineJS

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.

GitHub Repository: PrismarineJS/mineflayer
Path: blob/master/test/internalTest.js
Views: 789
1
/* eslint-env mocha */
2
3
const mineflayer = require('../')
4
const vec3 = require('vec3')
5
const mc = require('minecraft-protocol')
6
const assert = require('assert')
7
const { sleep } = require('../lib/promise_utils')
8
const nbt = require('prismarine-nbt')
9
10
for (const supportedVersion of mineflayer.testedVersions) {
11
const registry = require('prismarine-registry')(supportedVersion)
12
const version = registry.version
13
const Chunk = require('prismarine-chunk')(supportedVersion)
14
const isNewPlayerInfoFormat = registry.version['>=']('1.21.3')
15
function wrapPlayerInfo (n) {
16
if (isNewPlayerInfoFormat) {
17
return {
18
_value: n,
19
add_player: (n & 1) !== 0,
20
initialize_chat: (n & 2) !== 0,
21
update_game_mode: (n & 4) !== 0,
22
update_listed: (n & 8) !== 0,
23
update_latency: (n & 16) !== 0,
24
update_display_name: (n & 32) !== 0,
25
update_priority: (n & 64) !== 0
26
}
27
}
28
return n
29
}
30
31
const hasSignedChat = registry.supportFeature('signedChat')
32
function chatText (text) {
33
// TODO: move this to prismarine-chat in a new ChatMessage(text).toNotch(asNbt) method
34
return registry.supportFeature('chatPacketsUseNbtComponents')
35
? nbt.comp({ text: nbt.string(text) })
36
: JSON.stringify({ text })
37
}
38
39
function generateChunkPacket (chunk) {
40
const lights = chunk.dumpLight()
41
return {
42
x: 0,
43
z: 0,
44
groundUp: true,
45
biomes: chunk.dumpBiomes !== undefined ? chunk.dumpBiomes() : undefined,
46
heightmaps: {
47
type: 'compound',
48
name: '',
49
value: {
50
MOTION_BLOCKING: { type: 'longArray', value: new Array(36).fill([0, 0]) }
51
}
52
}, // send fake heightmap
53
bitMap: chunk.getMask(),
54
chunkData: chunk.dump(),
55
blockEntities: [],
56
trustEdges: false,
57
skyLightMask: lights?.skyLightMask,
58
blockLightMask: lights?.blockLightMask,
59
emptySkyLightMask: lights?.emptySkyLightMask,
60
emptyBlockLightMask: lights?.emptyBlockLightMask,
61
skyLight: lights?.skyLight,
62
blockLight: lights?.blockLight
63
}
64
}
65
66
describe(`mineflayer_internal ${supportedVersion}v`, function () {
67
this.timeout(10 * 1000)
68
let bot
69
let server
70
beforeEach((done) => {
71
server = mc.createServer({
72
'online-mode': false,
73
version: supportedVersion,
74
// 25565 - local server, 25566 - proxy server
75
port: 25567
76
})
77
server.on('listening', () => {
78
bot = mineflayer.createBot({
79
username: 'player',
80
version: supportedVersion,
81
port: 25567
82
})
83
bot.test = {}
84
85
bot.test.buildChunk = () => {
86
if (bot.supportFeature('tallWorld')) {
87
return new Chunk({ minY: -64, worldHeight: 384 })
88
} else {
89
return new Chunk()
90
}
91
}
92
93
bot.test.generateLoginPacket = () => {
94
let loginPacket
95
if (bot.supportFeature('usesLoginPacket')) {
96
loginPacket = registry.loginPacket
97
loginPacket.entityId = 0 // Default login packet in minecraft-data 1.16.5 is 1, so set it to 0
98
} else {
99
loginPacket = {
100
entityId: 0,
101
levelType: 'fogetaboutit',
102
gameMode: 0,
103
previousGameMode: 255,
104
worldNames: ['minecraft:overworld'],
105
dimension: 0,
106
worldName: 'minecraft:overworld',
107
hashedSeed: [0, 0],
108
difficulty: 0,
109
maxPlayers: 20,
110
reducedDebugInfo: 1,
111
enableRespawnScreen: true
112
}
113
}
114
return loginPacket
115
}
116
done()
117
})
118
})
119
afterEach((done) => {
120
bot.on('end', () => {
121
done()
122
})
123
server.close()
124
})
125
it('chat', (done) => {
126
bot.once('chat', (username, message) => {
127
assert.strictEqual(username, 'gary')
128
assert.strictEqual(message, 'hello')
129
bot.chat('hi')
130
})
131
server.on('playerJoin', (client) => {
132
client.write('login', bot.test.generateLoginPacket())
133
const message = hasSignedChat
134
? JSON.stringify({ text: 'hello' })
135
: JSON.stringify({
136
translate: 'chat.type.text',
137
with: [{
138
text: 'gary'
139
},
140
'hello'
141
]
142
})
143
144
if (hasSignedChat) {
145
const uuid = 'd3527a0b-bc03-45d5-a878-2aafdd8c8a43' // random
146
const networkName = chatText('gary')
147
148
if (registry.supportFeature('incrementedChatType')) {
149
client.write('player_chat', {
150
plainMessage: 'hello',
151
filterType: 0,
152
type: { registryIndex: 1 },
153
networkName,
154
previousMessages: [],
155
senderUuid: uuid,
156
timestamp: Date.now(),
157
index: 0,
158
salt: 0n
159
})
160
} else if (registry.supportFeature('useChatSessions')) {
161
client.write('player_chat', {
162
plainMessage: 'hello',
163
filterType: 0,
164
type: 0,
165
networkName,
166
previousMessages: [],
167
senderUuid: uuid,
168
timestamp: Date.now(),
169
index: 0,
170
salt: 0n
171
})
172
} else if (registry.supportFeature('chainedChatWithHashing')) {
173
client.write('player_chat', {
174
plainMessage: 'hello',
175
filterType: 0,
176
type: 0,
177
networkName,
178
previousMessages: [],
179
senderUuid: uuid,
180
timestamp: Date.now(),
181
salt: 0n,
182
signature: Buffer.alloc(0)
183
})
184
} else {
185
client.write('player_chat', {
186
signedChatContent: '',
187
unsignedChatContent: message,
188
type: 0,
189
senderUuid: uuid,
190
senderName: JSON.stringify({ text: 'gary' }),
191
senderTeam: undefined,
192
timestamp: Date.now(),
193
salt: 0n,
194
signature: Buffer.alloc(0)
195
})
196
}
197
} else {
198
client.write('chat', { message, position: 0, sender: '0' })
199
}
200
function onChat (packet) {
201
const msg = packet.message || packet.unsignedChatContent || packet.signedChatContent
202
assert.strictEqual(msg, 'hi')
203
done()
204
}
205
client.on('chat_message', onChat)
206
client.on('chat', onChat)
207
})
208
})
209
it('entity effects', (done) => {
210
bot.once('entityEffect', (entity, effect) => {
211
assert.strictEqual(entity.id, 8)
212
assert.strictEqual(effect.id, 10)
213
assert.strictEqual(effect.amplifier, 1)
214
assert.strictEqual(effect.duration, 11)
215
done()
216
})
217
// Versions prior to 1.11 have capital first letter
218
const entities = bot.registry.entitiesByName
219
const creeperId = entities.creeper ? entities.creeper.id : entities.Creeper.id
220
server.on('playerJoin', (client) => {
221
client.write(bot.registry.supportFeature('consolidatedEntitySpawnPacket') ? 'spawn_entity' : 'spawn_entity_living', {
222
entityId: 8, // random
223
entityUUID: '00112233-4455-6677-8899-aabbccddeeff',
224
objectUUID: '00112233-4455-6677-8899-aabbccddeeff',
225
type: creeperId,
226
x: 10,
227
y: 11,
228
z: 12,
229
yaw: 13,
230
pitch: 14,
231
headPitch: 14,
232
velocityX: 16,
233
velocityY: 17,
234
velocityZ: 18,
235
metadata: []
236
})
237
client.write('entity_effect', {
238
entityId: 8,
239
effectId: 10,
240
amplifier: 1,
241
duration: 11,
242
hideParticles: false
243
})
244
})
245
})
246
it('blockAt', (done) => {
247
const pos = vec3(1, 65, 1)
248
const goldId = bot.registry.blocksByName.gold_block.id
249
bot.on('chunkColumnLoad', (columnPoint) => {
250
assert.strictEqual(columnPoint.x, 0)
251
assert.strictEqual(columnPoint.z, 0)
252
assert.strictEqual(bot.blockAt(pos).type, goldId)
253
done()
254
})
255
server.on('playerJoin', (client) => {
256
client.write('login', bot.test.generateLoginPacket())
257
const chunk = bot.test.buildChunk()
258
chunk.setBlockType(pos, goldId)
259
client.write('map_chunk', generateChunkPacket(chunk))
260
})
261
})
262
263
describe('physics', () => {
264
const pos = vec3(1, 65, 1)
265
const pos2 = vec3(2, 65, 1)
266
const goldId = 41
267
it('no physics if there is no chunk', (done) => {
268
let fail = 0
269
const basePosition = {
270
x: 1.5,
271
y: 66,
272
z: 1.5,
273
dx: 0, // 1.21.3
274
dy: 0, // 1.21.3
275
dz: 0, // 1.21.3
276
pitch: 0,
277
yaw: 0,
278
flags: bot.registry.version['>=']('1.21.3') ? {} : 0,
279
teleportId: 0
280
}
281
server.on('playerJoin', async (client) => {
282
await client.write('login', bot.test.generateLoginPacket())
283
await client.write('position', basePosition)
284
client.on('packet', (data, meta) => {
285
const packetName = meta.name
286
switch (packetName) {
287
case 'position':
288
fail++
289
break
290
case 'position_look':
291
fail++
292
break
293
case 'look':
294
fail++
295
break
296
}
297
if (fail > 1) assert.fail('position packet sent')
298
})
299
await sleep(2000)
300
done()
301
})
302
})
303
it('absolute position & relative position (velocity)', (done) => {
304
server.on('playerJoin', async (client) => {
305
await client.write('login', bot.test.generateLoginPacket())
306
const chunk = await bot.test.buildChunk()
307
308
await chunk.setBlockType(pos, goldId)
309
await chunk.setBlockType(pos2, goldId)
310
await client.write('map_chunk', generateChunkPacket(chunk))
311
let check = true
312
let absolute = true
313
const basePosition = {
314
x: 1.5,
315
y: 66,
316
z: 1.5,
317
pitch: 0,
318
yaw: 0,
319
flags: 0,
320
teleportId: 0
321
}
322
client.on('packet', (data, meta) => {
323
const packetName = meta.name
324
switch (packetName) {
325
case 'teleport_confirm': {
326
assert.ok(basePosition.teleportId === data.teleportId)
327
break
328
}
329
case 'position_look': {
330
if (!check) return
331
if (absolute) {
332
assert.ok(bot.entity.velocity.y === 0)
333
} else {
334
assert.ok(bot.entity.velocity.y !== 0)
335
}
336
assert.ok(basePosition.x === data.x)
337
assert.ok(basePosition.y === data.y)
338
assert.ok(basePosition.z === data.z)
339
assert.ok(basePosition.yaw === data.yaw)
340
assert.ok(basePosition.pitch === data.pitch)
341
check = false
342
break
343
}
344
default:
345
break
346
}
347
})
348
// Absolute Position Tests
349
// absolute position test
350
check = true
351
await client.write('position', basePosition)
352
await bot.waitForTicks(5)
353
// absolute position test 2
354
basePosition.x = 2.5
355
basePosition.teleportId = 1
356
await bot.waitForTicks(1)
357
check = true
358
await client.write('position', basePosition)
359
await bot.waitForTicks(2)
360
// absolute position test 3
361
basePosition.x = 1.5
362
basePosition.teleportId = 2
363
await bot.waitForTicks(1)
364
check = true
365
await client.write('position', basePosition)
366
await bot.waitForTicks(2)
367
368
// Relative Position Tests
369
const relativePosition = {
370
x: 1,
371
y: 0,
372
z: 0,
373
dx: 0, // 1.21.3
374
dy: 0, // 1.21.3
375
dz: 0, // 1.21.3
376
pitch: 0,
377
yaw: 0,
378
flags: bot.registry.version['>=']('1.21.3')
379
? {
380
// flags = ["x", "y", "z", "yaw", "pitch", "dx", "dy", "dz", "yawDelta"]
381
// 31 = 0b11111
382
x: true,
383
y: true,
384
z: true,
385
yaw: true,
386
pitch: true
387
}
388
: 31,
389
teleportId: 3
390
}
391
absolute = false
392
// relative position test 1
393
basePosition.x = 2.5
394
basePosition.teleportId = 3
395
relativePosition.x = 1
396
relativePosition.teleportId = 3
397
await bot.waitForTicks(1)
398
check = true
399
await client.write('position', relativePosition)
400
await bot.waitForTicks(2)
401
// relative position test 2
402
basePosition.x = 1.5
403
basePosition.teleportId = 4
404
relativePosition.x = -1
405
relativePosition.teleportId = 4
406
await bot.waitForTicks(1)
407
check = true
408
await client.write('position', relativePosition)
409
await bot.waitForTicks(2)
410
// relative position test 3
411
basePosition.x = 2.5
412
basePosition.teleportId = 5
413
relativePosition.x = 1
414
relativePosition.teleportId = 5
415
await bot.waitForTicks(1)
416
check = true
417
await client.write('position', relativePosition)
418
await bot.waitForTicks(2)
419
done()
420
})
421
})
422
it('gravity + land on solid block + jump', (done) => {
423
let y = 80
424
let landed = false
425
bot.on('move', () => {
426
if (landed) return
427
assert.ok(bot.entity.position.y <= y)
428
assert.ok(bot.entity.position.y >= pos.y)
429
y = bot.entity.position.y
430
if (bot.entity.position.y <= pos.y + 1) {
431
assert.strictEqual(bot.entity.position.y, pos.y + 1)
432
assert.strictEqual(bot.entity.onGround, true)
433
landed = true
434
done()
435
} else {
436
assert.strictEqual(bot.entity.onGround, false)
437
}
438
})
439
server.on('playerJoin', (client) => {
440
client.write('login', bot.test.generateLoginPacket())
441
const chunk = bot.test.buildChunk()
442
443
chunk.setBlockType(pos, goldId)
444
client.write('map_chunk', generateChunkPacket(chunk))
445
client.write('position', {
446
x: 1.5,
447
y: 80,
448
z: 1.5,
449
pitch: 0,
450
yaw: 0,
451
flags: 0,
452
teleportId: 0
453
})
454
})
455
})
456
})
457
458
describe('world', () => {
459
const pos = vec3(1, 65, 1)
460
const goldId = 41
461
it('switchWorld respawn', (done) => {
462
const loginPacket = bot.test.generateLoginPacket()
463
let respawnPacket
464
if (bot.supportFeature('usesLoginPacket')) {
465
loginPacket.worldName = 'minecraft:overworld'
466
loginPacket.hashedSeed = [0, 0]
467
loginPacket.entityId = 0
468
respawnPacket = {
469
// 1.19+ the `dimension` filed is a string in respawn packet and undefined in login packet, in previous versions it's same NBT data in login/respawn
470
dimension: bot.supportFeature('dimensionDataInCodec') ? 'minecraft:overworld' : loginPacket.dimension,
471
worldName: loginPacket.worldName,
472
hashedSeed: loginPacket.hashedSeed,
473
gamemode: 0,
474
previousGamemode: 255,
475
isDebug: false,
476
isFlat: false,
477
copyMetadata: true,
478
death: {
479
dimensionName: '',
480
location: {
481
x: 0,
482
y: 0,
483
z: 0
484
}
485
}
486
}
487
if (bot.supportFeature('spawnRespawnWorldDataField')) {
488
respawnPacket = {
489
worldState: respawnPacket
490
}
491
respawnPacket.worldState.name = loginPacket.worldName
492
respawnPacket.worldState.dimension = loginPacket.dimension
493
}
494
} else {
495
respawnPacket = {
496
dimension: 0,
497
hashedSeed: [0, 0],
498
gamemode: 0,
499
levelType: 'default'
500
}
501
}
502
const chunk = bot.test.buildChunk()
503
chunk.setBlockType(pos, goldId)
504
const chunkPacket = generateChunkPacket(chunk)
505
const positionPacket = {
506
x: 1.5,
507
y: 80,
508
z: 1.5,
509
pitch: 0,
510
yaw: 0,
511
flags: 0,
512
teleportId: 0
513
}
514
server.on('playerJoin', async (client) => {
515
bot.once('respawn', () => {
516
assert.ok(bot.world.getColumn(0, 0) !== undefined)
517
bot.once('respawn', () => {
518
assert.ok(bot.world.getColumn(0, 0) === undefined)
519
done()
520
})
521
if (bot.supportFeature('spawnRespawnWorldDataField')) {
522
respawnPacket.worldState.name = 'minecraft:nether'
523
} else {
524
respawnPacket.worldName = 'minecraft:nether'
525
}
526
if (bot.supportFeature('spawnRespawnWorldDataField')) {
527
respawnPacket.worldState.dimension = 1
528
} else if (bot.supportFeature('usesLoginPacket')) {
529
respawnPacket.dimension.name = 'e'
530
} else {
531
respawnPacket.dimension = 1
532
}
533
client.write('respawn', respawnPacket)
534
})
535
await client.write('login', loginPacket)
536
await client.write('map_chunk', chunkPacket)
537
await client.write('position', positionPacket)
538
await client.write('update_health', {
539
health: 20,
540
food: 20,
541
foodSaturation: 0
542
})
543
await bot.waitForTicks(1)
544
await client.write('respawn', respawnPacket)
545
})
546
})
547
})
548
549
describe('game', () => {
550
it('responds to ping / transaction packets', (done) => { // only on 1.17
551
server.on('playerJoin', async (client) => {
552
if (bot.supportFeature('transactionPacketExists')) {
553
const transactionPacket = { windowId: 0, action: 42, accepted: false }
554
client.once('transaction', (data, meta) => {
555
assert.ok(meta.name === 'transaction')
556
assert.ok(data.action === 42)
557
assert.ok(data.accepted === true)
558
done()
559
})
560
client.write('transaction', transactionPacket)
561
} else {
562
client.once('pong', (data) => {
563
assert(data.id === 42)
564
done()
565
})
566
client.write('ping', { id: 42 })
567
}
568
})
569
})
570
})
571
572
describe('entities', () => {
573
it('entity id changes on login', (done) => {
574
const loginPacket = bot.test.generateLoginPacket()
575
server.on('playerJoin', (client) => {
576
if (bot.supportFeature('usesLoginPacket')) {
577
loginPacket.entityId = 0 // Default login packet in minecraft-data 1.16.5 is 1, so set it to 0
578
}
579
client.write('login', loginPacket)
580
bot.once('login', () => {
581
assert.ok(bot.entity.id === 0)
582
loginPacket.entityId = 42
583
bot.once('login', () => {
584
assert.ok(bot.entity.id === 42)
585
done()
586
})
587
client.write('login', loginPacket)
588
})
589
})
590
})
591
592
it('player displayName', (done) => {
593
server.on('playerJoin', (client) => {
594
bot.on('entitySpawn', (entity) => {
595
const player = bot.players[entity.username]
596
assert.strictEqual(player.username, entity.username)
597
// TODO: this test is broken as it updates the display name twice (once with, once without)
598
if (!isNewPlayerInfoFormat) assert.strictEqual(entity.username, player.displayName.toString())
599
if (registry.supportFeature('playerInfoActionIsBitfield')) {
600
client.write('player_info', {
601
action: wrapPlayerInfo(53),
602
data: [{
603
uuid: '1-2-3-4',
604
player: {
605
name: 'bot5',
606
properties: []
607
},
608
gamemode: 0,
609
latency: 0,
610
displayName: chatText('wvffle')
611
}]
612
})
613
} else {
614
client.write('player_info', {
615
id: 56,
616
state: 'play',
617
action: 3,
618
length: 1,
619
data: [{
620
UUID: '1-2-3-4',
621
name: 'bot5',
622
propertiesLength: 0,
623
properties: [],
624
gamemode: 0,
625
ping: 0,
626
hasDisplayName: true,
627
displayName: chatText('wvffle')
628
}]
629
})
630
}
631
})
632
633
bot.once('playerUpdated', (player) => {
634
assert.strictEqual('wvffle', player.displayName.toString())
635
if (registry.supportFeature('playerInfoActionIsBitfield')) {
636
client.write('player_info', {
637
action: wrapPlayerInfo(53),
638
data: [{
639
uuid: '1-2-3-4',
640
player: {
641
name: 'bot5',
642
properties: []
643
},
644
gamemode: 0,
645
latency: 0
646
}]
647
})
648
} else {
649
client.write('player_info', {
650
id: 56,
651
state: 'play',
652
action: 3,
653
length: 1,
654
data: [{
655
UUID: '1-2-3-4',
656
name: 'bot5',
657
propertiesLength: 0,
658
properties: [],
659
gamemode: 0,
660
ping: 0,
661
hasDisplayName: false
662
}]
663
})
664
}
665
666
bot.once('playerUpdated', (player) => {
667
// TODO: this test is broken as it updates the display name twice (once with, once without)
668
if (!isNewPlayerInfoFormat) assert.strictEqual(player.entity.username, player.displayName.toString())
669
done()
670
})
671
})
672
673
if (registry.supportFeature('playerInfoActionIsBitfield')) {
674
client.write('player_info', {
675
action: wrapPlayerInfo(53),
676
data: [{
677
uuid: '1-2-3-4',
678
player: {
679
name: 'bot5',
680
properties: []
681
},
682
gamemode: 0,
683
latency: 0
684
}]
685
})
686
} else {
687
client.write('player_info', {
688
id: 56,
689
state: 'play',
690
action: 0,
691
length: 1,
692
data: [{
693
UUID: '1-2-3-4',
694
name: 'bot5',
695
propertiesLength: 0,
696
properties: [],
697
gamemode: 0,
698
ping: 0,
699
hasDisplayName: false
700
}]
701
})
702
}
703
704
if (bot.registry.supportFeature('unifiedPlayerAndEntitySpawnPacket')) {
705
client.write('spawn_entity', {
706
entityId: 56,
707
objectUUID: '1-2-3-4',
708
type: bot.registry.entitiesByName.player.internalId,
709
x: 1,
710
y: 2,
711
z: 3,
712
pitch: 0,
713
yaw: 0,
714
headPitch: 0,
715
objectData: 1,
716
velocityX: 0,
717
velocityY: 0,
718
velocityZ: 0
719
})
720
} else {
721
client.write('named_entity_spawn', {
722
entityId: 56,
723
playerUUID: '1-2-3-4',
724
x: 1,
725
y: 2,
726
z: 3,
727
yaw: 0,
728
pitch: 0,
729
currentItem: -1,
730
metadata: []
731
})
732
}
733
})
734
})
735
736
it('sets players[player].entity to null upon despawn', (done) => {
737
let serverClient = null
738
bot.once('entitySpawn', (entity) => {
739
if (bot.version !== '1.17') {
740
serverClient.write('entity_destroy', {
741
entityIds: [8]
742
})
743
} else {
744
serverClient.write('destroy_entity', {
745
entityIds: 8
746
})
747
}
748
})
749
bot.once('entityGone', (entity) => {
750
assert.strictEqual(bot.players[entity.username], undefined)
751
done()
752
})
753
server.on('playerJoin', (client) => {
754
serverClient = client
755
756
if (registry.supportFeature('playerInfoActionIsBitfield')) {
757
client.write('player_info', {
758
action: wrapPlayerInfo(53),
759
data: [{
760
uuid: '1-2-3-4',
761
player: {
762
name: 'bot5',
763
properties: []
764
},
765
gamemode: 0,
766
latency: 0
767
}]
768
})
769
} else {
770
client.write('player_info', {
771
id: 56,
772
state: 'play',
773
action: 0,
774
length: 1,
775
data: [{
776
UUID: '1-2-3-4',
777
name: 'bot5',
778
propertiesLength: 0,
779
properties: [],
780
gamemode: 0,
781
ping: 0,
782
hasDisplayName: false
783
}]
784
})
785
}
786
787
if (bot.registry.supportFeature('unifiedPlayerAndEntitySpawnPacket')) {
788
client.write('spawn_entity', {
789
entityId: 56,
790
objectUUID: '1-2-3-4',
791
type: bot.registry.entitiesByName.player.internalId,
792
x: 1,
793
y: 2,
794
z: 3,
795
pitch: 0,
796
yaw: 0,
797
headPitch: 0,
798
objectData: 1,
799
velocityX: 0,
800
velocityY: 0,
801
velocityZ: 0
802
})
803
} else {
804
client.write('named_entity_spawn', {
805
entityId: 56,
806
playerUUID: '1-2-3-4',
807
x: 1,
808
y: 2,
809
z: 3,
810
yaw: 0,
811
pitch: 0,
812
currentItem: -1,
813
metadata: []
814
})
815
}
816
})
817
})
818
819
it('metadata', (done) => {
820
server.on('playerJoin', (client) => {
821
bot.on('entitySpawn', (entity) => {
822
assert.strictEqual(entity.displayName, 'Creeper')
823
824
const lastMeta = entity.metadata
825
bot.on('entityUpdate', (entity) => {
826
assert.ok('0' in entity.metadata)
827
assert.strictEqual(entity.metadata[0], 1)
828
assert.strictEqual(entity.metadata[1], lastMeta[1])
829
done()
830
})
831
832
client.write('entity_metadata', {
833
entityId: 8,
834
metadata: [
835
{ key: 0, type: bot.registry.supportFeature('mcDataHasEntityMetadata') ? 'int' : 0, value: 1 }
836
]
837
})
838
})
839
840
// Versions prior to 1.11 have capital first letter
841
const entities = bot.registry.entitiesByName
842
const creeperId = entities.creeper ? entities.creeper.id : entities.Creeper.id
843
client.write(bot.registry.supportFeature('consolidatedEntitySpawnPacket') ? 'spawn_entity' : 'spawn_entity_living', {
844
entityId: 8, // random
845
entityUUID: '00112233-4455-6677-8899-aabbccddeeff',
846
objectUUID: '00112233-4455-6677-8899-aabbccddeeff',
847
type: creeperId,
848
x: 10,
849
y: 11,
850
z: 12,
851
yaw: 13,
852
pitch: 14,
853
headPitch: 14,
854
velocityX: 16,
855
velocityY: 17,
856
velocityZ: 18,
857
metadata: [
858
{ type: 0, key: bot.registry.supportFeature('mcDataHasEntityMetadata') ? 'byte' : 0, value: 0 },
859
{ type: 0, key: bot.registry.supportFeature('mcDataHasEntityMetadata') ? 'int' : 1, value: 1 }
860
]
861
})
862
})
863
})
864
865
it('\'itemDrop\' event', function (done) {
866
const itemData = {
867
itemId: 149,
868
itemCount: 5
869
}
870
871
server.on('playerJoin', (client) => {
872
bot.on('itemDrop', (entity) => {
873
const slotPosition = metadataPacket.metadata[0].key
874
875
if (bot.supportFeature('itemsAreAlsoBlocks')) {
876
assert.strictEqual(entity.metadata[slotPosition].blockId, itemData.itemId)
877
} else if (bot.supportFeature('itemsAreNotBlocks')) {
878
assert.strictEqual(entity.metadata[slotPosition].itemId, itemData.itemId)
879
}
880
assert.strictEqual(entity.metadata[slotPosition].itemCount, itemData.itemCount)
881
882
done()
883
})
884
885
let entityType
886
if (['1.8', '1.9', '1.10', '1.11', '1.12'].includes(bot.majorVersion)) {
887
entityType = 2
888
} else {
889
entityType = bot.registry.entitiesArray.find(e => e.name.toLowerCase() === 'item' || e.name.toLowerCase() === 'item_stack').id
890
}
891
client.write('spawn_entity', {
892
entityId: 16,
893
objectUUID: '00112233-4455-6677-8899-aabbccddeeff',
894
type: Number(entityType),
895
x: 0,
896
y: 0,
897
z: 0,
898
pitch: 0,
899
yaw: 0,
900
headPitch: 0,
901
objectData: 1,
902
velocityX: 0,
903
velocityY: 0,
904
velocityZ: 0
905
})
906
907
const metadataPacket = {
908
entityId: 16,
909
metadata: [
910
{ key: 7, type: 6, value: { itemCount: itemData.itemCount } }
911
]
912
}
913
// Versions prior to 1.13 use 5 as type field value of metadata for storing a slot. 1.13 and so on, use 6
914
// Also the structure of a slot changes from 1.12 to 1.13
915
if (bot.supportFeature('itemsAreAlsoBlocks')) {
916
metadataPacket.metadata[0].key = 6
917
metadataPacket.metadata[0].type = 5
918
metadataPacket.metadata[0].value.blockId = itemData.itemId
919
metadataPacket.metadata[0].value.itemDamage = 0
920
} else if (bot.supportFeature('itemsAreNotBlocks')) {
921
if (bot.majorVersion === '1.13') metadataPacket.metadata[0].key = 6
922
metadataPacket.metadata[0].value.itemId = itemData.itemId
923
metadataPacket.metadata[0].value.present = true
924
}
925
926
if (bot.supportFeature('entityMetadataHasLong')) {
927
metadataPacket.metadata[0].type = 7
928
}
929
930
if (bot.registry.supportFeature('mcDataHasEntityMetadata')) {
931
metadataPacket.metadata[0].type = 'item_stack'
932
}
933
metadataPacket.metadata[0].value.addedComponentCount = 0
934
metadataPacket.metadata[0].value.removedComponentCount = 0
935
metadataPacket.metadata[0].value.components = []
936
metadataPacket.metadata[0].value.removeComponents = []
937
938
client.write('entity_metadata', metadataPacket)
939
})
940
})
941
})
942
943
it('bed', (done) => {
944
const blocks = bot.registry.blocksByName
945
const entities = bot.registry.entitiesByName
946
947
const playerPos = vec3(10, 0, 0)
948
const zombiePos = vec3(0, 0, 0)
949
const beds = [
950
{ head: vec3(10, 0, 3), foot: vec3(10, 0, 2), facing: 2, throws: false },
951
{ head: vec3(9, 0, 4), foot: vec3(10, 0, 4), facing: 3, throws: true, error: new Error('the bed is too far') },
952
{ head: vec3(8, 0, 0), foot: vec3(8, 0, 1), facing: 0, throws: true, error: new Error('there are monsters nearby') },
953
{ head: vec3(12, 0, 0), foot: vec3(11, 0, 0), facing: 1, throws: false }
954
]
955
956
const zombieId = entities.zombie ? entities.zombie.id : entities.Zombie.id
957
let bedBlock
958
if (bot.supportFeature('oneBlockForSeveralVariations', version.majorVersion)) {
959
bedBlock = blocks.bed
960
} else if (bot.supportFeature('blockSchemeIsFlat', version.majorVersion)) {
961
bedBlock = blocks.red_bed
962
}
963
const bedId = bedBlock.id
964
965
bot.once('chunkColumnLoad', (columnPoint) => {
966
for (const bed in beds) {
967
const bedBock = bot.blockAt(beds[bed].foot)
968
const bedBockMetadata = bot.parseBedMetadata(bedBock)
969
assert.strictEqual(bedBockMetadata.facing, beds[bed].facing, 'The facing property seems to be wrong')
970
assert.strictEqual(bedBockMetadata.part, false, 'The part property seems to be wrong') // Is the foot
971
972
if (beds[bed].throws) {
973
bot.sleep(bedBock).catch(err => assert.strictEqual(err, beds[bed].error))
974
} else {
975
bot.sleep(bedBock).catch(err => assert.ifError(err))
976
}
977
}
978
979
done()
980
})
981
982
server.once('playerJoin', (client) => {
983
bot.time.timeOfDay = 18000
984
const loginPacket = bot.test.generateLoginPacket()
985
client.write('login', loginPacket)
986
987
const chunk = bot.test.buildChunk()
988
989
for (const bed in beds) {
990
chunk.setBlockType(beds[bed].head, bedId)
991
chunk.setBlockType(beds[bed].foot, bedId)
992
}
993
994
if (bot.supportFeature('blockStateId', version.majorVersion)) {
995
chunk.setBlockStateId(beds[0].foot, 3 + bedBlock.minStateId) // { facing: north, occupied: false, part: foot }
996
chunk.setBlockStateId(beds[0].head, 2 + bedBlock.minStateId) // { facing:north, occupied: false, part: head }
997
998
chunk.setBlockStateId(beds[1].foot, 15 + bedBlock.minStateId) // { facing: east, occupied:false, part:foot }
999
chunk.setBlockStateId(beds[1].head, 14 + bedBlock.minStateId) // { facing: east, occupied: false, part: head }
1000
1001
chunk.setBlockStateId(beds[2].foot, 7 + bedBlock.minStateId) // { facing: south, occupied: false, part: foot }
1002
chunk.setBlockStateId(beds[2].head, 6 + bedBlock.minStateId) // { facing: south, occupied: false, part: head }
1003
1004
chunk.setBlockStateId(beds[3].foot, 11 + bedBlock.minStateId) // { facing: west, occupied: false, part: foot }
1005
chunk.setBlockStateId(beds[3].head, 10 + bedBlock.minStateId) // { facing: west, occupied: false, part: head }
1006
} else if (bot.supportFeature('blockMetadata', version.majorVersion)) {
1007
chunk.setBlockData(beds[0].foot, 2) // { facing: north, occupied: false, part: foot }
1008
chunk.setBlockData(beds[0].head, 10) // { facing:north, occupied: false, part: head }
1009
1010
chunk.setBlockData(beds[1].foot, 3) // { facing: east, occupied:false, part:foot }
1011
chunk.setBlockData(beds[1].head, 11) // { facing: east, occupied: false, part: head }
1012
1013
chunk.setBlockData(beds[2].foot, 0) // { facing: south, occupied: false, part: foot }
1014
chunk.setBlockData(beds[2].head, 8) // { facing: south, occupied: false, part: head }
1015
1016
chunk.setBlockData(beds[3].foot, 1) // { facing: west, occupied: false, part: foot }
1017
chunk.setBlockData(beds[3].head, 9) // { facing: west, occupied: false, part: head }
1018
}
1019
1020
client.write('position', {
1021
x: playerPos.x,
1022
y: playerPos.y,
1023
z: playerPos.z,
1024
yaw: 0,
1025
pitch: 0,
1026
flags: 0,
1027
teleportId: 1
1028
})
1029
1030
client.write(bot.registry.supportFeature('consolidatedEntitySpawnPacket') ? 'spawn_entity' : 'spawn_entity_living', {
1031
entityId: 8,
1032
entityUUID: '00112233-4455-6677-8899-aabbccddeeff',
1033
objectUUID: '00112233-4455-6677-8899-aabbccddeeff',
1034
type: zombieId,
1035
x: zombiePos.x,
1036
y: zombiePos.y,
1037
z: zombiePos.z,
1038
yaw: 0,
1039
pitch: 0,
1040
headPitch: 0,
1041
velocityX: 0,
1042
velocityY: 0,
1043
velocityZ: 0,
1044
metadata: []
1045
})
1046
1047
client.write('map_chunk', generateChunkPacket(chunk))
1048
})
1049
})
1050
1051
describe('tablist', () => {
1052
it('handles newlines in header and footer', (done) => {
1053
const HEADER = 'asd\ndsa'
1054
const FOOTER = '\nas\nas\nas\n'
1055
bot._client.on('playerlist_header', (packet) => {
1056
setImmediate(() => {
1057
assert.strictEqual(bot.tablist.header.toString(), HEADER)
1058
assert.strictEqual(bot.tablist.footer.toString(), FOOTER)
1059
done()
1060
})
1061
})
1062
// TODO: figure out how the "extra" should be encoded in NBT so this branch can be removed
1063
if (registry.supportFeature('chatPacketsUseNbtComponents')) {
1064
server.on('playerJoin', (client) => {
1065
client.write('playerlist_header', {
1066
header: chatText(HEADER),
1067
footer: chatText(FOOTER)
1068
})
1069
})
1070
} else {
1071
server.on('playerJoin', (client) => {
1072
client.write('playerlist_header', {
1073
header: JSON.stringify({ text: '', extra: [{ text: HEADER, color: 'yellow' }] }),
1074
footer: JSON.stringify({ text: '', extra: [{ text: FOOTER, color: 'yellow' }] })
1075
})
1076
})
1077
}
1078
})
1079
})
1080
})
1081
}
1082
1083