From 7aadecdf0d97f9bd5541a9516c1d8f33b0c8191e Mon Sep 17 00:00:00 2001 From: aesthetic <15858616+aesthetic0001@users.noreply.github.com> Date: Fri, 15 Dec 2023 17:00:36 -0500 Subject: [PATCH] fix entity vehicles for higher versions --- lib/plugins/entities.js | 1556 ++++++++++++++++++++------------------- 1 file changed, 785 insertions(+), 771 deletions(-) diff --git a/lib/plugins/entities.js b/lib/plugins/entities.js index 85cbf2e25..4f96fd058 100644 --- a/lib/plugins/entities.js +++ b/lib/plugins/entities.js @@ -7,855 +7,869 @@ 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' + 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' + 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 - } + 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 } - return resultSet - } - bot.players = {} - bot.uuidToUsername = {} - bot.entities = {} + bot.players = {} + bot.uuidToUsername = {} + bot.entities = {} - bot._playerFromUUID = (uuid) => Object.values(bot.players).find(player => player.uuid === uuid) + 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 + 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 - } + 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 - } + const dist = bot.entity.position.distanceSquared(entity.position) + if (dist < bestDistance) { + best = entity + bestDistance = dist + } + } + + return best } - 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' + }) - // 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) - } + 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) + }) - 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) - } + 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) + }) - entity.count = packet.count - bot.emit('entitySpawn', entity) - }) + bot._client.on('animation', (packet) => { + // animation + const entity = fetchEntity(packet.entityId) + const eventName = animationEvents[packet.animation] + if (eventName) bot.emit(eventName, 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] + 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) + } + }) - setEntityData(entity, packet.type, entityData) + bot.on('entityCrouch', (entity) => { + entity.height = CROUCH_HEIGHT + }) - 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) - } + bot.on('entityUncrouch', (entity) => { + entity.height = NAMED_ENTITY_HEIGHT + }) - 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('collect', (packet) => { + // collect item + const collector = fetchEntity(packet.collectorEntityId) + const collected = fetchEntity(packet.collectedEntityId) + bot.emit('playerCollect', collector, collected) }) - }) - - 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) + 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' + } } - 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 + // 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) + } - 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) - }) + 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.on('spawn', () => { - bot.emit('entitySpawn', bot.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) + }) - bot._client.on('player_info', (packet) => { - // player list item(s) + // 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] - 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 + setEntityData(entity, packet.type, entityData) - const obj = { - uuid: item.uuid + 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) } - if (!player) newPlayer = true + entity.yaw = conv.fromNotchianYawByte(packet.yaw) + entity.pitch = conv.fromNotchianPitchByte(packet.pitch) + entity.headPitch = conv.fromNotchianPitchByte(packet.headPitch) - player = player || obj + const notchVel = new Vec3(packet.velocityX, packet.velocityY, packet.velocityZ) + entity.velocity.update(conv.fromNotchVelocity(notchVel)) + entity.metadata = parseMetadata(packet.metadata, entity.metadata) - 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) + 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) + }) - if (packet.action & 4) { - obj.gamemode = item.gamemode + 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) + }) - if (packet.action & 16) { - obj.ping = item.latency + 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 (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 (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) + }) - if (newPlayer) { - if (!obj.username) continue // Should be unreachable - player = bot.players[obj.username] = obj - bot.uuidToUsername[obj.uuid] = obj.username + 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 { - Object.assign(player, obj) + 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 playerEntity = Object.values(bot.entities).find(e => e.type === 'player' && e.username === player.username) - player.entity = playerEntity + 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) + } + } + } + }) - if (playerEntity === bot.entity) { - bot.player = player + 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) + }) - if (newPlayer) { - bot.emit('playerJoined', player) + 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 { - 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 + // 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 + } - 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 (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) + } } - } - - 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 + } 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[item.UUID] + delete bot.uuidToUsername[uuid] bot.emit('playerLeft', player) - continue - } else { - continue - } + } + }) - bot.emit('playerUpdated', 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) { + const vehicle = bot.vehicle + if (entityId === -1) { + bot.vehicle = null + bot.emit('dismount', vehicle) + } else { + bot.vehicle = bot.entities[entityId] + bot.emit('mount') + } + } else { + const vehicle = fetchEntity(entityId) + if (!passengers[0]) { + const passenger = bot.entities[vehicle.passengers[0]] + if (!passenger) return + passenger.vehicle = null + bot.emit('entityDetach', passenger, vehicle) + } else { + const passenger = bot.entities[passengers[0]] + passenger.vehicle = vehicle + bot.emit('entityAttach', passenger, vehicle) + } + vehicle.passengers = passengers + } + }) - // (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 + bot.swingArm = swingArm + bot.attack = attack + bot.mount = mount + bot.dismount = dismount + bot.useOn = useOn + bot.moveVehicle = moveVehicle - if (!player || player.entity === bot.entity) continue + 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) + } - player.entity = null - delete bot.players[player.username] - delete bot.uuidToUsername[uuid] - bot.emit('playerLeft', player) + function useOn (target) { + // TODO: check if not crouching will make make this action always use the item + useEntity(target, 0) } - }) - - // 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') + + 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() + } + } } - }) - - 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') + + function mount (target) { + // TODO: check if crouching will make make this action always mount + useEntity(target, 0) } - }) - - 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 moveVehicle (left, forward) { + bot._client.write('steer_vehicle', { + sideways: left, + forward, + jump: 0x01 + }) } - } - - 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 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 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 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 + if (metadata !== undefined) { + for (const { key, value } of metadata) { + entityMetadata[key] = value + } } - } - return entityMetadata + return entityMetadata } function extractSkinInformation (properties) { - if (!properties) { - return undefined - } + if (!properties) { + return undefined + } - const props = Object.fromEntries(properties.map((e) => [e.name, e])) - if (!props.textures || !props.textures.value) { - 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 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 + const skinTextureUrl = skinTexture?.textures?.SKIN?.url ?? undefined + const skinTextureModel = skinTexture?.textures?.SKIN?.metadata?.model ?? undefined - if (!skinTextureUrl) { - return undefined - } + if (!skinTextureUrl) { + return undefined + } - return { url: skinTextureUrl, model: skinTextureModel } + return { url: skinTextureUrl, model: skinTextureModel } }