862 lines
28 KiB
JavaScript
862 lines
28 KiB
JavaScript
const { Vec3 } = require('vec3')
|
|
const conv = require('../conversions')
|
|
const NAMED_ENTITY_HEIGHT = 1.62
|
|
const NAMED_ENTITY_WIDTH = 0.6
|
|
const CROUCH_HEIGHT = NAMED_ENTITY_HEIGHT - 0.08
|
|
|
|
module.exports = inject
|
|
|
|
const animationEvents = {
|
|
0: 'entitySwingArm',
|
|
1: 'entityHurt',
|
|
2: 'entityWake',
|
|
3: 'entityEat',
|
|
4: 'entityCriticalEffect',
|
|
5: 'entityMagicCriticalEffect'
|
|
}
|
|
|
|
const entityStatusEvents = {
|
|
2: 'entityHurt',
|
|
3: 'entityDead',
|
|
6: 'entityTaming',
|
|
7: 'entityTamed',
|
|
8: 'entityShakingOffWater',
|
|
10: 'entityEatingGrass',
|
|
55: 'entityHandSwap'
|
|
}
|
|
|
|
function inject (bot) {
|
|
const { mobs, entitiesArray } = bot.registry
|
|
const Entity = require('prismarine-entity')(bot.version)
|
|
const Item = require('prismarine-item')(bot.version)
|
|
const ChatMessage = require('prismarine-chat')(bot.registry)
|
|
|
|
// ONLY 1.17 has this destroy_entity packet which is the same thing as entity_destroy packet except the entity is singular
|
|
// 1.17.1 reverted this change so this is just a simpler fix
|
|
bot._client.on('destroy_entity', (packet) => {
|
|
bot._client.emit('entity_destroy', { entityIds: [packet.entityId] })
|
|
})
|
|
|
|
bot.findPlayer = bot.findPlayers = (filter) => {
|
|
const filterFn = (entity) => {
|
|
if (entity.type !== 'player') return false
|
|
if (filter === null) return true
|
|
if (typeof filter === 'object' && filter instanceof RegExp) {
|
|
return entity.username.search(filter) !== -1
|
|
} else if (typeof filter === 'function') {
|
|
return filter(entity)
|
|
} else if (typeof filter === 'string') {
|
|
return entity.username.toLowerCase() === filter.toLowerCase()
|
|
}
|
|
return false
|
|
}
|
|
const resultSet = Object.values(bot.entities)
|
|
.filter(filterFn)
|
|
|
|
if (typeof filter === 'string') {
|
|
switch (resultSet.length) {
|
|
case 0:
|
|
return null
|
|
case 1:
|
|
return resultSet[0]
|
|
default:
|
|
return resultSet
|
|
}
|
|
}
|
|
return resultSet
|
|
}
|
|
|
|
bot.players = {}
|
|
bot.uuidToUsername = {}
|
|
bot.entities = {}
|
|
|
|
bot._playerFromUUID = (uuid) => Object.values(bot.players).find(player => player.uuid === uuid)
|
|
|
|
bot.nearestEntity = (match = (entity) => { return true }) => {
|
|
let best = null
|
|
let bestDistance = Number.MAX_VALUE
|
|
|
|
for (const entity of Object.values(bot.entities)) {
|
|
if (entity === bot.entity || !match(entity)) {
|
|
continue
|
|
}
|
|
|
|
const dist = bot.entity.position.distanceSquared(entity.position)
|
|
if (dist < bestDistance) {
|
|
best = entity
|
|
bestDistance = dist
|
|
}
|
|
}
|
|
|
|
return best
|
|
}
|
|
|
|
// Reset list of players and entities on login
|
|
bot._client.on('login', (packet) => {
|
|
bot.players = {}
|
|
bot.uuidToUsername = {}
|
|
bot.entities = {}
|
|
// login
|
|
bot.entity = fetchEntity(packet.entityId)
|
|
bot.username = bot._client.username
|
|
bot.entity.username = bot._client.username
|
|
bot.entity.type = 'player'
|
|
bot.entity.name = 'player'
|
|
})
|
|
|
|
bot._client.on('entity_equipment', (packet) => {
|
|
// entity equipment
|
|
const entity = fetchEntity(packet.entityId)
|
|
if (packet.equipments !== undefined) {
|
|
packet.equipments.forEach(equipment => entity.setEquipment(equipment.slot, equipment.item ? Item.fromNotch(equipment.item) : null))
|
|
} else {
|
|
entity.setEquipment(packet.slot, packet.item ? Item.fromNotch(packet.item) : null)
|
|
}
|
|
bot.emit('entityEquip', entity)
|
|
})
|
|
|
|
bot._client.on('bed', (packet) => {
|
|
// use bed
|
|
const entity = fetchEntity(packet.entityId)
|
|
entity.position.set(packet.location.x, packet.location.y, packet.location.z)
|
|
bot.emit('entitySleep', entity)
|
|
})
|
|
|
|
bot._client.on('animation', (packet) => {
|
|
// animation
|
|
const entity = fetchEntity(packet.entityId)
|
|
const eventName = animationEvents[packet.animation]
|
|
if (eventName) bot.emit(eventName, entity)
|
|
})
|
|
|
|
bot._client.on('named_entity_spawn', (packet) => {
|
|
// in case player_info packet was not sent before named_entity_spawn : ignore named_entity_spawn (see #213)
|
|
if (packet.playerUUID in bot.uuidToUsername) {
|
|
// spawn named entity
|
|
const entity = fetchEntity(packet.entityId)
|
|
entity.type = 'player'
|
|
entity.name = 'player'
|
|
entity.username = bot.uuidToUsername[packet.playerUUID]
|
|
entity.uuid = packet.playerUUID
|
|
entity.dataBlobs = packet.data
|
|
if (bot.supportFeature('fixedPointPosition')) {
|
|
entity.position.set(packet.x / 32, packet.y / 32, packet.z / 32)
|
|
} else if (bot.supportFeature('doublePosition')) {
|
|
entity.position.set(packet.x, packet.y, packet.z)
|
|
}
|
|
entity.yaw = conv.fromNotchianYawByte(packet.yaw)
|
|
entity.pitch = conv.fromNotchianPitchByte(packet.pitch)
|
|
entity.height = NAMED_ENTITY_HEIGHT
|
|
entity.width = NAMED_ENTITY_WIDTH
|
|
entity.metadata = parseMetadata(packet.metadata, entity.metadata)
|
|
if (bot.players[entity.username] !== undefined && !bot.players[entity.username].entity) {
|
|
bot.players[entity.username].entity = entity
|
|
}
|
|
bot.emit('entitySpawn', entity)
|
|
}
|
|
})
|
|
|
|
bot.on('entityCrouch', (entity) => {
|
|
entity.height = CROUCH_HEIGHT
|
|
})
|
|
|
|
bot.on('entityUncrouch', (entity) => {
|
|
entity.height = NAMED_ENTITY_HEIGHT
|
|
})
|
|
|
|
bot._client.on('collect', (packet) => {
|
|
// collect item
|
|
const collector = fetchEntity(packet.collectorEntityId)
|
|
const collected = fetchEntity(packet.collectedEntityId)
|
|
bot.emit('playerCollect', collector, collected)
|
|
})
|
|
|
|
function setEntityData (entity, type, entityData) {
|
|
if (entityData === undefined) {
|
|
entityData = entitiesArray.find(entity => entity.internalId === type)
|
|
}
|
|
if (entityData) {
|
|
entity.displayName = entityData.displayName
|
|
entity.entityType = entityData.id
|
|
entity.name = entityData.name
|
|
entity.kind = entityData.category
|
|
entity.height = entityData.height
|
|
entity.width = entityData.width
|
|
} else {
|
|
// unknown entity
|
|
entity.type = 'other'
|
|
entity.entityType = type
|
|
entity.displayName = 'unknown'
|
|
entity.name = 'unknown'
|
|
entity.kind = 'unknown'
|
|
}
|
|
}
|
|
|
|
// spawn object/vehicle on versions < 1.19, on versions > 1.19 handles all non-player entities
|
|
bot._client.on('spawn_entity', (packet) => {
|
|
const entity = fetchEntity(packet.entityId)
|
|
const entityData = bot.registry.entities[packet.type]
|
|
|
|
entity.type = entityData.type || 'object'
|
|
setEntityData(entity, packet.type, entityData)
|
|
|
|
if (bot.supportFeature('fixedPointPosition')) {
|
|
entity.position.set(packet.x / 32, packet.y / 32, packet.z / 32)
|
|
} else if (bot.supportFeature('doublePosition')) {
|
|
entity.position.set(packet.x, packet.y, packet.z)
|
|
} else if (bot.supportFeature('consolidatedEntitySpawnPacket')) {
|
|
entity.headPitch = conv.fromNotchianPitchByte(packet.headPitch)
|
|
}
|
|
|
|
entity.uuid = packet.objectUUID
|
|
entity.yaw = conv.fromNotchianYawByte(packet.yaw)
|
|
entity.pitch = conv.fromNotchianPitchByte(packet.pitch)
|
|
entity.objectData = packet.objectData
|
|
bot.emit('entitySpawn', entity)
|
|
})
|
|
|
|
bot._client.on('spawn_entity_experience_orb', (packet) => {
|
|
const entity = fetchEntity(packet.entityId)
|
|
entity.type = 'orb'
|
|
entity.name = 'experience_orb'
|
|
entity.width = 0.5
|
|
entity.height = 0.5
|
|
|
|
if (bot.supportFeature('fixedPointPosition')) {
|
|
entity.position.set(packet.x / 32, packet.y / 32, packet.z / 32)
|
|
} else if (bot.supportFeature('doublePosition')) {
|
|
entity.position.set(packet.x, packet.y, packet.z)
|
|
}
|
|
|
|
entity.count = packet.count
|
|
bot.emit('entitySpawn', entity)
|
|
})
|
|
|
|
// This packet is removed since 1.19 and merged into spawn_entity
|
|
bot._client.on('spawn_entity_living', (packet) => {
|
|
// spawn mob
|
|
const entity = fetchEntity(packet.entityId)
|
|
entity.type = 'mob'
|
|
entity.uuid = packet.entityUUID
|
|
const entityData = mobs[packet.type]
|
|
|
|
setEntityData(entity, packet.type, entityData)
|
|
|
|
if (bot.supportFeature('fixedPointPosition')) {
|
|
entity.position.set(packet.x / 32, packet.y / 32, packet.z / 32)
|
|
} else if (bot.supportFeature('doublePosition')) {
|
|
entity.position.set(packet.x, packet.y, packet.z)
|
|
}
|
|
|
|
entity.yaw = conv.fromNotchianYawByte(packet.yaw)
|
|
entity.pitch = conv.fromNotchianPitchByte(packet.pitch)
|
|
entity.headPitch = conv.fromNotchianPitchByte(packet.headPitch)
|
|
|
|
const notchVel = new Vec3(packet.velocityX, packet.velocityY, packet.velocityZ)
|
|
entity.velocity.update(conv.fromNotchVelocity(notchVel))
|
|
entity.metadata = parseMetadata(packet.metadata, entity.metadata)
|
|
|
|
bot.emit('entitySpawn', entity)
|
|
})
|
|
|
|
bot._client.on('entity_velocity', (packet) => {
|
|
// entity velocity
|
|
const entity = fetchEntity(packet.entityId)
|
|
const notchVel = new Vec3(packet.velocityX, packet.velocityY, packet.velocityZ)
|
|
entity.velocity.update(conv.fromNotchVelocity(notchVel))
|
|
})
|
|
|
|
bot._client.on('entity_destroy', (packet) => {
|
|
// destroy entity
|
|
packet.entityIds.forEach((id) => {
|
|
const entity = fetchEntity(id)
|
|
bot.emit('entityGone', entity)
|
|
entity.isValid = false
|
|
if (entity.username && bot.players[entity.username]) {
|
|
bot.players[entity.username].entity = null
|
|
}
|
|
delete bot.entities[id]
|
|
})
|
|
})
|
|
|
|
bot._client.on('rel_entity_move', (packet) => {
|
|
// entity relative move
|
|
const entity = fetchEntity(packet.entityId)
|
|
if (bot.supportFeature('fixedPointDelta')) {
|
|
entity.position.translate(packet.dX / 32, packet.dY / 32, packet.dZ / 32)
|
|
} else if (bot.supportFeature('fixedPointDelta128')) {
|
|
entity.position.translate(packet.dX / (128 * 32), packet.dY / (128 * 32), packet.dZ / (128 * 32))
|
|
}
|
|
bot.emit('entityMoved', entity)
|
|
})
|
|
|
|
bot._client.on('entity_look', (packet) => {
|
|
// entity look
|
|
const entity = fetchEntity(packet.entityId)
|
|
entity.yaw = conv.fromNotchianYawByte(packet.yaw)
|
|
entity.pitch = conv.fromNotchianPitchByte(packet.pitch)
|
|
bot.emit('entityMoved', entity)
|
|
})
|
|
|
|
bot._client.on('entity_move_look', (packet) => {
|
|
// entity look and relative move
|
|
const entity = fetchEntity(packet.entityId)
|
|
if (bot.supportFeature('fixedPointDelta')) {
|
|
entity.position.translate(packet.dX / 32, packet.dY / 32, packet.dZ / 32)
|
|
} else if (bot.supportFeature('fixedPointDelta128')) {
|
|
entity.position.translate(packet.dX / (128 * 32), packet.dY / (128 * 32), packet.dZ / (128 * 32))
|
|
}
|
|
entity.yaw = conv.fromNotchianYawByte(packet.yaw)
|
|
entity.pitch = conv.fromNotchianPitchByte(packet.pitch)
|
|
bot.emit('entityMoved', entity)
|
|
})
|
|
|
|
bot._client.on('entity_teleport', (packet) => {
|
|
// entity teleport
|
|
const entity = fetchEntity(packet.entityId)
|
|
if (bot.supportFeature('fixedPointPosition')) {
|
|
entity.position.set(packet.x / 32, packet.y / 32, packet.z / 32)
|
|
}
|
|
if (bot.supportFeature('doublePosition')) {
|
|
entity.position.set(packet.x, packet.y, packet.z)
|
|
}
|
|
entity.yaw = conv.fromNotchianYawByte(packet.yaw)
|
|
entity.pitch = conv.fromNotchianPitchByte(packet.pitch)
|
|
bot.emit('entityMoved', entity)
|
|
})
|
|
|
|
bot._client.on('entity_head_rotation', (packet) => {
|
|
// entity head look
|
|
const entity = fetchEntity(packet.entityId)
|
|
entity.headYaw = conv.fromNotchianYawByte(packet.headYaw)
|
|
bot.emit('entityMoved', entity)
|
|
})
|
|
|
|
bot._client.on('entity_status', (packet) => {
|
|
// entity status
|
|
const entity = fetchEntity(packet.entityId)
|
|
const eventName = entityStatusEvents[packet.entityStatus]
|
|
|
|
if (eventName === 'entityHandSwap' && entity.equipment) {
|
|
[entity.equipment[0], entity.equipment[1]] = [entity.equipment[1], entity.equipment[0]]
|
|
entity.heldItem = entity.equipment[0] // Update held item like prismarine-entity does upon equipment updates
|
|
}
|
|
|
|
if (eventName) bot.emit(eventName, entity)
|
|
})
|
|
|
|
bot._client.on('attach_entity', (packet) => {
|
|
// attach entity
|
|
const entity = fetchEntity(packet.entityId)
|
|
if (packet.vehicleId === -1) {
|
|
const vehicle = entity.vehicle
|
|
delete entity.vehicle
|
|
bot.emit('entityDetach', entity, vehicle)
|
|
} else {
|
|
entity.vehicle = fetchEntity(packet.vehicleId)
|
|
bot.emit('entityAttach', entity, entity.vehicle)
|
|
}
|
|
})
|
|
|
|
bot.fireworkRocketDuration = 0
|
|
function setElytraFlyingState (entity, elytraFlying) {
|
|
let startedFlying = false
|
|
if (elytraFlying) {
|
|
startedFlying = !entity.elytraFlying
|
|
entity.elytraFlying = true
|
|
} else if (entity.elytraFlying) {
|
|
entity.elytraFlying = false
|
|
}
|
|
if (bot.fireworkRocketDuration !== 0 && entity.id === bot.entity?.id && !elytraFlying) {
|
|
bot.fireworkRocketDuration = 0
|
|
knownFireworks.splice(0, knownFireworks.length)
|
|
}
|
|
|
|
if (startedFlying) {
|
|
bot.emit('entityElytraFlew', entity)
|
|
}
|
|
}
|
|
|
|
const knownFireworks = []
|
|
function handleBotUsedFireworkRocket (fireworkEntityId, fireworkInfo) {
|
|
if (knownFireworks.includes(fireworkEntityId)) return
|
|
knownFireworks.push(fireworkEntityId)
|
|
let flightDur = 1
|
|
if (fireworkInfo?.nbtData != null) {
|
|
let nbt = fireworkInfo.nbtData
|
|
if (nbt.type === 'compound' && nbt.value.Fireworks != null) {
|
|
nbt = nbt.value.Fireworks
|
|
if (nbt.type === 'compound' && nbt.value.Flight != null) {
|
|
nbt = nbt.value.Flight
|
|
if (nbt.type === 'int') {
|
|
flightDur += nbt.value
|
|
}
|
|
}
|
|
}
|
|
}
|
|
const baseDuration = 10 * flightDur
|
|
const randomDuration = Math.floor(Math.random() * 6) + Math.floor(Math.random() * 7)
|
|
bot.fireworkRocketDuration = baseDuration + randomDuration
|
|
|
|
bot.emit('usedFirework')
|
|
}
|
|
|
|
let fireworkEntityName
|
|
if (bot.supportFeature('fireworkNamePlural')) {
|
|
fireworkEntityName = 'fireworks_rocket'
|
|
} else if (bot.supportFeature('fireworkNameSingular')) {
|
|
fireworkEntityName = 'firework_rocket'
|
|
}
|
|
|
|
let fireworkMetadataIdx
|
|
let fireworkMetadataIsOpt
|
|
if (bot.supportFeature('fireworkMetadataVarInt7')) {
|
|
fireworkMetadataIdx = 7
|
|
fireworkMetadataIsOpt = false
|
|
} else if (bot.supportFeature('fireworkMetadataOptVarInt8')) {
|
|
fireworkMetadataIdx = 8
|
|
fireworkMetadataIsOpt = true
|
|
} else if (bot.supportFeature('fireworkMetadataOptVarInt9')) {
|
|
fireworkMetadataIdx = 9
|
|
fireworkMetadataIsOpt = true
|
|
}
|
|
const hasFireworkSupport = fireworkEntityName !== undefined && fireworkMetadataIdx !== undefined && fireworkMetadataIsOpt !== undefined
|
|
|
|
bot._client.on('entity_metadata', (packet) => {
|
|
// entity metadata
|
|
const entity = fetchEntity(packet.entityId)
|
|
const metadata = parseMetadata(packet.metadata, entity.metadata)
|
|
entity.metadata = metadata
|
|
bot.emit('entityUpdate', entity)
|
|
|
|
if (bot.supportFeature('mcDataHasEntityMetadata')) {
|
|
const metadataKeys = bot.registry.entitiesByName[entity.name]?.metadataKeys
|
|
const metas = metadataKeys ? Object.fromEntries(packet.metadata.map(e => [metadataKeys[e.key], e.value])) : {}
|
|
if (packet.metadata.some(m => m.type === 'item_stack')) {
|
|
bot.emit('itemDrop', entity)
|
|
}
|
|
if (metas.sleeping_pos || metas.pose === 2) {
|
|
bot.emit('entitySleep', entity)
|
|
}
|
|
|
|
if (hasFireworkSupport && fireworkEntityName === entity.name && metas.attached_to_target !== undefined) {
|
|
// fireworkMetadataOptVarInt9 and later is implied by
|
|
// mcDataHasEntityMetadata, so no need to check metadata index and type
|
|
// (eg fireworkMetadataOptVarInt8)
|
|
if (metas.attached_to_target !== 0) {
|
|
const entityId = metas.attached_to_target - 1
|
|
if (entityId === bot.entity?.id) {
|
|
handleBotUsedFireworkRocket(entity.id, metas.fireworks_item)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (metas.shared_flags != null) {
|
|
if (bot.supportFeature('hasElytraFlying')) {
|
|
const elytraFlying = metas.shared_flags & 0x80
|
|
setElytraFlyingState(entity, Boolean(elytraFlying))
|
|
}
|
|
|
|
if (metas.shared_flags & 2) {
|
|
entity.crouching = true
|
|
bot.emit('entityCrouch', entity)
|
|
} else if (entity.crouching) { // prevent the initial entity_metadata packet from firing off an uncrouch event
|
|
entity.crouching = false
|
|
bot.emit('entityUncrouch', entity)
|
|
}
|
|
}
|
|
} else {
|
|
const typeSlot = (bot.supportFeature('itemsAreAlsoBlocks') ? 5 : 6) + (bot.supportFeature('entityMetadataHasLong') ? 1 : 0)
|
|
const slot = packet.metadata.find(e => e.type === typeSlot)
|
|
if (entity.name && (entity.name.toLowerCase() === 'item' || entity.name === 'item_stack') && slot) {
|
|
bot.emit('itemDrop', entity)
|
|
}
|
|
|
|
const typePose = bot.supportFeature('entityMetadataHasLong') ? 19 : 18
|
|
const pose = packet.metadata.find(e => e.type === typePose)
|
|
if (pose && pose.value === 2) {
|
|
bot.emit('entitySleep', entity)
|
|
}
|
|
|
|
if (hasFireworkSupport && fireworkEntityName === entity.name) {
|
|
const attachedToTarget = packet.metadata.find(e => e.key === fireworkMetadataIdx)
|
|
if (attachedToTarget !== undefined) {
|
|
let entityId
|
|
if (fireworkMetadataIsOpt) {
|
|
if (attachedToTarget.value !== 0) {
|
|
entityId = attachedToTarget.value - 1
|
|
} // else, not attached to an entity
|
|
} else {
|
|
entityId = attachedToTarget.value
|
|
}
|
|
if (entityId !== undefined && entityId === bot.entity?.id) {
|
|
const fireworksItem = packet.metadata.find(e => e.key === (fireworkMetadataIdx - 1))
|
|
handleBotUsedFireworkRocket(entity.id, fireworksItem?.value)
|
|
}
|
|
}
|
|
}
|
|
|
|
const bitField = packet.metadata.find(p => p.key === 0)
|
|
if (bitField !== undefined) {
|
|
if (bot.supportFeature('hasElytraFlying')) {
|
|
const elytraFlying = bitField.value & 0x80
|
|
setElytraFlyingState(entity, Boolean(elytraFlying))
|
|
}
|
|
|
|
if ((bitField.value & 2) !== 0) {
|
|
entity.crouching = true
|
|
bot.emit('entityCrouch', entity)
|
|
} else if (entity.crouching) { // prevent the initial entity_metadata packet from firing off an uncrouch event
|
|
entity.crouching = false
|
|
bot.emit('entityUncrouch', entity)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
bot._client.on('entity_effect', (packet) => {
|
|
// entity effect
|
|
const entity = fetchEntity(packet.entityId)
|
|
const effect = {
|
|
id: packet.effectId,
|
|
amplifier: packet.amplifier,
|
|
duration: packet.duration
|
|
}
|
|
entity.effects[effect.id] = effect
|
|
bot.emit('entityEffect', entity, effect)
|
|
})
|
|
|
|
bot._client.on('remove_entity_effect', (packet) => {
|
|
// remove entity effect
|
|
const entity = fetchEntity(packet.entityId)
|
|
let effect = entity.effects[packet.effectId]
|
|
if (effect) {
|
|
delete entity.effects[effect.id]
|
|
} else {
|
|
// unknown effect
|
|
effect = {
|
|
id: packet.effectId,
|
|
amplifier: -1,
|
|
duration: -1
|
|
}
|
|
}
|
|
bot.emit('entityEffectEnd', entity, effect)
|
|
})
|
|
|
|
const updateAttributes = (packet) => {
|
|
const entity = fetchEntity(packet.entityId)
|
|
if (!entity.attributes) entity.attributes = {}
|
|
for (const prop of packet.properties) {
|
|
entity.attributes[prop.key] = {
|
|
value: prop.value,
|
|
modifiers: prop.modifiers
|
|
}
|
|
}
|
|
bot.emit('entityAttributes', entity)
|
|
}
|
|
bot._client.on('update_attributes', updateAttributes) // 1.8
|
|
bot._client.on('entity_update_attributes', updateAttributes) // others
|
|
|
|
bot._client.on('spawn_entity_weather', (packet) => {
|
|
// spawn global entity
|
|
const entity = fetchEntity(packet.entityId)
|
|
entity.type = 'global'
|
|
entity.globalType = 'thunderbolt'
|
|
entity.uuid = packet.entityUUID
|
|
entity.position.set(packet.x / 32, packet.y / 32, packet.z / 32)
|
|
bot.emit('entitySpawn', entity)
|
|
})
|
|
|
|
bot.on('spawn', () => {
|
|
bot.emit('entitySpawn', bot.entity)
|
|
})
|
|
|
|
bot._client.on('player_info', (packet) => {
|
|
// player list item(s)
|
|
|
|
if (bot.supportFeature('playerInfoActionIsBitfield')) {
|
|
for (const item of packet.data) {
|
|
let player = bot.uuidToUsername[item.uuid] ? bot.players[bot.uuidToUsername[item.uuid]] : null
|
|
let newPlayer = false
|
|
|
|
const obj = {
|
|
uuid: item.uuid
|
|
}
|
|
|
|
if (!player) newPlayer = true
|
|
|
|
player = player || obj
|
|
|
|
if (packet.action & 1) {
|
|
obj.username = item.player.name
|
|
obj.displayName = player.displayName || new ChatMessage({ text: '', extra: [{ text: item.player.name }] })
|
|
obj.skinData = extractSkinInformation(item.player.properties)
|
|
}
|
|
|
|
if (packet.action & 4) {
|
|
obj.gamemode = item.gamemode
|
|
}
|
|
|
|
if (packet.action & 16) {
|
|
obj.ping = item.latency
|
|
}
|
|
|
|
if (item.displayName) {
|
|
obj.displayName = new ChatMessage(JSON.parse(item.displayName))
|
|
} else if (packet.action & 32) obj.displayName = new ChatMessage({ text: '', extra: [{ text: player.username || obj.username }] })
|
|
|
|
if (newPlayer) {
|
|
if (!obj.username) continue // Should be unreachable
|
|
player = bot.players[obj.username] = obj
|
|
bot.uuidToUsername[obj.uuid] = obj.username
|
|
} else {
|
|
Object.assign(player, obj)
|
|
}
|
|
|
|
const playerEntity = Object.values(bot.entities).find(e => e.type === 'player' && e.username === player.username)
|
|
player.entity = playerEntity
|
|
|
|
if (playerEntity === bot.entity) {
|
|
bot.player = player
|
|
}
|
|
|
|
if (newPlayer) {
|
|
bot.emit('playerJoined', player)
|
|
} else {
|
|
bot.emit('playerUpdated', player)
|
|
}
|
|
}
|
|
} else {
|
|
for (const item of packet.data) {
|
|
let player = bot.uuidToUsername[item.UUID] ? bot.players[bot.uuidToUsername[item.UUID]] : null
|
|
if (packet.action === 0) {
|
|
let newPlayer = false
|
|
|
|
// New Player
|
|
if (!player) {
|
|
player = bot.players[item.name] = {
|
|
username: item.name,
|
|
ping: item.ping,
|
|
uuid: item.UUID,
|
|
displayName: new ChatMessage({ text: '', extra: [{ text: item.name }] }),
|
|
skinData: extractSkinInformation(item.properties),
|
|
profileKeys: item.crypto
|
|
? {
|
|
publicKey: item.crypto.publicKey, // DER-encoded public key
|
|
signature: item.crypto.signature // Signature
|
|
}
|
|
: null
|
|
}
|
|
|
|
bot.uuidToUsername[item.UUID] = item.name
|
|
bot.emit('playerJoined', player)
|
|
newPlayer = true
|
|
} else {
|
|
// Just an Update
|
|
player.gamemode = item.gamemode
|
|
player.ping = item.ping
|
|
player.skinData = extractSkinInformation(item.properties)
|
|
if (item.crypto) {
|
|
player.profileKeys = {
|
|
publicKey: item.crypto.publicKey,
|
|
signature: item.crypto.signature
|
|
}
|
|
}
|
|
}
|
|
|
|
if (item.displayName) {
|
|
player.displayName = new ChatMessage(JSON.parse(item.displayName))
|
|
}
|
|
|
|
const playerEntity = Object.values(bot.entities).find(e => e.type === 'player' && e.username === item.name)
|
|
player.entity = playerEntity
|
|
|
|
if (playerEntity === bot.entity) {
|
|
bot.player = player
|
|
}
|
|
|
|
if (!newPlayer) {
|
|
bot.emit('playerUpdated', player)
|
|
}
|
|
} else if (player) {
|
|
if (packet.action === 1) {
|
|
player.gamemode = item.gamemode
|
|
} else if (packet.action === 2) {
|
|
player.ping = item.ping
|
|
} else if (packet.action === 3 && !item.displayName) {
|
|
player.displayName = new ChatMessage({ text: '', extra: [{ text: player.username }] })
|
|
} else if (packet.action === 3 && item.displayName) {
|
|
player.displayName = new ChatMessage(JSON.parse(item.displayName))
|
|
} else if (packet.action === 4) {
|
|
if (player.entity === bot.entity) continue
|
|
|
|
player.entity = null
|
|
delete bot.players[player.username]
|
|
delete bot.uuidToUsername[item.UUID]
|
|
bot.emit('playerLeft', player)
|
|
continue
|
|
} else {
|
|
continue
|
|
}
|
|
|
|
bot.emit('playerUpdated', player)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
// (1.19.3) player(s) leave the game
|
|
bot._client.on('player_remove', (packet) => {
|
|
for (const uuid of packet.players) {
|
|
const player = bot.uuidToUsername[uuid] ? bot.players[bot.uuidToUsername[uuid]] : null
|
|
|
|
if (!player || player.entity === bot.entity) continue
|
|
|
|
player.entity = null
|
|
delete bot.players[player.username]
|
|
delete bot.uuidToUsername[uuid]
|
|
bot.emit('playerLeft', player)
|
|
}
|
|
})
|
|
|
|
// attaching to a vehicle
|
|
bot._client.on('attach_entity', (packet) => {
|
|
if (packet.entityId !== bot.entity.id) return
|
|
const vehicle = bot.vehicle
|
|
if (packet.vehicleId === -1) {
|
|
bot.vehicle = null
|
|
bot.emit('dismount', vehicle)
|
|
} else {
|
|
bot.vehicle = bot.entities[packet.vehicleId]
|
|
bot.emit('mount')
|
|
}
|
|
})
|
|
|
|
bot._client.on('set_passengers', ({ entityId, passengers }) => {
|
|
if (passengers[0] !== bot.entity.id) return
|
|
const vehicle = bot.vehicle
|
|
if (entityId === -1) {
|
|
bot.vehicle = null
|
|
bot.emit('dismount', vehicle)
|
|
} else {
|
|
bot.vehicle = bot.entities[entityId]
|
|
bot.emit('mount')
|
|
}
|
|
})
|
|
|
|
bot.swingArm = swingArm
|
|
bot.attack = attack
|
|
bot.mount = mount
|
|
bot.dismount = dismount
|
|
bot.useOn = useOn
|
|
bot.moveVehicle = moveVehicle
|
|
|
|
function swingArm (arm = 'right', showHand = true) {
|
|
const hand = arm === 'right' ? 0 : 1
|
|
const packet = {}
|
|
if (showHand) packet.hand = hand
|
|
bot._client.write('arm_animation', packet)
|
|
}
|
|
|
|
function useOn (target) {
|
|
// TODO: check if not crouching will make make this action always use the item
|
|
useEntity(target, 0)
|
|
}
|
|
|
|
function attack (target, swing = true) {
|
|
// arm animation comes before the use_entity packet on 1.8
|
|
if (bot.supportFeature('armAnimationBeforeUse')) {
|
|
if (swing) {
|
|
swingArm()
|
|
}
|
|
useEntity(target, 1)
|
|
} else {
|
|
useEntity(target, 1)
|
|
if (swing) {
|
|
swingArm()
|
|
}
|
|
}
|
|
}
|
|
|
|
function mount (target) {
|
|
// TODO: check if crouching will make make this action always mount
|
|
useEntity(target, 0)
|
|
}
|
|
|
|
function moveVehicle (left, forward) {
|
|
bot._client.write('steer_vehicle', {
|
|
sideways: left,
|
|
forward,
|
|
jump: 0x01
|
|
})
|
|
}
|
|
|
|
function dismount () {
|
|
if (bot.vehicle) {
|
|
bot._client.write('steer_vehicle', {
|
|
sideways: 0.0,
|
|
forward: 0.0,
|
|
jump: 0x02
|
|
})
|
|
} else {
|
|
bot.emit('error', new Error('dismount: not mounted'))
|
|
}
|
|
}
|
|
|
|
function useEntity (target, leftClick, x, y, z) {
|
|
const sneaking = bot.getControlState('sneak')
|
|
if (x && y && z) {
|
|
bot._client.write('use_entity', {
|
|
target: target.id,
|
|
mouse: leftClick,
|
|
x,
|
|
y,
|
|
z,
|
|
sneaking
|
|
})
|
|
} else {
|
|
bot._client.write('use_entity', {
|
|
target: target.id,
|
|
mouse: leftClick,
|
|
sneaking
|
|
})
|
|
}
|
|
}
|
|
|
|
function fetchEntity (id) {
|
|
return bot.entities[id] || (bot.entities[id] = new Entity(id))
|
|
}
|
|
}
|
|
|
|
function parseMetadata (metadata, entityMetadata = {}) {
|
|
if (metadata !== undefined) {
|
|
for (const { key, value } of metadata) {
|
|
entityMetadata[key] = value
|
|
}
|
|
}
|
|
|
|
return entityMetadata
|
|
}
|
|
|
|
function extractSkinInformation (properties) {
|
|
if (!properties) {
|
|
return undefined
|
|
}
|
|
|
|
const props = Object.fromEntries(properties.map((e) => [e.name, e]))
|
|
if (!props.textures || !props.textures.value) {
|
|
return undefined
|
|
}
|
|
|
|
const skinTexture = JSON.parse(Buffer.from(props.textures.value, 'base64').toString('utf8'))
|
|
|
|
const skinTextureUrl = skinTexture?.textures?.SKIN?.url ?? undefined
|
|
const skinTextureModel = skinTexture?.textures?.SKIN?.metadata?.model ?? undefined
|
|
|
|
if (!skinTextureUrl) {
|
|
return undefined
|
|
}
|
|
|
|
return { url: skinTextureUrl, model: skinTextureModel }
|
|
}
|