diff --git a/prismarine-viewer/viewer/lib/hand.ts b/prismarine-viewer/viewer/lib/hand.ts new file mode 100644 index 000000000..6c9aacc53 --- /dev/null +++ b/prismarine-viewer/viewer/lib/hand.ts @@ -0,0 +1,96 @@ +import * as THREE from 'three' +import { loadSkinToCanvas } from 'skinview-utils' +import stevePng from 'mc-assets/dist/other-textures/latest/entity/player/wide/steve.png' + +let steveTexture: THREE.Texture +export const getMyHand = async (image?: string) => { + let newMap: THREE.Texture + if (!image && steveTexture) { + newMap = steveTexture + } else { + image ??= stevePng + const skinCanvas = document.createElement('canvas') + const img = new Image() + img.src = image + await new Promise(resolve => { + img.onload = () => { + resolve() + } + }) + loadSkinToCanvas(skinCanvas, img) + newMap = new THREE.CanvasTexture(skinCanvas) + // newMap.flipY = false + newMap.magFilter = THREE.NearestFilter + newMap.minFilter = THREE.NearestFilter + if (!image) { + steveTexture = newMap + } + } + + // right arm + const box = new THREE.BoxGeometry() + const material = new THREE.MeshStandardMaterial() + const slim = false + const mesh = new THREE.Mesh(box, material) + mesh.scale.x = slim ? 3 : 4 + mesh.scale.y = 12 + mesh.scale.z = 4 + setSkinUVs(box, 40, 16, slim ? 3 : 4, 12, 4) + material.map = newMap + material.needsUpdate = true + const group = new THREE.Group() + group.add(mesh) + group.scale.set(0.1, 0.1, 0.1) + mesh.rotation.z = Math.PI + return group +} + +function setUVs ( + box: THREE.BoxGeometry, + u: number, + v: number, + width: number, + height: number, + depth: number, + textureWidth: number, + textureHeight: number +): void { + const toFaceVertices = (x1: number, y1: number, x2: number, y2: number) => [ + new THREE.Vector2(x1 / textureWidth, 1 - y2 / textureHeight), + new THREE.Vector2(x2 / textureWidth, 1 - y2 / textureHeight), + new THREE.Vector2(x2 / textureWidth, 1 - y1 / textureHeight), + new THREE.Vector2(x1 / textureWidth, 1 - y1 / textureHeight), + ] + + const top = toFaceVertices(u + depth, v, u + width + depth, v + depth) + const bottom = toFaceVertices(u + width + depth, v, u + width * 2 + depth, v + depth) + const left = toFaceVertices(u, v + depth, u + depth, v + depth + height) + const front = toFaceVertices(u + depth, v + depth, u + width + depth, v + depth + height) + const right = toFaceVertices(u + width + depth, v + depth, u + width + depth * 2, v + height + depth) + const back = toFaceVertices(u + width + depth * 2, v + depth, u + width * 2 + depth * 2, v + height + depth) + + const uvAttr = box.attributes.uv as THREE.BufferAttribute + const uvRight = [right[3], right[2], right[0], right[1]] + const uvLeft = [left[3], left[2], left[0], left[1]] + const uvTop = [top[3], top[2], top[0], top[1]] + const uvBottom = [bottom[0], bottom[1], bottom[3], bottom[2]] + const uvFront = [front[3], front[2], front[0], front[1]] + const uvBack = [back[3], back[2], back[0], back[1]] + + // Create a new array to hold the modified UV data + const newUVData = [] as number[] + + // Iterate over the arrays and copy the data to uvData + for (const uvArray of [uvRight, uvLeft, uvTop, uvBottom, uvFront, uvBack]) { + for (const uv of uvArray) { + newUVData.push(uv.x, uv.y) + } + } + + uvAttr.set(new Float32Array(newUVData)) + uvAttr.needsUpdate = true +} + +function setSkinUVs (box: THREE.BoxGeometry, u: number, v: number, width: number, height: number, depth: number): void { + setUVs(box, u, v, width, height, depth, 64, 64) +} diff --git a/prismarine-viewer/viewer/lib/holdingBlock.ts b/prismarine-viewer/viewer/lib/holdingBlock.ts index f5a0ca799..5fc3e03c0 100644 --- a/prismarine-viewer/viewer/lib/holdingBlock.ts +++ b/prismarine-viewer/viewer/lib/holdingBlock.ts @@ -1,14 +1,19 @@ import * as THREE from 'three' import * as tweenJs from '@tweenjs/tween.js' import worldBlockProvider from 'mc-assets/dist/worldBlockProvider' +import { GUI } from 'lil-gui' import { getThreeBlockModelGroup, renderBlockThree, setBlockPosition } from './mesher/standaloneRenderer' +import { getMyHand } from './hand' export type HandItemBlock = { - name - properties + name? + properties? + type: 'block' | 'item' | 'hand' + id?: number } export default class HoldingBlock { + // TODO refactor with the tree builder for better visual understanding holdingBlock: THREE.Object3D | undefined = undefined swingAnimation: tweenJs.Group | undefined = undefined blockSwapAnimation: { @@ -16,22 +21,25 @@ export default class HoldingBlock { hidden: boolean } | undefined = undefined cameraGroup = new THREE.Mesh() - objectOuterGroup = new THREE.Group() - objectInnerGroup = new THREE.Group() - camera: THREE.Group | THREE.PerspectiveCamera + objectOuterGroup = new THREE.Group() // 3 + objectInnerGroup = new THREE.Group() // 4 + holdingBlockInnerGroup = new THREE.Group() // 5 + camera = new THREE.PerspectiveCamera(75, 1, 0.1, 100) stopUpdate = false lastHeldItem: HandItemBlock | undefined toBeRenderedItem: HandItemBlock | undefined isSwinging = false nextIterStopCallbacks: Array<() => void> | undefined + rightSide = true - constructor (public scene: THREE.Scene) { + debug = {} as Record + + constructor () { this.initCameraGroup() } initCameraGroup () { this.cameraGroup = new THREE.Mesh() - this.scene.add(this.cameraGroup) } startSwing () { @@ -44,17 +52,18 @@ export default class HoldingBlock { // const DURATION = 1000 * 0.35 / 2 const DURATION = 1000 * 0.35 / 3 // const DURATION = 1000 + const { position, rotation, object } = this.getFinalSwingPositionRotation() const initialPos = { - x: this.objectInnerGroup.position.x, - y: this.objectInnerGroup.position.y, - z: this.objectInnerGroup.position.z + x: object.position.x, + y: object.position.y, + z: object.position.z } const initialRot = { - x: this.objectInnerGroup.rotation.x, - y: this.objectInnerGroup.rotation.y, - z: this.objectInnerGroup.rotation.z + x: object.rotation.x, + y: object.rotation.y, + z: object.rotation.z } - const mainAnim = new tweenJs.Tween(this.objectInnerGroup.position, this.swingAnimation).to({ y: this.objectInnerGroup.position.y - this.objectInnerGroup.scale.y / 2 }, DURATION).yoyo(true).repeat(Infinity).start() + const mainAnim = new tweenJs.Tween(object.position, this.swingAnimation).to(position, DURATION).yoyo(true).repeat(Infinity).start() let i = 0 mainAnim.onRepeat(() => { i++ @@ -67,14 +76,66 @@ export default class HoldingBlock { this.swingAnimation!.removeAll() this.swingAnimation = undefined // todo refactor to be more generic for animations - this.objectInnerGroup.position.set(initialPos.x, initialPos.y, initialPos.z) - // this.objectInnerGroup.rotation.set(initialRot.x, initialRot.y, initialRot.z) - Object.assign(this.objectInnerGroup.rotation, initialRot) + object.position.set(initialPos.x, initialPos.y, initialPos.z) + // object.rotation.set(initialRot.x, initialRot.y, initialRot.z) + Object.assign(object.rotation, initialRot) } }) - new tweenJs.Tween(this.objectInnerGroup.rotation, this.swingAnimation).to({ z: THREE.MathUtils.degToRad(90) }, DURATION).yoyo(true).repeat(Infinity).start() - new tweenJs.Tween(this.objectInnerGroup.rotation, this.swingAnimation).to({ x: -THREE.MathUtils.degToRad(90) }, DURATION).yoyo(true).repeat(Infinity).start() + new tweenJs.Tween(object.rotation, this.swingAnimation).to(rotation, DURATION).yoyo(true).repeat(Infinity).start() + } + } + + getFinalSwingPositionRotation (origPosition?: THREE.Vector3) { + const object = this.objectInnerGroup + if (this.lastHeldItem?.type === 'block') { + origPosition ??= object.position + return { + position: { y: origPosition.y - this.objectInnerGroup.scale.y / 2 }, + rotation: { z: THREE.MathUtils.degToRad(90), x: -THREE.MathUtils.degToRad(90) }, + object + } + } + if (this.lastHeldItem?.type === 'item') { + const object = this.holdingBlockInnerGroup + origPosition ??= object.position + return { + position: { + y: origPosition.y - object.scale.y * 2, + // z: origPosition.z - window.zFinal, + // x: origPosition.x - window.xFinal, + }, + // rotation: { z: THREE.MathUtils.degToRad(90), x: -THREE.MathUtils.degToRad(90) } + rotation: { + // z: THREE.MathUtils.degToRad(window.zRotationFinal ?? 0), + // x: THREE.MathUtils.degToRad(window.xRotationFinal ?? 0), + // y: THREE.MathUtils.degToRad(window.yRotationFinal ?? 0), + x: THREE.MathUtils.degToRad(-120) + }, + object + } + } + if (this.lastHeldItem?.type === 'hand') { + const object = this.holdingBlockInnerGroup + origPosition ??= object.position + return { + position: { + y: origPosition.y - (window.yFinal ?? 0.15), + z: origPosition.z - window.zFinal, + x: origPosition.x - window.xFinal, + }, + rotation: { + x: THREE.MathUtils.degToRad(window.xRotationFinal || -14.7), + y: THREE.MathUtils.degToRad(window.yRotationFinal || 33.95), + z: THREE.MathUtils.degToRad(window.zRotationFinal || -28), + }, + object + } + } + return { + position: {}, + rotation: {}, + object } } @@ -89,11 +150,35 @@ export default class HoldingBlock { }) } - update (camera: typeof this.camera) { - this.camera = camera + render (originalCamera: THREE.PerspectiveCamera, renderer: THREE.WebGLRenderer, ambientLight: THREE.AmbientLight, directionalLight: THREE.DirectionalLight) { + if (!this.lastHeldItem) return this.swingAnimation?.update() this.blockSwapAnimation?.tween.update() + + const scene = new THREE.Scene() + scene.add(this.cameraGroup) + // if (this.camera.aspect !== originalCamera.aspect) { + // this.camera.aspect = originalCamera.aspect + // this.camera.updateProjectionMatrix() + // } this.updateCameraGroup() + scene.add(ambientLight.clone()) + scene.add(directionalLight.clone()) + + const viewerSize = renderer.getSize(new THREE.Vector2()) + const minSize = Math.min(viewerSize.width, viewerSize.height) + + renderer.autoClear = false + renderer.clearDepth() + if (this.rightSide) { + const x = viewerSize.width - minSize + // if (x) x -= x / 4 + renderer.setViewport(x, 0, minSize, minSize) + } else { + renderer.setViewport(0, 0, minSize, minSize) + } + renderer.render(scene, this.camera) + renderer.setViewport(0, 0, viewerSize.width, viewerSize.height) } // worldTest () { @@ -142,23 +227,36 @@ export default class HoldingBlock { this.cameraGroup.position.copy(camera.position) this.cameraGroup.rotation.copy(camera.rotation) - const viewerSize = viewer.renderer.getSize(new THREE.Vector2()) - // const x = window.x ?? 0.25 * viewerSize.width / viewerSize.height - // const x = 0 * viewerSize.width / viewerSize.height - const x = 0.2 * viewerSize.width / viewerSize.height - this.objectOuterGroup.position.set(x, -0.3, -0.45) + // const viewerSize = viewer.renderer.getSize(new THREE.Vector2()) + // const aspect = viewerSize.width / viewerSize.height + const aspect = 1 + + + // Adjust the position based on the aspect ratio + const { position, scale: scaleData } = this.getHandHeld3d() + const distance = -position.z + const side = this.rightSide ? 1 : -1 + this.objectOuterGroup.position.set( + distance * position.x * aspect * side, + distance * position.y, + -distance + ) + + // const scale = Math.min(0.8, Math.max(1, 1 * aspect)) + const scale = scaleData * 2.22 * 0.2 + this.objectOuterGroup.scale.set(scale, scale, scale) } - async initHandObject (material: THREE.Material, blockstatesModels: any, blocksAtlases: any, block?: HandItemBlock) { + async initHandObject (material: THREE.Material, blockstatesModels: any, blocksAtlases: any, handItem?: HandItemBlock) { let animatingCurrent = false - if (!this.swingAnimation && !this.blockSwapAnimation && this.isDifferentItem(block)) { + if (!this.swingAnimation && !this.blockSwapAnimation && this.isDifferentItem(handItem)) { animatingCurrent = true await this.playBlockSwapAnimation() this.holdingBlock?.removeFromParent() this.holdingBlock = undefined } - this.lastHeldItem = block - if (!block) { + this.lastHeldItem = handItem + if (!handItem) { this.holdingBlock?.removeFromParent() this.holdingBlock = undefined this.swingAnimation = undefined @@ -166,16 +264,28 @@ export default class HoldingBlock { return } const blockProvider = worldBlockProvider(blockstatesModels, blocksAtlases, 'latest') - const models = blockProvider.getAllResolvedModels0_1(block, true) - const blockInner = getThreeBlockModelGroup(material, models, undefined, 'plains', loadedData) - // const { mesh: itemMesh } = viewer.entities.getItemMesh({ - // itemId: 541, - // })! - // itemMesh.position.set(0.5, 0.5, 0.5) - // const blockInner = itemMesh + let blockInner + if (handItem.type === 'block') { + const models = blockProvider.getAllResolvedModels0_1({ + name: handItem.name, + properties: handItem.properties ?? {} + }, true) + blockInner = getThreeBlockModelGroup(material, models, undefined, 'plains', loadedData) + } else if (handItem.type === 'item') { + const { mesh: itemMesh } = viewer.entities.getItemMesh({ + itemId: handItem.id, + })! + itemMesh.position.set(0.5, 0.5, 0.5) + blockInner = itemMesh + } else { + blockInner = await getMyHand() + } blockInner.name = 'holdingBlock' const blockOuterGroup = new THREE.Group() - blockOuterGroup.add(blockInner) + this.holdingBlockInnerGroup.removeFromParent() + this.holdingBlockInnerGroup = new THREE.Group() + this.holdingBlockInnerGroup.add(blockInner) + blockOuterGroup.add(this.holdingBlockInnerGroup) this.holdingBlock = blockInner this.objectInnerGroup = new THREE.Group() this.objectInnerGroup.add(blockOuterGroup) @@ -190,18 +300,113 @@ export default class HoldingBlock { this.objectOuterGroup.add(this.objectInnerGroup) this.cameraGroup.add(this.objectOuterGroup) - const rotation = -45 + -90 - // const rotation = -45 // should be for item - this.holdingBlock.rotation.set(0, THREE.MathUtils.degToRad(rotation), 0, 'ZYX') + const rotationDeg = this.getHandHeld3d().rotation + let origPosition + const setRotation = () => { + const final = this.getFinalSwingPositionRotation(origPosition) + origPosition ??= final.object.position.clone() + if (this.debug.displayFinal) { + Object.assign(final.object.position, final.position) + Object.assign(final.object.rotation, final.rotation) + } else if (this.debug.displayFinal === false) { + final.object.rotation.set(0, 0, 0) + } - // const scale = window.scale ?? 0.2 - const scale = 0.2 - this.objectOuterGroup.scale.set(scale, scale, scale) - // this.objectOuterGroup.position.set(x, window.y ?? -0.41, window.z ?? -0.45) - // this.objectOuterGroup.position.set(x, 0, -0.45) + this.holdingBlock!.rotation.x = THREE.MathUtils.degToRad(rotationDeg.x) + this.holdingBlock!.rotation.y = THREE.MathUtils.degToRad(rotationDeg.y) + this.holdingBlock!.rotation.z = THREE.MathUtils.degToRad(rotationDeg.z) + this.objectOuterGroup.rotation.y = THREE.MathUtils.degToRad(rotationDeg.yOuter) + } + // const gui = new GUI() + // gui.add(rotationDeg, 'x', -180, 180, 0.1) + // gui.add(rotationDeg, 'y', -180, 180, 0.1) + // gui.add(rotationDeg, 'z', -180, 180, 0.1) + // gui.add(rotationDeg, 'yOuter', -180, 180, 0.1) + // Object.assign(window, { xFinal: 0, yFinal: 0, zFinal: 0, xRotationFinal: 0, yRotationFinal: 0, zRotationFinal: 0, displayFinal: true }) + // gui.add(window, 'xFinal', -10, 10, 0.05) + // gui.add(window, 'yFinal', -10, 10, 0.05) + // gui.add(window, 'zFinal', -10, 10, 0.05) + // gui.add(window, 'xRotationFinal', -180, 180, 0.05) + // gui.add(window, 'yRotationFinal', -180, 180, 0.05) + // gui.add(window, 'zRotationFinal', -180, 180, 0.05) + // gui.add(window, 'displayFinal') + // gui.onChange(setRotation) + setRotation() if (animatingCurrent) { await this.playBlockSwapAnimation() } } + + getHandHeld3d () { + const type = this.lastHeldItem?.type ?? 'hand' + const { debug } = this + + let scale = type === 'item' ? 0.68 : 0.45 + + const position = { + x: debug.x ?? 0.4, + y: debug.y ?? -0.7, + z: -0.45 + } + + if (type === 'item') { + position.x = -0.05 + // position.y -= 3.2 / 10 + // position.z += 1.13 / 10 + } + + if (type === 'hand') { + // position.x = viewer.camera.aspect > 1 ? 0.7 : 1.1 + position.y = -0.8 + scale = 0.8 + } + + const rotations = { + block: { + x: 0, + y: -45 + 90, + z: 0, + yOuter: 0 + }, + // hand: { + // x: 166.7, + // // y: -180, + // y: -165.2, + // // z: -156.3, + // z: -134.2, + // yOuter: -81.1 + // }, + hand: { + x: -32.4, + // y: 25.1 + y: 42.8, + z: -41.3, + yOuter: 0 + }, + // item: { + // x: -174, + // y: 47.3, + // z: -134.2, + // yOuter: -41.2 + // } + item: { + // x: -174, + // y: 47.3, + // z: -134.2, + // yOuter: -41.2 + x: 0, + // y: -90, // todo thats the correct one but we don't make it look too cheap because of no depth + y: -70, + z: window.z ?? 25, + yOuter: 0 + } + } + + return { + rotation: rotations[type], + position, + scale + } + } } diff --git a/prismarine-viewer/viewer/lib/viewer.ts b/prismarine-viewer/viewer/lib/viewer.ts index 73f29fb2d..20f10284e 100644 --- a/prismarine-viewer/viewer/lib/viewer.ts +++ b/prismarine-viewer/viewer/lib/viewer.ts @@ -3,12 +3,14 @@ import * as THREE from 'three' import { Vec3 } from 'vec3' import { generateSpiralMatrix } from 'flying-squid/dist/utils' import worldBlockProvider from 'mc-assets/dist/worldBlockProvider' +import stevePng from 'mc-assets/dist/other-textures/latest/entity/player/wide/steve.png' import { Entities } from './entities' import { Primitives } from './primitives' import { WorldRendererThree } from './worldrendererThree' import { WorldRendererCommon, WorldRendererConfig, defaultWorldRendererConfig } from './worldrendererCommon' import { getThreeBlockModelGroup, renderBlockThree, setBlockPosition } from './mesher/standaloneRenderer' import { addNewStat } from './ui/newStats' +import { getMyHand } from './hand' export class Viewer { scene: THREE.Scene @@ -88,6 +90,7 @@ export class Viewer { return new THREE.TextureLoader().loadAsync(this.world.itemsAtlasParser!.latestImage) }).then((texture) => { this.entities.itemsTexture = texture + this.world.renderUpdateEmitter.emit('itemsTextureDownloaded') }) } @@ -114,18 +117,12 @@ export class Viewer { void set() } - demoModel () { + async demoModel () { //@ts-expect-error const pos = cursorBlockRel(0, 1, 0).position const blockProvider = worldBlockProvider(this.world.blockstatesModels, this.world.blocksAtlases, 'latest') - const models = blockProvider.getAllResolvedModels0_1({ - name: 'item_frame', - properties: { - // map: false - } - }, true) - const { material } = this.world - const mesh = getThreeBlockModelGroup(material, models, undefined, 'plains', loadedData) + + const mesh = await getMyHand() // mesh.rotation.y = THREE.MathUtils.degToRad(90) setBlockPosition(mesh, pos) const helper = new THREE.BoxHelper(mesh, 0xff_ff_00) diff --git a/prismarine-viewer/viewer/lib/worldDataEmitter.ts b/prismarine-viewer/viewer/lib/worldDataEmitter.ts index 0223b855b..371ef8f58 100644 --- a/prismarine-viewer/viewer/lib/worldDataEmitter.ts +++ b/prismarine-viewer/viewer/lib/worldDataEmitter.ts @@ -107,23 +107,33 @@ export class WorldDataEmitter extends EventEmitter { time: () => { this.emitter.emit('time', bot.time.timeOfDay) }, - heldItemChanged: () => { - if (!this.handDisplay) { - viewer.world.onHandItemSwitch(undefined) - return - } - const newItem = bot.heldItem - if (!newItem) { - viewer.world.onHandItemSwitch(undefined) - return - } - const block = loadedData.blocksByName[newItem.name] - // todo clean types - const blockProperties = block ? new window.PrismarineBlock(block.id, 'void', newItem.metadata).getProperties() : {} - viewer.world.onHandItemSwitch({ name: newItem.name, properties: blockProperties }) + heldItemChanged () { + handChanged(false) }, } satisfies Partial - this.eventListeners.heldItemChanged() + const handChanged = (isLeftHand: boolean) => { + if (!this.handDisplay) { + viewer.world.onHandItemSwitch(undefined, isLeftHand) + return + } + const newItem = isLeftHand ? bot.inventory.slots[45] : bot.heldItem + if (!newItem) { + viewer.world.onHandItemSwitch(undefined, isLeftHand) + return + } + const block = loadedData.blocksByName[newItem.name] + // todo clean types + const blockProperties = block ? new window.PrismarineBlock(block.id, 'void', newItem.metadata).getProperties() : {} + // todo item props + viewer.world.onHandItemSwitch({ name: newItem.name, properties: blockProperties, id: newItem.type, type: block ? 'block' : 'item', }, isLeftHand) + } + bot.inventory.on('updateSlot', (index) => { + if (index === 45) { + handChanged(true) + } + }) + handChanged(false) + handChanged(true) bot._client.on('update_light', ({ chunkX, chunkZ }) => { diff --git a/prismarine-viewer/viewer/lib/worldrendererCommon.ts b/prismarine-viewer/viewer/lib/worldrendererCommon.ts index 71c094b4f..a7c3972c1 100644 --- a/prismarine-viewer/viewer/lib/worldrendererCommon.ts +++ b/prismarine-viewer/viewer/lib/worldrendererCommon.ts @@ -217,8 +217,8 @@ export abstract class WorldRendererCommon } } - onHandItemSwitch (item: HandItemBlock | undefined): void { } - changeHandSwingingState (isAnimationPlaying: boolean): void { } + onHandItemSwitch (item: HandItemBlock | undefined, isLeftHand: boolean): void { } + changeHandSwingingState (isAnimationPlaying: boolean, isLeftHand: boolean): void { } abstract handleWorkerMessage (data: WorkerReceive): void diff --git a/prismarine-viewer/viewer/lib/worldrendererThree.ts b/prismarine-viewer/viewer/lib/worldrendererThree.ts index 8671bfaf0..de1116908 100644 --- a/prismarine-viewer/viewer/lib/worldrendererThree.ts +++ b/prismarine-viewer/viewer/lib/worldrendererThree.ts @@ -23,6 +23,7 @@ export class WorldRendererThree extends WorldRendererCommon { starField: StarField cameraSectionPos: Vec3 = new Vec3(0, 0, 0) holdingBlock: HoldingBlock + holdingBlockLeft: HoldingBlock rendererDevice = '...' get tilesRendered () { @@ -37,31 +38,44 @@ export class WorldRendererThree extends WorldRendererCommon { super(config) this.rendererDevice = String(WorldRendererThree.getRendererInfo(this.renderer)) this.starField = new StarField(scene) - this.holdingBlock = new HoldingBlock(this.scene) + this.holdingBlock = new HoldingBlock() + this.holdingBlockLeft = new HoldingBlock() + this.holdingBlockLeft.rightSide = false - this.renderUpdateEmitter.on('textureDownloaded', () => { + this.renderUpdateEmitter.on('itemsTextureDownloaded', () => { if (this.holdingBlock.toBeRenderedItem) { this.onHandItemSwitch(this.holdingBlock.toBeRenderedItem) this.holdingBlock.toBeRenderedItem = undefined } + if (this.holdingBlockLeft.toBeRenderedItem) { + this.onHandItemSwitch(this.holdingBlock.toBeRenderedItem, true) + this.holdingBlockLeft.toBeRenderedItem = undefined + } }) this.addDebugOverlay() } - onHandItemSwitch (item: HandItemBlock | undefined) { + onHandItemSwitch (item: HandItemBlock | undefined, isLeft = false) { + if (!isLeft) { + item ??= { + type: 'hand', + } + } + const holdingBlock = isLeft ? this.holdingBlockLeft : this.holdingBlock if (!this.currentTextureImage) { - this.holdingBlock.toBeRenderedItem = item + holdingBlock.toBeRenderedItem = item return } - void this.holdingBlock.initHandObject(this.material, this.blockstatesModels, this.blocksAtlases, item) + void holdingBlock.initHandObject(this.material, this.blockstatesModels, this.blocksAtlases, item) } - changeHandSwingingState (isAnimationPlaying: boolean) { + changeHandSwingingState (isAnimationPlaying: boolean, isLeft = false) { + const holdingBlock = isLeft ? this.holdingBlockLeft : this.holdingBlock if (isAnimationPlaying) { - this.holdingBlock.startSwing() + holdingBlock.startSwing() } else { - void this.holdingBlock.stopSwing() + void holdingBlock.stopSwing() } } @@ -223,10 +237,11 @@ export class WorldRendererThree extends WorldRendererCommon { render () { tweenJs.update() - this.holdingBlock.update(this.camera) // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style const cam = this.camera instanceof THREE.Group ? this.camera.children.find(child => child instanceof THREE.PerspectiveCamera) as THREE.PerspectiveCamera : this.camera this.renderer.render(this.scene, cam) + this.holdingBlock.render(this.camera, this.renderer, viewer.ambientLight, viewer.directionalLight) + this.holdingBlockLeft.render(this.camera, this.renderer, viewer.ambientLight, viewer.directionalLight) } renderSign (position: Vec3, rotation: number, isWall: boolean, isHanging: boolean, blockEntity) { diff --git a/src/devtools.ts b/src/devtools.ts index 16337c1d7..e87b6d333 100644 --- a/src/devtools.ts +++ b/src/devtools.ts @@ -46,7 +46,7 @@ customEvents.on('gameLoaded', () => { window.inspectPacket = (packetName, full = false) => { const listener = (...args) => console.log('packet', packetName, full ? args : args[0]) const attach = () => { - bot?._client.on(packetName, listener) + bot?._client.prependListener(packetName, listener) } attach() customEvents.on('mineflayerBotCreated', attach) diff --git a/src/flyingSquidUtils.ts b/src/flyingSquidUtils.ts index 78254cf23..012830d9f 100644 --- a/src/flyingSquidUtils.ts +++ b/src/flyingSquidUtils.ts @@ -27,8 +27,10 @@ export async function savePlayers (autoSave: boolean) { export const saveServer = async (autoSave = true) => { if (!localServer || fsState.isReadonly) return // todo + console.time('save server') const worlds = [(localServer as any).overworld] as Array await Promise.all([localServer.writeLevelDat(), savePlayers(autoSave), ...worlds.map(async world => world.saveNow())]) + console.timeEnd('save server') } export const disconnect = async () => { if (localServer) { diff --git a/src/optionsStorage.ts b/src/optionsStorage.ts index 618d99e88..a242107c6 100644 --- a/src/optionsStorage.ts +++ b/src/optionsStorage.ts @@ -80,6 +80,7 @@ const defaultOptions = { autoParkour: false, vrSupport: true, // doesn't directly affect the VR mode, should only disable the button which is annoying to android users renderDebug: (isDev ? 'advanced' : 'basic') as 'none' | 'advanced' | 'basic', + autoVersionSelect: '1.20.4', // advanced bot options autoRespawn: false, diff --git a/src/topRightStats.ts b/src/topRightStats.ts index 4bcd7264e..fd717ef48 100644 --- a/src/topRightStats.ts +++ b/src/topRightStats.ts @@ -88,13 +88,40 @@ export const statsEnd = () => { // for advanced debugging, use with watch expression +window.statsPerSecAvg = {} +let currentStatsPerSec = {} as Record +const waitingStatsPerSec = {} +window.markStart = (label) => { + waitingStatsPerSec[label] ??= [] + waitingStatsPerSec[label][0] = performance.now() +} +window.markEnd = (label) => { + if (!waitingStatsPerSec[label]?.[0]) return + currentStatsPerSec[label] ??= [] + currentStatsPerSec[label].push(performance.now() - waitingStatsPerSec[label][0]) + delete waitingStatsPerSec[label] +} +const updateStatsPerSecAvg = () => { + window.statsPerSecAvg = Object.fromEntries(Object.entries(currentStatsPerSec).map(([key, value]) => { + return [key, { + avg: value.reduce((a, b) => a + b, 0) / value.length, + count: value.length + }] + })) + currentStatsPerSec = {} +} + + window.statsPerSec = {} -let statsPerSec = {} +let statsPerSecCurrent = {} window.addStatPerSec = (name) => { - statsPerSec[name] ??= 0 - statsPerSec[name]++ + statsPerSecCurrent[name] ??= 0 + statsPerSecCurrent[name]++ } +window.statsPerSecCurrent = statsPerSecCurrent setInterval(() => { - window.statsPerSec = statsPerSec - statsPerSec = {} + window.statsPerSec = statsPerSecCurrent + statsPerSecCurrent = {} + window.statsPerSecCurrent = statsPerSecCurrent + updateStatsPerSecAvg() }, 1000) diff --git a/src/worldInteractions.ts b/src/worldInteractions.ts index d6674ce31..1134d7f89 100644 --- a/src/worldInteractions.ts +++ b/src/worldInteractions.ts @@ -257,8 +257,8 @@ class WorldInteraction { bot.lookAt = oldLookAt }).catch(console.warn) } - viewer.world.changeHandSwingingState(true) - viewer.world.changeHandSwingingState(false) + viewer.world.changeHandSwingingState(true, false) + viewer.world.changeHandSwingingState(false, false) } else if (!stop) { const offhand = activate ? false : activatableItems(bot.inventory.slots[45]?.name ?? '') bot.activateItem(offhand) // todo offhand @@ -317,14 +317,14 @@ class WorldInteraction { }) customEvents.emit('digStart') this.lastDigged = Date.now() - viewer.world.changeHandSwingingState(true) + viewer.world.changeHandSwingingState(true, false) } else if (performance.now() - this.lastSwing > 200) { bot.swingArm('right') this.lastSwing = performance.now() } } if (!this.buttons[0] && this.lastButtons[0]) { - viewer.world.changeHandSwingingState(false) + viewer.world.changeHandSwingingState(false, false) } this.prevOnGround = onGround