diff --git a/src/globals.d.ts b/src/globals.d.ts index fee3f5ec..27c4bba9 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -13,7 +13,7 @@ declare const loadedData: import('minecraft-data').IndexedData declare const customEvents: import('typed-emitter').default<{ /** Singleplayer load requested */ singleplayer (): void - digStart () + blockDig (block: import('prismarine-block').Block): void gameLoaded (): void search (q: string): void }> diff --git a/src/mineflayerUtils.ts b/src/mineflayerUtils.ts new file mode 100644 index 00000000..f8e35edc --- /dev/null +++ b/src/mineflayerUtils.ts @@ -0,0 +1,62 @@ +import { proxy, subscribe } from 'valtio' +import { Vec3 } from 'vec3' +import { watchUnloadForCleanup } from './gameUnload' + +// todo rename botUtils then + +export const diggingState = proxy({ + isDigging: false, + diggingHand: 'right' as 'right' | 'left', + block: null as import('prismarine-block').Block | null, +}) + +const blockUpdateListener = (_, newBlock: import('prismarine-block').Block | null) => { + // https://github.com/PrismarineJS/mineflayer/blob/f85a381c2c67558a5d96363eb68b5092fac6cdd2/lib/plugins/digging.js#L162 + + if (newBlock?.type === 0) { + stopDigging() + } +} + +export const digGlobally = (block: import('prismarine-block').Block, diggingFace: number, hand: 'right' | 'left') => { + console.log(block.name) + bot._client.write('block_dig', { + status: 0, // start digging + location: block.position, + face: diggingFace // default face is 1 (top) + }) + diggingState.isDigging = true + diggingState.diggingHand = hand + diggingState.block = block + bot.on(`blockUpdate:${block.position as any}` as 'blockUpdate', blockUpdateListener) +} +window.diggingState = diggingState + +export const stopDigging = () => { + if (!diggingState.isDigging) return + const { position } = diggingState.block! + bot._client.write('block_dig', { + status: 2, // cancel digging + location: position, + face: 1 // hard coded to 1 (top) + }) + customEvents.emit('blockDig' as any, diggingState.block!) + diggingState.isDigging = false + diggingState.block = null + bot.off(`blockUpdate:${position as any}` as 'blockUpdate', blockUpdateListener) +} + +let armSwingInterval: NodeJS.Timeout + +subscribe(diggingState, () => { + if (diggingState.isDigging) { + armSwingInterval = setInterval(() => { + bot.swingArm(diggingState.diggingHand) + }, 350) + watchUnloadForCleanup(() => { + clearInterval(armSwingInterval) + }) + } else { + clearInterval(armSwingInterval) + } +}) diff --git a/src/soundSystem.ts b/src/soundSystem.ts index 520cd837..47988407 100644 --- a/src/soundSystem.ts +++ b/src/soundSystem.ts @@ -202,14 +202,11 @@ subscribeKey(miscUiState, 'gameLoaded', async () => { } // todo add support for all current world events }) - let diggingBlock: Block | null = null - customEvents.on('digStart', () => { - diggingBlock = bot.blockAtCursor(5) + customEvents.on('blockDig', async (block) => { + await playBlockBreak(block.name, block.position) }) - bot.on('diggingCompleted', async () => { - if (diggingBlock) { - await playBlockBreak(diggingBlock.name, diggingBlock.position) - } + bot.on('blockBreakProgressEnd', async (block) => { + console.log('blockBreakProgressEnd', block) }) } diff --git a/src/worldInteractions.ts b/src/worldInteractions.ts index 9353670c..2e0b4718 100644 --- a/src/worldInteractions.ts +++ b/src/worldInteractions.ts @@ -17,6 +17,7 @@ import { LineMaterial, Wireframe, LineSegmentsGeometry } from 'three-stdlib' import { isGameActive } from './globalState' import { assertDefined } from './utils' import { options } from './optionsStorage' +import { digGlobally, stopDigging } from './mineflayerUtils' function getViewDirection (pitch, yaw) { const csPitch = Math.cos(pitch) @@ -29,18 +30,20 @@ function getViewDirection (pitch, yaw) { class WorldInteraction { ready = false interactionLines: null | { blockPos; mesh } = null + breakStartTime: number | undefined = 0 + blockBreakMesh: THREE.Mesh + breakTextures: THREE.Texture[] + lineMaterial: LineMaterial + // update state + cursorBlock: import('prismarine-block').Block | null = null + lastDigged: number prevBreakState currentDigTime prevOnGround lastBlockPlaced: number buttons = [false, false, false] lastButtons = [false, false, false] - breakStartTime: number | undefined = 0 - cursorBlock: import('prismarine-block').Block | null = null - blockBreakMesh: THREE.Mesh - breakTextures: THREE.Texture[] - lastDigged: number - lineMaterial: LineMaterial + breakMeshes = {} as { [key: string]: THREE.Mesh } oneTimeInit () { const loader = new THREE.TextureLoader() @@ -68,9 +71,7 @@ class WorldInteraction { blending: THREE.MultiplyBlending }) this.blockBreakMesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), breakMaterial) - this.blockBreakMesh.visible = false this.blockBreakMesh.renderOrder = 999 - viewer.scene.add(this.blockBreakMesh) // Setup events document.addEventListener('mouseup', (e) => { @@ -99,6 +100,50 @@ class WorldInteraction { }) } + addWorldBreakMesh (position: Vec3, stage: number | null) { + const posKey = `${position.x},${position.y},${position.z}` + let mesh = this.breakMeshes[posKey] + if (stage === null) { + if (mesh) { + viewer.scene.remove(mesh) + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete this.breakMeshes[posKey] + } + return + } + + if (!mesh) { + mesh = this.blockBreakMesh.clone(true) + viewer.scene.add(mesh) + this.breakMeshes[posKey] = mesh + + // #region set position and scale from shape + const block = bot.world.getBlock(position) + const allShapes = [...block.shapes, ...block['interactionShapes'] ?? []] + // union of all values + const breakShape = allShapes.reduce((acc, cur) => { + return [ + Math.min(acc[0], cur[0]), + Math.min(acc[1], cur[1]), + Math.min(acc[2], cur[2]), + Math.max(acc[3], cur[3]), + Math.max(acc[4], cur[4]), + Math.max(acc[5], cur[5]) + ] + }) + const { position: shapePos, width, height, depth } = getDataFromShape(breakShape) + mesh.scale.set(width * 1.001, height * 1.001, depth * 1.001) + shapePos.add(position) + mesh.position.set(shapePos.x, shapePos.y, shapePos.z) + // #endregion + } + + const material = mesh.material as THREE.MeshBasicMaterial + const oldMap = material.map + material.map = this.breakTextures[stage] ?? this.breakTextures.at(-1) + if (oldMap !== material.map) material.needsUpdate = true + } + initBot () { if (!this.ready) { this.ready = true @@ -112,6 +157,12 @@ class WorldInteraction { bot.on('diggingAborted', () => { this.breakStartTime = undefined }) + bot.on('blockBreakProgressObserved', (block, destroyStage) => { + this.addWorldBreakMesh(block.position, destroyStage) + }) + bot.on('blockBreakProgressEnd', (block) => { + this.addWorldBreakMesh(block.position, null) + }) const upLineMaterial = () => { const inCreative = bot.game.gameMode === 'creative' @@ -205,33 +256,25 @@ class WorldInteraction { } // Stop break - if ((!this.buttons[0] && this.lastButtons[0]) || cursorChanged) { - try { - bot.stopDigging() // this shouldnt throw anything... - } catch (e) { } // to be reworked in mineflayer, then remove the try here + if (!this.buttons[0] || cursorChanged) { + stopDigging() } const onGround = bot.entity.onGround || bot.game.gameMode === 'creative' - this.prevOnGround ??= onGround // todo this should be fixed in mineflayer to involve correct calculations when this changes as this is very important when mining straight down // todo this should be fixed in mineflayer to involve correct calculations when this changes as this is very important when mining straight down // todo this should be fixed in mineflayer to involve correct calculations when this changes as this is very important when mining straight down // Start break // todo last check doesnt work as cursorChanged happens once (after that check is false) if ( this.buttons[0] ) { + // todo hold mouse state if (cursorBlockDiggable - && (!this.lastButtons[0] || (cursorChanged && Date.now() - (this.lastDigged ?? 0) > 100) || onGround !== this.prevOnGround) - && onGround) { + && (!this.lastButtons[0] || (cursorChanged && Date.now() - (this.lastDigged ?? 0) > 100))) { this.currentDigTime = bot.digTime(cursorBlockDiggable) this.breakStartTime = performance.now() const vecArray = [new Vec3(0, -1, 0), new Vec3(0, 1, 0), new Vec3(0, 0, -1), new Vec3(0, 0, 1), new Vec3(-1, 0, 0), new Vec3(1, 0, 0)] - bot.dig( - //@ts-expect-error - cursorBlockDiggable, 'ignore', vecArray[cursorBlockDiggable.face] - ).catch((err) => { - if (err.message === 'Digging aborted') return - throw err - }) - customEvents.emit('digStart') + //@ts-expect-error + const blockFace = cursorBlockDiggable.face + digGlobally(cursorBlockDiggable, blockFace, 'right') this.lastDigged = Date.now() } else { bot.swingArm('right') @@ -245,47 +288,30 @@ class WorldInteraction { this.updateBlockInteractionLines(cursorBlock.position, allShapes.map(shape => { return getDataFromShape(shape) })) - { - // union of all values - const breakShape = allShapes.reduce((acc, cur) => { - return [ - Math.min(acc[0], cur[0]), - Math.min(acc[1], cur[1]), - Math.min(acc[2], cur[2]), - Math.max(acc[3], cur[3]), - Math.max(acc[4], cur[4]), - Math.max(acc[5], cur[5]) - ] - }) - const { position, width, height, depth } = getDataFromShape(breakShape) - this.blockBreakMesh.scale.set(width * 1.001, height * 1.001, depth * 1.001) - position.add(cursorBlock.position) - this.blockBreakMesh.position.set(position.x, position.y, position.z) - } } else { this.updateBlockInteractionLines(null) } // Show break animation - if (cursorBlockDiggable && this.breakStartTime && bot.game.gameMode !== 'creative') { - const elapsed = performance.now() - this.breakStartTime - const time = bot.digTime(cursorBlockDiggable) - if (time !== this.currentDigTime) { - console.warn('dig time changed! cancelling!', time, 'from', this.currentDigTime) // todo - try { bot.stopDigging() } catch { } - } - const state = Math.floor((elapsed / time) * 10) - //@ts-expect-error - this.blockBreakMesh.material.map = this.breakTextures[state] ?? this.breakTextures.at(-1) - if (state !== this.prevBreakState) { - //@ts-expect-error - this.blockBreakMesh.material.needsUpdate = true - } - this.prevBreakState = state - this.blockBreakMesh.visible = true - } else { - this.blockBreakMesh.visible = false - } + // if (cursorBlockDiggable && this.breakStartTime && bot.game.gameMode !== 'creative') { + // const elapsed = performance.now() - this.breakStartTime + // const time = bot.digTime(cursorBlockDiggable) + // if (time !== this.currentDigTime) { + // console.warn('dig time changed! cancelling!', time, 'from', this.currentDigTime) // todo + // try { bot.stopDigging() } catch { } + // } + // const state = Math.floor((elapsed / time) * 10) + // //@ts-expect-error + // this.blockBreakMesh.material.map = this.breakTextures[state] ?? this.breakTextures.at(-1) + // if (state !== this.prevBreakState) { + // //@ts-expect-error + // this.blockBreakMesh.material.needsUpdate = true + // } + // this.prevBreakState = state + // this.blockBreakMesh.visible = true + // } else { + // this.blockBreakMesh.visible = false + // } // Update state this.cursorBlock = cursorBlock