234 lines
7.8 KiB
JavaScript
234 lines
7.8 KiB
JavaScript
|
const { performance } = require('perf_hooks')
|
||
|
const { createDoneTask, createTask } = require('../promise_utils')
|
||
|
const BlockFaces = require('prismarine-world').iterators.BlockFace
|
||
|
const { Vec3 } = require('vec3')
|
||
|
|
||
|
module.exports = inject
|
||
|
|
||
|
function inject (bot) {
|
||
|
let swingInterval = null
|
||
|
let waitTimeout = null
|
||
|
|
||
|
let diggingTask = createDoneTask()
|
||
|
|
||
|
bot.targetDigBlock = null
|
||
|
bot.lastDigTime = null
|
||
|
|
||
|
async function dig (block, forceLook, digFace) {
|
||
|
if (block === null || block === undefined) {
|
||
|
throw new Error('dig was called with an undefined or null block')
|
||
|
}
|
||
|
if (!digFace || typeof digFace === 'function') {
|
||
|
digFace = 'auto'
|
||
|
}
|
||
|
|
||
|
if (bot.targetDigBlock) bot.stopDigging()
|
||
|
|
||
|
let diggingFace = 1 // Default (top)
|
||
|
|
||
|
if (forceLook !== 'ignore') {
|
||
|
if (digFace?.x || digFace?.y || digFace?.z) {
|
||
|
// Determine the block face the bot should mine
|
||
|
if (digFace.x) {
|
||
|
diggingFace = digFace.x > 0 ? BlockFaces.EAST : BlockFaces.WEST
|
||
|
} else if (digFace.y) {
|
||
|
diggingFace = digFace.y > 0 ? BlockFaces.TOP : BlockFaces.BOTTOM
|
||
|
} else if (digFace.z) {
|
||
|
diggingFace = digFace.z > 0 ? BlockFaces.SOUTH : BlockFaces.NORTH
|
||
|
}
|
||
|
await bot.lookAt(
|
||
|
block.position.offset(0.5, 0.5, 0.5).offset(digFace.x * 0.5, digFace.y * 0.5, digFace.z * 0.5),
|
||
|
forceLook
|
||
|
)
|
||
|
} else if (digFace === 'raycast') {
|
||
|
// Check faces that could be seen from the current position. If the delta is smaller then 0.5 that means the
|
||
|
// bot cam most likely not see the face as the block is 1 block thick
|
||
|
// this could be false for blocks that have a smaller bounding box then 1x1x1
|
||
|
const dx = bot.entity.position.x - (block.position.x + 0.5)
|
||
|
const dy = bot.entity.position.y + bot.entity.height - (block.position.y + 0.5)
|
||
|
const dz = bot.entity.position.z - (block.position.z + 0.5)
|
||
|
// Check y first then x and z
|
||
|
const visibleFaces = {
|
||
|
y: Math.sign(Math.abs(dy) > 0.5 ? dy : 0),
|
||
|
x: Math.sign(Math.abs(dx) > 0.5 ? dx : 0),
|
||
|
z: Math.sign(Math.abs(dz) > 0.5 ? dz : 0)
|
||
|
}
|
||
|
const validFaces = []
|
||
|
for (const i in visibleFaces) {
|
||
|
if (!visibleFaces[i]) continue // skip as this face is not visible
|
||
|
// target position on the target block face. -> 0.5 + (current face) * 0.5
|
||
|
const targetPos = block.position.offset(
|
||
|
0.5 + (i === 'x' ? visibleFaces[i] * 0.5 : 0),
|
||
|
0.5 + (i === 'y' ? visibleFaces[i] * 0.5 : 0),
|
||
|
0.5 + (i === 'z' ? visibleFaces[i] * 0.5 : 0)
|
||
|
)
|
||
|
const startPos = bot.entity.position.offset(0, bot.entity.height, 0)
|
||
|
const rayBlock = bot.world.raycast(startPos, targetPos.clone().subtract(startPos).normalize(), 5)
|
||
|
if (rayBlock) {
|
||
|
const rayPos = rayBlock.position
|
||
|
if (
|
||
|
rayPos.x === block.position.x &&
|
||
|
rayPos.y === block.position.y &&
|
||
|
rayPos.z === block.position.z
|
||
|
) {
|
||
|
// console.info(rayBlock)
|
||
|
validFaces.push({
|
||
|
face: rayBlock.face,
|
||
|
targetPos: rayBlock.intersect
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (validFaces.length > 0) {
|
||
|
// Chose closest valid face
|
||
|
let closest
|
||
|
let distSqrt = 999
|
||
|
for (const i in validFaces) {
|
||
|
const tPos = validFaces[i].targetPos
|
||
|
const cDist = new Vec3(tPos.x, tPos.y, tPos.z).distanceSquared(
|
||
|
bot.entity.position.offset(0, bot.entity.height, 0)
|
||
|
)
|
||
|
if (distSqrt > cDist) {
|
||
|
closest = validFaces[i]
|
||
|
distSqrt = cDist
|
||
|
}
|
||
|
}
|
||
|
await bot.lookAt(closest.targetPos, forceLook)
|
||
|
diggingFace = closest.face
|
||
|
} else {
|
||
|
// Block is obstructed return error?
|
||
|
throw new Error('Block not in view')
|
||
|
}
|
||
|
} else {
|
||
|
await bot.lookAt(block.position.offset(0.5, 0.5, 0.5), forceLook)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
diggingTask = createTask()
|
||
|
bot._client.write('block_dig', {
|
||
|
status: 0, // start digging
|
||
|
location: block.position,
|
||
|
face: diggingFace // default face is 1 (top)
|
||
|
})
|
||
|
const waitTime = bot.digTime(block)
|
||
|
waitTimeout = setTimeout(finishDigging, waitTime)
|
||
|
bot.targetDigBlock = block
|
||
|
bot.swingArm()
|
||
|
|
||
|
swingInterval = setInterval(() => {
|
||
|
bot.swingArm()
|
||
|
}, 350)
|
||
|
|
||
|
function finishDigging () {
|
||
|
clearInterval(swingInterval)
|
||
|
clearTimeout(waitTimeout)
|
||
|
swingInterval = null
|
||
|
waitTimeout = null
|
||
|
if (bot.targetDigBlock) {
|
||
|
bot._client.write('block_dig', {
|
||
|
status: 2, // finish digging
|
||
|
location: bot.targetDigBlock.position,
|
||
|
face: diggingFace // hard coded to always dig from the top
|
||
|
})
|
||
|
}
|
||
|
bot.targetDigBlock = null
|
||
|
bot.lastDigTime = performance.now()
|
||
|
bot._updateBlockState(block.position, 0)
|
||
|
}
|
||
|
|
||
|
const eventName = `blockUpdate:${block.position}`
|
||
|
bot.on(eventName, onBlockUpdate)
|
||
|
|
||
|
bot.stopDigging = () => {
|
||
|
if (!bot.targetDigBlock) return
|
||
|
bot.removeListener(eventName, onBlockUpdate)
|
||
|
clearInterval(swingInterval)
|
||
|
clearTimeout(waitTimeout)
|
||
|
swingInterval = null
|
||
|
waitTimeout = null
|
||
|
bot._client.write('block_dig', {
|
||
|
status: 1, // cancel digging
|
||
|
location: bot.targetDigBlock.position,
|
||
|
face: 1 // hard coded to always dig from the top
|
||
|
})
|
||
|
const block = bot.targetDigBlock
|
||
|
bot.targetDigBlock = null
|
||
|
bot.lastDigTime = performance.now()
|
||
|
bot.emit('diggingAborted', block)
|
||
|
bot.stopDigging = noop
|
||
|
diggingTask.cancel(new Error('Digging aborted'))
|
||
|
}
|
||
|
|
||
|
function onBlockUpdate (oldBlock, newBlock) {
|
||
|
// vanilla server never actually interrupt digging, but some server send block update when you start digging
|
||
|
// so ignore block update if not air
|
||
|
// All block update listeners receive (null, null) when the world is unloaded. So newBlock can be null.
|
||
|
if (newBlock?.type !== 0) return
|
||
|
bot.removeListener(eventName, onBlockUpdate)
|
||
|
clearInterval(swingInterval)
|
||
|
clearTimeout(waitTimeout)
|
||
|
swingInterval = null
|
||
|
waitTimeout = null
|
||
|
bot.targetDigBlock = null
|
||
|
bot.lastDigTime = performance.now()
|
||
|
bot.emit('diggingCompleted', newBlock)
|
||
|
diggingTask.finish()
|
||
|
}
|
||
|
|
||
|
await diggingTask.promise
|
||
|
}
|
||
|
|
||
|
bot.on('death', () => {
|
||
|
bot.removeAllListeners('diggingAborted')
|
||
|
bot.removeAllListeners('diggingCompleted')
|
||
|
bot.stopDigging()
|
||
|
})
|
||
|
|
||
|
function canDigBlock (block) {
|
||
|
return (
|
||
|
block &&
|
||
|
block.diggable &&
|
||
|
block.position.offset(0.5, 0.5, 0.5).distanceTo(bot.entity.position.offset(0, 1.65, 0)) <= 5.1
|
||
|
)
|
||
|
}
|
||
|
|
||
|
function digTime (block) {
|
||
|
let type = null
|
||
|
let enchantments = []
|
||
|
|
||
|
// Retrieve currently held item ID and active enchantments from heldItem
|
||
|
const currentlyHeldItem = bot.heldItem
|
||
|
if (currentlyHeldItem) {
|
||
|
type = currentlyHeldItem.type
|
||
|
enchantments = currentlyHeldItem.enchants
|
||
|
}
|
||
|
|
||
|
// Append helmet enchantments (because Aqua Affinity actually affects dig speed)
|
||
|
const headEquipmentSlot = bot.getEquipmentDestSlot('head')
|
||
|
const headEquippedItem = bot.inventory.slots[headEquipmentSlot]
|
||
|
if (headEquippedItem) {
|
||
|
const helmetEnchantments = headEquippedItem.enchants
|
||
|
enchantments = enchantments.concat(helmetEnchantments)
|
||
|
}
|
||
|
|
||
|
const creative = bot.game.gameMode === 'creative'
|
||
|
return block.digTime(
|
||
|
type,
|
||
|
creative,
|
||
|
bot.entity.isInWater,
|
||
|
!bot.entity.onGround,
|
||
|
enchantments,
|
||
|
bot.entity.effects
|
||
|
)
|
||
|
}
|
||
|
|
||
|
bot.dig = dig
|
||
|
bot.stopDigging = noop
|
||
|
bot.canDigBlock = canDigBlock
|
||
|
bot.digTime = digTime
|
||
|
}
|
||
|
|
||
|
function noop (err) {
|
||
|
if (err) throw err
|
||
|
}
|