Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PrismarineJS
GitHub Repository: PrismarineJS/mineflayer
Path: blob/master/lib/plugins/entities.js
9427 views
1
const { Vec3 } = require('vec3')
2
const conv = require('../conversions')
3
const mojangson = require('mojangson')
4
// These values are only accurate for versions 1.14 and above (crouch hitbox changes)
5
// Todo: hitbox sizes for sleeping, swimming/crawling, and flying with elytra
6
const PLAYER_HEIGHT = 1.8
7
const CROUCH_HEIGHT = 1.5
8
const PLAYER_WIDTH = 0.6
9
const PLAYER_EYEHEIGHT = 1.62
10
const CROUCH_EYEHEIGHT = 1.27
11
12
module.exports = inject
13
14
const animationEvents = {
15
0: 'entitySwingArm',
16
1: 'entityHurt',
17
2: 'entityWake',
18
3: 'entityEat',
19
4: 'entityCriticalEffect',
20
5: 'entityMagicCriticalEffect'
21
}
22
23
const entityStatusEvents = {
24
2: 'entityHurt',
25
3: 'entityDead',
26
6: 'entityTaming',
27
7: 'entityTamed',
28
8: 'entityShakingOffWater',
29
10: 'entityEatingGrass',
30
55: 'entityHandSwap'
31
}
32
33
function inject (bot) {
34
const { mobs } = bot.registry
35
const Entity = require('prismarine-entity')(bot.version)
36
const Item = require('prismarine-item')(bot.version)
37
const ChatMessage = require('prismarine-chat')(bot.registry)
38
39
// ONLY 1.17 has this destroy_entity packet which is the same thing as entity_destroy packet except the entity is singular
40
// 1.17.1 reverted this change so this is just a simpler fix
41
bot._client.on('destroy_entity', (packet) => {
42
bot._client.emit('entity_destroy', { entityIds: [packet.entityId] })
43
})
44
45
bot.findPlayer = bot.findPlayers = (filter) => {
46
const filterFn = (entity) => {
47
if (entity.type !== 'player') return false
48
if (filter === null) return true
49
if (typeof filter === 'object' && filter instanceof RegExp) {
50
return entity.username.search(filter) !== -1
51
} else if (typeof filter === 'function') {
52
return filter(entity)
53
} else if (typeof filter === 'string') {
54
return entity.username.toLowerCase() === filter.toLowerCase()
55
}
56
return false
57
}
58
const resultSet = Object.values(bot.entities)
59
.filter(filterFn)
60
61
if (typeof filter === 'string') {
62
switch (resultSet.length) {
63
case 0:
64
return null
65
case 1:
66
return resultSet[0]
67
default:
68
return resultSet
69
}
70
}
71
return resultSet
72
}
73
74
bot.players = {}
75
bot.uuidToUsername = {}
76
bot.entities = {}
77
78
bot._playerFromUUID = (uuid) => Object.values(bot.players).find(player => player.uuid === uuid)
79
80
bot.nearestEntity = (match = (entity) => { return true }) => {
81
let best = null
82
let bestDistance = Number.MAX_VALUE
83
84
for (const entity of Object.values(bot.entities)) {
85
if (entity === bot.entity || !match(entity)) {
86
continue
87
}
88
89
const dist = bot.entity.position.distanceSquared(entity.position)
90
if (dist < bestDistance) {
91
best = entity
92
bestDistance = dist
93
}
94
}
95
96
return best
97
}
98
99
// Reset list of players and entities on login
100
bot._client.on('login', (packet) => {
101
bot.players = {}
102
bot.uuidToUsername = {}
103
bot.entities = {}
104
// login
105
bot.entity = fetchEntity(packet.entityId)
106
bot.username = bot._client.username
107
bot.entity.username = bot._client.username
108
bot.entity.type = 'player'
109
bot.entity.name = 'player'
110
bot.entity.height = PLAYER_HEIGHT
111
bot.entity.width = PLAYER_WIDTH
112
bot.entity.eyeHeight = PLAYER_EYEHEIGHT
113
})
114
115
bot._client.on('entity_equipment', (packet) => {
116
// entity equipment
117
const entity = fetchEntity(packet.entityId)
118
if (packet.equipments !== undefined) {
119
packet.equipments.forEach(equipment => entity.setEquipment(equipment.slot, equipment.item ? Item.fromNotch(equipment.item) : null))
120
} else {
121
entity.setEquipment(packet.slot, packet.item ? Item.fromNotch(packet.item) : null)
122
}
123
bot.emit('entityEquip', entity)
124
})
125
126
bot._client.on('bed', (packet) => {
127
// use bed
128
const entity = fetchEntity(packet.entityId)
129
entity.position.set(packet.location.x, packet.location.y, packet.location.z)
130
bot.emit('entitySleep', entity)
131
})
132
133
bot._client.on('animation', (packet) => {
134
// animation
135
const entity = fetchEntity(packet.entityId)
136
const eventName = animationEvents[packet.animation]
137
if (eventName) bot.emit(eventName, entity)
138
})
139
140
bot.on('entityCrouch', (entity) => {
141
entity.eyeHeight = CROUCH_EYEHEIGHT
142
entity.height = CROUCH_HEIGHT
143
})
144
145
bot.on('entityUncrouch', (entity) => {
146
entity.eyeHeight = PLAYER_EYEHEIGHT
147
entity.height = PLAYER_HEIGHT
148
})
149
150
bot._client.on('collect', (packet) => {
151
// collect item
152
const collector = fetchEntity(packet.collectorEntityId)
153
const collected = fetchEntity(packet.collectedEntityId)
154
bot.emit('playerCollect', collector, collected)
155
})
156
157
// What is internalId?
158
const entityDataByInternalId = Object.fromEntries(bot.registry.entitiesArray.map((e) => [e.internalId, e]))
159
160
function setEntityData (entity, type, entityData) {
161
entityData ??= entityDataByInternalId[type]
162
if (entityData) {
163
entity.type = entityData.type || 'object'
164
entity.displayName = entityData.displayName
165
entity.entityType = entityData.id
166
entity.name = entityData.name
167
entity.kind = entityData.category
168
entity.height = entityData.height
169
entity.width = entityData.width
170
} else {
171
// unknown entity
172
entity.type = 'other'
173
entity.entityType = type
174
entity.displayName = 'unknown'
175
entity.name = 'unknown'
176
entity.kind = 'unknown'
177
}
178
}
179
180
function updateEntityPos (entity, pos) {
181
if (bot.supportFeature('fixedPointPosition')) {
182
entity.position.set(pos.x / 32, pos.y / 32, pos.z / 32)
183
} else if (bot.supportFeature('doublePosition')) {
184
entity.position.set(pos.x, pos.y, pos.z)
185
}
186
entity.yaw = conv.fromNotchianYawByte(pos.yaw)
187
entity.pitch = conv.fromNotchianPitchByte(pos.pitch)
188
}
189
190
function addNewPlayer (entityId, uuid, pos) {
191
const entity = fetchEntity(entityId)
192
entity.type = 'player'
193
entity.name = 'player'
194
entity.username = bot.uuidToUsername[uuid]
195
entity.uuid = uuid
196
updateEntityPos(entity, pos)
197
entity.eyeHeight = PLAYER_EYEHEIGHT
198
entity.height = PLAYER_HEIGHT
199
entity.width = PLAYER_WIDTH
200
if (bot.players[entity.username] !== undefined && !bot.players[entity.username].entity) {
201
bot.players[entity.username].entity = entity
202
}
203
return entity
204
}
205
206
function addNewNonPlayer (entityId, uuid, entityType, pos) {
207
const entity = fetchEntity(entityId)
208
const entityData = bot.registry.entities[entityType]
209
setEntityData(entity, entityType, entityData)
210
updateEntityPos(entity, pos)
211
entity.uuid = uuid
212
return entity
213
}
214
215
bot._client.on('named_entity_spawn', (packet) => {
216
// in case player_info packet was not sent before named_entity_spawn : ignore named_entity_spawn (see #213)
217
if (packet.playerUUID in bot.uuidToUsername) {
218
// spawn named entity
219
const entity = addNewPlayer(packet.entityId, packet.playerUUID, packet, packet.metadata)
220
entity.dataBlobs = packet.data // this field doesn't appear to be listed on any version
221
entity.metadata = parseMetadata(packet.metadata, entity.metadata) // 1.8
222
bot.emit('entitySpawn', entity)
223
}
224
})
225
226
// spawn object/vehicle on versions < 1.19, on versions > 1.19 handles all non-player entities
227
// on versions >= 1.20.2, this also handles player entities
228
bot._client.on('spawn_entity', (packet) => {
229
const entityData = entityDataByInternalId[packet.type]
230
const entity = entityData?.type === 'player'
231
? addNewPlayer(packet.entityId, packet.objectUUID, packet)
232
: addNewNonPlayer(packet.entityId, packet.objectUUID, packet.type, packet)
233
bot.emit('entitySpawn', entity)
234
})
235
236
// spawn_entity_experience_orb packet was removed in 1.21.5+
237
// XP orbs are now handled through the general spawn_entity packet
238
bot._client.on('spawn_entity_experience_orb', (packet) => {
239
const entity = fetchEntity(packet.entityId)
240
entity.type = 'orb'
241
entity.name = 'experience_orb'
242
entity.width = 0.5
243
entity.height = 0.5
244
245
if (bot.supportFeature('fixedPointPosition')) {
246
entity.position.set(packet.x / 32, packet.y / 32, packet.z / 32)
247
} else if (bot.supportFeature('doublePosition')) {
248
entity.position.set(packet.x, packet.y, packet.z)
249
}
250
251
entity.count = packet.count
252
bot.emit('entitySpawn', entity)
253
})
254
255
// This packet is removed since 1.19 and merged into spawn_entity
256
bot._client.on('spawn_entity_living', (packet) => {
257
// spawn mob
258
const entity = fetchEntity(packet.entityId)
259
entity.type = 'mob'
260
entity.uuid = packet.entityUUID
261
const entityData = mobs[packet.type]
262
263
setEntityData(entity, packet.type, entityData)
264
265
if (bot.supportFeature('fixedPointPosition')) {
266
entity.position.set(packet.x / 32, packet.y / 32, packet.z / 32)
267
} else if (bot.supportFeature('doublePosition')) {
268
entity.position.set(packet.x, packet.y, packet.z)
269
}
270
271
entity.yaw = conv.fromNotchianYawByte(packet.yaw)
272
entity.pitch = conv.fromNotchianPitchByte(packet.pitch)
273
entity.headPitch = conv.fromNotchianPitchByte(packet.headPitch)
274
275
let notchVel
276
if (bot.supportFeature('entityVelocityIsLpVec3')) {
277
notchVel = new Vec3(packet.velocity.x, packet.velocity.y, packet.velocity.z)
278
} else {
279
notchVel = new Vec3(packet.velocityX, packet.velocityY, packet.velocityZ)
280
}
281
entity.velocity.update(conv.fromNotchVelocity(notchVel))
282
entity.metadata = parseMetadata(packet.metadata, entity.metadata)
283
284
bot.emit('entitySpawn', entity)
285
})
286
287
bot._client.on('entity_velocity', (packet) => {
288
// entity velocity
289
const entity = fetchEntity(packet.entityId)
290
let notchVel
291
if (bot.supportFeature('entityVelocityIsLpVec3')) {
292
notchVel = new Vec3(packet.velocity.x, packet.velocity.y, packet.velocity.z)
293
} else {
294
notchVel = new Vec3(packet.velocityX, packet.velocityY, packet.velocityZ)
295
}
296
entity.velocity.update(conv.fromNotchVelocity(notchVel))
297
})
298
299
bot._client.on('entity_destroy', (packet) => {
300
// destroy entity
301
packet.entityIds.forEach((id) => {
302
const entity = fetchEntity(id)
303
bot.emit('entityGone', entity)
304
entity.isValid = false
305
if (entity.username && bot.players[entity.username]) {
306
bot.players[entity.username].entity = null
307
}
308
delete bot.entities[id]
309
})
310
})
311
312
bot._client.on('rel_entity_move', (packet) => {
313
// entity relative move
314
const entity = fetchEntity(packet.entityId)
315
if (bot.supportFeature('fixedPointDelta')) {
316
entity.position.translate(packet.dX / 32, packet.dY / 32, packet.dZ / 32)
317
} else if (bot.supportFeature('fixedPointDelta128')) {
318
entity.position.translate(packet.dX / (128 * 32), packet.dY / (128 * 32), packet.dZ / (128 * 32))
319
}
320
bot.emit('entityMoved', entity)
321
})
322
323
bot._client.on('entity_look', (packet) => {
324
// entity look
325
const entity = fetchEntity(packet.entityId)
326
entity.yaw = conv.fromNotchianYawByte(packet.yaw)
327
entity.pitch = conv.fromNotchianPitchByte(packet.pitch)
328
bot.emit('entityMoved', entity)
329
})
330
331
bot._client.on('entity_move_look', (packet) => {
332
// entity look and relative move
333
const entity = fetchEntity(packet.entityId)
334
if (bot.supportFeature('fixedPointDelta')) {
335
entity.position.translate(packet.dX / 32, packet.dY / 32, packet.dZ / 32)
336
} else if (bot.supportFeature('fixedPointDelta128')) {
337
entity.position.translate(packet.dX / (128 * 32), packet.dY / (128 * 32), packet.dZ / (128 * 32))
338
}
339
entity.yaw = conv.fromNotchianYawByte(packet.yaw)
340
entity.pitch = conv.fromNotchianPitchByte(packet.pitch)
341
bot.emit('entityMoved', entity)
342
})
343
344
bot._client.on('entity_teleport', (packet) => {
345
// entity teleport
346
const entity = fetchEntity(packet.entityId)
347
if (bot.supportFeature('fixedPointPosition')) {
348
entity.position.set(packet.x / 32, packet.y / 32, packet.z / 32)
349
}
350
if (bot.supportFeature('doublePosition')) {
351
entity.position.set(packet.x, packet.y, packet.z)
352
}
353
entity.yaw = conv.fromNotchianYawByte(packet.yaw)
354
entity.pitch = conv.fromNotchianPitchByte(packet.pitch)
355
bot.emit('entityMoved', entity)
356
})
357
358
// 1.21.3 - merges the packets above
359
bot._client.on('sync_entity_position', (packet) => {
360
const entity = fetchEntity(packet.entityId)
361
entity.position.set(packet.x, packet.y, packet.z)
362
entity.velocity.set(packet.dx, packet.dy, packet.dz)
363
entity.yaw = packet.yaw
364
entity.pitch = packet.pitch
365
bot.emit('entityMoved', entity)
366
})
367
368
bot._client.on('entity_head_rotation', (packet) => {
369
// entity head look
370
const entity = fetchEntity(packet.entityId)
371
entity.headYaw = conv.fromNotchianYawByte(packet.headYaw)
372
bot.emit('entityMoved', entity)
373
})
374
375
bot._client.on('entity_status', (packet) => {
376
// entity status
377
const entity = fetchEntity(packet.entityId)
378
const eventName = entityStatusEvents[packet.entityStatus]
379
380
if (eventName === 'entityHandSwap' && entity.equipment) {
381
[entity.equipment[0], entity.equipment[1]] = [entity.equipment[1], entity.equipment[0]]
382
entity.heldItem = entity.equipment[0] // Update held item like prismarine-entity does upon equipment updates
383
}
384
385
if (eventName) bot.emit(eventName, entity)
386
})
387
388
bot._client.on('damage_event', (packet) => { // 1.20+
389
const entity = bot.entities[packet.entityId]
390
const source = bot.entities[packet.sourceCauseId - 1] // damage_event : SourceCauseId : The ID + 1 of the entity responsible for the damage, if present. If not present, the value is 0
391
bot.emit('entityHurt', entity, source)
392
})
393
394
bot._client.on('attach_entity', (packet) => {
395
// attach entity
396
const entity = fetchEntity(packet.entityId)
397
if (packet.vehicleId === -1) {
398
const vehicle = entity.vehicle
399
delete entity.vehicle
400
bot.emit('entityDetach', entity, vehicle)
401
} else {
402
entity.vehicle = fetchEntity(packet.vehicleId)
403
bot.emit('entityAttach', entity, entity.vehicle)
404
}
405
})
406
407
bot.fireworkRocketDuration = 0
408
function setElytraFlyingState (entity, elytraFlying) {
409
let startedFlying = false
410
if (elytraFlying) {
411
startedFlying = !entity.elytraFlying
412
entity.elytraFlying = true
413
} else if (entity.elytraFlying) {
414
entity.elytraFlying = false
415
}
416
if (bot.fireworkRocketDuration !== 0 && entity.id === bot.entity?.id && !elytraFlying) {
417
bot.fireworkRocketDuration = 0
418
knownFireworks.clear()
419
}
420
421
if (startedFlying) {
422
bot.emit('entityElytraFlew', entity)
423
}
424
}
425
426
const knownFireworks = new Set()
427
function handleBotUsedFireworkRocket (fireworkEntityId, fireworkInfo) {
428
if (knownFireworks.has(fireworkEntityId)) return
429
knownFireworks.add(fireworkEntityId)
430
let flightDur = fireworkInfo?.nbtData?.value?.Fireworks?.value?.Flight.value ?? 1
431
if (typeof flightDur !== 'number') { flightDur = 1 }
432
const baseDuration = 10 * (flightDur + 1)
433
const randomDuration = Math.floor(Math.random() * 6) + Math.floor(Math.random() * 7)
434
bot.fireworkRocketDuration = baseDuration + randomDuration
435
436
bot.emit('usedFirework', fireworkEntityId)
437
}
438
439
let fireworkEntityName
440
if (bot.supportFeature('fireworkNamePlural')) {
441
fireworkEntityName = 'fireworks_rocket'
442
} else if (bot.supportFeature('fireworkNameSingular')) {
443
fireworkEntityName = 'firework_rocket'
444
}
445
446
let fireworkMetadataIdx
447
let fireworkMetadataIsOpt
448
if (bot.supportFeature('fireworkMetadataVarInt7')) {
449
fireworkMetadataIdx = 7
450
fireworkMetadataIsOpt = false
451
} else if (bot.supportFeature('fireworkMetadataOptVarInt8')) {
452
fireworkMetadataIdx = 8
453
fireworkMetadataIsOpt = true
454
} else if (bot.supportFeature('fireworkMetadataOptVarInt9')) {
455
fireworkMetadataIdx = 9
456
fireworkMetadataIsOpt = true
457
}
458
const hasFireworkSupport = fireworkEntityName !== undefined && fireworkMetadataIdx !== undefined && fireworkMetadataIsOpt !== undefined
459
460
bot._client.on('entity_metadata', (packet) => {
461
// entity metadata
462
const entity = fetchEntity(packet.entityId)
463
const metadata = parseMetadata(packet.metadata, entity.metadata)
464
entity.metadata = metadata
465
bot.emit('entityUpdate', entity)
466
467
if (bot.supportFeature('mcDataHasEntityMetadata')) {
468
const metadataKeys = bot.registry.entitiesByName[entity.name]?.metadataKeys
469
const metas = metadataKeys ? Object.fromEntries(packet.metadata.map(e => [metadataKeys[e.key], e.value])) : {}
470
if (packet.metadata.some(m => m.type === 'item_stack')) {
471
bot.emit('itemDrop', entity)
472
}
473
if (metas.sleeping_pos || metas.pose === 2) {
474
bot.emit('entitySleep', entity)
475
}
476
477
if (hasFireworkSupport && fireworkEntityName === entity.name && metas.attached_to_target !== undefined) {
478
// fireworkMetadataOptVarInt9 and later is implied by
479
// mcDataHasEntityMetadata, so no need to check metadata index and type
480
// (eg fireworkMetadataOptVarInt8)
481
if (metas.attached_to_target !== 0) {
482
const entityId = metas.attached_to_target - 1
483
if (entityId === bot.entity?.id) {
484
handleBotUsedFireworkRocket(entity.id, metas.fireworks_item)
485
}
486
}
487
}
488
489
if (metas.shared_flags != null) {
490
if (bot.supportFeature('hasElytraFlying')) {
491
const elytraFlying = metas.shared_flags & 0x80
492
setElytraFlyingState(entity, Boolean(elytraFlying))
493
}
494
495
if (metas.shared_flags & 2) {
496
entity.crouching = true
497
bot.emit('entityCrouch', entity)
498
} else if (entity.crouching) { // prevent the initial entity_metadata packet from firing off an uncrouch event
499
entity.crouching = false
500
bot.emit('entityUncrouch', entity)
501
}
502
}
503
504
// Breathing (formerly in breath.js)
505
if (metas.air_supply != null) {
506
bot.oxygenLevel = Math.round(metas.air_supply / 15)
507
bot.emit('breath')
508
}
509
} else {
510
const typeSlot = (bot.supportFeature('itemsAreAlsoBlocks') ? 5 : 6) + (bot.supportFeature('entityMetadataHasLong') ? 1 : 0)
511
const slot = packet.metadata.find(e => e.type === typeSlot)
512
if (entity.name && (entity.name.toLowerCase() === 'item' || entity.name === 'item_stack') && slot) {
513
bot.emit('itemDrop', entity)
514
}
515
516
const typePose = bot.supportFeature('entityMetadataHasLong') ? 19 : 18
517
const pose = packet.metadata.find(e => e.type === typePose)
518
if (pose && pose.value === 2) {
519
bot.emit('entitySleep', entity)
520
}
521
522
if (hasFireworkSupport && fireworkEntityName === entity.name) {
523
const attachedToTarget = packet.metadata.find(e => e.key === fireworkMetadataIdx)
524
if (attachedToTarget !== undefined) {
525
let entityId
526
if (fireworkMetadataIsOpt) {
527
if (attachedToTarget.value !== 0) {
528
entityId = attachedToTarget.value - 1
529
} // else, not attached to an entity
530
} else {
531
entityId = attachedToTarget.value
532
}
533
if (entityId !== undefined && entityId === bot.entity?.id) {
534
const fireworksItem = packet.metadata.find(e => e.key === (fireworkMetadataIdx - 1))
535
handleBotUsedFireworkRocket(entity.id, fireworksItem?.value)
536
}
537
}
538
}
539
540
const bitField = packet.metadata.find(p => p.key === 0)
541
if (bitField !== undefined) {
542
if (bot.supportFeature('hasElytraFlying')) {
543
const elytraFlying = bitField.value & 0x80
544
setElytraFlyingState(entity, Boolean(elytraFlying))
545
}
546
547
if ((bitField.value & 2) !== 0) {
548
entity.crouching = true
549
bot.emit('entityCrouch', entity)
550
} else if (entity.crouching) { // prevent the initial entity_metadata packet from firing off an uncrouch event
551
entity.crouching = false
552
bot.emit('entityUncrouch', entity)
553
}
554
}
555
}
556
})
557
558
bot._client.on('entity_effect', (packet) => {
559
// entity effect
560
const entity = fetchEntity(packet.entityId)
561
const effect = {
562
id: packet.effectId,
563
amplifier: packet.amplifier,
564
duration: packet.duration
565
}
566
entity.effects[effect.id] = effect
567
bot.emit('entityEffect', entity, effect)
568
})
569
570
bot._client.on('remove_entity_effect', (packet) => {
571
// remove entity effect
572
const entity = fetchEntity(packet.entityId)
573
let effect = entity.effects[packet.effectId]
574
if (effect) {
575
delete entity.effects[effect.id]
576
} else {
577
// unknown effect
578
effect = {
579
id: packet.effectId,
580
amplifier: -1,
581
duration: -1
582
}
583
}
584
bot.emit('entityEffectEnd', entity, effect)
585
})
586
587
const updateAttributes = (packet) => {
588
const entity = fetchEntity(packet.entityId)
589
if (!entity.attributes) entity.attributes = {}
590
for (const prop of packet.properties) {
591
entity.attributes[prop.key] = {
592
value: prop.value,
593
modifiers: prop.modifiers
594
}
595
}
596
bot.emit('entityAttributes', entity)
597
}
598
bot._client.on('update_attributes', updateAttributes) // 1.8
599
bot._client.on('entity_update_attributes', updateAttributes) // others
600
601
bot._client.on('spawn_entity_weather', (packet) => {
602
// spawn global entity
603
const entity = fetchEntity(packet.entityId)
604
entity.type = 'global'
605
entity.globalType = 'thunderbolt'
606
entity.uuid = packet.entityUUID
607
entity.position.set(packet.x / 32, packet.y / 32, packet.z / 32)
608
bot.emit('entitySpawn', entity)
609
})
610
611
bot.on('spawn', () => {
612
bot.emit('entitySpawn', bot.entity)
613
})
614
615
function handlePlayerInfoBitfield (packet) {
616
for (const item of packet.data) {
617
let player = bot._playerFromUUID(item.uuid)
618
const newPlayer = !player
619
620
if (newPlayer) {
621
player = { uuid: item.uuid }
622
}
623
624
if (packet.action.add_player) {
625
player.username = item.player.name
626
player.displayName = new ChatMessage({ text: '', extra: [{ text: item.player.name }] })
627
player.skinData = extractSkinInformation(item.player.properties)
628
}
629
if (packet.action.initialize_chat && item.chatSession) {
630
player.chatSession = {
631
publicKey: item.chatSession.publicKey,
632
sessionUuid: item.chatSession.uuid
633
}
634
}
635
if (packet.action.update_game_mode) {
636
player.gamemode = item.gamemode
637
}
638
if (packet.action.update_listed) {
639
player.listed = item.listed
640
}
641
if (packet.action.update_latency) {
642
player.ping = item.latency
643
}
644
if (packet.action.update_display_name) {
645
player.displayName = item.displayName ? ChatMessage.fromNotch(item.displayName) : new ChatMessage({ text: '', extra: [{ text: player.username }] })
646
}
647
648
if (newPlayer) {
649
if (!player.username) continue // Should be unreachable if add_player is always sent for new players
650
bot.players[player.username] = player
651
bot.uuidToUsername[player.uuid] = player.username
652
}
653
654
const playerEntity = Object.values(bot.entities).find(e => e.type === 'player' && e.username === player.username)
655
player.entity = playerEntity
656
657
if (playerEntity === bot.entity) {
658
bot.player = player
659
}
660
661
if (newPlayer) {
662
bot.emit('playerJoined', player)
663
} else {
664
bot.emit('playerUpdated', player)
665
}
666
}
667
}
668
669
function handlePlayerInfoLegacy (packet) {
670
for (const item of packet.data) {
671
let player = bot._playerFromUUID(item.uuid)
672
673
switch (packet.action) {
674
case 'add_player': {
675
const newPlayer = !player
676
if (newPlayer) {
677
player = bot.players[item.name] = {
678
username: item.name,
679
uuid: item.uuid
680
}
681
bot.uuidToUsername[item.uuid] = item.name
682
}
683
684
player.ping = item.ping
685
player.gamemode = item.gamemode
686
player.displayName = item.displayName ? ChatMessage.fromNotch(item.displayName) : new ChatMessage({ text: '', extra: [{ text: item.name }] })
687
if (item.properties) {
688
player.skinData = extractSkinInformation(item.properties)
689
}
690
if (item.crypto) {
691
player.profileKeys = {
692
publicKey: item.crypto.publicKey,
693
signature: item.crypto.signature
694
}
695
}
696
697
const playerEntity = Object.values(bot.entities).find(e => e.type === 'player' && e.username === item.name)
698
player.entity = playerEntity
699
if (playerEntity === bot.entity) {
700
bot.player = player
701
}
702
703
if (newPlayer) bot.emit('playerJoined', player)
704
else bot.emit('playerUpdated', player)
705
break
706
}
707
case 'update_gamemode': {
708
if (player) {
709
player.gamemode = item.gamemode
710
bot.emit('playerUpdated', player)
711
}
712
break
713
}
714
case 'update_latency': {
715
if (player) {
716
player.ping = item.ping
717
bot.emit('playerUpdated', player)
718
}
719
break
720
}
721
case 'update_display_name': {
722
if (player) {
723
player.displayName = item.displayName ? ChatMessage.fromNotch(item.displayName) : new ChatMessage({ text: '', extra: [{ text: player.username }] })
724
bot.emit('playerUpdated', player)
725
}
726
break
727
}
728
case 'remove_player': {
729
if (player) {
730
if (player.entity === bot.entity) continue
731
player.entity = null
732
delete bot.players[player.username]
733
delete bot.uuidToUsername[item.uuid]
734
bot.emit('playerLeft', player)
735
}
736
break
737
}
738
}
739
}
740
}
741
742
bot._client.on('player_info', bot.supportFeature('playerInfoActionIsBitfield') ? handlePlayerInfoBitfield : handlePlayerInfoLegacy)
743
744
// 1.19.3+ - player(s) leave the game
745
bot._client.on('player_remove', (packet) => {
746
for (const uuid of packet.players) {
747
const player = bot._playerFromUUID(uuid)
748
if (!player || player.entity === bot.entity) continue
749
750
player.entity = null
751
delete bot.players[player.username]
752
delete bot.uuidToUsername[uuid]
753
bot.emit('playerLeft', player)
754
}
755
})
756
757
// attaching to a vehicle
758
bot._client.on('attach_entity', (packet) => {
759
const passenger = fetchEntity(packet.entityId)
760
const vehicle = packet.vehicleId === -1 ? null : fetchEntity(packet.vehicleId)
761
762
const originalVehicle = passenger.vehicle
763
if (originalVehicle) {
764
const index = originalVehicle.passengers.indexOf(passenger)
765
originalVehicle.passengers.splice(index, 1)
766
}
767
passenger.vehicle = vehicle
768
if (vehicle) {
769
vehicle.passengers.push(passenger)
770
}
771
772
if (packet.entityId === bot.entity.id) {
773
const vehicle = bot.vehicle
774
if (packet.vehicleId === -1) {
775
bot.vehicle = null
776
bot.emit('dismount', vehicle)
777
} else {
778
bot.vehicle = bot.entities[packet.vehicleId]
779
bot.emit('mount')
780
}
781
}
782
})
783
784
bot._client.on('set_passengers', ({ entityId, passengers }) => {
785
const passengerEntities = passengers.map((passengerId) => fetchEntity(passengerId))
786
const vehicle = entityId === -1 ? null : bot.entities[entityId]
787
788
for (const passengerEntity of passengerEntities) {
789
const originalVehicle = passengerEntity.vehicle
790
if (originalVehicle) {
791
const index = originalVehicle.passengers.indexOf(passengerEntity)
792
originalVehicle.passengers.splice(index, 1)
793
}
794
passengerEntity.vehicle = vehicle
795
if (vehicle) {
796
vehicle.passengers.push(passengerEntity)
797
}
798
}
799
800
if (passengers.includes(bot.entity.id)) {
801
const originalVehicle = bot.vehicle
802
if (entityId === -1) {
803
bot.vehicle = null
804
bot.emit('dismount', originalVehicle)
805
} else {
806
bot.vehicle = bot.entities[entityId]
807
bot.emit('mount')
808
}
809
}
810
})
811
812
// dismounting when the vehicle is gone
813
bot._client.on('entityGone', (entity) => {
814
if (bot.vehicle === entity) {
815
bot.vehicle = null
816
bot.emit('dismount', (entity))
817
}
818
if (entity.passengers) {
819
for (const passenger of entity.passengers) {
820
passenger.vehicle = null
821
}
822
}
823
if (entity.vehicle) {
824
const index = entity.vehicle.passengers.indexOf(entity)
825
if (index !== -1) {
826
entity.vehicle.passengers.splice(index, 1)
827
}
828
}
829
})
830
831
bot.swingArm = swingArm
832
bot.attack = attack
833
bot.mount = mount
834
bot.dismount = dismount
835
bot.useOn = useOn
836
bot.moveVehicle = moveVehicle
837
838
function swingArm (arm = 'right', showHand = true) {
839
const hand = arm === 'right' ? 0 : 1
840
const packet = {}
841
if (showHand) packet.hand = hand
842
bot._client.write('arm_animation', packet)
843
}
844
845
function useOn (target) {
846
// TODO: check if not crouching will make make this action always use the item
847
useEntity(target, 0)
848
}
849
850
function attack (target, swing = true) {
851
// arm animation comes before the use_entity packet on 1.8
852
if (bot.supportFeature('armAnimationBeforeUse')) {
853
if (swing) {
854
swingArm()
855
}
856
useEntity(target, 1)
857
} else {
858
useEntity(target, 1)
859
if (swing) {
860
swingArm()
861
}
862
}
863
}
864
865
function mount (target) {
866
// TODO: check if crouching will make make this action always mount
867
useEntity(target, 0)
868
}
869
870
function moveVehicle (left, forward) {
871
if (bot.supportFeature('newPlayerInputPacket')) {
872
// docs:
873
// * left can take -1 or 1 : -1 means right, 1 means left
874
// * forward can take -1 or 1 : -1 means backward, 1 means forward
875
bot._client.write('player_input', {
876
inputs: {
877
forward: forward > 0,
878
backward: forward < 0,
879
left: left > 0,
880
right: left < 0
881
}
882
})
883
} else {
884
bot._client.write('steer_vehicle', {
885
sideways: left,
886
forward,
887
jump: 0x01
888
})
889
}
890
}
891
892
function dismount () {
893
if (bot.vehicle) {
894
if (bot.supportFeature('newPlayerInputPacket')) {
895
bot._client.write('player_input', {
896
inputs: {
897
jump: true
898
}
899
})
900
} else {
901
bot._client.write('steer_vehicle', {
902
sideways: 0.0,
903
forward: 0.0,
904
jump: 0x02
905
})
906
}
907
} else {
908
bot.emit('error', new Error('dismount: not mounted'))
909
}
910
}
911
912
function useEntity (target, leftClick, x, y, z) {
913
const sneaking = bot.getControlState('sneak')
914
if (x && y && z) {
915
bot._client.write('use_entity', {
916
target: target.id,
917
mouse: leftClick,
918
x,
919
y,
920
z,
921
sneaking
922
})
923
} else {
924
bot._client.write('use_entity', {
925
target: target.id,
926
mouse: leftClick,
927
sneaking
928
})
929
}
930
}
931
932
function fetchEntity (id) {
933
return bot.entities[id] || (bot.entities[id] = new Entity(id))
934
}
935
}
936
937
function parseMetadata (metadata, entityMetadata = {}) {
938
if (metadata !== undefined) {
939
for (const { key, value } of metadata) {
940
entityMetadata[key] = value
941
}
942
}
943
944
return entityMetadata
945
}
946
947
function extractSkinInformation (properties) {
948
if (!properties) {
949
return undefined
950
}
951
952
const props = Object.fromEntries(properties.map((e) => [e.name, e]))
953
if (!props.textures || !props.textures.value) {
954
return undefined
955
}
956
957
let skinTexture
958
try { // Handles mojangson-style player data
959
skinTexture = JSON.parse(Buffer.from(props.textures.value, 'base64'))
960
} catch (e) {
961
skinTexture = mojangson.simplify(mojangson.parse(Buffer.from(props.textures.value, 'base64').toString('utf-8')))
962
}
963
964
const skinTextureUrl = skinTexture?.textures?.SKIN?.url ?? undefined
965
const skinTextureModel = skinTexture?.textures?.SKIN?.metadata?.model ?? undefined
966
967
if (!skinTextureUrl) {
968
return undefined
969
}
970
971
return { url: skinTextureUrl, model: skinTextureModel }
972
}
973
974