Compare commits

...
Sign in to create a new pull request.

2 commits

Author SHA1 Message Date
Vitaly Turovsky
8340dd0fd8 add file! 2024-04-16 20:12:02 +03:00
Vitaly Turovsky
d13fbc22c1 display block break mesh for everyone, fix recalculation 2024-02-16 07:57:31 +03:00
4 changed files with 152 additions and 67 deletions

2
src/globals.d.ts vendored
View file

@ -13,7 +13,7 @@ declare const loadedData: import('minecraft-data').IndexedData
declare const customEvents: import('typed-emitter').default<{ declare const customEvents: import('typed-emitter').default<{
/** Singleplayer load requested */ /** Singleplayer load requested */
singleplayer (): void singleplayer (): void
digStart () blockDig (block: import('prismarine-block').Block): void
gameLoaded (): void gameLoaded (): void
search (q: string): void search (q: string): void
}> }>

62
src/mineflayerUtils.ts Normal file
View file

@ -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)
}
})

View file

@ -202,14 +202,11 @@ subscribeKey(miscUiState, 'gameLoaded', async () => {
} }
// todo add support for all current world events // todo add support for all current world events
}) })
let diggingBlock: Block | null = null customEvents.on('blockDig', async (block) => {
customEvents.on('digStart', () => { await playBlockBreak(block.name, block.position)
diggingBlock = bot.blockAtCursor(5)
}) })
bot.on('diggingCompleted', async () => { bot.on('blockBreakProgressEnd', async (block) => {
if (diggingBlock) { console.log('blockBreakProgressEnd', block)
await playBlockBreak(diggingBlock.name, diggingBlock.position)
}
}) })
} }

View file

