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