@ -17,6 +17,7 @@ import { LineMaterial, Wireframe, LineSegmentsGeometry } from 'three-stdlib'
import { isGameActive } from './globalState' import { isGameActive } from './globalState'
import { assertDefined } from './utils' import { assertDefined } from './utils'
import { options } from './optionsStorage' import { options } from './optionsStorage'
import { digGlobally, stopDigging } from './mineflayerUtils'
function getViewDirection (pitch, yaw) { function getViewDirection (pitch, yaw) {
const csPitch = Math.cos(pitch) const csPitch = Math.cos(pitch)
@ -29,18 +30,20 @@ function getViewDirection (pitch, yaw) {
class WorldInteraction { class WorldInteraction {
ready = false ready = false
interactionLines: null | { blockPos; mesh } = null 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 prevBreakState
currentDigTime currentDigTime
prevOnGround prevOnGround
lastBlockPlaced: number lastBlockPlaced: number
buttons = [false, false, false] buttons = [false, false, false]
lastButtons = [false, false, false] lastButtons = [false, false, false]
breakStartTime: number | undefined = 0 breakMeshes = {} as { [key: string]: THREE.Mesh }
cursorBlock: import('prismarine-block').Block | null = null
blockBreakMesh: THREE.Mesh
breakTextures: THREE.Texture[]
lastDigged: number
lineMaterial: LineMaterial
oneTimeInit () { oneTimeInit () {
const loader = new THREE.TextureLoader() const loader = new THREE.TextureLoader()
@ -68,9 +71,7 @@ class WorldInteraction {
blending: THREE.MultiplyBlending blending: THREE.MultiplyBlending
}) })
this.blockBreakMesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), breakMaterial) this.blockBreakMesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), breakMaterial)
this.blockBreakMesh.visible = false
this.blockBreakMesh.renderOrder = 999 this.blockBreakMesh.renderOrder = 999
viewer.scene.add(this.blockBreakMesh)
// Setup events // Setup events
document.addEventListener('mouseup', (e) => { 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 () { initBot () {
if (!this.ready) { if (!this.ready) {
this.ready = true this.ready = true
@ -112,6 +157,12 @@ class WorldInteraction {
bot.on('diggingAborted', () => { bot.on('diggingAborted', () => {
this.breakStartTime = undefined 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 upLineMaterial = () => {
const inCreative = bot.game.gameMode === 'creative' const inCreative = bot.game.gameMode === 'creative'
@ -205,33 +256,25 @@ class WorldInteraction {
} }
// Stop break // Stop break
if ((!this.buttons[0] && this.lastButtons[0]) || cursorChanged) { if (!this.buttons[0] || cursorChanged) {
try { stopDigging()
bot.stopDigging() // this shouldnt throw anything...
} catch (e) { } // to be reworked in mineflayer, then remove the try here
} }
const onGround = bot.entity.onGround || bot.game.gameMode === 'creative' 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 // Start break
// todo last check doesnt work as cursorChanged happens once (after that check is false) // todo last check doesnt work as cursorChanged happens once (after that check is false)
if ( if (
this.buttons[0] this.buttons[0]
) { ) {
// todo hold mouse state
if (cursorBlockDiggable if (cursorBlockDiggable
&& (!this.lastButtons[0] || (cursorChanged && Date.now() - (this.lastDigged ?? 0) > 100) || onGround !== this.prevOnGround) && (!this.lastButtons[0] || (cursorChanged && Date.now() - (this.lastDigged ?? 0) > 100))) {
&& onGround) {
this.currentDigTime = bot.digTime(cursorBlockDiggable) this.currentDigTime = bot.digTime(cursorBlockDiggable)
this.breakStartTime = performance.now() 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)] 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
//@ts-expect-error const blockFace = cursorBlockDiggable.face
cursorBlockDiggable, 'ignore', vecArray[cursorBlockDiggable.face] digGlobally(cursorBlockDiggable, blockFace, 'right')
).catch((err) => {
if (err.message === 'Digging aborted') return
throw err
})
customEvents.emit('digStart')
this.lastDigged = Date.now() this.lastDigged = Date.now()
} else { } else {
bot.swingArm('right') bot.swingArm('right')
@ -245,47 +288,30 @@ class WorldInteraction {
this.updateBlockInteractionLines(cursorBlock.position, allShapes.map(shape => { this.updateBlockInteractionLines(cursorBlock.position, allShapes.map(shape => {
return getDataFromShape(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 { } else {
this.updateBlockInteractionLines(null) this.updateBlockInteractionLines(null)
} }
// Show break animation // Show break animation
if (cursorBlockDiggable && this.breakStartTime && bot.game.gameMode !== 'creative') { // if (cursorBlockDiggable && this.breakStartTime && bot.game.gameMode !== 'creative') {
const elapsed = performance.now() - this.breakStartTime // const elapsed = performance.now() - this.breakStartTime
const time = bot.digTime(cursorBlockDiggable) // const time = bot.digTime(cursorBlockDiggable)
if (time !== this.currentDigTime) { // if (time !== this.currentDigTime) {
console.warn('dig time changed! cancelling!', time, 'from', this.currentDigTime) // todo // console.warn('dig time changed! cancelling!', time, 'from', this.currentDigTime) // todo
try { bot.stopDigging() } catch { } // try { bot.stopDigging() } catch { }
} // }
const state = Math.floor((elapsed / time) * 10) // const state = Math.floor((elapsed / time) * 10)
//@ts-expect-error // //@ts-expect-error
this.blockBreakMesh.material.map = this.breakTextures[state] ?? this.breakTextures.at(-1) // this.blockBreakMesh.material.map = this.breakTextures[state] ?? this.breakTextures.at(-1)
if (state !== this.prevBreakState) { // if (state !== this.prevBreakState) {
//@ts-expect-error // //@ts-expect-error
this.blockBreakMesh.material.needsUpdate = true // this.blockBreakMesh.material.needsUpdate = true
} // }
this.prevBreakState = state // this.prevBreakState = state
this.blockBreakMesh.visible = true // this.blockBreakMesh.visible = true
} else { // } else {
this.blockBreakMesh.visible = false // this.blockBreakMesh.visible = false
} // }
// Update state // Update state
this.cursorBlock = cursorBlock this.cursorBlock = cursorBlock