From b6349093fa21213ca6f52840b6d8a10b333f47f1 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 27 Aug 2024 03:34:49 +0300 Subject: [PATCH 001/834] wip --- prismarine-viewer/viewer/lib/worldrendererThree.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/prismarine-viewer/viewer/lib/worldrendererThree.ts b/prismarine-viewer/viewer/lib/worldrendererThree.ts index 6c14e243..98c0a5f8 100644 --- a/prismarine-viewer/viewer/lib/worldrendererThree.ts +++ b/prismarine-viewer/viewer/lib/worldrendererThree.ts @@ -28,10 +28,19 @@ export class WorldRendererThree extends WorldRendererCommon { constructor (public scene: THREE.Scene, public renderer: THREE.WebGLRenderer, public config: WorldRendererConfig) { super(config) this.starField = new StarField(scene) - // this.initCameraGroup() + this.initCameraGroup() // this.initHandObject() } + initCameraGroup () { + this.cameraGroup = new THREE.Group() + this.cameraGroup.onBeforeRender = (renderer, scene, camera) => { + this.cameraGroup.position.copy(camera.position.clone().add(new THREE.Vector3(0, 0, 1))) + this.cameraGroup.rotation.copy(camera.rotation) + } + this.scene.add(this.cameraGroup) + } + timeUpdated (newTime: number): void { const nightTime = 13_500 const morningStart = 23_000 From 8132dea5bbe8a48ce3809f7592bdef828ddda516 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 31 Aug 2024 18:02:10 +0300 Subject: [PATCH 002/834] display is almost done --- .../viewer/lib/worldrendererThree.ts | 51 ++++++++++++++++--- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/prismarine-viewer/viewer/lib/worldrendererThree.ts b/prismarine-viewer/viewer/lib/worldrendererThree.ts index 98c0a5f8..bce5c448 100644 --- a/prismarine-viewer/viewer/lib/worldrendererThree.ts +++ b/prismarine-viewer/viewer/lib/worldrendererThree.ts @@ -19,7 +19,7 @@ export class WorldRendererThree extends WorldRendererCommon { signsCache = new Map() starField: StarField cameraSectionPos: Vec3 = new Vec3(0, 0, 0) - cameraGroup = new THREE.Group() + cameraGroup = new THREE.Mesh() get tilesRendered () { return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj as any).tilesCount, 0) @@ -29,18 +29,54 @@ export class WorldRendererThree extends WorldRendererCommon { super(config) this.starField = new StarField(scene) this.initCameraGroup() - // this.initHandObject() + this.renderUpdateEmitter.on('textureDownloaded', () => { + this.initHandObject() + }) } initCameraGroup () { - this.cameraGroup = new THREE.Group() - this.cameraGroup.onBeforeRender = (renderer, scene, camera) => { - this.cameraGroup.position.copy(camera.position.clone().add(new THREE.Vector3(0, 0, 1))) - this.cameraGroup.rotation.copy(camera.rotation) - } + this.cameraGroup = new THREE.Mesh() this.scene.add(this.cameraGroup) } + updateCameraGroup () { + const { camera } = this + this.cameraGroup.position.copy(camera.position) + this.cameraGroup.rotation.copy(camera.rotation) + // this.cameraGroup.children[0]?.position.set(window.x ?? 0.25, window.y ?? -0.41, window.z ?? -0.5) + this.cameraGroup.children[0]?.position.set(0.25, window.y ?? -0.41, window.z ?? -0.45) + const scale = window.scale ?? 0.2 + this.cameraGroup.children[0]?.scale.set(scale, scale, scale) + // holding block rotation + // this.cameraGroup.children[0]?.rotation.set(-THREE.MathUtils.degToRad(window.X), -THREE.MathUtils.degToRad(rotation), THREE.MathUtils.degToRad(window.Z), 'ZYX') + // if (window.rotated) {} + } + + startAnim () { + const blockRot = this.cameraGroup.children[0]?.rotation + new tweenJs.Tween(blockRot).to({ + z: THREE.MathUtils.degToRad(90), + x: THREE.MathUtils.degToRad(-45) + }, 400) + } + + initHandObject () { + const blockProvider = worldBlockProvider(this.blockstatesModels, this.blocksAtlases, 'latest') + const models = blockProvider.getAllResolvedModels0_1({ + name: 'stone', + properties: { + } + }, true) + const geometry = renderBlockThree(models, undefined, 'plains', loadedData) + const { material } = this + // block material + const mesh = new THREE.Mesh(geometry, material) + mesh.name = 'hand' + const rotation = 45 + this.cameraGroup.add(mesh) + this.cameraGroup.children[0]?.rotation.set(0, -THREE.MathUtils.degToRad(rotation), 0, 'ZYX') + } + timeUpdated (newTime: number): void { const nightTime = 13_500 const morningStart = 23_000 @@ -184,6 +220,7 @@ export class WorldRendererThree extends WorldRendererCommon { tweenJs.update() // 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.updateCameraGroup() this.renderer.render(this.scene, cam) } From e2e20d5075352d2d743de9a9601a427a943178aa Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 31 Aug 2024 23:07:01 +0300 Subject: [PATCH 003/834] it just works for now --- prismarine-viewer/viewer/lib/holdingBlock.ts | 86 +++++++++++++++++++ .../viewer/lib/worldrendererThree.ts | 54 ++---------- 2 files changed, 92 insertions(+), 48 deletions(-) create mode 100644 prismarine-viewer/viewer/lib/holdingBlock.ts diff --git a/prismarine-viewer/viewer/lib/holdingBlock.ts b/prismarine-viewer/viewer/lib/holdingBlock.ts new file mode 100644 index 00000000..4b75082e --- /dev/null +++ b/prismarine-viewer/viewer/lib/holdingBlock.ts @@ -0,0 +1,86 @@ +import * as THREE from 'three' +import * as tweenJs from '@tweenjs/tween.js' +import worldBlockProvider from 'mc-assets/dist/worldBlockProvider' +import { renderBlockThree } from './mesher/standaloneRenderer' + +export default class HoldingBlock { + holdingBlock: THREE.Object3D | null = null + swingAnimation: tweenJs.Group = new tweenJs.Group() + cameraGroup = new THREE.Mesh() + objectOuterGroup = new THREE.Group() + objectInnerGroup = new THREE.Group() + camera: THREE.Group | THREE.PerspectiveCamera + + constructor (public scene: THREE.Scene) { + this.initCameraGroup() + } + + initCameraGroup () { + this.cameraGroup = new THREE.Mesh() + this.scene.add(this.cameraGroup) + } + + updateCameraGroup () { + const { camera } = this + this.cameraGroup.position.copy(camera.position) + this.cameraGroup.rotation.copy(camera.rotation) + } + + startSwing () { + this.stopSwing() + const cube = this.cameraGroup.children[0] + if (cube) { + // const DURATION = 1000 * 0.35 / 2 + const DURATION = 1000 + // new tweenJs.Tween(this.holdingBlock!.position, this.swingAnimation).to({ y: this.holdingBlock!.position.y - this.holdingBlock!.scale.y * 2 }, DURATION).yoyo(true).repeat(Infinity).start() + new tweenJs.Tween(this.objectInnerGroup.rotation, this.swingAnimation).to({ z: THREE.MathUtils.degToRad(90) }, DURATION).yoyo(true).repeat(Infinity).start() + } + } + + stopSwing () { + this.swingAnimation.removeAll() + } + + update (camera: typeof this.camera) { + this.camera = camera + this.swingAnimation.update() + this.updateCameraGroup() + } + + initHandObject (material: THREE.Material, blockstatesModels: any, blocksAtlases: any) { + const blockProvider = worldBlockProvider(blockstatesModels, blocksAtlases, 'latest') + const models = blockProvider.getAllResolvedModels0_1({ + name: 'furnace', + properties: { + } + }, true) + // const geometry = new THREE.BoxGeometry(1, 1, 1) + const geometry = renderBlockThree(models, undefined, 'plains', loadedData) + // block material + const block = new THREE.Mesh(geometry, material) + block.name = 'holdingBlock' + this.holdingBlock = block + this.objectInnerGroup = new THREE.Group() + this.objectInnerGroup.add(block) + this.objectInnerGroup.position.set(-0.5, -1, -0.5) + block.position.set(0.5, 1, 0.5) + + this.objectOuterGroup = new THREE.Group() + this.objectOuterGroup.add(this.objectInnerGroup) + + this.cameraGroup.add(this.objectOuterGroup) + // const rotation = 45 + // this.holdingBlock.rotation.set(0, -THREE.MathUtils.degToRad(rotation), 0, 'ZYX') + + const viewerSize = viewer.renderer.getSize(new THREE.Vector2()) + // const x = window.x ?? 0.25 * viewerSize.width / viewerSize.height + // const x = 0.15 * viewerSize.width / viewerSize.height + const x = 0 * viewerSize.width / viewerSize.height + // 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.objectOuterGroup.position.set(x, -0.41, -0.45) + } +} diff --git a/prismarine-viewer/viewer/lib/worldrendererThree.ts b/prismarine-viewer/viewer/lib/worldrendererThree.ts index bce5c448..a797ea5c 100644 --- a/prismarine-viewer/viewer/lib/worldrendererThree.ts +++ b/prismarine-viewer/viewer/lib/worldrendererThree.ts @@ -9,7 +9,7 @@ import { renderSign } from '../sign-renderer' import { chunkPos, sectionPos } from './simpleUtils' import { WorldRendererCommon, WorldRendererConfig } from './worldrendererCommon' import { disposeObject } from './threeJsUtils' -import { renderBlockThree } from './mesher/standaloneRenderer' +import HoldingBlock from './holdingBlock' export class WorldRendererThree extends WorldRendererCommon { outputFormat = 'threeJs' as const @@ -19,7 +19,7 @@ export class WorldRendererThree extends WorldRendererCommon { signsCache = new Map() starField: StarField cameraSectionPos: Vec3 = new Vec3(0, 0, 0) - cameraGroup = new THREE.Mesh() + holdingBlock: HoldingBlock get tilesRendered () { return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj as any).tilesCount, 0) @@ -28,55 +28,13 @@ export class WorldRendererThree extends WorldRendererCommon { constructor (public scene: THREE.Scene, public renderer: THREE.WebGLRenderer, public config: WorldRendererConfig) { super(config) this.starField = new StarField(scene) - this.initCameraGroup() + this.holdingBlock = new HoldingBlock(this.scene) + this.renderUpdateEmitter.on('textureDownloaded', () => { - this.initHandObject() + this.holdingBlock.initHandObject(this.material, this.blockstatesModels, this.blocksAtlases) }) } - initCameraGroup () { - this.cameraGroup = new THREE.Mesh() - this.scene.add(this.cameraGroup) - } - - updateCameraGroup () { - const { camera } = this - this.cameraGroup.position.copy(camera.position) - this.cameraGroup.rotation.copy(camera.rotation) - // this.cameraGroup.children[0]?.position.set(window.x ?? 0.25, window.y ?? -0.41, window.z ?? -0.5) - this.cameraGroup.children[0]?.position.set(0.25, window.y ?? -0.41, window.z ?? -0.45) - const scale = window.scale ?? 0.2 - this.cameraGroup.children[0]?.scale.set(scale, scale, scale) - // holding block rotation - // this.cameraGroup.children[0]?.rotation.set(-THREE.MathUtils.degToRad(window.X), -THREE.MathUtils.degToRad(rotation), THREE.MathUtils.degToRad(window.Z), 'ZYX') - // if (window.rotated) {} - } - - startAnim () { - const blockRot = this.cameraGroup.children[0]?.rotation - new tweenJs.Tween(blockRot).to({ - z: THREE.MathUtils.degToRad(90), - x: THREE.MathUtils.degToRad(-45) - }, 400) - } - - initHandObject () { - const blockProvider = worldBlockProvider(this.blockstatesModels, this.blocksAtlases, 'latest') - const models = blockProvider.getAllResolvedModels0_1({ - name: 'stone', - properties: { - } - }, true) - const geometry = renderBlockThree(models, undefined, 'plains', loadedData) - const { material } = this - // block material - const mesh = new THREE.Mesh(geometry, material) - mesh.name = 'hand' - const rotation = 45 - this.cameraGroup.add(mesh) - this.cameraGroup.children[0]?.rotation.set(0, -THREE.MathUtils.degToRad(rotation), 0, 'ZYX') - } - timeUpdated (newTime: number): void { const nightTime = 13_500 const morningStart = 23_000 @@ -218,9 +176,9 @@ 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.updateCameraGroup() this.renderer.render(this.scene, cam) } From fd6b2e9a088e2f981fd1af0254559d9fd6a0b3c4 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 1 Sep 2024 00:51:50 +0300 Subject: [PATCH 004/834] everything works for blocks! --- experiments/three.html | 1 + experiments/three.ts | 99 +++++++++ prismarine-viewer/viewer/lib/holdingBlock.ts | 188 ++++++++++++++---- .../viewer/lib/mesher/standaloneRenderer.ts | 16 ++ prismarine-viewer/viewer/lib/viewer.ts | 17 +- .../viewer/lib/worldDataEmitter.ts | 6 + .../viewer/lib/worldrendererCommon.ts | 5 + .../viewer/lib/worldrendererThree.ts | 19 +- 8 files changed, 303 insertions(+), 48 deletions(-) create mode 100644 experiments/three.html create mode 100644 experiments/three.ts diff --git a/experiments/three.html b/experiments/three.html new file mode 100644 index 00000000..8765081b --- /dev/null +++ b/experiments/three.html @@ -0,0 +1 @@ + diff --git a/experiments/three.ts b/experiments/three.ts new file mode 100644 index 00000000..8168ac54 --- /dev/null +++ b/experiments/three.ts @@ -0,0 +1,99 @@ +import * as THREE from 'three' +import * as tweenJs from '@tweenjs/tween.js' +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' +import * as THREE from 'three'; +import Jimp from 'jimp'; +const scene = new THREE.Scene() +const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000) +camera.position.set(0, 0, 5) +const renderer = new THREE.WebGLRenderer() +renderer.setSize(window.innerWidth, window.innerHeight) +document.body.appendChild(renderer.domElement) + +const controls = new OrbitControls(camera, renderer.domElement) + +const geometry = new THREE.BoxGeometry(1, 1, 1) +const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }) +const cube = new THREE.Mesh(geometry, material) +cube.position.set(0.5, 0.5, 0.5); +const group = new THREE.Group() +group.add(cube) +group.position.set(-0.5, -0.5, -0.5); +const outerGroup = new THREE.Group() +outerGroup.add(group) +outerGroup.scale.set(0.2, 0.2, 0.2) +outerGroup.position.set(1, 1, 0) +scene.add(outerGroup) + +// const mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshBasicMaterial({ color: 0x00_00_ff, transparent: true, opacity: 0.5 })) +// mesh.position.set(0.5, 1, 0.5) +// const group = new THREE.Group() +// group.add(mesh) +// group.position.set(-0.5, -1, -0.5) +// const outerGroup = new THREE.Group() +// outerGroup.add(group) +// // outerGroup.position.set(this.camera.position.x, this.camera.position.y, this.camera.position.z) +// scene.add(outerGroup) + + new tweenJs.Tween(group.rotation).to({ z: THREE.MathUtils.degToRad(90) }, 1000).yoyo(true).repeat(Infinity).start() + +const tweenGroup = new tweenJs.Group() +function animate () { + tweenGroup.update() + requestAnimationFrame(animate) +// cube.rotation.x += 0.01 +// cube.rotation.y += 0.01 + renderer.render(scene, camera) +} +animate() + +// let animation + +window.animate = () => { + // new Tween.Tween(group.position).to({ y: group.position.y - 1}, 1000 * 0.35/2).yoyo(true).repeat(1).start() + new tweenJs.Tween(group.rotation, tweenGroup).to({ z: THREE.MathUtils.degToRad(90) }, 1000 * 0.35 / 2).yoyo(true).repeat(Infinity).start().onRepeat(() => { + console.log('done') + }) +} + +window.stop = () => { + tweenGroup.removeAll() +} + + +function createGeometryFromImage(imagePath) { + return new Promise((resolve, reject) => { + const img = new Image(); + img.src = imagePath; + img.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + const context = canvas.getContext('2d'); + context.drawImage(img, 0, 0, img.width, img.height); + const imgData = context.getImageData(0, 0, img.width, img.height); + + const shape = new THREE.Shape(); + for (let y = 0; y < img.height; y++) { + for (let x = 0; x < img.width; x++) { + const index = (y * img.width + x) * 4; + const alpha = imgData.data[index + 3]; + if (alpha !== 0) { + shape.lineTo(x, y); + } + } + } + + const geometry = new THREE.ShapeGeometry(shape); + resolve(geometry); + }; + img.onerror = reject; + }); +} + +// Usage: +const shapeGeomtry = createGeometryFromImage('path/to/image.png').then(geometry => { + const material = new THREE.MeshBasicMaterial({ color: 0xffffff }); + const mesh = new THREE.Mesh(geometry, material); + scene.add(mesh); +}) diff --git a/prismarine-viewer/viewer/lib/holdingBlock.ts b/prismarine-viewer/viewer/lib/holdingBlock.ts index 4b75082e..739a5cea 100644 --- a/prismarine-viewer/viewer/lib/holdingBlock.ts +++ b/prismarine-viewer/viewer/lib/holdingBlock.ts @@ -1,15 +1,29 @@ import * as THREE from 'three' import * as tweenJs from '@tweenjs/tween.js' import worldBlockProvider from 'mc-assets/dist/worldBlockProvider' -import { renderBlockThree } from './mesher/standaloneRenderer' +import { getThreeBlockModelGroup, renderBlockThree, setBlockPosition } from './mesher/standaloneRenderer' + +export type HandItemBlock = { + name + properties +} export default class HoldingBlock { - holdingBlock: THREE.Object3D | null = null - swingAnimation: tweenJs.Group = new tweenJs.Group() + holdingBlock: THREE.Object3D | undefined = undefined + swingAnimation: tweenJs.Group | undefined = undefined + blockSwapAnimation: { + tween: tweenJs.Group + hidden: boolean + } | undefined = undefined cameraGroup = new THREE.Mesh() objectOuterGroup = new THREE.Group() objectInnerGroup = new THREE.Group() camera: THREE.Group | THREE.PerspectiveCamera + stopUpdate = false + lastHeldItem: HandItemBlock | undefined + toBeRenderedItem: HandItemBlock | undefined + isSwinging = false + nextIterStopCallbacks: Array<() => void> | undefined constructor (public scene: THREE.Scene) { this.initCameraGroup() @@ -20,67 +34,165 @@ export default class HoldingBlock { this.scene.add(this.cameraGroup) } - updateCameraGroup () { - const { camera } = this - this.cameraGroup.position.copy(camera.position) - this.cameraGroup.rotation.copy(camera.rotation) - } - startSwing () { - this.stopSwing() + if (this.isSwinging) return + this.swingAnimation = new tweenJs.Group() + this.isSwinging = true const cube = this.cameraGroup.children[0] if (cube) { // const DURATION = 1000 * 0.35 / 2 - const DURATION = 1000 - // new tweenJs.Tween(this.holdingBlock!.position, this.swingAnimation).to({ y: this.holdingBlock!.position.y - this.holdingBlock!.scale.y * 2 }, DURATION).yoyo(true).repeat(Infinity).start() + const DURATION = 1000 * 0.35 / 3 + // const DURATION = 1000 + const initialPos = { + x: this.objectInnerGroup.position.x, + y: this.objectInnerGroup.position.y, + z: this.objectInnerGroup.position.z + } + const initialRot = { + x: this.objectInnerGroup.rotation.x, + y: this.objectInnerGroup.rotation.y, + z: this.objectInnerGroup.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() + let i = 0 + mainAnim.onRepeat(() => { + i++ + if (this.nextIterStopCallbacks && i % 2 === 0) { + for (const callback of this.nextIterStopCallbacks) { + callback() + } + this.nextIterStopCallbacks = undefined + this.isSwinging = false + 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) + } + }) + 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() } } - stopSwing () { - this.swingAnimation.removeAll() + async stopSwing () { + if (!this.isSwinging) return + return new Promise((resolve) => { + this.nextIterStopCallbacks ??= [] + this.nextIterStopCallbacks.push(() => { + resolve() + }) + }) } update (camera: typeof this.camera) { this.camera = camera - this.swingAnimation.update() + this.swingAnimation?.update() + this.blockSwapAnimation?.tween.update() this.updateCameraGroup() } - initHandObject (material: THREE.Material, blockstatesModels: any, blocksAtlases: any) { + // worldTest () { + // const mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshPhongMaterial({ color: 0x00_00_ff, transparent: true, opacity: 0.5 })) + // mesh.position.set(0.5, 0.5, 0.5) + // const group = new THREE.Group() + // group.add(mesh) + // group.position.set(-0.5, -0.5, -0.5) + // const outerGroup = new THREE.Group() + // outerGroup.add(group) + // outerGroup.position.set(this.camera.position.x, this.camera.position.y, this.camera.position.z) + // this.scene.add(outerGroup) + + // new tweenJs.Tween(group.rotation).to({ z: THREE.MathUtils.degToRad(90) }, 1000).yoyo(true).repeat(Infinity).start() + // } + + async playBlockSwapAnimation () { + // if (this.blockSwapAnimation) return + this.blockSwapAnimation ??= { + tween: new tweenJs.Group(), + hidden: false + } + const DURATION = 1000 * 0.35 / 2 + const tween = new tweenJs.Tween(this.objectInnerGroup.position, this.blockSwapAnimation.tween).to({ + y: this.objectInnerGroup.position.y + (this.objectInnerGroup.scale.y * 1.5 * (this.blockSwapAnimation.hidden ? 1 : -1)) + }, DURATION).start() + return new Promise((resolve) => { + tween.onComplete(() => { + if (this.blockSwapAnimation!.hidden) { + this.blockSwapAnimation = undefined + } else { + this.blockSwapAnimation!.hidden = !this.blockSwapAnimation!.hidden + } + resolve() + }) + }) + } + + isDifferentItem (block: HandItemBlock | undefined) { + return this.lastHeldItem && (this.lastHeldItem.name !== block?.name || JSON.stringify(this.lastHeldItem.properties) !== JSON.stringify(block?.properties ?? '{}')) + } + + updateCameraGroup () { + if (this.stopUpdate) return + const { camera } = this + 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) + } + + async initHandObject (material: THREE.Material, blockstatesModels: any, blocksAtlases: any, block?: HandItemBlock) { + let animatingCurrent = false + if (!this.swingAnimation && !this.blockSwapAnimation && this.isDifferentItem(block)) { + console.log('play swap') + animatingCurrent = true + await this.playBlockSwapAnimation() + } + this.lastHeldItem = block + if (!block) { + this.holdingBlock?.removeFromParent() + this.holdingBlock = undefined + this.swingAnimation = undefined + this.blockSwapAnimation = undefined + return + } const blockProvider = worldBlockProvider(blockstatesModels, blocksAtlases, 'latest') - const models = blockProvider.getAllResolvedModels0_1({ - name: 'furnace', - properties: { - } - }, true) - // const geometry = new THREE.BoxGeometry(1, 1, 1) - const geometry = renderBlockThree(models, undefined, 'plains', loadedData) - // block material - const block = new THREE.Mesh(geometry, material) - block.name = 'holdingBlock' - this.holdingBlock = block + const models = blockProvider.getAllResolvedModels0_1(block, true) + const blockInner = getThreeBlockModelGroup(material, models, undefined, 'plains', loadedData) + blockInner.name = 'holdingBlock' + const blockOuterGroup = new THREE.Group() + blockOuterGroup.add(blockInner) + this.holdingBlock = blockInner this.objectInnerGroup = new THREE.Group() - this.objectInnerGroup.add(block) - this.objectInnerGroup.position.set(-0.5, -1, -0.5) - block.position.set(0.5, 1, 0.5) + this.objectInnerGroup.add(blockOuterGroup) + this.objectInnerGroup.position.set(-0.5, -0.5, -0.5) + // todo cleanup + if (animatingCurrent) { + this.objectInnerGroup.position.y -= this.objectInnerGroup.scale.y * 1.5 + } + Object.assign(blockOuterGroup.position, { x: 0.5, y: 0.5, z: 0.5 }) this.objectOuterGroup = new THREE.Group() this.objectOuterGroup.add(this.objectInnerGroup) this.cameraGroup.add(this.objectOuterGroup) - // const rotation = 45 - // this.holdingBlock.rotation.set(0, -THREE.MathUtils.degToRad(rotation), 0, 'ZYX') + const rotation = -45 + -90 + this.holdingBlock.rotation.set(0, THREE.MathUtils.degToRad(rotation), 0, 'ZYX') - const viewerSize = viewer.renderer.getSize(new THREE.Vector2()) - // const x = window.x ?? 0.25 * viewerSize.width / viewerSize.height - // const x = 0.15 * viewerSize.width / viewerSize.height - const x = 0 * viewerSize.width / viewerSize.height // 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.objectOuterGroup.position.set(x, -0.41, -0.45) + // this.objectOuterGroup.position.set(x, 0, -0.45) + + if (animatingCurrent) { + await this.playBlockSwapAnimation() + } } } diff --git a/prismarine-viewer/viewer/lib/mesher/standaloneRenderer.ts b/prismarine-viewer/viewer/lib/mesher/standaloneRenderer.ts index 2dc2f599..43369cc2 100644 --- a/prismarine-viewer/viewer/lib/mesher/standaloneRenderer.ts +++ b/prismarine-viewer/viewer/lib/mesher/standaloneRenderer.ts @@ -272,3 +272,19 @@ export const renderBlockThree = (...args: Parameters) => { + const geometry = renderBlockThree(...args) + const mesh = new THREE.Mesh(geometry, material) + mesh.position.set(-0.5, -0.5, -0.5) + const group = new THREE.Group() + group.add(mesh) + group.rotation.set(0, -THREE.MathUtils.degToRad(90), 0, 'ZYX') + globalThis.mesh = group + return group + // return new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshPhongMaterial({ color: 0x00_00_ff, transparent: true, opacity: 0.5 })) +} + +export const setBlockPosition = (object: THREE.Object3D, position: { x: number, y: number, z: number }) => { + object.position.set(position.x + 0.5, position.y + 0.5, position.z + 0.5) +} diff --git a/prismarine-viewer/viewer/lib/viewer.ts b/prismarine-viewer/viewer/lib/viewer.ts index 7893bccc..5f6590f7 100644 --- a/prismarine-viewer/viewer/lib/viewer.ts +++ b/prismarine-viewer/viewer/lib/viewer.ts @@ -7,7 +7,7 @@ import { Entities } from './entities' import { Primitives } from './primitives' import { WorldRendererThree } from './worldrendererThree' import { WorldRendererCommon, WorldRendererConfig, defaultWorldRendererConfig } from './worldrendererCommon' -import { renderBlockThree } from './mesher/standaloneRenderer' +import { getThreeBlockModelGroup, renderBlockThree, setBlockPosition } from './mesher/standaloneRenderer' export class Viewer { scene: THREE.Scene @@ -101,18 +101,19 @@ export class Viewer { } 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', + name: 'furnace', properties: { - map: false + // map: false } - }) - const geometry = renderBlockThree(models, undefined, 'plains', loadedData) + }, true) const { material } = this.world - // block material - const mesh = new THREE.Mesh(geometry, material) - mesh.position.set(this.camera.position.x, this.camera.position.y, this.camera.position.z) + const mesh = getThreeBlockModelGroup(material, models, undefined, 'plains', loadedData) + // mesh.rotation.y = THREE.MathUtils.degToRad(90) + setBlockPosition(mesh, pos) const helper = new THREE.BoxHelper(mesh, 0xff_ff_00) mesh.add(helper) this.scene.add(mesh) diff --git a/prismarine-viewer/viewer/lib/worldDataEmitter.ts b/prismarine-viewer/viewer/lib/worldDataEmitter.ts index ea956b81..d42d57bd 100644 --- a/prismarine-viewer/viewer/lib/worldDataEmitter.ts +++ b/prismarine-viewer/viewer/lib/worldDataEmitter.ts @@ -79,7 +79,13 @@ export class WorldDataEmitter extends EventEmitter { time: () => { this.emitter.emit('time', bot.time.timeOfDay) }, + heldItemChanged (newItem) { + // todo + viewer.world.onHandItemSwitch(newItem ? { name: newItem.name, properties: {} } : undefined) + }, } satisfies Partial + this.eventListeners[bot.username].heldItemChanged(bot.heldItem) + bot._client.on('update_light', ({ chunkX, chunkZ }) => { const chunkPos = new Vec3(chunkX * 16, 0, chunkZ * 16) diff --git a/prismarine-viewer/viewer/lib/worldrendererCommon.ts b/prismarine-viewer/viewer/lib/worldrendererCommon.ts index 204ad65f..901b7f17 100644 --- a/prismarine-viewer/viewer/lib/worldrendererCommon.ts +++ b/prismarine-viewer/viewer/lib/worldrendererCommon.ts @@ -16,6 +16,7 @@ import { toMajorVersion } from '../../../src/utils' import { buildCleanupDecorator } from './cleanupDecorator' import { defaultMesherConfig } from './mesher/shared' import { chunkPos } from './simpleUtils' +import { HandItemBlock } from './holdingBlock' function mod (x, n) { return ((x % n) + n) % n @@ -37,6 +38,7 @@ type CustomTexturesData = { export abstract class WorldRendererCommon { worldConfig = { minY: 0, worldHeight: 256 } + // todo need to cleanup material = new THREE.MeshLambertMaterial({ vertexColors: true, transparent: true, alphaTest: 0.1 }) @worldCleanup() @@ -59,6 +61,7 @@ export abstract class WorldRendererCommon textureDownloaded (): void }> customTexturesDataUrl = undefined as string | undefined + @worldCleanup() currentTextureImage = undefined as any workers: any[] = [] viewerPosition?: Vec3 @@ -157,6 +160,8 @@ export abstract class WorldRendererCommon } } + onHandItemSwitch (item: HandItemBlock | undefined): void { } + abstract handleWorkerMessage (data: WorkerReceive): void abstract updateCamera (pos: Vec3 | null, yaw: number, pitch: number): void diff --git a/prismarine-viewer/viewer/lib/worldrendererThree.ts b/prismarine-viewer/viewer/lib/worldrendererThree.ts index a797ea5c..e1b7aa7d 100644 --- a/prismarine-viewer/viewer/lib/worldrendererThree.ts +++ b/prismarine-viewer/viewer/lib/worldrendererThree.ts @@ -9,7 +9,7 @@ import { renderSign } from '../sign-renderer' import { chunkPos, sectionPos } from './simpleUtils' import { WorldRendererCommon, WorldRendererConfig } from './worldrendererCommon' import { disposeObject } from './threeJsUtils' -import HoldingBlock from './holdingBlock' +import HoldingBlock, { HandItemBlock } from './holdingBlock' export class WorldRendererThree extends WorldRendererCommon { outputFormat = 'threeJs' as const @@ -29,12 +29,27 @@ export class WorldRendererThree extends WorldRendererCommon { super(config) this.starField = new StarField(scene) this.holdingBlock = new HoldingBlock(this.scene) + this.onHandItemSwitch({ + name: 'furnace', + properties: {} + }) this.renderUpdateEmitter.on('textureDownloaded', () => { - this.holdingBlock.initHandObject(this.material, this.blockstatesModels, this.blocksAtlases) + if (this.holdingBlock.toBeRenderedItem) { + this.onHandItemSwitch(this.holdingBlock.toBeRenderedItem) + this.holdingBlock.toBeRenderedItem = undefined + } }) } + onHandItemSwitch (item: HandItemBlock | undefined) { + if (!this.currentTextureImage) { + this.holdingBlock.toBeRenderedItem = item + return + } + void this.holdingBlock.initHandObject(this.material, this.blockstatesModels, this.blocksAtlases, item) + } + timeUpdated (newTime: number): void { const nightTime = 13_500 const morningStart = 23_000 From d4cd8c37dec40229214d926986f0fc6dfbbcf0f4 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 1 Sep 2024 02:13:16 +0300 Subject: [PATCH 005/834] finish integration! +setting --- experiments/three.ts | 8 ++++-- prismarine-viewer/viewer/lib/holdingBlock.ts | 4 ++- .../viewer/lib/worldDataEmitter.ts | 28 +++++++++++++------ .../viewer/lib/worldrendererCommon.ts | 1 + .../viewer/lib/worldrendererThree.ts | 8 ++++++ src/optionsGuiScheme.tsx | 1 + src/optionsStorage.ts | 1 + src/watchOptions.ts | 2 +- src/worldInteractions.ts | 6 ++++ 9 files changed, 46 insertions(+), 13 deletions(-) diff --git a/experiments/three.ts b/experiments/three.ts index 8168ac54..7a629a13 100644 --- a/experiments/three.ts +++ b/experiments/three.ts @@ -3,6 +3,7 @@ import * as tweenJs from '@tweenjs/tween.js' import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' import * as THREE from 'three'; import Jimp from 'jimp'; + const scene = new THREE.Scene() const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000) camera.position.set(0, 0, 5) @@ -61,10 +62,11 @@ window.stop = () => { } -function createGeometryFromImage(imagePath) { +function createGeometryFromImage() { return new Promise((resolve, reject) => { const img = new Image(); - img.src = imagePath; + img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAABEElEQVQ4jWNkIAPw2Zv9J0cfXPOSvx/+L/n74T+HqsJ/JlI1T9u3i6H91B7ybdY+vgZuO1majV+fppFmPnuz/+ihy2dv9t/49Wm8mlECkV1FHh5FfPZm/1XXTGX4cechA4eKPMNVq1CGH7cfMBJ0rlxX+X8OVYX/xq9P/5frKifoZ0Z0AwS8HRkYGBgYvt+8xyDXUUbQZgwJPnuz/+wq8gw/7zxk+PXsFUFno0h6mon+l5fgZFhwnYmBTUqMgYGBgaAhLMiaHQyFGOZvf8Lw49FXRgYGhv8MDAwwg/7jMoQFFury/C8Y5m9/wnADohnZVryJhoWBARJ9Cw69gtmMAgiFAcuvZ68Yfj17hU8NXgAATdKfkzbQhBEAAAAASUVORK5CYII=' + console.log('img.complete', img.complete) img.onload = () => { const canvas = document.createElement('canvas'); canvas.width = img.width; @@ -92,7 +94,7 @@ function createGeometryFromImage(imagePath) { } // Usage: -const shapeGeomtry = createGeometryFromImage('path/to/image.png').then(geometry => { +const shapeGeomtry = createGeometryFromImage().then(geometry => { const material = new THREE.MeshBasicMaterial({ color: 0xffffff }); const mesh = new THREE.Mesh(geometry, material); scene.add(mesh); diff --git a/prismarine-viewer/viewer/lib/holdingBlock.ts b/prismarine-viewer/viewer/lib/holdingBlock.ts index 739a5cea..25dd879f 100644 --- a/prismarine-viewer/viewer/lib/holdingBlock.ts +++ b/prismarine-viewer/viewer/lib/holdingBlock.ts @@ -35,6 +35,7 @@ export default class HoldingBlock { } startSwing () { + this.nextIterStopCallbacks = undefined // forget about cancelling if (this.isSwinging) return this.swingAnimation = new tweenJs.Group() this.isSwinging = true @@ -79,7 +80,8 @@ export default class HoldingBlock { async stopSwing () { if (!this.isSwinging) return - return new Promise((resolve) => { + // might never resolve! + /* return */void new Promise((resolve) => { this.nextIterStopCallbacks ??= [] this.nextIterStopCallbacks.push(() => { resolve() diff --git a/prismarine-viewer/viewer/lib/worldDataEmitter.ts b/prismarine-viewer/viewer/lib/worldDataEmitter.ts index d42d57bd..a4a45fef 100644 --- a/prismarine-viewer/viewer/lib/worldDataEmitter.ts +++ b/prismarine-viewer/viewer/lib/worldDataEmitter.ts @@ -20,6 +20,14 @@ export class WorldDataEmitter extends EventEmitter { private eventListeners: Record = {} private readonly emitter: WorldDataEmitter keepChunksDistance = 0 + _handDisplay = false + get handDisplay () { + return this._handDisplay + } + set handDisplay (newVal) { + this._handDisplay = newVal + this.eventListeners.heldItemChanged?.() + } constructor (public world: typeof __type_bot['world'], public viewDistance: number, position: Vec3 = new Vec3(0, 0, 0)) { super() @@ -55,7 +63,7 @@ export class WorldDataEmitter extends EventEmitter { }) } - this.eventListeners[bot.username] = { + this.eventListeners = { // 'move': botPosition, entitySpawn (e: any) { emitEntity(e) @@ -70,7 +78,7 @@ export class WorldDataEmitter extends EventEmitter { this.emitter.emit('entity', { id: e.id, delete: true }) }, chunkColumnLoad: (pos: Vec3) => { - this.loadChunk(pos) + void this.loadChunk(pos) }, blockUpdate: (oldBlock: any, newBlock: any) => { const stateId = newBlock.stateId ?? ((newBlock.type << 4) | newBlock.metadata) @@ -79,12 +87,17 @@ export class WorldDataEmitter extends EventEmitter { time: () => { this.emitter.emit('time', bot.time.timeOfDay) }, - heldItemChanged (newItem) { - // todo + heldItemChanged: () => { + if (!this.handDisplay) { + viewer.world.onHandItemSwitch(undefined) + return + } + // todo properties + const newItem = bot.heldItem viewer.world.onHandItemSwitch(newItem ? { name: newItem.name, properties: {} } : undefined) }, } satisfies Partial - this.eventListeners[bot.username].heldItemChanged(bot.heldItem) + this.eventListeners.heldItemChanged() bot._client.on('update_light', ({ chunkX, chunkZ }) => { @@ -108,7 +121,7 @@ export class WorldDataEmitter extends EventEmitter { this.emitter.emit('listening') } - for (const [evt, listener] of Object.entries(this.eventListeners[bot.username])) { + for (const [evt, listener] of Object.entries(this.eventListeners)) { bot.on(evt as any, listener) } @@ -119,10 +132,9 @@ export class WorldDataEmitter extends EventEmitter { } removeListenersFromBot (bot: import('mineflayer').Bot) { - for (const [evt, listener] of Object.entries(this.eventListeners[bot.username])) { + for (const [evt, listener] of Object.entries(this.eventListeners)) { bot.removeListener(evt as any, listener) } - delete this.eventListeners[bot.username] } async init (pos: Vec3) { diff --git a/prismarine-viewer/viewer/lib/worldrendererCommon.ts b/prismarine-viewer/viewer/lib/worldrendererCommon.ts index 901b7f17..836a5592 100644 --- a/prismarine-viewer/viewer/lib/worldrendererCommon.ts +++ b/prismarine-viewer/viewer/lib/worldrendererCommon.ts @@ -161,6 +161,7 @@ export abstract class WorldRendererCommon } onHandItemSwitch (item: HandItemBlock | undefined): void { } + changeHandSwingingState (isAnimationPlaying: boolean): void { } abstract handleWorkerMessage (data: WorkerReceive): void diff --git a/prismarine-viewer/viewer/lib/worldrendererThree.ts b/prismarine-viewer/viewer/lib/worldrendererThree.ts index e1b7aa7d..b1644ebd 100644 --- a/prismarine-viewer/viewer/lib/worldrendererThree.ts +++ b/prismarine-viewer/viewer/lib/worldrendererThree.ts @@ -50,6 +50,14 @@ export class WorldRendererThree extends WorldRendererCommon { void this.holdingBlock.initHandObject(this.material, this.blockstatesModels, this.blocksAtlases, item) } + changeHandSwingingState (isAnimationPlaying: boolean) { + if (isAnimationPlaying) { + this.holdingBlock.startSwing() + } else { + void this.holdingBlock.stopSwing() + } + } + timeUpdated (newTime: number): void { const nightTime = 13_500 const morningStart = 23_000 diff --git a/src/optionsGuiScheme.tsx b/src/optionsGuiScheme.tsx index 644ec66c..2082acdb 100644 --- a/src/optionsGuiScheme.tsx +++ b/src/optionsGuiScheme.tsx @@ -88,6 +88,7 @@ export const guiOptionsScheme: { unit: '', tooltip: 'Additional distance to keep the chunks loading before unloading them by marking them as too far', }, + handDisplay: {}, }, ], main: [ diff --git a/src/optionsStorage.ts b/src/optionsStorage.ts index cac8c9f8..b7bf4abe 100644 --- a/src/optionsStorage.ts +++ b/src/optionsStorage.ts @@ -47,6 +47,7 @@ const defaultOptions = { enabledResourcepack: null as string | null, useVersionsTextures: 'latest', serverResourcePacks: 'prompt' as 'prompt' | 'always' | 'never', + handDisplay: false, // antiAliasing: false, diff --git a/src/watchOptions.ts b/src/watchOptions.ts index 9deb46a5..d26d2b90 100644 --- a/src/watchOptions.ts +++ b/src/watchOptions.ts @@ -66,11 +66,11 @@ export const watchOptionsAfterViewerInit = () => { let viewWatched = false export const watchOptionsAfterWorldViewInit = () => { - worldView!.keepChunksDistance = options.keepChunksDistance if (viewWatched) return viewWatched = true watchValue(options, o => { if (!worldView) return worldView.keepChunksDistance = o.keepChunksDistance + worldView.handDisplay = o.handDisplay }) } diff --git a/src/worldInteractions.ts b/src/worldInteractions.ts index 7a9aa508..f178a785 100644 --- a/src/worldInteractions.ts +++ b/src/worldInteractions.ts @@ -293,6 +293,8 @@ class WorldInteraction { bot.lookAt = oldLookAt }).catch(console.warn) } + viewer.world.changeHandSwingingState(true) + viewer.world.changeHandSwingingState(false) } else if (!stop) { const offhand = activate ? false : activatableItems(bot.inventory.slots[45]?.name ?? '') bot.activateItem(offhand) // todo offhand @@ -350,10 +352,14 @@ class WorldInteraction { }) customEvents.emit('digStart') this.lastDigged = Date.now() + viewer.world.changeHandSwingingState(true) } else { bot.swingArm('right') } } + if (!this.buttons[0] && this.lastButtons[0]) { + viewer.world.changeHandSwingingState(false) + } this.prevOnGround = onGround // Show cursor From 33ab8745b30295aa388cbecd39bd4cea3bc262bb Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 1 Sep 2024 02:23:33 +0300 Subject: [PATCH 006/834] add debug stats --- src/topRightStats.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/topRightStats.ts b/src/topRightStats.ts index 71303e81..f0462ae6 100644 --- a/src/topRightStats.ts +++ b/src/topRightStats.ts @@ -75,3 +75,14 @@ export const statsEnd = () => { stats2.end() statsGl.end() } + +window.statsPerSec = {} +let statsPerSec = {} +window.addStatPerSec = (name) => { + statsPerSec[name] ??= 0 + statsPerSec[name]++ +} +setInterval(() => { + window.statsPerSec = statsPerSec + statsPerSec = {} +}, 1000) From 183c1edfa3d86a15eed589858e4932129b3a42e4 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 1 Sep 2024 02:37:22 +0300 Subject: [PATCH 007/834] fix: when left click was pressed down the swing arm packet sending was not limited --- src/worldInteractions.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/worldInteractions.ts b/src/worldInteractions.ts index f178a785..cef65b96 100644 --- a/src/worldInteractions.ts +++ b/src/worldInteractions.ts @@ -38,6 +38,7 @@ class WorldInteraction { currentDigTime prevOnGround lastBlockPlaced: number + lastSwing = 0 buttons = [false, false, false] lastButtons = [false, false, false] breakStartTime: number | undefined = 0 @@ -353,8 +354,9 @@ class WorldInteraction { customEvents.emit('digStart') this.lastDigged = Date.now() viewer.world.changeHandSwingingState(true) - } else { + } else if (performance.now() - this.lastSwing > 200) { bot.swingArm('right') + this.lastSwing = performance.now() } } if (!this.buttons[0] && this.lastButtons[0]) { From 0d224f50fdea267ef72deab3e7d84f2905e3ad13 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 1 Sep 2024 03:08:00 +0300 Subject: [PATCH 008/834] demo item and wip holding item display! --- prismarine-viewer/viewer/lib/entities.js | 85 +++++++++++--------- prismarine-viewer/viewer/lib/holdingBlock.ts | 9 ++- prismarine-viewer/viewer/lib/viewer.ts | 13 +++ 3 files changed, 69 insertions(+), 38 deletions(-) diff --git a/prismarine-viewer/viewer/lib/entities.js b/prismarine-viewer/viewer/lib/entities.js index 57a45a44..01ce7d6d 100644 --- a/prismarine-viewer/viewer/lib/entities.js +++ b/prismarine-viewer/viewer/lib/entities.js @@ -282,6 +282,46 @@ export class Entities extends EventEmitter { } } + getItemMesh(item) { + const textureUv = this.getItemUv?.(item.itemId ?? item.blockId) + if (textureUv) { + // todo use geometry buffer uv instead! + const { u, v, size, su, sv, texture } = textureUv + const itemsTexture = texture.clone() + itemsTexture.flipY = true + itemsTexture.offset.set(u, 1 - v - (sv ?? size)) + itemsTexture.repeat.set(su ?? size, sv ?? size) + itemsTexture.needsUpdate = true + itemsTexture.magFilter = THREE.NearestFilter + itemsTexture.minFilter = THREE.NearestFilter + const itemsTextureFlipped = itemsTexture.clone() + itemsTextureFlipped.repeat.x *= -1 + itemsTextureFlipped.needsUpdate = true + itemsTextureFlipped.offset.set(u + (su ?? size), 1 - v - (sv ?? size)) + const material = new THREE.MeshStandardMaterial({ + map: itemsTexture, + transparent: true, + alphaTest: 0.1, + }) + const materialFlipped = new THREE.MeshStandardMaterial({ + map: itemsTextureFlipped, + transparent: true, + alphaTest: 0.1, + }) + const mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 0), [ + // top left and right bottom are black box materials others are transparent + new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), + new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), + material, materialFlipped, + ]) + return { + mesh, + itemsTexture, + itemsTextureFlipped, + } + } + } + update(/** @type {import('prismarine-entity').Entity & {delete?, pos}} */entity, overrides) { let isPlayerModel = entity.name === 'player' if (entity.name === 'zombie' || entity.name === 'zombie_villager' || entity.name === 'husk') { @@ -296,52 +336,23 @@ export class Entities extends EventEmitter { //@ts-expect-error const item = entity.metadata?.find(m => typeof m === 'object' && m?.itemCount) if (item) { - const textureUv = this.getItemUv?.(item.itemId ?? item.blockId) - if (textureUv) { - // todo use geometry buffer uv instead! - const { u, v, size, su, sv, texture } = textureUv - const itemsTexture = texture.clone() - itemsTexture.flipY = true - itemsTexture.offset.set(u, 1 - v - (sv ?? size)) - itemsTexture.repeat.set(su ?? size, sv ?? size) - itemsTexture.needsUpdate = true - itemsTexture.magFilter = THREE.NearestFilter - itemsTexture.minFilter = THREE.NearestFilter - const itemsTextureFlipped = itemsTexture.clone() - itemsTextureFlipped.repeat.x *= -1 - itemsTextureFlipped.needsUpdate = true - itemsTextureFlipped.offset.set(u + (su ?? size), 1 - v - (sv ?? size)) - const material = new THREE.MeshStandardMaterial({ - map: itemsTexture, - transparent: true, - alphaTest: 0.1, - }) - const materialFlipped = new THREE.MeshStandardMaterial({ - map: itemsTextureFlipped, - transparent: true, - alphaTest: 0.1, - }) - mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 0), [ - // top left and right bottom are black box materials others are transparent - new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), - new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), - material, materialFlipped, - ]) - mesh.scale.set(0.5, 0.5, 0.5) - mesh.position.set(0, 0.2, 0) + const object = this.getItemMesh(item) + if (object) { + object.scale.set(0.5, 0.5, 0.5) + object.position.set(0, 0.2, 0) // set faces // mesh.position.set(targetPos.x + 0.5 + 2, targetPos.y + 0.5, targetPos.z + 0.5) // viewer.scene.add(mesh) const clock = new THREE.Clock() - mesh.onBeforeRender = () => { + object.onBeforeRender = () => { const delta = clock.getDelta() - mesh.rotation.y += delta + object.rotation.y += delta } //@ts-expect-error group.additionalCleanup = () => { // important: avoid texture memory leak and gpu slowdown - itemsTexture.dispose() - itemsTextureFlipped.dispose() + object.itemsTexture.dispose() + object.itemsTextureFlipped.dispose() } } } diff --git a/prismarine-viewer/viewer/lib/holdingBlock.ts b/prismarine-viewer/viewer/lib/holdingBlock.ts index 25dd879f..f5a0ca79 100644 --- a/prismarine-viewer/viewer/lib/holdingBlock.ts +++ b/prismarine-viewer/viewer/lib/holdingBlock.ts @@ -152,9 +152,10 @@ export default class HoldingBlock { async initHandObject (material: THREE.Material, blockstatesModels: any, blocksAtlases: any, block?: HandItemBlock) { let animatingCurrent = false if (!this.swingAnimation && !this.blockSwapAnimation && this.isDifferentItem(block)) { - console.log('play swap') animatingCurrent = true await this.playBlockSwapAnimation() + this.holdingBlock?.removeFromParent() + this.holdingBlock = undefined } this.lastHeldItem = block if (!block) { @@ -167,6 +168,11 @@ export default class HoldingBlock { 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 blockInner.name = 'holdingBlock' const blockOuterGroup = new THREE.Group() blockOuterGroup.add(blockInner) @@ -185,6 +191,7 @@ export default class HoldingBlock { 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 scale = window.scale ?? 0.2 diff --git a/prismarine-viewer/viewer/lib/viewer.ts b/prismarine-viewer/viewer/lib/viewer.ts index 5f6590f7..7cd759e4 100644 --- a/prismarine-viewer/viewer/lib/viewer.ts +++ b/prismarine-viewer/viewer/lib/viewer.ts @@ -119,6 +119,19 @@ export class Viewer { this.scene.add(mesh) } + demoItem () { + //@ts-expect-error + const pos = cursorBlockRel(0, 1, 0).position + const { mesh } = this.entities.getItemMesh({ + itemId: 541, + })! + mesh.position.set(pos.x + 0.5, pos.y + 0.5, pos.z + 0.5) + // mesh.scale.set(0.5, 0.5, 0.5) + const helper = new THREE.BoxHelper(mesh, 0xff_ff_00) + mesh.add(helper) + this.scene.add(mesh) + } + updateEntity (e) { this.entities.update(e, this.processEntityOverrides(e, { rotation: { From 45fb8fa5925d501989edda06298497e7a62da548 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 1 Sep 2024 03:08:17 +0300 Subject: [PATCH 009/834] workaround for recent regreession with mc data change --- src/inventoryWindows.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index 64e589f9..e5964646 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -87,8 +87,13 @@ export const onGameLoad = (onLoad) => { return } const craftingSlots = bot.inventory.slots.slice(1, 5) - const resultingItem = getResultingRecipe(craftingSlots, 2) - void bot.creative.setInventorySlot(craftingResultSlot, resultingItem ?? null) + try { + const resultingItem = getResultingRecipe(craftingSlots, 2) + void bot.creative.setInventorySlot(craftingResultSlot, resultingItem ?? null) + } catch (err) { + console.error(err) + // todo resolve the error! and why would we ever get here on every update? + } }) as any) bot.on('windowClose', () => { From 362c0106bcc1adc2a3fda1586ac2299d8a9916d1 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 1 Sep 2024 03:17:17 +0300 Subject: [PATCH 010/834] use block properties --- prismarine-viewer/viewer/lib/worldDataEmitter.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/prismarine-viewer/viewer/lib/worldDataEmitter.ts b/prismarine-viewer/viewer/lib/worldDataEmitter.ts index 9a54e2a1..d832d3db 100644 --- a/prismarine-viewer/viewer/lib/worldDataEmitter.ts +++ b/prismarine-viewer/viewer/lib/worldDataEmitter.ts @@ -5,6 +5,7 @@ import { EventEmitter } from 'events' import { generateSpiralMatrix, ViewRect } from 'flying-squid/dist/utils' import { Vec3 } from 'vec3' import { BotEvents } from 'mineflayer' +import { getItemFromBlock } from '../../../src/botUtils' import { chunkPos } from './simpleUtils' export type ChunkPosKey = string @@ -95,9 +96,15 @@ export class WorldDataEmitter extends EventEmitter { viewer.world.onHandItemSwitch(undefined) return } - // todo properties const newItem = bot.heldItem - viewer.world.onHandItemSwitch(newItem ? { name: newItem.name, properties: {} } : undefined) + 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 }) }, } satisfies Partial this.eventListeners.heldItemChanged() From ee966395c6ff78cdb5fe957a3c6ae0751432fb2f Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sun, 1 Sep 2024 03:32:53 +0300 Subject: [PATCH 011/834] feat: Display holding block (experimental setting) (#190) --- experiments/three.html | 1 + experiments/three.ts | 101 +++++++++ prismarine-viewer/viewer/lib/entities.js | 85 +++---- prismarine-viewer/viewer/lib/holdingBlock.ts | 207 ++++++++++++++++++ .../viewer/lib/mesher/standaloneRenderer.ts | 16 ++ prismarine-viewer/viewer/lib/viewer.ts | 30 ++- .../viewer/lib/worldDataEmitter.ts | 35 ++- .../viewer/lib/worldrendererCommon.ts | 6 + .../viewer/lib/worldrendererThree.ts | 35 ++- src/inventoryWindows.ts | 9 +- src/optionsGuiScheme.tsx | 1 + src/optionsStorage.ts | 1 + src/topRightStats.ts | 11 + src/watchOptions.ts | 2 +- src/worldInteractions.ts | 6 + 15 files changed, 489 insertions(+), 57 deletions(-) create mode 100644 experiments/three.html create mode 100644 experiments/three.ts create mode 100644 prismarine-viewer/viewer/lib/holdingBlock.ts diff --git a/experiments/three.html b/experiments/three.html new file mode 100644 index 00000000..8765081b --- /dev/null +++ b/experiments/three.html @@ -0,0 +1 @@ + diff --git a/experiments/three.ts b/experiments/three.ts new file mode 100644 index 00000000..7a629a13 --- /dev/null +++ b/experiments/three.ts @@ -0,0 +1,101 @@ +import * as THREE from 'three' +import * as tweenJs from '@tweenjs/tween.js' +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' +import * as THREE from 'three'; +import Jimp from 'jimp'; + +const scene = new THREE.Scene() +const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000) +camera.position.set(0, 0, 5) +const renderer = new THREE.WebGLRenderer() +renderer.setSize(window.innerWidth, window.innerHeight) +document.body.appendChild(renderer.domElement) + +const controls = new OrbitControls(camera, renderer.domElement) + +const geometry = new THREE.BoxGeometry(1, 1, 1) +const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }) +const cube = new THREE.Mesh(geometry, material) +cube.position.set(0.5, 0.5, 0.5); +const group = new THREE.Group() +group.add(cube) +group.position.set(-0.5, -0.5, -0.5); +const outerGroup = new THREE.Group() +outerGroup.add(group) +outerGroup.scale.set(0.2, 0.2, 0.2) +outerGroup.position.set(1, 1, 0) +scene.add(outerGroup) + +// const mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshBasicMaterial({ color: 0x00_00_ff, transparent: true, opacity: 0.5 })) +// mesh.position.set(0.5, 1, 0.5) +// const group = new THREE.Group() +// group.add(mesh) +// group.position.set(-0.5, -1, -0.5) +// const outerGroup = new THREE.Group() +// outerGroup.add(group) +// // outerGroup.position.set(this.camera.position.x, this.camera.position.y, this.camera.position.z) +// scene.add(outerGroup) + + new tweenJs.Tween(group.rotation).to({ z: THREE.MathUtils.degToRad(90) }, 1000).yoyo(true).repeat(Infinity).start() + +const tweenGroup = new tweenJs.Group() +function animate () { + tweenGroup.update() + requestAnimationFrame(animate) +// cube.rotation.x += 0.01 +// cube.rotation.y += 0.01 + renderer.render(scene, camera) +} +animate() + +// let animation + +window.animate = () => { + // new Tween.Tween(group.position).to({ y: group.position.y - 1}, 1000 * 0.35/2).yoyo(true).repeat(1).start() + new tweenJs.Tween(group.rotation, tweenGroup).to({ z: THREE.MathUtils.degToRad(90) }, 1000 * 0.35 / 2).yoyo(true).repeat(Infinity).start().onRepeat(() => { + console.log('done') + }) +} + +window.stop = () => { + tweenGroup.removeAll() +} + + +function createGeometryFromImage() { + return new Promise((resolve, reject) => { + const img = new Image(); + img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAABEElEQVQ4jWNkIAPw2Zv9J0cfXPOSvx/+L/n74T+HqsJ/JlI1T9u3i6H91B7ybdY+vgZuO1majV+fppFmPnuz/+ihy2dv9t/49Wm8mlECkV1FHh5FfPZm/1XXTGX4cechA4eKPMNVq1CGH7cfMBJ0rlxX+X8OVYX/xq9P/5frKifoZ0Z0AwS8HRkYGBgYvt+8xyDXUUbQZgwJPnuz/+wq8gw/7zxk+PXsFUFno0h6mon+l5fgZFhwnYmBTUqMgYGBgaAhLMiaHQyFGOZvf8Lw49FXRgYGhv8MDAwwg/7jMoQFFury/C8Y5m9/wnADohnZVryJhoWBARJ9Cw69gtmMAgiFAcuvZ68Yfj17hU8NXgAATdKfkzbQhBEAAAAASUVORK5CYII=' + console.log('img.complete', img.complete) + img.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + const context = canvas.getContext('2d'); + context.drawImage(img, 0, 0, img.width, img.height); + const imgData = context.getImageData(0, 0, img.width, img.height); + + const shape = new THREE.Shape(); + for (let y = 0; y < img.height; y++) { + for (let x = 0; x < img.width; x++) { + const index = (y * img.width + x) * 4; + const alpha = imgData.data[index + 3]; + if (alpha !== 0) { + shape.lineTo(x, y); + } + } + } + + const geometry = new THREE.ShapeGeometry(shape); + resolve(geometry); + }; + img.onerror = reject; + }); +} + +// Usage: +const shapeGeomtry = createGeometryFromImage().then(geometry => { + const material = new THREE.MeshBasicMaterial({ color: 0xffffff }); + const mesh = new THREE.Mesh(geometry, material); + scene.add(mesh); +}) diff --git a/prismarine-viewer/viewer/lib/entities.js b/prismarine-viewer/viewer/lib/entities.js index 57a45a44..01ce7d6d 100644 --- a/prismarine-viewer/viewer/lib/entities.js +++ b/prismarine-viewer/viewer/lib/entities.js @@ -282,6 +282,46 @@ export class Entities extends EventEmitter { } } + getItemMesh(item) { + const textureUv = this.getItemUv?.(item.itemId ?? item.blockId) + if (textureUv) { + // todo use geometry buffer uv instead! + const { u, v, size, su, sv, texture } = textureUv + const itemsTexture = texture.clone() + itemsTexture.flipY = true + itemsTexture.offset.set(u, 1 - v - (sv ?? size)) + itemsTexture.repeat.set(su ?? size, sv ?? size) + itemsTexture.needsUpdate = true + itemsTexture.magFilter = THREE.NearestFilter + itemsTexture.minFilter = THREE.NearestFilter + const itemsTextureFlipped = itemsTexture.clone() + itemsTextureFlipped.repeat.x *= -1 + itemsTextureFlipped.needsUpdate = true + itemsTextureFlipped.offset.set(u + (su ?? size), 1 - v - (sv ?? size)) + const material = new THREE.MeshStandardMaterial({ + map: itemsTexture, + transparent: true, + alphaTest: 0.1, + }) + const materialFlipped = new THREE.MeshStandardMaterial({ + map: itemsTextureFlipped, + transparent: true, + alphaTest: 0.1, + }) + const mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 0), [ + // top left and right bottom are black box materials others are transparent + new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), + new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), + material, materialFlipped, + ]) + return { + mesh, + itemsTexture, + itemsTextureFlipped, + } + } + } + update(/** @type {import('prismarine-entity').Entity & {delete?, pos}} */entity, overrides) { let isPlayerModel = entity.name === 'player' if (entity.name === 'zombie' || entity.name === 'zombie_villager' || entity.name === 'husk') { @@ -296,52 +336,23 @@ export class Entities extends EventEmitter { //@ts-expect-error const item = entity.metadata?.find(m => typeof m === 'object' && m?.itemCount) if (item) { - const textureUv = this.getItemUv?.(item.itemId ?? item.blockId) - if (textureUv) { - // todo use geometry buffer uv instead! - const { u, v, size, su, sv, texture } = textureUv - const itemsTexture = texture.clone() - itemsTexture.flipY = true - itemsTexture.offset.set(u, 1 - v - (sv ?? size)) - itemsTexture.repeat.set(su ?? size, sv ?? size) - itemsTexture.needsUpdate = true - itemsTexture.magFilter = THREE.NearestFilter - itemsTexture.minFilter = THREE.NearestFilter - const itemsTextureFlipped = itemsTexture.clone() - itemsTextureFlipped.repeat.x *= -1 - itemsTextureFlipped.needsUpdate = true - itemsTextureFlipped.offset.set(u + (su ?? size), 1 - v - (sv ?? size)) - const material = new THREE.MeshStandardMaterial({ - map: itemsTexture, - transparent: true, - alphaTest: 0.1, - }) - const materialFlipped = new THREE.MeshStandardMaterial({ - map: itemsTextureFlipped, - transparent: true, - alphaTest: 0.1, - }) - mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 0), [ - // top left and right bottom are black box materials others are transparent - new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), - new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), - material, materialFlipped, - ]) - mesh.scale.set(0.5, 0.5, 0.5) - mesh.position.set(0, 0.2, 0) + const object = this.getItemMesh(item) + if (object) { + object.scale.set(0.5, 0.5, 0.5) + object.position.set(0, 0.2, 0) // set faces // mesh.position.set(targetPos.x + 0.5 + 2, targetPos.y + 0.5, targetPos.z + 0.5) // viewer.scene.add(mesh) const clock = new THREE.Clock() - mesh.onBeforeRender = () => { + object.onBeforeRender = () => { const delta = clock.getDelta() - mesh.rotation.y += delta + object.rotation.y += delta } //@ts-expect-error group.additionalCleanup = () => { // important: avoid texture memory leak and gpu slowdown - itemsTexture.dispose() - itemsTextureFlipped.dispose() + object.itemsTexture.dispose() + object.itemsTextureFlipped.dispose() } } } diff --git a/prismarine-viewer/viewer/lib/holdingBlock.ts b/prismarine-viewer/viewer/lib/holdingBlock.ts new file mode 100644 index 00000000..f5a0ca79 --- /dev/null +++ b/prismarine-viewer/viewer/lib/holdingBlock.ts @@ -0,0 +1,207 @@ +import * as THREE from 'three' +import * as tweenJs from '@tweenjs/tween.js' +import worldBlockProvider from 'mc-assets/dist/worldBlockProvider' +import { getThreeBlockModelGroup, renderBlockThree, setBlockPosition } from './mesher/standaloneRenderer' + +export type HandItemBlock = { + name + properties +} + +export default class HoldingBlock { + holdingBlock: THREE.Object3D | undefined = undefined + swingAnimation: tweenJs.Group | undefined = undefined + blockSwapAnimation: { + tween: tweenJs.Group + hidden: boolean + } | undefined = undefined + cameraGroup = new THREE.Mesh() + objectOuterGroup = new THREE.Group() + objectInnerGroup = new THREE.Group() + camera: THREE.Group | THREE.PerspectiveCamera + stopUpdate = false + lastHeldItem: HandItemBlock | undefined + toBeRenderedItem: HandItemBlock | undefined + isSwinging = false + nextIterStopCallbacks: Array<() => void> | undefined + + constructor (public scene: THREE.Scene) { + this.initCameraGroup() + } + + initCameraGroup () { + this.cameraGroup = new THREE.Mesh() + this.scene.add(this.cameraGroup) + } + + startSwing () { + this.nextIterStopCallbacks = undefined // forget about cancelling + if (this.isSwinging) return + this.swingAnimation = new tweenJs.Group() + this.isSwinging = true + const cube = this.cameraGroup.children[0] + if (cube) { + // const DURATION = 1000 * 0.35 / 2 + const DURATION = 1000 * 0.35 / 3 + // const DURATION = 1000 + const initialPos = { + x: this.objectInnerGroup.position.x, + y: this.objectInnerGroup.position.y, + z: this.objectInnerGroup.position.z + } + const initialRot = { + x: this.objectInnerGroup.rotation.x, + y: this.objectInnerGroup.rotation.y, + z: this.objectInnerGroup.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() + let i = 0 + mainAnim.onRepeat(() => { + i++ + if (this.nextIterStopCallbacks && i % 2 === 0) { + for (const callback of this.nextIterStopCallbacks) { + callback() + } + this.nextIterStopCallbacks = undefined + this.isSwinging = false + 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) + } + }) + + 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() + } + } + + async stopSwing () { + if (!this.isSwinging) return + // might never resolve! + /* return */void new Promise((resolve) => { + this.nextIterStopCallbacks ??= [] + this.nextIterStopCallbacks.push(() => { + resolve() + }) + }) + } + + update (camera: typeof this.camera) { + this.camera = camera + this.swingAnimation?.update() + this.blockSwapAnimation?.tween.update() + this.updateCameraGroup() + } + + // worldTest () { + // const mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshPhongMaterial({ color: 0x00_00_ff, transparent: true, opacity: 0.5 })) + // mesh.position.set(0.5, 0.5, 0.5) + // const group = new THREE.Group() + // group.add(mesh) + // group.position.set(-0.5, -0.5, -0.5) + // const outerGroup = new THREE.Group() + // outerGroup.add(group) + // outerGroup.position.set(this.camera.position.x, this.camera.position.y, this.camera.position.z) + // this.scene.add(outerGroup) + + // new tweenJs.Tween(group.rotation).to({ z: THREE.MathUtils.degToRad(90) }, 1000).yoyo(true).repeat(Infinity).start() + // } + + async playBlockSwapAnimation () { + // if (this.blockSwapAnimation) return + this.blockSwapAnimation ??= { + tween: new tweenJs.Group(), + hidden: false + } + const DURATION = 1000 * 0.35 / 2 + const tween = new tweenJs.Tween(this.objectInnerGroup.position, this.blockSwapAnimation.tween).to({ + y: this.objectInnerGroup.position.y + (this.objectInnerGroup.scale.y * 1.5 * (this.blockSwapAnimation.hidden ? 1 : -1)) + }, DURATION).start() + return new Promise((resolve) => { + tween.onComplete(() => { + if (this.blockSwapAnimation!.hidden) { + this.blockSwapAnimation = undefined + } else { + this.blockSwapAnimation!.hidden = !this.blockSwapAnimation!.hidden + } + resolve() + }) + }) + } + + isDifferentItem (block: HandItemBlock | undefined) { + return this.lastHeldItem && (this.lastHeldItem.name !== block?.name || JSON.stringify(this.lastHeldItem.properties) !== JSON.stringify(block?.properties ?? '{}')) + } + + updateCameraGroup () { + if (this.stopUpdate) return + const { camera } = this + 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) + } + + async initHandObject (material: THREE.Material, blockstatesModels: any, blocksAtlases: any, block?: HandItemBlock) { + let animatingCurrent = false + if (!this.swingAnimation && !this.blockSwapAnimation && this.isDifferentItem(block)) { + animatingCurrent = true + await this.playBlockSwapAnimation() + this.holdingBlock?.removeFromParent() + this.holdingBlock = undefined + } + this.lastHeldItem = block + if (!block) { + this.holdingBlock?.removeFromParent() + this.holdingBlock = undefined + this.swingAnimation = undefined + this.blockSwapAnimation = undefined + 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 + blockInner.name = 'holdingBlock' + const blockOuterGroup = new THREE.Group() + blockOuterGroup.add(blockInner) + this.holdingBlock = blockInner + this.objectInnerGroup = new THREE.Group() + this.objectInnerGroup.add(blockOuterGroup) + this.objectInnerGroup.position.set(-0.5, -0.5, -0.5) + // todo cleanup + if (animatingCurrent) { + this.objectInnerGroup.position.y -= this.objectInnerGroup.scale.y * 1.5 + } + Object.assign(blockOuterGroup.position, { x: 0.5, y: 0.5, z: 0.5 }) + + this.objectOuterGroup = new THREE.Group() + 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 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) + + if (animatingCurrent) { + await this.playBlockSwapAnimation() + } + } +} diff --git a/prismarine-viewer/viewer/lib/mesher/standaloneRenderer.ts b/prismarine-viewer/viewer/lib/mesher/standaloneRenderer.ts index 2dc2f599..43369cc2 100644 --- a/prismarine-viewer/viewer/lib/mesher/standaloneRenderer.ts +++ b/prismarine-viewer/viewer/lib/mesher/standaloneRenderer.ts @@ -272,3 +272,19 @@ export const renderBlockThree = (...args: Parameters) => { + const geometry = renderBlockThree(...args) + const mesh = new THREE.Mesh(geometry, material) + mesh.position.set(-0.5, -0.5, -0.5) + const group = new THREE.Group() + group.add(mesh) + group.rotation.set(0, -THREE.MathUtils.degToRad(90), 0, 'ZYX') + globalThis.mesh = group + return group + // return new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshPhongMaterial({ color: 0x00_00_ff, transparent: true, opacity: 0.5 })) +} + +export const setBlockPosition = (object: THREE.Object3D, position: { x: number, y: number, z: number }) => { + object.position.set(position.x + 0.5, position.y + 0.5, position.z + 0.5) +} diff --git a/prismarine-viewer/viewer/lib/viewer.ts b/prismarine-viewer/viewer/lib/viewer.ts index 7893bccc..7cd759e4 100644 --- a/prismarine-viewer/viewer/lib/viewer.ts +++ b/prismarine-viewer/viewer/lib/viewer.ts @@ -7,7 +7,7 @@ import { Entities } from './entities' import { Primitives } from './primitives' import { WorldRendererThree } from './worldrendererThree' import { WorldRendererCommon, WorldRendererConfig, defaultWorldRendererConfig } from './worldrendererCommon' -import { renderBlockThree } from './mesher/standaloneRenderer' +import { getThreeBlockModelGroup, renderBlockThree, setBlockPosition } from './mesher/standaloneRenderer' export class Viewer { scene: THREE.Scene @@ -101,18 +101,32 @@ export class Viewer { } 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', + name: 'furnace', properties: { - map: false + // map: false } - }) - const geometry = renderBlockThree(models, undefined, 'plains', loadedData) + }, true) const { material } = this.world - // block material - const mesh = new THREE.Mesh(geometry, material) - mesh.position.set(this.camera.position.x, this.camera.position.y, this.camera.position.z) + const mesh = getThreeBlockModelGroup(material, models, undefined, 'plains', loadedData) + // mesh.rotation.y = THREE.MathUtils.degToRad(90) + setBlockPosition(mesh, pos) + const helper = new THREE.BoxHelper(mesh, 0xff_ff_00) + mesh.add(helper) + this.scene.add(mesh) + } + + demoItem () { + //@ts-expect-error + const pos = cursorBlockRel(0, 1, 0).position + const { mesh } = this.entities.getItemMesh({ + itemId: 541, + })! + mesh.position.set(pos.x + 0.5, pos.y + 0.5, pos.z + 0.5) + // mesh.scale.set(0.5, 0.5, 0.5) const helper = new THREE.BoxHelper(mesh, 0xff_ff_00) mesh.add(helper) this.scene.add(mesh) diff --git a/prismarine-viewer/viewer/lib/worldDataEmitter.ts b/prismarine-viewer/viewer/lib/worldDataEmitter.ts index 381526d9..d832d3db 100644 --- a/prismarine-viewer/viewer/lib/worldDataEmitter.ts +++ b/prismarine-viewer/viewer/lib/worldDataEmitter.ts @@ -5,6 +5,7 @@ import { EventEmitter } from 'events' import { generateSpiralMatrix, ViewRect } from 'flying-squid/dist/utils' import { Vec3 } from 'vec3' import { BotEvents } from 'mineflayer' +import { getItemFromBlock } from '../../../src/botUtils' import { chunkPos } from './simpleUtils' export type ChunkPosKey = string @@ -20,6 +21,14 @@ export class WorldDataEmitter extends EventEmitter { private eventListeners: Record = {} private readonly emitter: WorldDataEmitter keepChunksDistance = 0 + _handDisplay = false + get handDisplay () { + return this._handDisplay + } + set handDisplay (newVal) { + this._handDisplay = newVal + this.eventListeners.heldItemChanged?.() + } constructor (public world: typeof __type_bot['world'], public viewDistance: number, position: Vec3 = new Vec3(0, 0, 0)) { super() @@ -55,7 +64,7 @@ export class WorldDataEmitter extends EventEmitter { }) } - this.eventListeners[bot.username] = { + this.eventListeners = { // 'move': botPosition, entitySpawn (e: any) { emitEntity(e) @@ -70,7 +79,7 @@ export class WorldDataEmitter extends EventEmitter { this.emitter.emit('entity', { id: e.id, delete: true }) }, chunkColumnLoad: (pos: Vec3) => { - this.loadChunk(pos) + void this.loadChunk(pos) }, chunkColumnUnload: (pos: Vec3) => { this.unloadChunk(pos) @@ -82,7 +91,24 @@ 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 }) + }, } satisfies Partial + this.eventListeners.heldItemChanged() + bot._client.on('update_light', ({ chunkX, chunkZ }) => { const chunkPos = new Vec3(chunkX * 16, 0, chunkZ * 16) @@ -105,7 +131,7 @@ export class WorldDataEmitter extends EventEmitter { this.emitter.emit('listening') } - for (const [evt, listener] of Object.entries(this.eventListeners[bot.username])) { + for (const [evt, listener] of Object.entries(this.eventListeners)) { bot.on(evt as any, listener) } @@ -116,10 +142,9 @@ export class WorldDataEmitter extends EventEmitter { } removeListenersFromBot (bot: import('mineflayer').Bot) { - for (const [evt, listener] of Object.entries(this.eventListeners[bot.username])) { + for (const [evt, listener] of Object.entries(this.eventListeners)) { bot.removeListener(evt as any, listener) } - delete this.eventListeners[bot.username] } async init (pos: Vec3) { diff --git a/prismarine-viewer/viewer/lib/worldrendererCommon.ts b/prismarine-viewer/viewer/lib/worldrendererCommon.ts index 58e5a6f5..482907d4 100644 --- a/prismarine-viewer/viewer/lib/worldrendererCommon.ts +++ b/prismarine-viewer/viewer/lib/worldrendererCommon.ts @@ -16,6 +16,7 @@ import { toMajorVersion } from '../../../src/utils' import { buildCleanupDecorator } from './cleanupDecorator' import { defaultMesherConfig } from './mesher/shared' import { chunkPos } from './simpleUtils' +import { HandItemBlock } from './holdingBlock' function mod (x, n) { return ((x % n) + n) % n @@ -37,6 +38,7 @@ type CustomTexturesData = { export abstract class WorldRendererCommon { worldConfig = { minY: 0, worldHeight: 256 } + // todo need to cleanup material = new THREE.MeshLambertMaterial({ vertexColors: true, transparent: true, alphaTest: 0.1 }) @worldCleanup() @@ -59,6 +61,7 @@ export abstract class WorldRendererCommon textureDownloaded (): void }> customTexturesDataUrl = undefined as string | undefined + @worldCleanup() currentTextureImage = undefined as any workers: any[] = [] viewerPosition?: Vec3 @@ -157,6 +160,9 @@ export abstract class WorldRendererCommon } } + onHandItemSwitch (item: HandItemBlock | undefined): void { } + changeHandSwingingState (isAnimationPlaying: boolean): void { } + abstract handleWorkerMessage (data: WorkerReceive): void abstract updateCamera (pos: Vec3 | null, yaw: number, pitch: number): void diff --git a/prismarine-viewer/viewer/lib/worldrendererThree.ts b/prismarine-viewer/viewer/lib/worldrendererThree.ts index 6c14e243..b1644ebd 100644 --- a/prismarine-viewer/viewer/lib/worldrendererThree.ts +++ b/prismarine-viewer/viewer/lib/worldrendererThree.ts @@ -9,7 +9,7 @@ import { renderSign } from '../sign-renderer' import { chunkPos, sectionPos } from './simpleUtils' import { WorldRendererCommon, WorldRendererConfig } from './worldrendererCommon' import { disposeObject } from './threeJsUtils' -import { renderBlockThree } from './mesher/standaloneRenderer' +import HoldingBlock, { HandItemBlock } from './holdingBlock' export class WorldRendererThree extends WorldRendererCommon { outputFormat = 'threeJs' as const @@ -19,7 +19,7 @@ export class WorldRendererThree extends WorldRendererCommon { signsCache = new Map() starField: StarField cameraSectionPos: Vec3 = new Vec3(0, 0, 0) - cameraGroup = new THREE.Group() + holdingBlock: HoldingBlock get tilesRendered () { return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj as any).tilesCount, 0) @@ -28,8 +28,34 @@ export class WorldRendererThree extends WorldRendererCommon { constructor (public scene: THREE.Scene, public renderer: THREE.WebGLRenderer, public config: WorldRendererConfig) { super(config) this.starField = new StarField(scene) - // this.initCameraGroup() - // this.initHandObject() + this.holdingBlock = new HoldingBlock(this.scene) + this.onHandItemSwitch({ + name: 'furnace', + properties: {} + }) + + this.renderUpdateEmitter.on('textureDownloaded', () => { + if (this.holdingBlock.toBeRenderedItem) { + this.onHandItemSwitch(this.holdingBlock.toBeRenderedItem) + this.holdingBlock.toBeRenderedItem = undefined + } + }) + } + + onHandItemSwitch (item: HandItemBlock | undefined) { + if (!this.currentTextureImage) { + this.holdingBlock.toBeRenderedItem = item + return + } + void this.holdingBlock.initHandObject(this.material, this.blockstatesModels, this.blocksAtlases, item) + } + + changeHandSwingingState (isAnimationPlaying: boolean) { + if (isAnimationPlaying) { + this.holdingBlock.startSwing() + } else { + void this.holdingBlock.stopSwing() + } } timeUpdated (newTime: number): void { @@ -173,6 +199,7 @@ 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) diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index 64e589f9..e5964646 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -87,8 +87,13 @@ export const onGameLoad = (onLoad) => { return } const craftingSlots = bot.inventory.slots.slice(1, 5) - const resultingItem = getResultingRecipe(craftingSlots, 2) - void bot.creative.setInventorySlot(craftingResultSlot, resultingItem ?? null) + try { + const resultingItem = getResultingRecipe(craftingSlots, 2) + void bot.creative.setInventorySlot(craftingResultSlot, resultingItem ?? null) + } catch (err) { + console.error(err) + // todo resolve the error! and why would we ever get here on every update? + } }) as any) bot.on('windowClose', () => { diff --git a/src/optionsGuiScheme.tsx b/src/optionsGuiScheme.tsx index 644ec66c..2082acdb 100644 --- a/src/optionsGuiScheme.tsx +++ b/src/optionsGuiScheme.tsx @@ -88,6 +88,7 @@ export const guiOptionsScheme: { unit: '', tooltip: 'Additional distance to keep the chunks loading before unloading them by marking them as too far', }, + handDisplay: {}, }, ], main: [ diff --git a/src/optionsStorage.ts b/src/optionsStorage.ts index cac8c9f8..b7bf4abe 100644 --- a/src/optionsStorage.ts +++ b/src/optionsStorage.ts @@ -47,6 +47,7 @@ const defaultOptions = { enabledResourcepack: null as string | null, useVersionsTextures: 'latest', serverResourcePacks: 'prompt' as 'prompt' | 'always' | 'never', + handDisplay: false, // antiAliasing: false, diff --git a/src/topRightStats.ts b/src/topRightStats.ts index 71303e81..f0462ae6 100644 --- a/src/topRightStats.ts +++ b/src/topRightStats.ts @@ -75,3 +75,14 @@ export const statsEnd = () => { stats2.end() statsGl.end() } + +window.statsPerSec = {} +let statsPerSec = {} +window.addStatPerSec = (name) => { + statsPerSec[name] ??= 0 + statsPerSec[name]++ +} +setInterval(() => { + window.statsPerSec = statsPerSec + statsPerSec = {} +}, 1000) diff --git a/src/watchOptions.ts b/src/watchOptions.ts index 9deb46a5..d26d2b90 100644 --- a/src/watchOptions.ts +++ b/src/watchOptions.ts @@ -66,11 +66,11 @@ export const watchOptionsAfterViewerInit = () => { let viewWatched = false export const watchOptionsAfterWorldViewInit = () => { - worldView!.keepChunksDistance = options.keepChunksDistance if (viewWatched) return viewWatched = true watchValue(options, o => { if (!worldView) return worldView.keepChunksDistance = o.keepChunksDistance + worldView.handDisplay = o.handDisplay }) } diff --git a/src/worldInteractions.ts b/src/worldInteractions.ts index bc54efcd..cef65b96 100644 --- a/src/worldInteractions.ts +++ b/src/worldInteractions.ts @@ -294,6 +294,8 @@ class WorldInteraction { bot.lookAt = oldLookAt }).catch(console.warn) } + viewer.world.changeHandSwingingState(true) + viewer.world.changeHandSwingingState(false) } else if (!stop) { const offhand = activate ? false : activatableItems(bot.inventory.slots[45]?.name ?? '') bot.activateItem(offhand) // todo offhand @@ -351,11 +353,15 @@ class WorldInteraction { }) customEvents.emit('digStart') this.lastDigged = Date.now() + viewer.world.changeHandSwingingState(true) } 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) + } this.prevOnGround = onGround // Show cursor From 66d26ad2e6a6673e97e6bd08b67d97de052984ba Mon Sep 17 00:00:00 2001 From: Valery-a <83373303+Valery-a@users.noreply.github.com> Date: Sun, 1 Sep 2024 17:53:48 +0300 Subject: [PATCH 012/834] feat: add visuals for entities damaging (#186) --- docs-assets/handled-packets.md | 2 +- prismarine-viewer/viewer/lib/entities.js | 18 ++++++++++++++++++ src/entities.ts | 16 ++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/docs-assets/handled-packets.md b/docs-assets/handled-packets.md index 0671987c..497ec5ec 100644 --- a/docs-assets/handled-packets.md +++ b/docs-assets/handled-packets.md @@ -32,8 +32,8 @@ ❌ world_border_warning_reach ❌ simulation_distance ❌ chunk_biomes -❌ damage_event ❌ hurt_animation +✅ damage_event ✅ spawn_entity ✅ spawn_entity_experience_orb ✅ named_entity_spawn diff --git a/prismarine-viewer/viewer/lib/entities.js b/prismarine-viewer/viewer/lib/entities.js index 01ce7d6d..59d1164a 100644 --- a/prismarine-viewer/viewer/lib/entities.js +++ b/prismarine-viewer/viewer/lib/entities.js @@ -496,4 +496,22 @@ export class Entities extends EventEmitter { new TWEEN.Tween(e.rotation).to({ y: e.rotation.y + dy }, TWEEN_DURATION).start() } } + + handleDamageEvent(entityId, damageAmount) { + const entityMesh = this.entities[entityId]?.children.find(c => c.name === 'mesh') + if (entityMesh) { + entityMesh.traverse((child) => { + if (child instanceof THREE.Mesh) { + const clonedMaterial = child.material.clone() + clonedMaterial.dispose() + child.material = child.material.clone() + const originalColor = child.material.color.clone() + child.material.color.set(0xff_00_00) + new TWEEN.Tween(child.material.color) + .to(originalColor, 500) + .start() + } + }) + } + } } diff --git a/src/entities.ts b/src/entities.ts index b238d4ea..b5daa220 100644 --- a/src/entities.ts +++ b/src/entities.ts @@ -1,4 +1,5 @@ import { Entity } from 'prismarine-entity' +import { versionToNumber } from 'prismarine-viewer/viewer/prepare/utils' import tracker from '@nxg-org/mineflayer-tracker' import { loader as autoJumpPlugin } from '@nxg-org/mineflayer-auto-jump' import { subscribeKey } from 'valtio/utils' @@ -88,6 +89,21 @@ customEvents.on('gameLoaded', () => { } }) + bot._client.on('damage_event', (data) => { + const { entityId, sourceTypeId: damage } = data + if (viewer.entities.entities[entityId]) { + viewer.entities.handleDamageEvent(entityId, damage) + } + }) + + bot._client.on('entity_status', (data) => { + if (versionToNumber(bot.version) >= versionToNumber('1.19.4')) return + const { entityId, entityStatus } = data + if (entityStatus === 2 && viewer.entities.entities[entityId]) { + viewer.entities.handleDamageEvent(entityId, entityStatus) + } + }) + const loadedSkinEntityIds = new Set() const playerRenderSkin = (e: Entity) => { From 574dbafc282ddac71355e68fed8c1e29f5589c06 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 2 Sep 2024 23:46:22 +0300 Subject: [PATCH 013/834] fix(renderer,important): fix all known rendering issues with starfield by @sa2urami --- prismarine-viewer/viewer/lib/worldrendererThree.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/prismarine-viewer/viewer/lib/worldrendererThree.ts b/prismarine-viewer/viewer/lib/worldrendererThree.ts index b1644ebd..c89bc4cb 100644 --- a/prismarine-viewer/viewer/lib/worldrendererThree.ts +++ b/prismarine-viewer/viewer/lib/worldrendererThree.ts @@ -413,6 +413,7 @@ class StarField { this.points?.position.copy?.(camera.position) material.uniforms.time.value = clock.getElapsedTime() * speed } + this.points.renderOrder = -1 } remove () { @@ -439,7 +440,7 @@ class StarfieldMaterial extends THREE.ShaderMaterial { void main() { vColor = color; vec4 mvPosition = modelViewMatrix * vec4(position, 0.5); - gl_PointSize = size * (30.0 / -mvPosition.z) * (3.0 + sin(time + 100.0)); + gl_PointSize = 0.7 * size * (30.0 / -mvPosition.z) * (3.0 + sin(time + 100.0)); gl_Position = projectionMatrix * mvPosition; }`, fragmentShader: /* glsl */ ` @@ -448,11 +449,7 @@ class StarfieldMaterial extends THREE.ShaderMaterial { varying vec3 vColor; void main() { float opacity = 1.0; - if (fade == 1.0) { - float d = distance(gl_PointCoord, vec2(0.5, 0.5)); - opacity = 1.0 / (1.0 + exp(16.0 * (d - 0.25))); - } - gl_FragColor = vec4(vColor, opacity); + gl_FragColor = vec4(vColor, 1.0); #include #include <${version >= 154 ? 'colorspace_fragment' : 'encodings_fragment'}> From 00dd6060914a4347d6d59b1e1fb9da8c3dc6b6b6 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 2 Sep 2024 23:50:46 +0300 Subject: [PATCH 014/834] [skip ci] cleanup starfield code --- prismarine-viewer/viewer/lib/worldrendererThree.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prismarine-viewer/viewer/lib/worldrendererThree.ts b/prismarine-viewer/viewer/lib/worldrendererThree.ts index c89bc4cb..deec264e 100644 --- a/prismarine-viewer/viewer/lib/worldrendererThree.ts +++ b/prismarine-viewer/viewer/lib/worldrendererThree.ts @@ -62,9 +62,9 @@ export class WorldRendererThree extends WorldRendererCommon { const nightTime = 13_500 const morningStart = 23_000 const displayStars = newTime > nightTime && newTime < morningStart - if (displayStars && !this.starField.points) { + if (displayStars) { this.starField.addToScene() - } else if (!displayStars && this.starField.points) { + } else { this.starField.remove() } } From 0d3a3affd75c72de49a02132adf8ef5bd13210b7 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 3 Sep 2024 01:00:54 +0300 Subject: [PATCH 015/834] fix recently introduced bug with crafting in singleplayer --- scripts/makeOptimizedMcData.mjs | 4 ++- scripts/testOptimizedMcdata.ts | 35 ++++++++++++++----- ...tation_console_controller_gamepad_icon.svg | 1 - src/inventoryWindows.ts | 6 ++-- src/optimizeJson.ts | 7 +++- src/{ => react}/GlobalSearchInput.tsx | 5 ++- src/react/Input.tsx | 12 +++++-- src/reactUi.tsx | 2 +- 8 files changed, 52 insertions(+), 20 deletions(-) delete mode 100644 src/cross_playstation_console_controller_gamepad_icon.svg rename src/{ => react}/GlobalSearchInput.tsx (87%) diff --git a/scripts/makeOptimizedMcData.mjs b/scripts/makeOptimizedMcData.mjs index d0adcae2..9794aeed 100644 --- a/scripts/makeOptimizedMcData.mjs +++ b/scripts/makeOptimizedMcData.mjs @@ -118,7 +118,9 @@ const dataTypeBundling = { blockLoot: { arrKey: 'block' }, - recipes: {}, // todo we can do better + recipes: { + raw: true + }, // todo we can do better blockCollisionShapes: {}, loginPacket: {}, protocol: { diff --git a/scripts/testOptimizedMcdata.ts b/scripts/testOptimizedMcdata.ts index 17b5f7ed..d6c74384 100644 --- a/scripts/testOptimizedMcdata.ts +++ b/scripts/testOptimizedMcdata.ts @@ -8,7 +8,7 @@ const json = JSON.parse(fs.readFileSync('./generated/minecraft-data-optimized.js const dataPaths = require('minecraft-data/minecraft-data/data/dataPaths.json') const validateData = (ver, type) => { - const target = JsonOptimizer.restoreData(json[type], ver) + const target = JsonOptimizer.restoreData(structuredClone(json[type]), ver) const arrKey = json[type].arrKey const originalPath = dataPaths.pc[ver][type] const original = require(`minecraft-data/minecraft-data/data/${originalPath}/${type}.json`) @@ -43,16 +43,33 @@ const validateData = (ver, type) => { } } -const checkObj = (source, diffing) => { - checkKeys(Object.keys(source), Object.keys(diffing)) - for (const [key, val] of Object.entries(source)) { - if (JSON.stringify(val) !== JSON.stringify(diffing[key])) { - throw new Error(`different value of ${key}: ${val} ${diffing[key]}`) - } +const sortObj = (obj) => { + const sorted = {} + for (const key of Object.keys(obj).sort()) { + sorted[key] = obj[key] } + return sorted } -const checkKeys = (source, diffing, isUniq = true, msg = '', redunantOk = false) => { +const checkObj = (source, diffing) => { + if (!Array.isArray(source)) { + source = sortObj(source) + } + if (!Array.isArray(diffing)) { + diffing = sortObj(diffing) + } + if (JSON.stringify(source) !== JSON.stringify(diffing)) { + throw new Error(`different value: ${JSON.stringify(source)} ${JSON.stringify(diffing)}`) + } + // checkKeys(Object.keys(source), Object.keys(diffing)) + // for (const [key, val] of Object.entries(source)) { + // if (JSON.stringify(val) !== JSON.stringify(diffing[key])) { + // throw new Error(`different value of ${key}: ${val} ${diffing[key]}`) + // } + // } +} + +const checkKeys = (source, diffing, isUniq = true, msg = '', redundantIsOk = false) => { if (isUniq) { for (const [i, item] of diffing.entries()) { if (diffing.indexOf(item) !== i) { @@ -65,7 +82,7 @@ const checkKeys = (source, diffing, isUniq = true, msg = '', redunantOk = false) throw new Error(`Diffing does not include "${key}" (${msg})`) } } - if (!redunantOk) { + if (!redundantIsOk) { for (const key of diffing) { if (!source.includes(key)) { throw new Error(`Source does not include "${key}" (${msg})`) diff --git a/src/cross_playstation_console_controller_gamepad_icon.svg b/src/cross_playstation_console_controller_gamepad_icon.svg deleted file mode 100644 index d7d176e2..00000000 --- a/src/cross_playstation_console_controller_gamepad_icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index e5964646..b23c88fa 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -69,11 +69,12 @@ export const onGameLoad = (onLoad) => { } }) + // workaround: singleplayer player inventory crafting bot.inventory.on('updateSlot', ((_oldSlot, oldItem, newItem) => { - const oldSlot = _oldSlot as number + const currentSlot = _oldSlot as number if (!miscUiState.singleplayer) return const { craftingResultSlot } = bot.inventory - if (oldSlot === craftingResultSlot && oldItem && !newItem) { + if (currentSlot === craftingResultSlot && oldItem && !newItem) { for (let i = 1; i < 5; i++) { const count = bot.inventory.slots[i]?.count if (count && count > 1) { @@ -86,6 +87,7 @@ export const onGameLoad = (onLoad) => { } return } + if (currentSlot > 4) return const craftingSlots = bot.inventory.slots.slice(1, 5) try { const resultingItem = getResultingRecipe(craftingSlots, 2) diff --git a/src/optimizeJson.ts b/src/optimizeJson.ts index 442a72a1..00547ffe 100644 --- a/src/optimizeJson.ts +++ b/src/optimizeJson.ts @@ -192,7 +192,12 @@ export default class JsonOptimizer { for (const [key, removePropsId] of removedProps) { for (const removePropId of removePropsId) { const removeProp = propertiesById[removePropId] - delete dataByKeys[key][removeProp] + // todo: this is not correct! + if (Array.isArray(dataByKeys[key])) { + dataByKeys[key].splice(removeProp, 1) + } else { + delete dataByKeys[key][removeProp] + } } } if (versionToNumber(versionKey) <= versionToNumber(targetKey)) { diff --git a/src/GlobalSearchInput.tsx b/src/react/GlobalSearchInput.tsx similarity index 87% rename from src/GlobalSearchInput.tsx rename to src/react/GlobalSearchInput.tsx index d9266950..6f2d56d1 100644 --- a/src/GlobalSearchInput.tsx +++ b/src/react/GlobalSearchInput.tsx @@ -1,6 +1,6 @@ import { useSnapshot } from 'valtio' -import { miscUiState } from './globalState' -import Input from './react/Input' +import { miscUiState } from '../globalState' +import Input from './Input' function InnerSearch () { const { currentTouch } = useSnapshot(miscUiState) @@ -19,7 +19,6 @@ function InnerSearch () { autoFocus={currentTouch === false} width={50} placeholder='Search...' - defaultValue="" onChange={({ target: { value } }) => { customEvents.emit('search', value) }} diff --git a/src/react/Input.tsx b/src/react/Input.tsx index c3d40491..41dbc7ba 100644 --- a/src/react/Input.tsx +++ b/src/react/Input.tsx @@ -27,8 +27,16 @@ export default ({ autoFocus, rootStyles, inputRef, validateInput, ...inputProps return
{ setValidationStyle(validateInput?.(e.target.value) ?? {}) setValue(e.target.value) diff --git a/src/reactUi.tsx b/src/reactUi.tsx index b40c47a1..029b2493 100644 --- a/src/reactUi.tsx +++ b/src/reactUi.tsx @@ -28,7 +28,7 @@ import SoundMuffler from './react/SoundMuffler' import TouchControls from './react/TouchControls' import widgets from './react/widgets' import { useIsWidgetActive } from './react/utilsApp' -import GlobalSearchInput from './GlobalSearchInput' +import GlobalSearchInput from './react/GlobalSearchInput' import TouchAreasControlsProvider from './react/TouchAreasControlsProvider' import NotificationProvider, { showNotification } from './react/NotificationProvider' import HotbarRenderApp from './react/HotbarRenderApp' From b2ac80602c8850b4f54ba13e653f56cda66b4190 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 3 Sep 2024 01:10:11 +0300 Subject: [PATCH 016/834] feat(important): redirect to origin website from maps.mcraft.fun which makes testing maps so much easier on preview deploys and locally --- src/react/MainMenuRenderApp.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/react/MainMenuRenderApp.tsx b/src/react/MainMenuRenderApp.tsx index acd61803..37c7966b 100644 --- a/src/react/MainMenuRenderApp.tsx +++ b/src/react/MainMenuRenderApp.tsx @@ -70,6 +70,9 @@ export default () => { } }, []) + let mapsProviderUrl = appConfig?.mapsProvider + if (mapsProviderUrl && location.origin !== 'https://mcraft.fun') mapsProviderUrl = mapsProviderUrl + '?to=' + encodeURIComponent(location.href) + // todo clean, use custom csstransition return {(state) =>
@@ -107,7 +110,7 @@ export default () => { openFilePicker() } }} - mapsProvider={appConfig?.mapsProvider} + mapsProvider={mapsProviderUrl} versionStatus={versionStatus} versionTitle={versionTitle} onVersionClick={async () => { From 559f535207c5dbeab23dbeb82b965a84bf8a377b Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 3 Sep 2024 01:11:49 +0300 Subject: [PATCH 017/834] don't lie of resoure pack support --- README.MD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.MD b/README.MD index b21e8c8a..8ecd7e78 100644 --- a/README.MD +++ b/README.MD @@ -17,7 +17,7 @@ For building the project yourself / contributing, see [Development, Debugging & - Works offline - Play with friends over internet! (P2P is powered by Peer.js discovery servers) - First-class touch (mobile) & controller support -- FULL Resource pack support: Custom GUI, all textures & custom models! Server resource packs are also supported. +- Basic Resource pack support: Custom GUI, all textures. Server resource packs are not supported yet. - Builtin JEI with recipes & guides for every item (also replaces creative inventory) - even even more! From 698fb1d388a0c0dc2805a9ae6d2af7c4cdcc0183 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 3 Sep 2024 01:13:12 +0300 Subject: [PATCH 018/834] fix tsc --- src/optimizeJson.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/optimizeJson.ts b/src/optimizeJson.ts index 00547ffe..491c8301 100644 --- a/src/optimizeJson.ts +++ b/src/optimizeJson.ts @@ -194,7 +194,7 @@ export default class JsonOptimizer { const removeProp = propertiesById[removePropId] // todo: this is not correct! if (Array.isArray(dataByKeys[key])) { - dataByKeys[key].splice(removeProp, 1) + dataByKeys[key].splice(removeProp as any, 1) // splice accepts strings as well } else { delete dataByKeys[key][removeProp] } From c2a34ea9f1874ef675e8bccebfc250f2ea4273fa Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 3 Sep 2024 02:48:16 +0300 Subject: [PATCH 019/834] fix(preflat-worlds): improve mesher performance by 2x by syncing the code from webgpu branch fixes #191 --- prismarine-viewer/examples/shared.ts | 11 ++ prismarine-viewer/viewer/lib/mesher/mesher.ts | 12 +- prismarine-viewer/viewer/lib/mesher/models.ts | 158 +++++++++++------- prismarine-viewer/viewer/lib/mesher/shared.ts | 26 ++- prismarine-viewer/viewer/lib/mesher/world.ts | 2 + .../viewer/lib/worldrendererCommon.ts | 19 ++- 6 files changed, 164 insertions(+), 64 deletions(-) create mode 100644 prismarine-viewer/examples/shared.ts diff --git a/prismarine-viewer/examples/shared.ts b/prismarine-viewer/examples/shared.ts new file mode 100644 index 00000000..4ef9b417 --- /dev/null +++ b/prismarine-viewer/examples/shared.ts @@ -0,0 +1,11 @@ +export type BlockFaceType = { + side: number + textureIndex: number + textureName?: string + tint?: [number, number, number] + isTransparent?: boolean +} + +export type BlockType = { + faces: BlockFaceType[] +} diff --git a/prismarine-viewer/viewer/lib/mesher/mesher.ts b/prismarine-viewer/viewer/lib/mesher/mesher.ts index 118f79c7..4813cfc9 100644 --- a/prismarine-viewer/viewer/lib/mesher/mesher.ts +++ b/prismarine-viewer/viewer/lib/mesher/mesher.ts @@ -11,6 +11,7 @@ if (module.require) { global.performance = r('perf_hooks').performance } +let workerIndex = 0 let world: World let dirtySections = new Map() let allDataReady = false @@ -85,8 +86,9 @@ const handleMessage = data => { switch (data.type) { case 'mesherData': { - setMesherData(data.blockstatesModels, data.blocksAtlas) + setMesherData(data.blockstatesModels, data.blocksAtlas, data.config.outputFormat === 'webgpu') allDataReady = true + workerIndex = data.workerIndex break } @@ -148,18 +150,22 @@ setInterval(() => { for (const key of dirtySections.keys()) { const [x, y, z] = key.split(',').map(v => parseInt(v, 10)) const chunk = world.getColumn(x, z) + let processTime = 0 if (chunk?.getSection(new Vec3(x, y, z))) { + const start = performance.now() const geometry = getSectionGeometry(x, y, z, world) - const transferable = [geometry.positions.buffer, geometry.normals.buffer, geometry.colors.buffer, geometry.uvs.buffer] + const transferable = [geometry.positions?.buffer, geometry.normals?.buffer, geometry.colors?.buffer, geometry.uvs?.buffer].filter(Boolean) //@ts-expect-error postMessage({ type: 'geometry', key, geometry }, transferable) + processTime = performance.now() - start } else { // console.info('[mesher] Missing section', x, y, z) } const dirtyTimes = dirtySections.get(key) if (!dirtyTimes) throw new Error('dirtySections.get(key) is falsy') for (let i = 0; i < dirtyTimes; i++) { - postMessage({ type: 'sectionFinished', key }) + postMessage({ type: 'sectionFinished', key, workerIndex, processTime }) + processTime = 0 } dirtySections.delete(key) } diff --git a/prismarine-viewer/viewer/lib/mesher/models.ts b/prismarine-viewer/viewer/lib/mesher/models.ts index c4249ac4..54c879b8 100644 --- a/prismarine-viewer/viewer/lib/mesher/models.ts +++ b/prismarine-viewer/viewer/lib/mesher/models.ts @@ -1,8 +1,10 @@ import { Vec3 } from 'vec3' import worldBlockProvider, { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider' import legacyJson from '../../../../src/preflatMap.json' +import { BlockType } from '../../../examples/shared' import { World, BlockModelPartsResolved, WorldBlock as Block } from './world' import { BlockElement, buildRotationMatrix, elemFaces, matmul3, matmulmat3, vecadd3, vecsub3 } from './modelsGeometryCommon' +import { MesherGeometryOutput } from './shared' let blockProvider: WorldBlockProvider @@ -19,6 +21,19 @@ for (const key of Object.keys(tintsData)) { tints[key] = prepareTints(tintsData[key]) } +type TestTileData = { + block: string + faces: Array<{ + face: string + neighbor: string + light?: number + }> +} + +type Tiles = { + [blockPos: string]: BlockType & TestTileData +} + function prepareTints (tints) { const map = new Map() const defaultValue = tintToGl(tints.default) @@ -54,19 +69,25 @@ export function preflatBlockCalculation (block: Block, world: World, position: V ] // set needed props to true: east:'false',north:'false',south:'false',west:'false' const props = {} + let changed = false for (const [i, neighbor] of neighbors.entries()) { const isConnectedToSolid = isSolidConnection ? (neighbor && !neighbor.transparent) : false if (isConnectedToSolid || neighbor?.name === block.name) { props[['south', 'north', 'east', 'west'][i]] = 'true' + changed = true } } - return props + return changed ? props : undefined } // case 'gate_in_wall': {} case 'block_snowy': { const aboveIsSnow = world.getBlock(position.offset(0, 1, 0))?.name === 'snow' - return { - snowy: `${aboveIsSnow}` + if (aboveIsSnow) { + return { + snowy: `${aboveIsSnow}` + } + } else { + return } } case 'door': { @@ -139,7 +160,7 @@ function renderLiquid (world: World, cursor: Vec3, texture: any | undefined, typ if (!neighbor) continue if (neighbor.type === type) continue const isGlass = neighbor.name.includes('glass') - if ((isCube(neighbor) && !isUp) || neighbor.getProperties().waterlogged) continue + if ((isCube(neighbor) && !isUp) || neighbor.material === 'plant' || neighbor.getProperties().waterlogged) continue let tint = [1, 1, 1] if (water) { @@ -151,11 +172,12 @@ function renderLiquid (world: World, cursor: Vec3, texture: any | undefined, typ } if (needTiles) { - attr.tiles[`${cursor.x},${cursor.y},${cursor.z}`] ??= { + const tiles = attr.tiles as Tiles + tiles[`${cursor.x},${cursor.y},${cursor.z}`] ??= { block: 'water', faces: [], } - attr.tiles[`${cursor.x},${cursor.y},${cursor.z}`].faces.push({ + tiles[`${cursor.x},${cursor.y},${cursor.z}`].faces.push({ face, neighbor: `${neighborPos.x},${neighborPos.y},${neighborPos.z}`, // texture: eFace.texture.name, @@ -183,7 +205,7 @@ function renderLiquid (world: World, cursor: Vec3, texture: any | undefined, typ let needRecompute = false -function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO: boolean, attr: Record, globalMatrix: any, globalShift: any, block: Block, biome: string) { +function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO: boolean, attr: MesherGeometryOutput, globalMatrix: any, globalShift: any, block: Block, biome: string) { const position = cursor // const key = `${position.x},${position.y},${position.z}` // if (!globalThis.allowedBlocks.includes(key)) return @@ -192,7 +214,7 @@ function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO: // eslint-disable-next-line guard-for-in for (const face in element.faces) { const eFace = element.faces[face] - const { corners, mask1, mask2 } = elemFaces[face] + const { corners, mask1, mask2, side } = elemFaces[face] const dir = matmul3(globalMatrix, elemFaces[face].dir) if (eFace.cullface) { @@ -214,7 +236,10 @@ function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO: const maxz = element.to[2] const texture = eFace.texture as any - const { u, v, su, sv } = texture + const { u } = texture + const { v } = texture + const { su } = texture + const { sv } = texture const ndx = Math.floor(attr.positions.length / 3) @@ -246,7 +271,7 @@ function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO: let localMatrix = null as any let localShift = null as any - if (element.rotation) { + if (element.rotation && !needTiles) { // todo do we support rescale? localMatrix = buildRotationMatrix( element.rotation.axis, @@ -272,21 +297,23 @@ function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO: (pos[2] ? maxz : minz) ] - vertex = vecadd3(matmul3(localMatrix, vertex), localShift) - vertex = vecadd3(matmul3(globalMatrix, vertex), globalShift) - vertex = vertex.map(v => v / 16) + if (!needTiles) { + vertex = vecadd3(matmul3(localMatrix, vertex), localShift) + vertex = vecadd3(matmul3(globalMatrix, vertex), globalShift) + vertex = vertex.map(v => v / 16) - attr.positions.push( - vertex[0] + (cursor.x & 15) - 8, - vertex[1] + (cursor.y & 15) - 8, - vertex[2] + (cursor.z & 15) - 8 - ) + attr.positions.push( + vertex[0] + (cursor.x & 15) - 8, + vertex[1] + (cursor.y & 15) - 8, + vertex[2] + (cursor.z & 15) - 8 + ) - attr.normals.push(...dir) + attr.normals.push(...dir) - const baseu = (pos[3] - 0.5) * uvcs - (pos[4] - 0.5) * uvsn + 0.5 - const basev = (pos[3] - 0.5) * uvsn + (pos[4] - 0.5) * uvcs + 0.5 - attr.uvs.push(baseu * su + u, basev * sv + v) + const baseu = (pos[3] - 0.5) * uvcs - (pos[4] - 0.5) * uvsn + 0.5 + const basev = (pos[3] - 0.5) * uvsn + (pos[4] - 0.5) * uvcs + 0.5 + attr.uvs.push(baseu * su + u, basev * sv + v) + } let light = 1 if (doAO) { @@ -322,40 +349,49 @@ function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO: aos.push(ao) } - attr.colors.push(baseLight * tint[0] * light, baseLight * tint[1] * light, baseLight * tint[2] * light) + if (!needTiles) { + attr.colors.push(baseLight * tint[0] * light, baseLight * tint[1] * light, baseLight * tint[2] * light) + } } + const lightWithColor = [baseLight * tint[0], baseLight * tint[1], baseLight * tint[2]] as [number, number, number] + if (needTiles) { - attr.tiles[`${cursor.x},${cursor.y},${cursor.z}`] ??= { + const tiles = attr.tiles as Tiles + tiles[`${cursor.x},${cursor.y},${cursor.z}`] ??= { block: block.name, faces: [], } - attr.tiles[`${cursor.x},${cursor.y},${cursor.z}`].faces.push({ - face, - neighbor: `${neighborPos.x},${neighborPos.y},${neighborPos.z}`, - light: baseLight - // texture: eFace.texture.name, - }) + const needsOnlyOneFace = false + const isTilesEmpty = tiles[`${cursor.x},${cursor.y},${cursor.z}`].faces.length < 1 + if (isTilesEmpty || !needsOnlyOneFace) { + tiles[`${cursor.x},${cursor.y},${cursor.z}`].faces.push({ + face, + side, + textureIndex: eFace.texture.tileIndex, + neighbor: `${neighborPos.x},${neighborPos.y},${neighborPos.z}`, + light: baseLight, + tint: lightWithColor, + //@ts-expect-error debug prop + texture: eFace.texture.debugName || block.name, + } satisfies BlockType['faces'][number] & TestTileData['faces'][number] as any) + } } - if (doAO && aos[0] + aos[3] >= aos[1] + aos[2]) { - attr.indices.push( - // eslint-disable-next-line @stylistic/function-call-argument-newline - ndx, ndx + 3, ndx + 2, - ndx, ndx + 1, ndx + 3 - ) - } else { - attr.indices.push( - // eslint-disable-next-line @stylistic/function-call-argument-newline - ndx, ndx + 1, ndx + 2, - ndx + 2, ndx + 1, ndx + 3 - ) + if (!needTiles) { + if (doAO && aos[0] + aos[3] >= aos[1] + aos[2]) { + attr.indices.push( + ndx, ndx + 3, ndx + 2, ndx, ndx + 1, ndx + 3 + ) + } else { + attr.indices.push( + ndx, ndx + 1, ndx + 2, ndx + 2, ndx + 1, ndx + 3 + ) + } } } } -const makeLooseObj = (obj: Record) => obj - const invisibleBlocks = new Set(['air', 'cave_air', 'void_air', 'barrier']) const isBlockWaterlogged = (block: Block) => block.getProperties().waterlogged === true || block.getProperties().waterlogged === 'true' @@ -365,7 +401,7 @@ let erroredBlockModel: BlockModelPartsResolved export function getSectionGeometry (sx, sy, sz, world: World) { let delayedRender = [] as Array<() => void> - const attr = makeLooseObj({ + const attr: MesherGeometryOutput = { sx: sx + 8, sy: sy + 8, sz: sz + 8, @@ -381,9 +417,10 @@ export function getSectionGeometry (sx, sy, sz, world: World) { tiles: {}, // todo this can be removed here signs: {}, + isFull: true, highestBlocks: {}, hadErrors: false - } as Record) + } const cursor = new Vec3(0, 0, 0) for (cursor.y = sy; cursor.y < sy + 16; cursor.y++) { @@ -419,19 +456,17 @@ export function getSectionGeometry (sx, sy, sz, world: World) { } const biome = block.biome.name - let preflatRecomputeVariant = !!(block as any)._originalProperties if (world.preflat) { const patchProperties = preflatBlockCalculation(block, world, cursor) if (patchProperties) { - //@ts-expect-error block._originalProperties ??= block._properties - //@ts-expect-error block._properties = { ...block._originalProperties, ...patchProperties } - preflatRecomputeVariant = true + if (block.models && JSON.stringify(block._originalProperties) !== JSON.stringify(block._properties)) { + // recompute models + block.models = undefined + } } else { - //@ts-expect-error block._properties = block._originalProperties ?? block._properties - //@ts-expect-error block._originalProperties = undefined } } @@ -449,7 +484,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) { if (block.name !== 'water' && block.name !== 'lava' && !invisibleBlocks.has(block.name)) { // cache let { models } = block - if (block.models === undefined || preflatRecomputeVariant) { + if (block.models === undefined) { try { models = blockProvider.getAllResolvedModels0_1({ name: block.name, @@ -515,7 +550,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) { delayedRender = [] let ndx = attr.positions.length / 3 - for (let i = 0; i < attr.t_positions.length / 12; i++) { + for (let i = 0; i < attr.t_positions!.length / 12; i++) { attr.indices.push( ndx, ndx + 1, ndx + 2, ndx + 2, ndx + 1, ndx + 3, // eslint-disable-next-line @stylistic/function-call-argument-newline @@ -525,10 +560,10 @@ export function getSectionGeometry (sx, sy, sz, world: World) { ndx += 4 } - attr.positions.push(...attr.t_positions) - attr.normals.push(...attr.t_normals) - attr.colors.push(...attr.t_colors) - attr.uvs.push(...attr.t_uvs) + attr.positions.push(...attr.t_positions!) + attr.normals.push(...attr.t_normals!) + attr.colors.push(...attr.t_colors!) + attr.uvs.push(...attr.t_uvs!) delete attr.t_positions delete attr.t_normals @@ -540,6 +575,13 @@ export function getSectionGeometry (sx, sy, sz, world: World) { attr.colors = new Float32Array(attr.colors) as any attr.uvs = new Float32Array(attr.uvs) as any + if (needTiles) { + delete attr.positions + delete attr.normals + delete attr.colors + delete attr.uvs + } + return attr } diff --git a/prismarine-viewer/viewer/lib/mesher/shared.ts b/prismarine-viewer/viewer/lib/mesher/shared.ts index 782a3141..f96c7d4b 100644 --- a/prismarine-viewer/viewer/lib/mesher/shared.ts +++ b/prismarine-viewer/viewer/lib/mesher/shared.ts @@ -1,11 +1,35 @@ +import { BlockType } from '../../../examples/shared' + export const defaultMesherConfig = { version: '', enableLighting: true, skyLight: 15, smoothLighting: true, - outputFormat: 'threeJs' as 'threeJs' | 'webgl', + outputFormat: 'threeJs' as 'threeJs' | 'webgpu', textureSize: 1024, // for testing debugModelVariant: undefined as undefined | number[] } export type MesherConfig = typeof defaultMesherConfig + +export type MesherGeometryOutput = { + sx: number, + sy: number, + sz: number, + // resulting: float32array + positions: any, + normals: any, + colors: any, + uvs: any, + t_positions?: number[], + t_normals?: number[], + t_colors?: number[], + t_uvs?: number[], + + indices: number[], + tiles: Record, + signs: Record, + isFull: boolean + highestBlocks: Record + hadErrors: boolean +} diff --git a/prismarine-viewer/viewer/lib/mesher/world.ts b/prismarine-viewer/viewer/lib/mesher/world.ts index db940f66..ae65118c 100644 --- a/prismarine-viewer/viewer/lib/mesher/world.ts +++ b/prismarine-viewer/viewer/lib/mesher/world.ts @@ -26,6 +26,8 @@ export type WorldBlock = Omit & { isCube: boolean /** cache */ models?: BlockModelPartsResolved | null + _originalProperties?: Record + _properties?: Record } diff --git a/prismarine-viewer/viewer/lib/worldrendererCommon.ts b/prismarine-viewer/viewer/lib/worldrendererCommon.ts index 482907d4..fe5a3ad2 100644 --- a/prismarine-viewer/viewer/lib/worldrendererCommon.ts +++ b/prismarine-viewer/viewer/lib/worldrendererCommon.ts @@ -91,8 +91,17 @@ export abstract class WorldRendererCommon items?: CustomTexturesData blocks?: CustomTexturesData } = {} + workersProcessAverageTime = 0 + workersProcessAverageTimeCount = 0 + maxWorkersProcessTime = 0 + edgeChunks = {} as Record + lastAddChunk = null as null | { + timeout: any + x: number + z: number + } - abstract outputFormat: 'threeJs' | 'webgl' + abstract outputFormat: 'threeJs' | 'webgpu' constructor (public config: WorldRendererConfig) { // this.initWorkers(1) // preload script on page load @@ -145,6 +154,11 @@ export abstract class WorldRendererCommon } this.renderUpdateEmitter.emit('update') + if (data.processTime) { + this.workersProcessAverageTimeCount++ + this.workersProcessAverageTime = ((this.workersProcessAverageTime * (this.workersProcessAverageTimeCount - 1)) + data.processTime) / this.workersProcessAverageTimeCount + this.maxWorkersProcessTime = Math.max(this.maxWorkersProcessTime, data.processTime) + } } } worker.onmessage = ({ data }) => { @@ -265,7 +279,7 @@ export abstract class WorldRendererCommon this.currentTextureImage = this.material.map.image this.mesherConfig.textureSize = this.material.map.image.width - for (const worker of this.workers) { + for (const [i, worker] of this.workers.entries()) { const { blockstatesModels } = this if (this.customBlockStates) { // TODO! remove from other versions as well @@ -282,6 +296,7 @@ export abstract class WorldRendererCommon } worker.postMessage({ type: 'mesherData', + workerIndex: i, blocksAtlas: { latest: blocksAtlas }, From 684261e5157a8891b8e2ec65283c314a517f7328 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 3 Sep 2024 03:08:19 +0300 Subject: [PATCH 020/834] fix building, update test types --- prismarine-viewer/examples/shared.ts | 19 +++++++++++++------ prismarine-viewer/viewer/lib/mesher/models.ts | 15 ++++----------- .../viewer/lib/worldrendererCommon.ts | 7 ++++--- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/prismarine-viewer/examples/shared.ts b/prismarine-viewer/examples/shared.ts index 4ef9b417..d7a402dd 100644 --- a/prismarine-viewer/examples/shared.ts +++ b/prismarine-viewer/examples/shared.ts @@ -1,11 +1,18 @@ export type BlockFaceType = { - side: number - textureIndex: number - textureName?: string - tint?: [number, number, number] - isTransparent?: boolean + side: number + textureIndex: number + tint?: [number, number, number] + isTransparent?: boolean + + // for testing + face: string + neighbor: string + light?: number } export type BlockType = { - faces: BlockFaceType[] + faces: BlockFaceType[] + + // for testing + block: string } diff --git a/prismarine-viewer/viewer/lib/mesher/models.ts b/prismarine-viewer/viewer/lib/mesher/models.ts index 54c879b8..1f19fc71 100644 --- a/prismarine-viewer/viewer/lib/mesher/models.ts +++ b/prismarine-viewer/viewer/lib/mesher/models.ts @@ -21,17 +21,8 @@ for (const key of Object.keys(tintsData)) { tints[key] = prepareTints(tintsData[key]) } -type TestTileData = { - block: string - faces: Array<{ - face: string - neighbor: string - light?: number - }> -} - type Tiles = { - [blockPos: string]: BlockType & TestTileData + [blockPos: string]: BlockType } function prepareTints (tints) { @@ -180,6 +171,8 @@ function renderLiquid (world: World, cursor: Vec3, texture: any | undefined, typ tiles[`${cursor.x},${cursor.y},${cursor.z}`].faces.push({ face, neighbor: `${neighborPos.x},${neighborPos.y},${neighborPos.z}`, + side: 0, // todo + textureIndex: 0, // texture: eFace.texture.name, }) } @@ -374,7 +367,7 @@ function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO: tint: lightWithColor, //@ts-expect-error debug prop texture: eFace.texture.debugName || block.name, - } satisfies BlockType['faces'][number] & TestTileData['faces'][number] as any) + } satisfies BlockType['faces'][number]) } } diff --git a/prismarine-viewer/viewer/lib/worldrendererCommon.ts b/prismarine-viewer/viewer/lib/worldrendererCommon.ts index fe5a3ad2..49de2ca0 100644 --- a/prismarine-viewer/viewer/lib/worldrendererCommon.ts +++ b/prismarine-viewer/viewer/lib/worldrendererCommon.ts @@ -14,7 +14,7 @@ import TypedEmitter from 'typed-emitter' import { dynamicMcDataFiles } from '../../buildMesherConfig.mjs' import { toMajorVersion } from '../../../src/utils' import { buildCleanupDecorator } from './cleanupDecorator' -import { defaultMesherConfig } from './mesher/shared' +import { MesherGeometryOutput, defaultMesherConfig } from './mesher/shared' import { chunkPos } from './simpleUtils' import { HandItemBlock } from './holdingBlock' @@ -123,8 +123,9 @@ export abstract class WorldRendererCommon if (!this.active) return this.handleWorkerMessage(data) if (data.type === 'geometry') { - for (const key in data.geometry.highestBlocks) { - const highest = data.geometry.highestBlocks[key] + const geometry = data.geometry as MesherGeometryOutput + for (const key in geometry.highestBlocks) { + const highest = geometry.highestBlocks[key] if (!this.highestBlocks[key] || this.highestBlocks[key].y < highest.y) { this.highestBlocks[key] = highest } From 9e7711e386ef3764f824b5e7d19e93ed79fdc5f4 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 3 Sep 2024 03:15:17 +0300 Subject: [PATCH 021/834] add eaglercraft as alternative, fix types again --- README.MD | 1 + prismarine-viewer/viewer/lib/mesher/models.ts | 2 +- prismarine-viewer/viewer/lib/mesher/shared.ts | 2 +- prismarine-viewer/viewer/lib/mesher/test/mesherTester.ts | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.MD b/README.MD index 8ecd7e78..e875873b 100644 --- a/README.MD +++ b/README.MD @@ -188,3 +188,4 @@ General: ### Alternatives - [https://github.com/ClassiCube/ClassiCube](ClassiCube - Better C# Rewrite) [DEMO](https://www.classicube.net/server/play/?warned=true) +- [https://m.eaglercraft.com/](EaglerCraft) - Eaglercraft runnable on mobile (real Minecraft in the browser) diff --git a/prismarine-viewer/viewer/lib/mesher/models.ts b/prismarine-viewer/viewer/lib/mesher/models.ts index 1f19fc71..88505c06 100644 --- a/prismarine-viewer/viewer/lib/mesher/models.ts +++ b/prismarine-viewer/viewer/lib/mesher/models.ts @@ -410,7 +410,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) { tiles: {}, // todo this can be removed here signs: {}, - isFull: true, + // isFull: true, highestBlocks: {}, hadErrors: false } diff --git a/prismarine-viewer/viewer/lib/mesher/shared.ts b/prismarine-viewer/viewer/lib/mesher/shared.ts index f96c7d4b..30b62c45 100644 --- a/prismarine-viewer/viewer/lib/mesher/shared.ts +++ b/prismarine-viewer/viewer/lib/mesher/shared.ts @@ -29,7 +29,7 @@ export type MesherGeometryOutput = { indices: number[], tiles: Record, signs: Record, - isFull: boolean + // isFull: boolean highestBlocks: Record hadErrors: boolean } diff --git a/prismarine-viewer/viewer/lib/mesher/test/mesherTester.ts b/prismarine-viewer/viewer/lib/mesher/test/mesherTester.ts index 986aa079..2885c2a7 100644 --- a/prismarine-viewer/viewer/lib/mesher/test/mesherTester.ts +++ b/prismarine-viewer/viewer/lib/mesher/test/mesherTester.ts @@ -41,7 +41,7 @@ export const setup = (version, initialBlocks: Array<[number[], string]>) => { reload() const getLights = () => { - return Object.fromEntries(getGeometry().faces.map(({ face, light }) => ([face, light * 15 - 2]))) + return Object.fromEntries(getGeometry().faces.map(({ face, light }) => ([face, (light ?? 0) * 15 - 2]))) } const setLight = (x: number, y: number, z: number, val = 0) => { From 306f894d8cc5c6314bb0495ddaaaa4a19be2cb4c Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 4 Sep 2024 03:18:56 +0300 Subject: [PATCH 022/834] fix(important,singleplayer): stop loading chunks in previous position when teleported to a new pos and always start loading chunks in new pos --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 5337cebf..bd18bc03 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "esbuild-plugin-polyfill-node": "^0.3.0", "express": "^4.18.2", "filesize": "^10.0.12", - "flying-squid": "npm:@zardoy/flying-squid@^0.0.36", + "flying-squid": "npm:@zardoy/flying-squid@^0.0.38", "fs-extra": "^11.1.1", "google-drive-browserfs": "github:zardoy/browserfs#google-drive", "jszip": "^3.10.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8075be60..77810ab0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -117,8 +117,8 @@ importers: specifier: ^10.0.12 version: 10.0.12 flying-squid: - specifier: npm:@zardoy/flying-squid@^0.0.36 - version: '@zardoy/flying-squid@0.0.36(encoding@0.1.13)' + specifier: npm:@zardoy/flying-squid@^0.0.38 + version: '@zardoy/flying-squid@0.0.38(encoding@0.1.13)' fs-extra: specifier: ^11.1.1 version: 11.1.1 @@ -3390,8 +3390,8 @@ packages: resolution: {integrity: sha512-6xm38yGVIa6mKm/DUCF2zFFJhERh/QWp1ufm4cNUvxsONBmfPg8uZ9pZBdOmF6qFGr/HlT6ABBkCSx/dlEtvWg==} engines: {node: '>=12 <14 || 14.2 - 14.9 || >14.10.0'} - '@zardoy/flying-squid@0.0.36': - resolution: {integrity: sha512-d4clMPDpw723SDF5P2mMVNfbthUFLX6OT+vTCECAMshX8/M7CyMq/q9BfBQoeJcBL0H9nplhwtFbnx3Edb2fzA==} + '@zardoy/flying-squid@0.0.38': + resolution: {integrity: sha512-xz/ZuWmva3mlT1cigOudOMqa5iQF2sWsUUVeBNUoqfHscXoXl0TIOXnRScBeEGZjY2fD7meJ24nlcInewgNfZg==} engines: {node: '>=8'} hasBin: true @@ -13366,7 +13366,7 @@ snapshots: '@types/emscripten': 1.39.8 tslib: 1.14.1 - '@zardoy/flying-squid@0.0.36(encoding@0.1.13)': + '@zardoy/flying-squid@0.0.38(encoding@0.1.13)': dependencies: '@tootallnate/once': 2.0.0 change-case: 4.1.2 From 5aaa687392cef67e153270463461ce3a46704813 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 4 Sep 2024 05:03:17 +0300 Subject: [PATCH 023/834] feat: display progress of downloading chunks visually (#195) --- prismarine-viewer/viewer/lib/mesher/models.ts | 10 +- src/react/AppStatus.tsx | 13 ++- src/react/AppStatusProvider.tsx | 33 ++++-- src/react/DiveTransition.tsx | 12 +- src/react/LoadingChunks.css | 12 ++ src/react/LoadingChunks.stories.tsx | 43 +++++++ src/react/LoadingChunks.tsx | 60 ++++++++++ src/react/PauseScreen.tsx | 108 ++++++++++++++---- src/utils.ts | 9 +- 9 files changed, 263 insertions(+), 37 deletions(-) create mode 100644 src/react/LoadingChunks.css create mode 100644 src/react/LoadingChunks.stories.tsx create mode 100644 src/react/LoadingChunks.tsx diff --git a/prismarine-viewer/viewer/lib/mesher/models.ts b/prismarine-viewer/viewer/lib/mesher/models.ts index 88505c06..8b9115e7 100644 --- a/prismarine-viewer/viewer/lib/mesher/models.ts +++ b/prismarine-viewer/viewer/lib/mesher/models.ts @@ -282,6 +282,7 @@ function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO: const aos: number[] = [] const neighborPos = position.plus(new Vec3(...dir)) + // 10% const baseLight = world.getLight(neighborPos, undefined, undefined, block.name) / 15 for (const pos of corners) { let vertex = [ @@ -290,7 +291,7 @@ function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO: (pos[2] ? maxz : minz) ] - if (!needTiles) { + if (!needTiles) { // 10% vertex = vecadd3(matmul3(localMatrix, vertex), localShift) vertex = vecadd3(matmul3(globalMatrix, vertex), globalShift) vertex = vertex.map(v => v / 16) @@ -411,7 +412,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) { // todo this can be removed here signs: {}, // isFull: true, - highestBlocks: {}, + highestBlocks: {}, // todo migrate to map for 2% boost perf hadErrors: false } @@ -449,7 +450,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) { } const biome = block.biome.name - if (world.preflat) { + if (world.preflat) { // 10% perf const patchProperties = preflatBlockCalculation(block, world, cursor) if (patchProperties) { block._originalProperties ??= block._properties @@ -505,6 +506,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) { const model = modelVars[useVariant] ?? modelVars[0] if (!model) continue + // #region 10% let globalMatrix = null as any let globalShift = null as any for (const axis of ['x', 'y', 'z'] as const) { @@ -518,6 +520,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) { globalShift = [8, 8, 8] globalShift = vecsub3(globalShift, matmul3(globalMatrix, globalShift)) } + // #endregion for (const element of model.elements ?? []) { const ao = model.ao ?? true @@ -527,6 +530,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) { renderElement(world, pos, element, ao, attr, globalMatrix, globalShift, block, biome) }) } else { + // 60% renderElement(world, cursor, element, ao, attr, globalMatrix, globalShift, block, biome) } } diff --git a/src/react/AppStatus.tsx b/src/react/AppStatus.tsx index ca83c29a..31f08641 100644 --- a/src/react/AppStatus.tsx +++ b/src/react/AppStatus.tsx @@ -2,8 +2,18 @@ import { useEffect, useState } from 'react' import styles from './appStatus.module.css' import Button from './Button' import Screen from './Screen' +import LoadingChunks from './LoadingChunks' -export default ({ status, isError, hideDots = false, lastStatus = '', backAction = undefined as undefined | (() => void), description = '', actionsSlot = null as React.ReactNode | null }) => { +export default ({ + status, + isError, + hideDots = false, + lastStatus = '', + backAction = undefined as undefined | (() => void), + description = '', + actionsSlot = null as React.ReactNode | null, + children +}) => { const [loadingDots, setLoadingDots] = useState('') useEffect(() => { @@ -52,6 +62,7 @@ export default ({ status, isError, hideDots = false, lastStatus = '', backAction +} + export const tryFindOptionConfig = (option: keyof AppOptions) => { for (const group of Object.values(guiOptionsScheme)) { for (const optionConfig of group) { diff --git a/src/optionsStorage.ts b/src/optionsStorage.ts index b7bf4abe..936d5bfc 100644 --- a/src/optionsStorage.ts +++ b/src/optionsStorage.ts @@ -82,6 +82,8 @@ const defaultOptions = { /** Wether to popup sign editor on server action */ autoSignEditor: true, wysiwygSignEditor: 'auto' as 'auto' | 'always' | 'never', + displayBossBars: false, // boss bar overlay was removed for some reason, enable safely + disabledUiParts: [] as string[], } function getDefaultTouchControlsPositions () { diff --git a/src/react/BossBarOverlayProvider.tsx b/src/react/BossBarOverlayProvider.tsx index 9bb7d948..5cac3c8a 100644 --- a/src/react/BossBarOverlayProvider.tsx +++ b/src/react/BossBarOverlayProvider.tsx @@ -1,5 +1,4 @@ import { useState, useEffect } from 'react' -import { BotEvents } from 'mineflayer' import BossBar, { BossBarType } from './BossBarOverlay' import './BossBarOverlay.css' @@ -8,9 +7,8 @@ export default () => { const [bossBars, setBossBars] = useState(new Map()) useEffect(() => { - // typescript error: no bossBarCreated in BotEvents. Why?? - bot.on('bossBarCreated' as keyof BotEvents, (bossBar) => { - setBossBars(prevBossBars => new Map(prevBossBars.set(bossBar.entityUUID, bossBar))) + bot.on('bossBarCreated', (bossBar) => { + setBossBars(prevBossBars => new Map(prevBossBars.set(bossBar.entityUUID, bossBar as any))) }) bot.on('bossBarUpdated', (bossBar) => { setBossBars(prevBossBars => new Map(prevBossBars.set(bossBar.entityUUID, bossBar as BossBarType))) diff --git a/src/react/DebugOverlay.tsx b/src/react/DebugOverlay.tsx index 9315d8a4..23f0d7db 100644 --- a/src/react/DebugOverlay.tsx +++ b/src/react/DebugOverlay.tsx @@ -14,10 +14,17 @@ export default () => { received: {} as { [key: string]: number }, sent: {} as { [key: string]: number } }) + window.packetsCountByNamePerSec = packetsCountByNamePerSec + const packetsCountByNamePer10Sec = useRef({ + received: {} as { [key: string]: number }, + sent: {} as { [key: string]: number } + }) + window.packetsCountByNamePer10Sec = packetsCountByNamePer10Sec const packetsCountByName = useRef({ received: {} as { [key: string]: number }, sent: {} as { [key: string]: number } }) + window.packetsCountByName = packetsCountByName const ignoredPackets = useRef(new Set([] as any[])) const [packetsString, setPacketsString] = useState('') const [showDebug, setShowDebug] = useState(false) @@ -62,9 +69,11 @@ export default () => { const managePackets = (type, name, data) => { packetsCountByName.current[type][name] ??= 0 packetsCountByName.current[type][name]++ + packetsCountByNamePerSec.current[type][name] ??= 0 + packetsCountByNamePerSec.current[type][name]++ + packetsCountByNamePer10Sec.current[type][name] ??= 0 + packetsCountByNamePer10Sec.current[type][name]++ if (options.debugLogNotFrequentPackets && !ignoredPackets.current.has(name) && !hardcodedListOfDebugPacketsToIgnore[type].includes(name)) { - packetsCountByNamePerSec.current[type][name] ??= 0 - packetsCountByNamePerSec.current[type][name]++ if (packetsCountByNamePerSec.current[type][name] > 5 || packetsCountByName.current[type][name] > 100) { // todo think of tracking the count within 10s console.info(`[packet ${name} was ${type} too frequent] Ignoring...`) ignoredPackets.current.add(name) @@ -76,12 +85,17 @@ export default () => { useEffect(() => { document.addEventListener('keydown', handleF3) + let update = 0 const packetsUpdateInterval = setInterval(() => { setPacketsString(`↓ ${received.current.count} (${(received.current.size / 1024).toFixed(2)} KB/s, ${getFixedFilesize(receivedTotal.current)}) ↑ ${sent.current.count}`) received.current = { ...defaultPacketsCount } sent.current = { ...defaultPacketsCount } packetsCountByNamePerSec.current.received = {} packetsCountByNamePerSec.current.sent = {} + if (update++ % 10 === 0) { + packetsCountByNamePer10Sec.current.received = {} + packetsCountByNamePer10Sec.current.sent = {} + } }, 1000) const freqUpdateInterval = setInterval(() => { diff --git a/src/react/IndicatorEffectsProvider.tsx b/src/react/IndicatorEffectsProvider.tsx index 00b78d8a..b82782e6 100644 --- a/src/react/IndicatorEffectsProvider.tsx +++ b/src/react/IndicatorEffectsProvider.tsx @@ -3,6 +3,7 @@ import { useEffect, useMemo } from 'react' import { inGameError } from '../utils' import { fsState } from '../loadSave' import { miscUiState } from '../globalState' +import { options } from '../optionsStorage' import IndicatorEffects, { EffectType, defaultIndicatorsState } from './IndicatorEffects' import { images } from './effectsImages' @@ -52,6 +53,7 @@ const getEffectIndex = (newEffect: EffectType) => { export default () => { const stateIndicators = useSnapshot(state.indicators) const { hasErrors } = useSnapshot(miscUiState) + const { disabledUiParts } = useSnapshot(options) const { isReadonly, openReadOperations, openWriteOperations } = useSnapshot(fsState) const allIndicators: typeof defaultIndicatorsState = { readonlyFiles: isReadonly, diff --git a/src/react/ScoreboardProvider.tsx b/src/react/ScoreboardProvider.tsx index bcae9907..cb67b941 100644 --- a/src/react/ScoreboardProvider.tsx +++ b/src/react/ScoreboardProvider.tsx @@ -10,6 +10,7 @@ export default function ScoreboardProvider () { useMemo(() => { // useMemo instead of useEffect to register them asap and not after the initial dom render const updateSidebarScoreboard = () => { + addStatPerSec('scoreboard') if (bot.scoreboard.sidebar) { setTitle(bot.scoreboard.sidebar.title) setItems([...bot.scoreboard.sidebar.items]) diff --git a/src/reactUi.tsx b/src/reactUi.tsx index 029b2493..599a18f7 100644 --- a/src/reactUi.tsx +++ b/src/reactUi.tsx @@ -42,6 +42,8 @@ import BedTime from './react/BedTime' import NoModalFoundProvider from './react/NoModalFoundProvider' import SignInMessageProvider from './react/SignInMessageProvider' import BookProvider from './react/BookProvider' +import { options } from './optionsStorage' +import BossBarOverlayProvider from './react/BossBarOverlayProvider' const RobustPortal = ({ children, to }) => { return createPortal({children}, to) @@ -99,6 +101,7 @@ const InGameComponent = ({ children }) => { const InGameUi = () => { const { gameLoaded, showUI: showUIRaw } = useSnapshot(miscUiState) + const { disabledUiParts, displayBossBars } = useSnapshot(options) const hasModals = useSnapshot(activeModalStack).length > 0 const showUI = showUIRaw || hasModals if (!gameLoaded || !bot) return @@ -107,26 +110,27 @@ const InGameUi = () => { {/* apply scaling */}
- - - - - + {!disabledUiParts.includes('death-screen') && } + {!disabledUiParts.includes('debug-overlay') && } + {!disabledUiParts.includes('mobile-top-buttons') && } + {!disabledUiParts.includes('players-list') && } + {!disabledUiParts.includes('chat') && } - - - - - + {!disabledUiParts.includes('title') && } + {!disabledUiParts.includes('scoreboard') && } + {!disabledUiParts.includes('effects-indicators') && } + {!disabledUiParts.includes('crosshair') && } + {!disabledUiParts.includes('books') && } + {!disabledUiParts.includes('bossbars') && displayBossBars && }
- - + {!disabledUiParts.includes('xp-bar') && } + {!disabledUiParts.includes('hud-bars') && }
- {showUI && } + {showUI && !disabledUiParts.includes('hotbar') && }
From 1c7fdc21a6b3c997a2cece632bf40184a02e57d7 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 7 Sep 2024 19:33:16 +0300 Subject: [PATCH 036/834] add a way to to disable neighbor chunk updates and all the UI (needed for perf testing) --- .../viewer/lib/worldrendererCommon.ts | 17 ++++++++++------- src/optionsGuiScheme.tsx | 7 ++++--- src/optionsStorage.ts | 1 + src/reactUi.tsx | 2 +- src/watchOptions.ts | 4 ++++ 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/prismarine-viewer/viewer/lib/worldrendererCommon.ts b/prismarine-viewer/viewer/lib/worldrendererCommon.ts index 49de2ca0..92fc9681 100644 --- a/prismarine-viewer/viewer/lib/worldrendererCommon.ts +++ b/prismarine-viewer/viewer/lib/worldrendererCommon.ts @@ -100,6 +100,7 @@ export abstract class WorldRendererCommon x: number z: number } + neighborChunkUpdates = true abstract outputFormat: 'threeJs' | 'webgpu' @@ -321,7 +322,7 @@ export abstract class WorldRendererCommon for (let y = this.worldConfig.minY; y < this.worldConfig.worldHeight; y += 16) { const loc = new Vec3(x, y, z) this.setSectionDirty(loc) - if (!isLightUpdate || this.mesherConfig.smoothLighting) { + if (this.neighborChunkUpdates && (!isLightUpdate || this.mesherConfig.smoothLighting)) { this.setSectionDirty(loc.offset(-16, 0, 0)) this.setSectionDirty(loc.offset(16, 0, 0)) this.setSectionDirty(loc.offset(0, 0, -16)) @@ -357,12 +358,14 @@ export abstract class WorldRendererCommon worker.postMessage({ type: 'blockUpdate', pos, stateId }) } this.setSectionDirty(pos) - if ((pos.x & 15) === 0) this.setSectionDirty(pos.offset(-16, 0, 0)) - if ((pos.x & 15) === 15) this.setSectionDirty(pos.offset(16, 0, 0)) - if ((pos.y & 15) === 0) this.setSectionDirty(pos.offset(0, -16, 0)) - if ((pos.y & 15) === 15) this.setSectionDirty(pos.offset(0, 16, 0)) - if ((pos.z & 15) === 0) this.setSectionDirty(pos.offset(0, 0, -16)) - if ((pos.z & 15) === 15) this.setSectionDirty(pos.offset(0, 0, 16)) + if (this.neighborChunkUpdates) { + if ((pos.x & 15) === 0) this.setSectionDirty(pos.offset(-16, 0, 0)) + if ((pos.x & 15) === 15) this.setSectionDirty(pos.offset(16, 0, 0)) + if ((pos.y & 15) === 0) this.setSectionDirty(pos.offset(0, -16, 0)) + if ((pos.y & 15) === 15) this.setSectionDirty(pos.offset(0, 16, 0)) + if ((pos.z & 15) === 0) this.setSectionDirty(pos.offset(0, 0, -16)) + if ((pos.z & 15) === 15) this.setSectionDirty(pos.offset(0, 0, 16)) + } } queueAwaited = false diff --git a/src/optionsGuiScheme.tsx b/src/optionsGuiScheme.tsx index fc9e347e..015013d1 100644 --- a/src/optionsGuiScheme.tsx +++ b/src/optionsGuiScheme.tsx @@ -91,6 +91,7 @@ export const guiOptionsScheme: { tooltip: 'Additional distance to keep the chunks loading before unloading them by marking them as too far', }, handDisplay: {}, + neighborChunkUpdates: {}, }, ], main: [ @@ -429,15 +430,15 @@ const Category = ({ children }) =>
{ const { disabledUiParts } = useSnapshot(options) - const currentlyEnabled = disabledUiParts.includes(name) + const currentlyDisabled = disabledUiParts.includes(name) if (addUiText) label = `${label} UI` return + >{currentlyDisabled ? 'Enable' : 'Disable'} {label} } export const tryFindOptionConfig = (option: keyof AppOptions) => { diff --git a/src/optionsStorage.ts b/src/optionsStorage.ts index 936d5bfc..d5a9e5aa 100644 --- a/src/optionsStorage.ts +++ b/src/optionsStorage.ts @@ -84,6 +84,7 @@ const defaultOptions = { wysiwygSignEditor: 'auto' as 'auto' | 'always' | 'never', displayBossBars: false, // boss bar overlay was removed for some reason, enable safely disabledUiParts: [] as string[], + neighborChunkUpdates: true } function getDefaultTouchControlsPositions () { diff --git a/src/reactUi.tsx b/src/reactUi.tsx index 599a18f7..2fb63c19 100644 --- a/src/reactUi.tsx +++ b/src/reactUi.tsx @@ -104,7 +104,7 @@ const InGameUi = () => { const { disabledUiParts, displayBossBars } = useSnapshot(options) const hasModals = useSnapshot(activeModalStack).length > 0 const showUI = showUIRaw || hasModals - if (!gameLoaded || !bot) return + if (!gameLoaded || !bot || disabledUiParts.includes('*')) return return <> diff --git a/src/watchOptions.ts b/src/watchOptions.ts index d26d2b90..926c6be1 100644 --- a/src/watchOptions.ts +++ b/src/watchOptions.ts @@ -62,6 +62,10 @@ export const watchOptionsAfterViewerInit = () => { if (!(viewer.world instanceof WorldRendererThree)) return viewer.world.starField.enabled = o.starfieldRendering }) + + watchValue(options, o => { + viewer.world.neighborChunkUpdates = o.neighborChunkUpdates + }) } let viewWatched = false From a063a0d75b58ecca6d692581aaf47bbc7f051966 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 7 Sep 2024 19:42:50 +0300 Subject: [PATCH 037/834] fix: fix cobblestone_wall and player head (skull) rendering in preflat versions --- prismarine-viewer/viewer/lib/mesher/models.ts | 23 +++++++++++++++--- src/preflatMap.json | 24 +++++++++---------- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/prismarine-viewer/viewer/lib/mesher/models.ts b/prismarine-viewer/viewer/lib/mesher/models.ts index 8b9115e7..c7bce0c0 100644 --- a/prismarine-viewer/viewer/lib/mesher/models.ts +++ b/prismarine-viewer/viewer/lib/mesher/models.ts @@ -479,15 +479,32 @@ export function getSectionGeometry (sx, sy, sz, world: World) { // cache let { models } = block if (block.models === undefined) { + const props = block.getProperties() try { + // fixme + if (world.preflat) { + if (block.name === 'cobblestone_wall') { + props.up = 'true' + for (const key of ['north', 'south', 'east', 'west']) { + const val = props[key] + if (val === 'false' || val === 'true') { + props[key] = val === 'true' ? 'low' : 'none' + } + } + } + } + models = blockProvider.getAllResolvedModels0_1({ name: block.name, - properties: block.getProperties(), + properties: props, })! - if (!models.length) models = null + if (!models.length) { + console.debug('[mesher] block to render not found', block.name, props) + models = null + } } catch (err) { models ??= erroredBlockModel - console.error(`Critical assets error. Unable to get block model for ${block.name}[${JSON.stringify(block.getProperties())}]: ` + err.message, err.stack) + console.error(`Critical assets error. Unable to get block model for ${block.name}[${JSON.stringify(props)}]: ` + err.message, err.stack) attr.hadErrors = true } } diff --git a/src/preflatMap.json b/src/preflatMap.json index fdf2640f..81c2a20a 100644 --- a/src/preflatMap.json +++ b/src/preflatMap.json @@ -925,18 +925,18 @@ "143:11": "oak_button[face=wall,facing=south,powered=true]", "143:12": "oak_button[face=wall,facing=north,powered=true]", "143:13": "oak_button[face=floor,facing=north,powered=true]", - "144:0": "undefined[facing=down,nodrop=false]", - "144:1": "undefined[facing=up,nodrop=false]", - "144:2": "undefined[facing=north,nodrop=false]", - "144:3": "undefined[facing=south,nodrop=false]", - "144:4": "undefined[facing=west,nodrop=false]", - "144:5": "undefined[facing=east,nodrop=false]", - "144:8": "undefined[facing=down,nodrop=true]", - "144:9": "undefined[facing=up,nodrop=true]", - "144:10": "undefined[facing=north,nodrop=true]", - "144:11": "undefined[facing=south,nodrop=true]", - "144:12": "undefined[facing=west,nodrop=true]", - "144:13": "undefined[facing=east,nodrop=true]", + "144:0": "player_head[facing=down]", + "144:1": "player_head[facing=up]", + "144:2": "player_head[facing=north]", + "144:3": "player_head[facing=south]", + "144:4": "player_head[facing=west]", + "144:5": "player_head[facing=east]", + "144:8": "player_head[facing=down]", + "144:9": "player_head[facing=up]", + "144:10": "player_head[facing=north]", + "144:11": "player_head[facing=south]", + "144:12": "player_head[facing=west]", + "144:13": "player_head[facing=east]", "145:0": "anvil[facing=south]", "145:1": "anvil[facing=west]", "145:2": "anvil[facing=north]", From fad9fd6e3a6052e8fe8b291d187e103f5e0b205d Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 7 Sep 2024 19:48:08 +0300 Subject: [PATCH 038/834] fix: provide a hack to just render blocks all the blocks even with unknown states for preflat versions --- prismarine-viewer/viewer/lib/mesher/models.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prismarine-viewer/viewer/lib/mesher/models.ts b/prismarine-viewer/viewer/lib/mesher/models.ts index c7bce0c0..e63a92ea 100644 --- a/prismarine-viewer/viewer/lib/mesher/models.ts +++ b/prismarine-viewer/viewer/lib/mesher/models.ts @@ -497,7 +497,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) { models = blockProvider.getAllResolvedModels0_1({ name: block.name, properties: props, - })! + }, world.preflat)! // fixme! this is a hack (also need a setting for all versions) if (!models.length) { console.debug('[mesher] block to render not found', block.name, props) models = null From d743981fc24f8d6f451c940f2b896b9ddb9e2ae9 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 8 Sep 2024 18:20:34 +0300 Subject: [PATCH 039/834] up workflow files: new commands --- .github/workflows/ci.yml | 2 +- .github/workflows/fix-lint.yml | 23 +++++++++++++++++++++++ .github/workflows/merge-next.yml | 24 ++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/fix-lint.yml create mode 100644 .github/workflows/merge-next.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b1882914..3649e89e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,6 +37,6 @@ jobs: name: cypress-images path: cypress/integration/__image_snapshots__/ - run: node scripts/outdatedGitPackages.mjs - if: github.ref == 'refs/heads/next' + if: ${{ github.ref == 'refs/heads/next' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/fix-lint.yml b/.github/workflows/fix-lint.yml new file mode 100644 index 00000000..990de3fa --- /dev/null +++ b/.github/workflows/fix-lint.yml @@ -0,0 +1,23 @@ +name: Fix Lint Command +on: + issue_comment: + types: [created] +jobs: + deploy: + runs-on: ubuntu-latest + if: >- + github.event.issue.pull_request != '' && + ( + contains(github.event.comment.body, '/fix') + ) + permissions: + pull-requests: write + steps: + - uses: actions/checkout@v2 + with: + ref: refs/pull/${{ github.event.issue.number }}/head + - run: pnpm lint --fix + - name: Push Changes + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/merge-next.yml b/.github/workflows/merge-next.yml new file mode 100644 index 00000000..c7a66454 --- /dev/null +++ b/.github/workflows/merge-next.yml @@ -0,0 +1,24 @@ +name: Update Base Branch Command +on: + issue_comment: + types: [created] +jobs: + deploy: + runs-on: ubuntu-latest + if: >- + github.event.issue.pull_request != '' && + ( + contains(github.event.comment.body, '/update') + ) + permissions: + pull-requests: write + steps: + - uses: actions/checkout@v2 + with: + ref: refs/pull/${{ github.event.issue.number }}/head + - name: Merge From Next + run: git merge next --strategy-option=theirs + - name: Push Changes + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} From f9a4960c3182e523e5492172b699691c128e1898 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 8 Sep 2024 18:28:55 +0300 Subject: [PATCH 040/834] ci: try to fix the commands --- .github/workflows/ci.yml | 2 ++ .github/workflows/merge-next.yml | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3649e89e..a57a1646 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,6 +36,8 @@ jobs: with: name: cypress-images path: cypress/integration/__image_snapshots__/ + - name: print current ref + run: echo ${{ github.ref }} - run: node scripts/outdatedGitPackages.mjs if: ${{ github.ref == 'refs/heads/next' }} env: diff --git a/.github/workflows/merge-next.yml b/.github/workflows/merge-next.yml index c7a66454..8bc34607 100644 --- a/.github/workflows/merge-next.yml +++ b/.github/workflows/merge-next.yml @@ -15,9 +15,11 @@ jobs: steps: - uses: actions/checkout@v2 with: - ref: refs/pull/${{ github.event.issue.number }}/head + fetch-depth: 0 # Fetch all history so we can merge branches + - name: Fetch All Branches + run: git fetch --all - name: Merge From Next - run: git merge next --strategy-option=theirs + run: git merge origin/next --strategy-option=theirs - name: Push Changes uses: ad-m/github-push-action@master with: From e89196041e6eb9d7e998de603fc100619f47811f Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 8 Sep 2024 18:37:06 +0300 Subject: [PATCH 041/834] ci: update commands --- .github/workflows/ci.yml | 4 ++-- .github/workflows/merge-next.yml | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a57a1646..29b2f2d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,8 +37,8 @@ jobs: name: cypress-images path: cypress/integration/__image_snapshots__/ - name: print current ref - run: echo ${{ github.ref }} + run: echo ${{ github.event.pull_request.base.ref }} - run: node scripts/outdatedGitPackages.mjs - if: ${{ github.ref == 'refs/heads/next' }} + if: ${{ github.event.pull_request.base.ref == 'next' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/merge-next.yml b/.github/workflows/merge-next.yml index 8bc34607..c2e5ddf3 100644 --- a/.github/workflows/merge-next.yml +++ b/.github/workflows/merge-next.yml @@ -21,6 +21,4 @@ jobs: - name: Merge From Next run: git merge origin/next --strategy-option=theirs - name: Push Changes - uses: ad-m/github-push-action@master - with: - github_token: ${{ secrets.GITHUB_TOKEN }} + run: git push From f518dce04d0588d7fc580a37846f810a53bdb2eb Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 8 Sep 2024 18:39:13 +0300 Subject: [PATCH 042/834] ci: checkout pr before merge --- .github/workflows/merge-next.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/merge-next.yml b/.github/workflows/merge-next.yml index c2e5ddf3..0b95e51e 100644 --- a/.github/workflows/merge-next.yml +++ b/.github/workflows/merge-next.yml @@ -18,6 +18,8 @@ jobs: fetch-depth: 0 # Fetch all history so we can merge branches - name: Fetch All Branches run: git fetch --all + - name: Checkout PR + run: git checkout ${{ refs/pull/${{ github.event.issue.number }}/head }} - name: Merge From Next run: git merge origin/next --strategy-option=theirs - name: Push Changes From a3ef16a81a5b385135ddd98ea458773547125c3f Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 8 Sep 2024 18:40:37 +0300 Subject: [PATCH 043/834] ci: checkout pr before merge --- .github/workflows/merge-next.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/merge-next.yml b/.github/workflows/merge-next.yml index 0b95e51e..bd84fb02 100644 --- a/.github/workflows/merge-next.yml +++ b/.github/workflows/merge-next.yml @@ -19,7 +19,7 @@ jobs: - name: Fetch All Branches run: git fetch --all - name: Checkout PR - run: git checkout ${{ refs/pull/${{ github.event.issue.number }}/head }} + run: git checkout ${{ github.event.issue.pull_request.head.ref }} - name: Merge From Next run: git merge origin/next --strategy-option=theirs - name: Push Changes From ad8dc1a21a64880d6f6900d4201a460d53d129aa Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 8 Sep 2024 21:03:11 +0300 Subject: [PATCH 044/834] fix: fix compatibility with some versions of new region format files --- pnpm-lock.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 84ea8fa1..bb94a569 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -160,7 +160,7 @@ importers: version: 6.1.1 prismarine-provider-anvil: specifier: github:zardoy/prismarine-provider-anvil#everything - version: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/0228b5252f48a0d6ad7f36d7189851c427fbe8c4(minecraft-data@3.65.0) + version: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/a3f462dc81ded5b46e88e3442f99aadf35f7f699(minecraft-data@3.65.0) prosemirror-example-setup: specifier: ^1.2.2 version: 1.2.2 @@ -7514,8 +7514,8 @@ packages: prismarine-physics@1.8.0: resolution: {integrity: sha512-gbM+S+bmVtOKVv+Z0WGaHMeEeBHISIDsRDRlv8sr0dex3ZJRhuq8djA02CBreguXtI18ZKh6q3TSj2qDr45NHA==} - prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/0228b5252f48a0d6ad7f36d7189851c427fbe8c4: - resolution: {tarball: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/0228b5252f48a0d6ad7f36d7189851c427fbe8c4} + prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/a3f462dc81ded5b46e88e3442f99aadf35f7f699: + resolution: {tarball: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/a3f462dc81ded5b46e88e3442f99aadf35f7f699} version: 2.8.0 prismarine-realms@1.3.2: @@ -13385,7 +13385,7 @@ snapshots: prismarine-entity: 2.3.1 prismarine-item: 1.14.0 prismarine-nbt: 2.5.0 - prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/0228b5252f48a0d6ad7f36d7189851c427fbe8c4(minecraft-data@3.65.0) + prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/a3f462dc81ded5b46e88e3442f99aadf35f7f699(minecraft-data@3.65.0) prismarine-windows: 2.9.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/187a87f6d71cba12881a7bbaa510ed9085bf6da7 rambda: 9.2.0 @@ -18623,7 +18623,7 @@ snapshots: prismarine-nbt: 2.5.0 vec3: 0.1.8 - prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/0228b5252f48a0d6ad7f36d7189851c427fbe8c4(minecraft-data@3.65.0): + prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/a3f462dc81ded5b46e88e3442f99aadf35f7f699(minecraft-data@3.65.0): dependencies: prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/a69b66ab1e4be6b67f25a5a6db15e0ad39e11819 prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/cea0b6c792d7dcbb69dfd20fa48be5fd60ce83ef(minecraft-data@3.65.0) From 3fb872129ed98b677cc4190e332237309f53f771 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 10 Sep 2024 00:29:30 +0300 Subject: [PATCH 045/834] fix: update autojump module --- .github/workflows/merge-next.yml | 1 + package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/merge-next.yml b/.github/workflows/merge-next.yml index bd84fb02..9bed1b3d 100644 --- a/.github/workflows/merge-next.yml +++ b/.github/workflows/merge-next.yml @@ -12,6 +12,7 @@ jobs: ) permissions: pull-requests: write + contents: write steps: - uses: actions/checkout@v2 with: diff --git a/package.json b/package.json index 80c6fdac..0f72fefc 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@dimaka/interface": "0.0.3-alpha.0", "@floating-ui/react": "^0.26.1", "@mui/base": "5.0.0-beta.40", - "@nxg-org/mineflayer-auto-jump": "^0.7.7", + "@nxg-org/mineflayer-auto-jump": "^0.7.8", "@nxg-org/mineflayer-tracker": "^1.2.1", "@react-oauth/google": "^0.12.1", "@stylistic/eslint-plugin": "^2.6.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bb94a569..987a9dd3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,8 +45,8 @@ importers: specifier: 5.0.0-beta.40 version: 5.0.0-beta.40(@types/react@18.2.20)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@nxg-org/mineflayer-auto-jump': - specifier: ^0.7.7 - version: 0.7.7 + specifier: ^0.7.8 + version: 0.7.8 '@nxg-org/mineflayer-tracker': specifier: ^1.2.1 version: 1.2.1 @@ -2110,8 +2110,8 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} deprecated: This functionality has been moved to @npmcli/fs - '@nxg-org/mineflayer-auto-jump@0.7.7': - resolution: {integrity: sha512-50FYsz5rxBuLzOh7wqmg9iN9zdVGD+QjuaPcw/mD7q8Bq6Bq+o1/DfXfpoNGIHaDag80q6FJSpc73MI3Scid8g==} + '@nxg-org/mineflayer-auto-jump@0.7.8': + resolution: {integrity: sha512-o3XVruz2siApRvJKMe9EjQYTMANTMhStM3mRUKpZ1ar/2QqJ6sgyqEZTD9sE/zNA7bcjr49sZfuB8bX4t07Hww==} '@nxg-org/mineflayer-physics-util@1.5.8': resolution: {integrity: sha512-KmCkAqpUo8BbuRdIBs6+V2hWHehz++PRz3lRwIsb47CuG0u4sgLYh37RY3ifAznC6uWvmPK+q3B4ZXwJzPy1MQ==} @@ -11514,7 +11514,7 @@ snapshots: rimraf: 3.0.2 optional: true - '@nxg-org/mineflayer-auto-jump@0.7.7': + '@nxg-org/mineflayer-auto-jump@0.7.8': dependencies: '@nxg-org/mineflayer-physics-util': 1.5.8 strict-event-emitter-types: 2.0.0 From c6c25a7bb94d9c20e48f9852c77831a860c1fc15 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 10 Sep 2024 00:52:39 +0300 Subject: [PATCH 046/834] up again --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 0f72fefc..dce99824 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@dimaka/interface": "0.0.3-alpha.0", "@floating-ui/react": "^0.26.1", "@mui/base": "5.0.0-beta.40", - "@nxg-org/mineflayer-auto-jump": "^0.7.8", + "@nxg-org/mineflayer-auto-jump": "^0.7.11", "@nxg-org/mineflayer-tracker": "^1.2.1", "@react-oauth/google": "^0.12.1", "@stylistic/eslint-plugin": "^2.6.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 987a9dd3..df10d754 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,8 +45,8 @@ importers: specifier: 5.0.0-beta.40 version: 5.0.0-beta.40(@types/react@18.2.20)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@nxg-org/mineflayer-auto-jump': - specifier: ^0.7.8 - version: 0.7.8 + specifier: ^0.7.11 + version: 0.7.11 '@nxg-org/mineflayer-tracker': specifier: ^1.2.1 version: 1.2.1 @@ -2110,8 +2110,8 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} deprecated: This functionality has been moved to @npmcli/fs - '@nxg-org/mineflayer-auto-jump@0.7.8': - resolution: {integrity: sha512-o3XVruz2siApRvJKMe9EjQYTMANTMhStM3mRUKpZ1ar/2QqJ6sgyqEZTD9sE/zNA7bcjr49sZfuB8bX4t07Hww==} + '@nxg-org/mineflayer-auto-jump@0.7.11': + resolution: {integrity: sha512-ex6lYch+YXXZKs/TGIMkspZqWTZ3pkteX4ZZHnrx1D3Yw8xfLaeU/lZ4O/8lH2uInuZbsx5pLKtwChOwKmJTlg==} '@nxg-org/mineflayer-physics-util@1.5.8': resolution: {integrity: sha512-KmCkAqpUo8BbuRdIBs6+V2hWHehz++PRz3lRwIsb47CuG0u4sgLYh37RY3ifAznC6uWvmPK+q3B4ZXwJzPy1MQ==} @@ -11514,7 +11514,7 @@ snapshots: rimraf: 3.0.2 optional: true - '@nxg-org/mineflayer-auto-jump@0.7.8': + '@nxg-org/mineflayer-auto-jump@0.7.11': dependencies: '@nxg-org/mineflayer-physics-util': 1.5.8 strict-event-emitter-types: 2.0.0 From a5dddfaad53ee94769a102ae662e1d2449254768 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 10 Sep 2024 01:20:39 +0300 Subject: [PATCH 047/834] up mineflayer-auto-jump --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index dce99824..0eb733fb 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@dimaka/interface": "0.0.3-alpha.0", "@floating-ui/react": "^0.26.1", "@mui/base": "5.0.0-beta.40", - "@nxg-org/mineflayer-auto-jump": "^0.7.11", + "@nxg-org/mineflayer-auto-jump": "^0.7.12", "@nxg-org/mineflayer-tracker": "^1.2.1", "@react-oauth/google": "^0.12.1", "@stylistic/eslint-plugin": "^2.6.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index df10d754..31adde17 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,8 +45,8 @@ importers: specifier: 5.0.0-beta.40 version: 5.0.0-beta.40(@types/react@18.2.20)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@nxg-org/mineflayer-auto-jump': - specifier: ^0.7.11 - version: 0.7.11 + specifier: ^0.7.12 + version: 0.7.12 '@nxg-org/mineflayer-tracker': specifier: ^1.2.1 version: 1.2.1 @@ -2110,8 +2110,8 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} deprecated: This functionality has been moved to @npmcli/fs - '@nxg-org/mineflayer-auto-jump@0.7.11': - resolution: {integrity: sha512-ex6lYch+YXXZKs/TGIMkspZqWTZ3pkteX4ZZHnrx1D3Yw8xfLaeU/lZ4O/8lH2uInuZbsx5pLKtwChOwKmJTlg==} + '@nxg-org/mineflayer-auto-jump@0.7.12': + resolution: {integrity: sha512-F5vX/lerlWx/5HVlkDNbvrtQ19PL6iG8i4ItPTIRtjGiFzusDefP7DI226zSFR8Wlaw45qHv0jn814p/4/qVdQ==} '@nxg-org/mineflayer-physics-util@1.5.8': resolution: {integrity: sha512-KmCkAqpUo8BbuRdIBs6+V2hWHehz++PRz3lRwIsb47CuG0u4sgLYh37RY3ifAznC6uWvmPK+q3B4ZXwJzPy1MQ==} @@ -11514,7 +11514,7 @@ snapshots: rimraf: 3.0.2 optional: true - '@nxg-org/mineflayer-auto-jump@0.7.11': + '@nxg-org/mineflayer-auto-jump@0.7.12': dependencies: '@nxg-org/mineflayer-physics-util': 1.5.8 strict-event-emitter-types: 2.0.0 From 2c971f331ed61c194ec6adf3b3c76025e63d62ab Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 10 Sep 2024 01:34:26 +0300 Subject: [PATCH 048/834] fix: update entities tracker which should fix playing walking animations when players are standing still --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 0eb733fb..c0ac0cc1 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "@floating-ui/react": "^0.26.1", "@mui/base": "5.0.0-beta.40", "@nxg-org/mineflayer-auto-jump": "^0.7.12", - "@nxg-org/mineflayer-tracker": "^1.2.1", + "@nxg-org/mineflayer-tracker": "^1.2.3", "@react-oauth/google": "^0.12.1", "@stylistic/eslint-plugin": "^2.6.1", "@types/gapi": "^0.0.47", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 31adde17..05b83f3c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,8 +48,8 @@ importers: specifier: ^0.7.12 version: 0.7.12 '@nxg-org/mineflayer-tracker': - specifier: ^1.2.1 - version: 1.2.1 + specifier: ^1.2.3 + version: 1.2.3 '@react-oauth/google': specifier: ^0.12.1 version: 0.12.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -2116,8 +2116,8 @@ packages: '@nxg-org/mineflayer-physics-util@1.5.8': resolution: {integrity: sha512-KmCkAqpUo8BbuRdIBs6+V2hWHehz++PRz3lRwIsb47CuG0u4sgLYh37RY3ifAznC6uWvmPK+q3B4ZXwJzPy1MQ==} - '@nxg-org/mineflayer-tracker@1.2.1': - resolution: {integrity: sha512-SI1ffF8zvg3/ZNE021Ja2W0FZPN+WbQDZf8yFqOcXtPRXAtM9W6HvoACdzXep8BZid7WYgYLIgjKpB+9RqvCNQ==} + '@nxg-org/mineflayer-tracker@1.2.3': + resolution: {integrity: sha512-E7Ik/scU117Rr6kQUHHMBk8qOGh63YlTCGN33jMfeP7L8xmLeSHN3JtV/fbog8Y+R+HgO99yfZiRAaV7z1T6gQ==} '@nxg-org/mineflayer-trajectories@1.1.1': resolution: {integrity: sha512-X103KXlX8+L3uMeK4jQxMUdTizv01sQRSfBizAF/iOAdfQZehRLXr3CYKeJzfwPYGLN0X0JCl++cMEcZVn4vbg==} @@ -11523,7 +11523,7 @@ snapshots: dependencies: '@nxg-org/mineflayer-util-plugin': 1.8.3 - '@nxg-org/mineflayer-tracker@1.2.1': + '@nxg-org/mineflayer-tracker@1.2.3': dependencies: '@nxg-org/mineflayer-trajectories': 1.1.1 '@nxg-org/mineflayer-util-plugin': 1.8.3 From 18bf1aa80a8c8bcbe472f5a6ae803bccdff31adf Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 10 Sep 2024 20:00:09 +0300 Subject: [PATCH 049/834] feat: The commit also adds a new keybind action for the 'F4' key, allowing the user to cycle through different game modes. Depending on the current game mode, the bot's chat command is updated accordingly. --- src/controls.ts | 70 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 19 deletions(-) diff --git a/src/controls.ts b/src/controls.ts index 39e4c8c7..d882e967 100644 --- a/src/controls.ts +++ b/src/controls.ts @@ -291,6 +291,27 @@ const alwaysPressedHandledCommand = (command: Command) => { hideCurrentModal() } } + if (command === 'advanced.lockUrl') { + lockUrl() + } +} + +function lockUrl () { + let newQs = '' + if (fsState.saveLoaded) { + const save = localServer!.options.worldFolder.split('/').at(-1) + newQs = `loadSave=${save}` + } else if (process.env.NODE_ENV === 'development') { + newQs = `reconnect=1` + } else { + const qs = new URLSearchParams() + const { server, version } = localStorage + qs.set('server', server) + if (version) qs.set('version', version) + newQs = String(qs.toString()) + } + + window.history.replaceState({}, '', `${window.location.pathname}?${newQs}`) } function cycleHotbarSlot (dir: 1 | -1) { @@ -390,24 +411,6 @@ contro.on('trigger', ({ command }) => { break } } - if (command === 'advanced.lockUrl') { - let newQs = '' - if (fsState.saveLoaded) { - const save = localServer!.options.worldFolder.split('/').at(-1) - newQs = `loadSave=${save}` - } else if (process.env.NODE_ENV === 'development') { - newQs = `reconnect=1` - } else { - const qs = new URLSearchParams() - const { server, version } = localStorage - qs.set('server', server) - if (version) qs.set('version', version) - newQs = String(qs.toString()) - } - - window.history.replaceState({}, '', `${window.location.pathname}?${newQs}`) - // return - } if (command === 'ui.pauseMenu') { showModal({ reactType: 'pause-screen' }) @@ -472,7 +475,36 @@ export const f3Keybinds = [ await completeTexturePackInstall('default', 'default') } }, - mobileTitle: 'Open Widget' + mobileTitle: 'Reload Textures' + }, + { + key: 'F4', + async action () { + switch (bot.game.gameMode) { + case 'creative': { + bot.chat('/gamemode survival') + + break + } + case 'survival': { + bot.chat('/gamemode adventure') + + break + } + case 'adventure': { + bot.chat('/gamemode spectator') + + break + } + case 'spectator': { + bot.chat('/gamemode creative') + + break + } + // No default + } + }, + mobileTitle: 'Cycle Game Mode' } ] From f9b87d5087629363c94008976b6a372429d9d2c9 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 11 Sep 2024 01:07:46 +0300 Subject: [PATCH 050/834] move playground to rsbuild! now fast reloads! reload on workers change fix cd --- .github/workflows/next-deploy.yml | 5 +- .github/workflows/preview.yml | 5 +- .github/workflows/publish.yml | 5 +- package.json | 13 ++- prismarine-viewer/buildMesherWorker.mjs | 8 +- prismarine-viewer/esbuild.mjs | 96 ---------------------- prismarine-viewer/playground.html | 2 +- prismarine-viewer/rsbuild.config.ts | 37 +++++++++ prismarine-viewer/rsbuildSharedConfig.ts | 100 +++++++++++++++++++++++ rsbuild.config.ts | 84 +++---------------- 10 files changed, 171 insertions(+), 184 deletions(-) delete mode 100644 prismarine-viewer/esbuild.mjs create mode 100644 prismarine-viewer/rsbuild.config.ts create mode 100644 prismarine-viewer/rsbuildSharedConfig.ts diff --git a/.github/workflows/next-deploy.yml b/.github/workflows/next-deploy.yml index f6915839..a01e0bb8 100644 --- a/.github/workflows/next-deploy.yml +++ b/.github/workflows/next-deploy.yml @@ -24,7 +24,10 @@ jobs: run: vercel build --token=${{ secrets.VERCEL_TOKEN }} - run: pnpm build-storybook - name: Copy playground files - run: pnpm build-playground && cp prismarine-viewer/public/index.html .vercel/output/static/playground.html && cp prismarine-viewer/public/playground.js .vercel/output/static/playground.js + run: | + mkdir -p .vercel/output/static/playground + pnpm build-playground + cp -r prismarine-viewer/dist/* .vercel/output/static/playground/ - name: Download Generated Sounds map run: node scripts/downloadSoundsMap.mjs - name: Deploy Project Artifacts to Vercel diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index e0f9736a..c074d2e5 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -35,7 +35,10 @@ jobs: run: vercel build --token=${{ secrets.VERCEL_TOKEN }} - run: pnpm build-storybook - name: Copy playground files - run: pnpm build-playground && cp prismarine-viewer/public/index.html .vercel/output/static/playground.html && cp prismarine-viewer/public/playground.js .vercel/output/static/playground.js + run: | + mkdir -p .vercel/output/static/playground + pnpm build-playground + cp -r prismarine-viewer/dist/* .vercel/output/static/playground/ - name: Download Generated Sounds map run: node scripts/downloadSoundsMap.mjs - name: Deploy Project Artifacts to Vercel diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9472118b..fa1f0281 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -23,7 +23,10 @@ jobs: - run: vercel build --token=${{ secrets.VERCEL_TOKEN }} --prod - run: pnpm build-storybook - name: Copy playground files - run: pnpm build-playground && cp prismarine-viewer/public/index.html .vercel/output/static/playground.html && cp prismarine-viewer/public/playground.js .vercel/output/static/playground.js + run: | + mkdir -p .vercel/output/static/playground + pnpm build-playground + cp -r prismarine-viewer/dist/* .vercel/output/static/playground/ - name: Download Generated Sounds map run: node scripts/downloadSoundsMap.mjs - name: Deploy Project to Vercel diff --git a/package.json b/package.json index c0ac0cc1..d715016a 100644 --- a/package.json +++ b/package.json @@ -6,9 +6,8 @@ "dev-rsbuild": "rsbuild dev", "dev-proxy": "node server.js", "start": "run-p dev-rsbuild dev-proxy watch-mesher", - "start-watch-script": "nodemon -w rsbuild.config.ts --watch", - "build": "rsbuild build", - "build-analyze": "BUNDLE_ANALYZE=true rsbuild build", + "build": "pnpm build-other-workers && rsbuild build", + "build-analyze": "BUNDLE_ANALYZE=true rsbuild build && pnpm build-other-workers", "check-build": "tsx scripts/genShims.ts && tsc && pnpm build", "test:cypress": "cypress run", "test-unit": "vitest", @@ -20,13 +19,13 @@ "build-storybook": "storybook build && node scripts/build.js moveStorybookFiles", "start-experiments": "vite --config experiments/vite.config.ts --host", "watch-other-workers": "echo NOT IMPLEMENTED", + "build-other-workers": "echo NOT IMPLEMENTED", "build-mesher": "node prismarine-viewer/buildMesherWorker.mjs", "watch-mesher": "pnpm build-mesher -w", - "run-playground": "run-p watch-mesher watch-other-workers playground-server watch-playground", + "run-playground": "run-p watch-mesher watch-other-workers watch-playground", "run-all": "run-p start run-playground", - "playground-server": "live-server --port=9090 prismarine-viewer/public", - "build-playground": "node prismarine-viewer/esbuild.mjs", - "watch-playground": "node prismarine-viewer/esbuild.mjs -w" + "build-playground": "rsbuild build --root ./prismarine-viewer", + "watch-playground": "rsbuild dev --root ./prismarine-viewer" }, "keywords": [ "prismarine", diff --git a/prismarine-viewer/buildMesherWorker.mjs b/prismarine-viewer/buildMesherWorker.mjs index f70b8128..03b952b4 100644 --- a/prismarine-viewer/buildMesherWorker.mjs +++ b/prismarine-viewer/buildMesherWorker.mjs @@ -30,7 +30,7 @@ const buildOptions = { sourcemap: 'linked', write: false, metafile: true, - outdir: path.join(__dirname, './public'), + outdir: path.join(__dirname, './dist'), define: { 'process.env.BROWSER': '"true"', }, @@ -108,9 +108,9 @@ const buildOptions = { }) build.onEnd(({ metafile, outputFiles }) => { if (!metafile) return - fs.mkdirSync(path.join(__dirname, './public'), { recursive: true }) - fs.writeFileSync(path.join(__dirname, './public/metafile.json'), JSON.stringify(metafile)) - for (const outDir of ['../dist/', './public/']) { + fs.mkdirSync(path.join(__dirname, './dist'), { recursive: true }) + fs.writeFileSync(path.join(__dirname, './dist/metafile.json'), JSON.stringify(metafile)) + for (const outDir of ['../dist/', './dist/']) { for (const outputFile of outputFiles) { if (outDir === '../dist/' && outputFile.path.endsWith('.map')) { // skip writing & browser loading sourcemap there, worker debugging should be done in playground diff --git a/prismarine-viewer/esbuild.mjs b/prismarine-viewer/esbuild.mjs deleted file mode 100644 index e410c8b6..00000000 --- a/prismarine-viewer/esbuild.mjs +++ /dev/null @@ -1,96 +0,0 @@ -//@ts-check -import * as fs from 'fs' -import fsExtra from 'fs-extra' - -import * as esbuild from 'esbuild' -import { polyfillNode } from 'esbuild-plugin-polyfill-node' -import path, { dirname, join } from 'path' -import { fileURLToPath } from 'url' -import childProcess from 'child_process' -import supportedVersions from '../src/supportedVersions.mjs' - -const dev = process.argv.includes('-w') - -const __dirname = path.dirname(fileURLToPath(new URL(import.meta.url))) - -const mcDataPath = join(__dirname, '../generated/minecraft-data-optimized.json') -if (!fs.existsSync(mcDataPath)) { - childProcess.execSync('tsx ../scripts/makeOptimizedMcData.mjs', { stdio: 'inherit', cwd: __dirname }) -} - -fs.copyFileSync(join(__dirname, 'playground.html'), join(__dirname, 'public/index.html')) - -/** @type {import('esbuild').BuildOptions} */ -const buildOptions = { - bundle: true, - entryPoints: [join(__dirname, './examples/playground.ts')], - // target: ['es2020'], - // logLevel: 'debug', - logLevel: 'info', - platform: 'browser', - sourcemap: dev ? 'inline' : false, - minify: !dev, - outfile: join(__dirname, 'public/playground.js'), - mainFields: [ - 'browser', 'module', 'main' - ], - keepNames: true, - banner: { - js: `globalThis.global = globalThis;globalThis.includedVersions = ${JSON.stringify(supportedVersions)};`, - }, - alias: { - events: 'events', - buffer: 'buffer', - 'fs': 'browserfs/dist/shims/fs.js', - http: 'http-browserify', - stream: 'stream-browserify', - net: 'net-browserify', - // 'mc-assets': '/Users/vitaly/Documents/mc-assets', - }, - inject: [], - metafile: true, - loader: { - '.png': 'dataurl', - '.obj': 'text', - }, - plugins: [ - { - name: 'minecraft-data', - setup(build) { - build.onLoad({ - filter: /minecraft-data[\/\\]data.js$/, - }, () => { - const defaultVersionsObj = {} - return { - contents: fs.readFileSync(join(__dirname, '../src/shims/minecraftData.ts'), 'utf8'), - loader: 'ts', - resolveDir: join(__dirname, '../src/shims'), - } - }) - build.onEnd((e) => { - if (e.errors.length) return - fs.writeFileSync(join(__dirname, './public/metafile.json'), JSON.stringify(e.metafile), 'utf8') - }) - } - }, - polyfillNode({ - polyfills: { - fs: false, - crypto: false, - events: false, - http: false, - stream: false, - buffer: false, - perf_hooks: false, - net: false, - }, - }) - ], -} -if (dev) { - (await esbuild.context(buildOptions)).watch() -} else { - await esbuild.build(buildOptions) -} - -// await ctx.rebuild() diff --git a/prismarine-viewer/playground.html b/prismarine-viewer/playground.html index f1b36015..92713c1c 100644 --- a/prismarine-viewer/playground.html +++ b/prismarine-viewer/playground.html @@ -31,6 +31,6 @@ - +
diff --git a/prismarine-viewer/rsbuild.config.ts b/prismarine-viewer/rsbuild.config.ts new file mode 100644 index 00000000..1299670d --- /dev/null +++ b/prismarine-viewer/rsbuild.config.ts @@ -0,0 +1,37 @@ +import { defineConfig, mergeRsbuildConfig } from '@rsbuild/core'; +import supportedVersions from '../src/supportedVersions.mjs' +import childProcess from 'child_process' +import path, { dirname, join } from 'path' +import { pluginReact } from '@rsbuild/plugin-react'; +import { pluginNodePolyfill } from '@rsbuild/plugin-node-polyfill'; +import fs from 'fs' +import { appAndRendererSharedConfig, rspackViewerConfig } from './rsbuildSharedConfig'; + +const mcDataPath = join(__dirname, '../generated/minecraft-data-optimized.json') + +if (!fs.existsSync(mcDataPath)) { + childProcess.execSync('tsx ./scripts/makeOptimizedMcData.mjs', { stdio: 'inherit', cwd: path.join(__dirname, '..') }) +} + +export default mergeRsbuildConfig( + appAndRendererSharedConfig(), + defineConfig({ + html: { + template: './playground.html', + }, + output: { + cleanDistPath: false, + }, + server: { + port: 9090, + }, + source: { + entry: { + index: join(__dirname, './examples/playground.ts') + }, + define: { + 'globalThis.includedVersions': JSON.stringify(supportedVersions), + }, + }, + }) +) diff --git a/prismarine-viewer/rsbuildSharedConfig.ts b/prismarine-viewer/rsbuildSharedConfig.ts new file mode 100644 index 00000000..3fba1ef1 --- /dev/null +++ b/prismarine-viewer/rsbuildSharedConfig.ts @@ -0,0 +1,100 @@ +import { defineConfig, ModifyRspackConfigUtils } from '@rsbuild/core'; +import { pluginNodePolyfill } from '@rsbuild/plugin-node-polyfill'; +import { pluginReact } from '@rsbuild/plugin-react'; +import path from 'path' + +export const appAndRendererSharedConfig = () => defineConfig({ + dev: { + progressBar: true, + writeToDisk: true, + watchFiles: { + paths: [ + path.join(__dirname, './dist/webgpuRendererWorker.js'), + path.join(__dirname, './dist/mesher.js'), + ] + }, + }, + output: { + polyfill: 'usage', + // 50kb limit for data uri + dataUriLimit: 50 * 1024 + }, + source: { + alias: { + fs: path.join(__dirname, `../src/shims/fs.js`), + http: 'http-browserify', + stream: 'stream-browserify', + net: 'net-browserify', + 'minecraft-protocol$': 'minecraft-protocol/src/index.js', + 'buffer$': 'buffer', + // avoid bundling, not used on client side + 'prismarine-auth': path.join(__dirname, `../src/shims/prismarineAuthReplacement.ts`), + perf_hooks: path.join(__dirname, `../src/shims/perf_hooks_replacement.js`), + crypto: path.join(__dirname, `../src/shims/crypto.js`), + dns: path.join(__dirname, `../src/shims/dns.js`), + yggdrasil: path.join(__dirname, `../src/shims/yggdrasilReplacement.ts`), + 'three$': 'three/src/Three.js', + 'stats.js$': 'stats.js/src/Stats.js', + }, + define: { + 'process.platform': '"browser"', + }, + decorators: { + version: 'legacy', // default is a lie + }, + }, + server: { + htmlFallback: false, + publicDir: false, + headers: { + // enable shared array buffer + 'Cross-Origin-Opener-Policy': 'same-origin', + 'Cross-Origin-Embedder-Policy': 'require-corp', + }, + open: process.env.OPEN_BROWSER === 'true', + }, + plugins: [ + pluginReact(), + pluginNodePolyfill() + ], + tools: { + rspack (config, helpers) { + rspackViewerConfig(config, helpers) + } + }, +}) + +export const rspackViewerConfig = (config, { appendPlugins, addRules, rspack }: ModifyRspackConfigUtils) => { + appendPlugins(new rspack.NormalModuleReplacementPlugin(/data/, (resource) => { + let absolute: string + const request = resource.request.replaceAll('\\', '/') + absolute = path.join(resource.context, request).replaceAll('\\', '/') + if (request.includes('minecraft-data/data/pc/1.')) { + console.log('Error: incompatible resource', request, resource.contextInfo.issuer) + process.exit(1) + // throw new Error(`${resource.request} was requested by ${resource.contextInfo.issuer}`) + } + if (absolute.endsWith('/minecraft-data/data.js')) { + resource.request = path.join(__dirname, `../src/shims/minecraftData.ts`) + } + })) + addRules([ + { + test: /\.obj$/, + type: 'asset/source', + }, + { + test: /\.wgsl$/, + type: 'asset/source', + }, + { + test: /\.mp3$/, + type: 'asset/source', + } + ]) + config.ignoreWarnings = [ + /the request of a dependency is an expression/, + /Unsupported pseudo class or element: xr-overlay/ + ] + +} diff --git a/rsbuild.config.ts b/rsbuild.config.ts index 588cc35d..f8a965dc 100644 --- a/rsbuild.config.ts +++ b/rsbuild.config.ts @@ -1,4 +1,4 @@ -import { defineConfig, RsbuildPluginAPI } from '@rsbuild/core' +import { defineConfig, mergeRsbuildConfig, RsbuildPluginAPI } from '@rsbuild/core' import { pluginReact } from '@rsbuild/plugin-react' import { pluginTypedCSSModules } from '@rsbuild/plugin-typed-css-modules' import { pluginNodePolyfill } from '@rsbuild/plugin-node-polyfill' @@ -10,6 +10,7 @@ import fsExtra from 'fs-extra' import { promisify } from 'util' import { generateSW } from 'workbox-build' import { getSwAdditionalEntries } from './scripts/build' +import { appAndRendererSharedConfig } from './prismarine-viewer/rsbuildSharedConfig' //@ts-ignore try { require('./localSettings.js') } catch { } @@ -20,16 +21,12 @@ const buildingVersion = new Date().toISOString().split(':')[0] const dev = process.env.NODE_ENV === 'development' -export default defineConfig({ - dev: { - progressBar: true, - writeToDisk: true - }, +// base options are in ./prismarine-viewer/rsbuildSharedConfig.ts +const appConfig = defineConfig({ html: { template: './index.html', }, output: { - polyfill: 'usage', externals: [ 'sharp' ], @@ -37,25 +34,8 @@ export default defineConfig({ js: 'source-map', css: true, }, - // 50kb limit for data uri - dataUriLimit: 50 * 1024 }, source: { - alias: { - fs: './src/shims/fs.js', - http: 'http-browserify', - stream: 'stream-browserify', - net: 'net-browserify', - 'minecraft-protocol$': 'minecraft-protocol/src/index.js', - 'buffer$': 'buffer', - // avoid bundling, not used on client side - 'prismarine-auth': './src/shims/prismarineAuthReplacement.ts', - perf_hooks: './src/shims/perf_hooks_replacement.js', - crypto: './src/shims/crypto.js', - dns: './src/shims/dns.js', - yggdrasil: './src/shims/yggdrasilReplacement.ts', - 'three$': 'three/src/Three.js' - }, entry: { index: './src/index.ts', }, @@ -65,36 +45,22 @@ export default defineConfig({ define: { 'process.env.BUILD_VERSION': JSON.stringify(!dev ? buildingVersion : 'undefined'), 'process.env.MAIN_MENU_LINKS': JSON.stringify(process.env.MAIN_MENU_LINKS), - 'process.platform': '"browser"', 'process.env.GITHUB_URL': JSON.stringify(`https://github.com/${process.env.GITHUB_REPOSITORY || `${process.env.VERCEL_GIT_REPO_OWNER}/${process.env.VERCEL_GIT_REPO_SLUG}`}`), 'process.env.DEPS_VERSIONS': JSON.stringify({}) }, - decorators: { - version: 'legacy', // default is a lie - }, }, server: { // strictPort: true, - htmlFallback: false, - publicDir: false, // publicDir: { // name: 'assets', // }, - headers: { - // enable shared array buffer - 'Cross-Origin-Opener-Policy': 'same-origin', - 'Cross-Origin-Embedder-Policy': 'require-corp', - }, - open: process.env.OPEN_BROWSER === 'true', proxy: { '/api': 'http://localhost:8080', }, }, plugins: [ - pluginReact(), pluginTypedCSSModules(), - pluginNodePolyfill(), { name: 'test', setup (build: RsbuildPluginAPI) { @@ -121,9 +87,9 @@ export default defineConfig({ // childProcess.execSync('./scripts/prepareSounds.mjs', { stdio: 'inherit' }) // childProcess.execSync('tsx ./scripts/genMcDataTypes.ts', { stdio: 'inherit' }) // childProcess.execSync('tsx ./scripts/genPixelartTypes.ts', { stdio: 'inherit' }) - if (fs.existsSync('./prismarine-viewer/public/mesher.js') && dev) { + if (fs.existsSync('./prismarine-viewer/dist/mesher.js') && dev) { // copy mesher - fs.copyFileSync('./prismarine-viewer/public/mesher.js', './dist/mesher.js') + fs.copyFileSync('./prismarine-viewer/dist/mesher.js', './dist/mesher.js') } else if (!dev) { await execAsync('pnpm run build-mesher') } @@ -150,39 +116,6 @@ export default defineConfig({ }, }, ], - tools: { - bundlerChain (chain, { CHAIN_ID }) { - }, - rspack (config, { addRules, appendPlugins, rspack }) { - appendPlugins(new rspack.NormalModuleReplacementPlugin(/data/, (resource) => { - let absolute: string - const request = resource.request.replaceAll('\\', '/') - absolute = path.join(resource.context, request).replaceAll('\\', '/') - if (request.includes('minecraft-data/data/pc/1.')) { - console.log('Error: incompatible resource', request, resource.contextInfo.issuer) - process.exit(1) - // throw new Error(`${resource.request} was requested by ${resource.contextInfo.issuer}`) - } - if (absolute.endsWith('/minecraft-data/data.js')) { - resource.request = path.join(__dirname, './src/shims/minecraftData.ts') - } - })) - addRules([ - { - test: /\.obj$/, - type: 'asset/source', - }, - { - test: /\.mp3$/, - type: 'asset/source', - } - ]) - config.ignoreWarnings = [ - /the request of a dependency is an expression/, - /Unsupported pseudo class or element: xr-overlay/ - ] - } - }, // performance: { // bundleAnalyze: { // analyzerMode: 'json', @@ -190,3 +123,8 @@ export default defineConfig({ // }, // }, }) + +export default mergeRsbuildConfig( + appAndRendererSharedConfig(), + appConfig +) From 1446ccc0a7a16b72facfe2c18f4229a8ca71e014 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 11 Sep 2024 02:35:29 +0300 Subject: [PATCH 051/834] should fix playground build --- package.json | 2 +- prismarine-viewer/rsbuild.config.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d715016a..84c3951a 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "watch-mesher": "pnpm build-mesher -w", "run-playground": "run-p watch-mesher watch-other-workers watch-playground", "run-all": "run-p start run-playground", - "build-playground": "rsbuild build --root ./prismarine-viewer", + "build-playground": "rsbuild build --config prismarine-viewer/rsbuild.config.ts", "watch-playground": "rsbuild dev --root ./prismarine-viewer" }, "keywords": [ diff --git a/prismarine-viewer/rsbuild.config.ts b/prismarine-viewer/rsbuild.config.ts index 1299670d..cea001db 100644 --- a/prismarine-viewer/rsbuild.config.ts +++ b/prismarine-viewer/rsbuild.config.ts @@ -17,10 +17,13 @@ export default mergeRsbuildConfig( appAndRendererSharedConfig(), defineConfig({ html: { - template: './playground.html', + template: join(__dirname, './playground.html'), }, output: { cleanDistPath: false, + distPath: { + root: join(__dirname, './dist'), + }, }, server: { port: 9090, From 4d3c92f6111b78af9d91813250aedda5329b6bcb Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 11 Sep 2024 03:19:24 +0300 Subject: [PATCH 052/834] some playground fixes --- README.MD | 2 +- assets/playground.html | 4 ++++ prismarine-viewer/rsbuild.config.ts | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 assets/playground.html diff --git a/README.MD b/README.MD index ae23b60b..6a9df6db 100644 --- a/README.MD +++ b/README.MD @@ -87,7 +87,7 @@ To open the console, press `F12`, or if you are on mobile, you can type `#dev` i It should be easy to build/start the project locally. See [CONTRIBUTING.MD](./CONTRIBUTING.md) for more info. Also you can look at Dockerfile for reference. -There is world renderer playground ([link](https://mcon.vercel.app/playground.html)). +There is world renderer playground ([link](https://mcon.vercel.app/playground)). However, there are many things that can be done in online production version (like debugging actual source code). Also you can access some global variables in the console and there are a few useful examples: diff --git a/assets/playground.html b/assets/playground.html new file mode 100644 index 00000000..e4bb88ec --- /dev/null +++ b/assets/playground.html @@ -0,0 +1,4 @@ + + diff --git a/prismarine-viewer/rsbuild.config.ts b/prismarine-viewer/rsbuild.config.ts index cea001db..fa666d99 100644 --- a/prismarine-viewer/rsbuild.config.ts +++ b/prismarine-viewer/rsbuild.config.ts @@ -24,6 +24,7 @@ export default mergeRsbuildConfig( distPath: { root: join(__dirname, './dist'), }, + assetPrefix: './', }, server: { port: 9090, From 76bed4d496c1b28744e182a7f308ddf8ea8c4e11 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 11 Sep 2024 03:34:55 +0300 Subject: [PATCH 053/834] eslint: ignore dist linting! --- .eslintignore | 1 + assets/playground.html | 2 +- rsbuild.config.ts | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.eslintignore b/.eslintignore index ce8d8d37..bf3201cf 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,4 +3,5 @@ rsbuild.config.ts *.module.css.d.ts *.generated.ts generated +dist public diff --git a/assets/playground.html b/assets/playground.html index e4bb88ec..d4179621 100644 --- a/assets/playground.html +++ b/assets/playground.html @@ -1,4 +1,4 @@ diff --git a/rsbuild.config.ts b/rsbuild.config.ts index f8a965dc..88280acf 100644 --- a/rsbuild.config.ts +++ b/rsbuild.config.ts @@ -77,6 +77,7 @@ const appConfig = defineConfig({ fsExtra.copySync('./node_modules/mc-assets/dist/other-textures/latest/entity', './dist/textures/entity') fsExtra.copySync('./assets/background', './dist/background') fs.copyFileSync('./assets/favicon.png', './dist/favicon.png') + fs.copyFileSync('./assets/playground.html', './dist/playground.html') fs.copyFileSync('./assets/manifest.json', './dist/manifest.json') fs.copyFileSync('./assets/loading-bg.jpg', './dist/loading-bg.jpg') const configJson = JSON.parse(fs.readFileSync('./config.json', 'utf8')) From 74fe84e10d9d3684b1eacdeca9247c4bd6804551 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 11 Sep 2024 19:27:17 +0300 Subject: [PATCH 054/834] fix playground on windows: rsbuild does not implement --root option for win --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 84c3951a..4b41660a 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "run-playground": "run-p watch-mesher watch-other-workers watch-playground", "run-all": "run-p start run-playground", "build-playground": "rsbuild build --config prismarine-viewer/rsbuild.config.ts", - "watch-playground": "rsbuild dev --root ./prismarine-viewer" + "watch-playground": "rsbuild dev --config prismarine-viewer/rsbuild.config.ts" }, "keywords": [ "prismarine", From 25db002bc3ede0620b938e2d41f28938b3d8f7da Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 11 Sep 2024 21:39:15 +0300 Subject: [PATCH 055/834] redirect to correct playground url --- .github/workflows/preview.yml | 2 +- README.MD | 2 +- assets/playground.html | 4 ++-- prismarine-viewer/playground.html | 6 ++++++ 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index c074d2e5..526705e9 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -51,7 +51,7 @@ jobs: allow-repeats: true message: | Deployed to Vercel Preview: ${{ steps.deploy.outputs.stdout }} - [Playground](${{ steps.deploy.outputs.stdout }}/playground.html) + [Playground](${{ steps.deploy.outputs.stdout }}/playground/) [Storybook](${{ steps.deploy.outputs.stdout }}/storybook/) # - run: git checkout next scripts/githubActions.mjs - name: Get deployment alias diff --git a/README.MD b/README.MD index 6a9df6db..9b07bdb5 100644 --- a/README.MD +++ b/README.MD @@ -87,7 +87,7 @@ To open the console, press `F12`, or if you are on mobile, you can type `#dev` i It should be easy to build/start the project locally. See [CONTRIBUTING.MD](./CONTRIBUTING.md) for more info. Also you can look at Dockerfile for reference. -There is world renderer playground ([link](https://mcon.vercel.app/playground)). +There is world renderer playground ([link](https://mcon.vercel.app/playground/)). However, there are many things that can be done in online production version (like debugging actual source code). Also you can access some global variables in the console and there are a few useful examples: diff --git a/assets/playground.html b/assets/playground.html index d4179621..8c394f91 100644 --- a/assets/playground.html +++ b/assets/playground.html @@ -1,4 +1,4 @@ - + diff --git a/prismarine-viewer/playground.html b/prismarine-viewer/playground.html index 92713c1c..c8ea00d2 100644 --- a/prismarine-viewer/playground.html +++ b/prismarine-viewer/playground.html @@ -29,6 +29,12 @@ src: url(../../../assets/mojangles.ttf); } +
From 755eead9764c5b0c9140cf38847f5b04b0f7b9b8 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 11 Sep 2024 21:51:12 +0300 Subject: [PATCH 056/834] correctly capture screenshots of cypress --- .eslintignore | 1 + .github/workflows/ci.yml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.eslintignore b/.eslintignore index bf3201cf..4c431739 100644 --- a/.eslintignore +++ b/.eslintignore @@ -5,3 +5,4 @@ rsbuild.config.ts generated dist public +**/*/rsbuildSharedConfig.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29b2f2d5..e2117da3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: if: failure() with: name: cypress-images - path: cypress/integration/__image_snapshots__/ + path: cypress/screenshots/ - name: print current ref run: echo ${{ github.event.pull_request.base.ref }} - run: node scripts/outdatedGitPackages.mjs From 5a3fb6f2253fa54c227e5e06b84bdf4db85e489f Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 11 Sep 2024 22:06:14 +0300 Subject: [PATCH 057/834] disable Java integration test because of issues with downloading server --- cypress.config.ts | 1 + cypress/e2e/index.spec.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cypress.config.ts b/cypress.config.ts index f9bd9478..861931e3 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -3,6 +3,7 @@ import { defineConfig } from 'cypress' export default defineConfig({ video: false, chromeWebSecurity: false, + screenshotOnRunFailure: true, // Enable screenshots on test failures e2e: { // We've imported your old cypress plugins here. // You may want to clean this up later by importing these. diff --git a/cypress/e2e/index.spec.ts b/cypress/e2e/index.spec.ts index cb1b6880..fc67ad21 100644 --- a/cypress/e2e/index.spec.ts +++ b/cypress/e2e/index.spec.ts @@ -49,7 +49,7 @@ it('Joins to local flying-squid server', () => { testWorldLoad() }) -it('Joins to local latest Java vanilla server', () => { +it.skip('Joins to local latest Java vanilla server', () => { const version = supportedVersions.at(-1)! cy.task('startServer', [version, 25_590]).then(() => { visit('/?ip=localhost:25590&username=bot') From 16bb43c7d929a211b15b474f50b1316eece2e323 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 11 Sep 2024 22:16:26 +0300 Subject: [PATCH 058/834] update current ref --- .github/workflows/ci.yml | 2 +- .github/workflows/merge-next.yml | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e2117da3..d5d77dc7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,6 +39,6 @@ jobs: - name: print current ref run: echo ${{ github.event.pull_request.base.ref }} - run: node scripts/outdatedGitPackages.mjs - if: ${{ github.event.pull_request.base.ref == 'next' }} + if: ${{ github.event.pull_request.base.ref == 'release' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/merge-next.yml b/.github/workflows/merge-next.yml index 9bed1b3d..ee02789b 100644 --- a/.github/workflows/merge-next.yml +++ b/.github/workflows/merge-next.yml @@ -17,10 +17,11 @@ jobs: - uses: actions/checkout@v2 with: fetch-depth: 0 # Fetch all history so we can merge branches + ref: refs/pull/${{ github.event.issue.number }}/head - name: Fetch All Branches run: git fetch --all - - name: Checkout PR - run: git checkout ${{ github.event.issue.pull_request.head.ref }} + # - name: Checkout PR + # run: git checkout ${{ github.event.issue.pull_request.head.ref }} - name: Merge From Next run: git merge origin/next --strategy-option=theirs - name: Push Changes From d0b921a48e50fea48dd2046c23b802e69a3b71ff Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 11 Sep 2024 22:39:51 +0300 Subject: [PATCH 059/834] revert update current ref --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d5d77dc7..e2117da3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,6 +39,6 @@ jobs: - name: print current ref run: echo ${{ github.event.pull_request.base.ref }} - run: node scripts/outdatedGitPackages.mjs - if: ${{ github.event.pull_request.base.ref == 'release' }} + if: ${{ github.event.pull_request.base.ref == 'next' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 33437823f3d408e2c4b1beece255e9a26a3ce102 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 11 Sep 2024 22:40:41 +0300 Subject: [PATCH 060/834] disable outdated packages check for now --- .github/workflows/ci.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e2117da3..2208e460 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,9 +36,7 @@ jobs: with: name: cypress-images path: cypress/screenshots/ - - name: print current ref - run: echo ${{ github.event.pull_request.base.ref }} - - run: node scripts/outdatedGitPackages.mjs - if: ${{ github.event.pull_request.base.ref == 'next' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # - run: node scripts/outdatedGitPackages.mjs + # if: ${{ github.event.pull_request.base.ref == 'release' }} + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 18a6f2c1f5a41bbf67d53436bbc9f5490862fc01 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 12 Sep 2024 04:32:37 +0300 Subject: [PATCH 061/834] fix: rare case where digging animation was not cancelled after actual dig cancel after respawn --- src/worldInteractions.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/worldInteractions.ts b/src/worldInteractions.ts index cef65b96..dd5f0aec 100644 --- a/src/worldInteractions.ts +++ b/src/worldInteractions.ts @@ -48,7 +48,7 @@ class WorldInteraction { breakTextures: THREE.Texture[] lastDigged: number lineMaterial: LineMaterial - debugStatus: string + debugDigStatus: string oneTimeInit () { const loader = new THREE.TextureLoader() @@ -129,11 +129,11 @@ class WorldInteraction { // TODO: If the tool and enchantments immediately exceed the hardness times 30, the block breaks with no delay; SO WE NEED TO CHECK THAT // TODO: Any blocks with a breaking time of 0.05 this.lastDigged = Date.now() - this.debugStatus = 'done' + this.debugDigStatus = 'done' }) bot.on('diggingAborted', (block) => { if (!this.cursorBlock?.position.equals(block.position)) return - this.debugStatus = 'aborted' + this.debugDigStatus = 'aborted' // if (this.lastDugBlock) this.breakStartTime = undefined if (this.buttons[0]) { @@ -326,7 +326,8 @@ class WorldInteraction { // We stopped breaking if ((!this.buttons[0] && this.lastButtons[0])) { this.lastDugBlock = null - this.debugStatus = 'cancelled' + this.breakStartTime = undefined + this.debugDigStatus = 'cancelled' } const onGround = bot.entity.onGround || bot.game.gameMode === 'creative' @@ -340,7 +341,7 @@ class WorldInteraction { && (!this.lastButtons[0] || ((cursorChanged || (this.lastDugBlock && !this.lastDugBlock.equals(cursorBlock!.position))) && Date.now() - (this.lastDigged ?? 0) > 300) || onGround !== this.prevOnGround) && onGround) { this.lastDugBlock = null - this.debugStatus = 'breaking' + this.debugDigStatus = 'breaking' 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)] From 7da41b02c95b61873fff8fd13f45712eb545c7aa Mon Sep 17 00:00:00 2001 From: Valery-a <83373303+Valery-a@users.noreply.github.com> Date: Thu, 12 Sep 2024 23:00:58 +0300 Subject: [PATCH 062/834] fix: fixed zombies and husks not having texture (#203) --- prismarine-viewer/viewer/lib/entities.ts | 3 +- .../viewer/lib/entity/EntityMesh.js | 10 + .../viewer/lib/entity/exportedModels.js | 1 + .../viewer/lib/entity/models/zombie.obj | 472 +++++++++--------- 4 files changed, 248 insertions(+), 238 deletions(-) diff --git a/prismarine-viewer/viewer/lib/entities.ts b/prismarine-viewer/viewer/lib/entities.ts index 281a442e..ee659263 100644 --- a/prismarine-viewer/viewer/lib/entities.ts +++ b/prismarine-viewer/viewer/lib/entities.ts @@ -340,9 +340,8 @@ export class Entities extends EventEmitter { } update (entity: import('prismarine-entity').Entity & { delete?; pos }, overrides) { - let isPlayerModel = entity.name === 'player' + const isPlayerModel = entity.name === 'player' if (entity.name === 'zombie' || entity.name === 'zombie_villager' || entity.name === 'husk') { - isPlayerModel = true overrides.texture = `textures/1.16.4/entity/${entity.name === 'zombie_villager' ? 'zombie_villager/zombie_villager.png' : `zombie/${entity.name}.png`}` } if (!this.entities[entity.id] && !entity.delete) { diff --git a/prismarine-viewer/viewer/lib/entity/EntityMesh.js b/prismarine-viewer/viewer/lib/entity/EntityMesh.js index 39b2d62d..1387d2cc 100644 --- a/prismarine-viewer/viewer/lib/entity/EntityMesh.js +++ b/prismarine-viewer/viewer/lib/entity/EntityMesh.js @@ -1,6 +1,7 @@ //@ts-check import * as THREE from 'three' import { OBJLoader } from 'three-stdlib' +import huskPng from 'mc-assets/dist/other-textures/latest/entity/zombie/husk.png' import entities from './entities.json' import { externalModels } from './objModels' import externalTexturesJson from './externalTextures.json' @@ -290,6 +291,10 @@ const getEntity = (name) => { // zombie_villager: 'zombie_villager/zombie_villager' // } +const scaleEntity = { + zombie: 1.9, + husk: 1.9 +} // eslint-disable-next-line @typescript-eslint/no-extraneous-class export class EntityMesh { constructor(version, type, scene, /** @type {{textures?, rotation?: Record}} */overrides = {}) { @@ -303,6 +308,9 @@ export class EntityMesh { if (originalType === 'zombie_horse') { texturePath = `textures/${version}/entity/horse/horse_zombie.png` } + if (originalType === 'husk') { + texturePath = huskPng + } if (originalType === 'skeleton_horse') { texturePath = `textures/${version}/entity/horse/horse_skeleton.png` } @@ -325,6 +333,8 @@ export class EntityMesh { alphaTest: 0.1 }) const obj = objLoader.parse(externalModels[type]) + const scale = scaleEntity[originalType] + if (scale) obj.scale.set(scale, scale, scale) if (type === 'boat') obj.position.y = -1 // todo, should not be hardcoded obj.traverse((child) => { if (child instanceof THREE.Mesh) { diff --git a/prismarine-viewer/viewer/lib/entity/exportedModels.js b/prismarine-viewer/viewer/lib/entity/exportedModels.js index b43658ec..53d55d5e 100644 --- a/prismarine-viewer/viewer/lib/entity/exportedModels.js +++ b/prismarine-viewer/viewer/lib/entity/exportedModels.js @@ -35,4 +35,5 @@ export { default as witch } from './models/witch.obj' export { default as wolf } from './models/wolf.obj' export { default as zombie_villager } from './models/zombie_villager.obj' export { default as zombie } from './models/zombie.obj' +export { default as husk } from './models/zombie.obj' export { default as boat } from './models/boat.obj' diff --git a/prismarine-viewer/viewer/lib/entity/models/zombie.obj b/prismarine-viewer/viewer/lib/entity/models/zombie.obj index bcd7444c..42f1e722 100644 --- a/prismarine-viewer/viewer/lib/entity/models/zombie.obj +++ b/prismarine-viewer/viewer/lib/entity/models/zombie.obj @@ -1,322 +1,322 @@ -# Made in Blockbench 4.9.4 +# Made in Blockbench 4.10.4 mtllib materials.mtl -o Body -v 0.25 1.5 0.125 -v 0.25 1.5 -0.125 -v 0.25 0.75 0.125 -v 0.25 0.75 -0.125 -v -0.25 1.5 -0.125 -v -0.25 1.5 0.125 -v -0.25 0.75 -0.125 -v -0.25 0.75 0.125 -vt 0.3125 0.375 -vt 0.4375 0.375 -vt 0.4375 0 -vt 0.3125 0 -vt 0.25 0.375 -vt 0.3125 0.375 -vt 0.3125 0 -vt 0.25 0 -vt 0.5 0.375 -vt 0.625 0.375 -vt 0.625 0 -vt 0.5 0 -vt 0.4375 0.375 -vt 0.5 0.375 -vt 0.5 0 -vt 0.4375 0 -vt 0.4375 0.375 -vt 0.3125 0.375 -vt 0.3125 0.5 -vt 0.4375 0.5 -vt 0.5625 0.5 -vt 0.4375 0.5 -vt 0.4375 0.375 -vt 0.5625 0.375 +o /head +v 0.125 1 0.125 +v 0.125 1 -0.125 +v 0.125 0.75 0.125 +v 0.125 0.75 -0.125 +v -0.125 1 -0.125 +v -0.125 1 0.125 +v -0.125 0.75 -0.125 +v -0.125 0.75 0.125 +vt 0.125 0.875 +vt 0.25 0.875 +vt 0.25 0.75 +vt 0.125 0.75 +vt 0 0.875 +vt 0.125 0.875 +vt 0.125 0.75 +vt 0 0.75 +vt 0.375 0.875 +vt 0.5 0.875 +vt 0.5 0.75 +vt 0.375 0.75 +vt 0.25 0.875 +vt 0.375 0.875 +vt 0.375 0.75 +vt 0.25 0.75 +vt 0.25 0.875 +vt 0.125 0.875 +vt 0.125 1 +vt 0.25 1 +vt 0.25 1 +vt 0.375 1 +vt 0.375 0.875 +vt 0.25 0.875 vn 0 0 -1 vn 1 0 0 vn 0 0 1 vn -1 0 0 vn 0 1 0 vn 0 -1 0 -usemtl m_9eb5cf2e-0212-52a4-6070-8cb3b67f2e24 +usemtl none f 4/4/1 7/3/1 5/2/1 2/1/1 f 3/8/2 4/7/2 2/6/2 1/5/2 f 8/12/3 3/11/3 1/10/3 6/9/3 f 7/16/4 8/15/4 6/14/4 5/13/4 f 6/20/5 1/19/5 2/18/5 5/17/5 f 7/24/6 4/23/6 3/22/6 8/21/6 -o Head -v 0.25 2 0.25 -v 0.25 2 -0.25 -v 0.25 1.5 0.25 -v 0.25 1.5 -0.25 -v -0.25 2 -0.25 -v -0.25 2 0.25 -v -0.25 1.5 -0.25 -v -0.25 1.5 0.25 -vt 0.125 0.75 -vt 0.25 0.75 -vt 0.25 0.5 -vt 0.125 0.5 -vt 0 0.75 -vt 0.125 0.75 -vt 0.125 0.5 -vt 0 0.5 -vt 0.375 0.75 -vt 0.5 0.75 -vt 0.5 0.5 -vt 0.375 0.5 -vt 0.25 0.75 -vt 0.375 0.75 -vt 0.375 0.5 -vt 0.25 0.5 -vt 0.25 0.75 -vt 0.125 0.75 -vt 0.125 1 -vt 0.25 1 -vt 0.375 1 -vt 0.25 1 -vt 0.25 0.75 -vt 0.375 0.75 +o /right_arm +v -0.125 0.75 0.0625 +v -0.125 0.75 -0.0625 +v -0.125 0.375 0.0625 +v -0.125 0.375 -0.0625 +v -0.25 0.75 -0.0625 +v -0.25 0.75 0.0625 +v -0.25 0.375 -0.0625 +v -0.25 0.375 0.0625 +vt 0.6875 0.6875 +vt 0.75 0.6875 +vt 0.75 0.5 +vt 0.6875 0.5 +vt 0.625 0.6875 +vt 0.6875 0.6875 +vt 0.6875 0.5 +vt 0.625 0.5 +vt 0.8125 0.6875 +vt 0.875 0.6875 +vt 0.875 0.5 +vt 0.8125 0.5 +vt 0.75 0.6875 +vt 0.8125 0.6875 +vt 0.8125 0.5 +vt 0.75 0.5 +vt 0.75 0.6875 +vt 0.6875 0.6875 +vt 0.6875 0.75 +vt 0.75 0.75 +vt 0.75 0.75 +vt 0.8125 0.75 +vt 0.8125 0.6875 +vt 0.75 0.6875 vn 0 0 -1 vn 1 0 0 vn 0 0 1 vn -1 0 0 vn 0 1 0 vn 0 -1 0 -usemtl m_9eb5cf2e-0212-52a4-6070-8cb3b67f2e24 +usemtl none f 12/28/7 15/27/7 13/26/7 10/25/7 f 11/32/8 12/31/8 10/30/8 9/29/8 f 16/36/9 11/35/9 9/34/9 14/33/9 f 15/40/10 16/39/10 14/38/10 13/37/10 f 14/44/11 9/43/11 10/42/11 13/41/11 f 15/48/12 12/47/12 11/46/12 16/45/12 -o Hat Layer -v 0.28125 2.03125 0.28125 -v 0.28125 2.03125 -0.28125 -v 0.28125 1.46875 0.28125 -v 0.28125 1.46875 -0.28125 -v -0.28125 2.03125 -0.28125 -v -0.28125 2.03125 0.28125 -v -0.28125 1.46875 -0.28125 -v -0.28125 1.46875 0.28125 -vt 0.625 0.75 -vt 0.75 0.75 -vt 0.75 0.5 -vt 0.625 0.5 -vt 0.5 0.75 -vt 0.625 0.75 -vt 0.625 0.5 -vt 0.5 0.5 -vt 0.875 0.75 -vt 1 0.75 -vt 1 0.5 -vt 0.875 0.5 -vt 0.75 0.75 -vt 0.875 0.75 -vt 0.875 0.5 -vt 0.75 0.5 -vt 0.75 0.75 -vt 0.625 0.75 -vt 0.625 1 -vt 0.75 1 -vt 0.875 1 -vt 0.75 1 -vt 0.75 0.75 -vt 0.875 0.75 +o /left_leg +v 0.12187499999999996 0.375 0.0625 +v 0.12187499999999996 0.375 -0.0625 +v 0.12187499999999996 0 0.0625 +v 0.12187499999999996 0 -0.0625 +v -0.003124999999999989 0.375 -0.0625 +v -0.003124999999999989 0.375 0.0625 +v -0.003124999999999989 0 -0.0625 +v -0.003124999999999989 0 0.0625 +vt 0.0625 0.6875 +vt 0.125 0.6875 +vt 0.125 0.5 +vt 0.0625 0.5 +vt 0 0.6875 +vt 0.0625 0.6875 +vt 0.0625 0.5 +vt 0 0.5 +vt 0.1875 0.6875 +vt 0.25 0.6875 +vt 0.25 0.5 +vt 0.1875 0.5 +vt 0.125 0.6875 +vt 0.1875 0.6875 +vt 0.1875 0.5 +vt 0.125 0.5 +vt 0.125 0.6875 +vt 0.0625 0.6875 +vt 0.0625 0.75 +vt 0.125 0.75 +vt 0.125 0.75 +vt 0.1875 0.75 +vt 0.1875 0.6875 +vt 0.125 0.6875 vn 0 0 -1 vn 1 0 0 vn 0 0 1 vn -1 0 0 vn 0 1 0 vn 0 -1 0 -usemtl m_9eb5cf2e-0212-52a4-6070-8cb3b67f2e24 +usemtl none f 20/52/13 23/51/13 21/50/13 18/49/13 f 19/56/14 20/55/14 18/54/14 17/53/14 f 24/60/15 19/59/15 17/58/15 22/57/15 f 23/64/16 24/63/16 22/62/16 21/61/16 f 22/68/17 17/67/17 18/66/17 21/65/17 f 23/72/18 20/71/18 19/70/18 24/69/18 -o RightArm -v 0.5 1.5 0.125 -v 0.5 1.5 -0.125 -v 0.5 0.75 0.125 -v 0.5 0.75 -0.125 -v 0.25 1.5 -0.125 -v 0.25 1.5 0.125 -v 0.25 0.75 -0.125 -v 0.25 0.75 0.125 -vt 0.6875 0.375 -vt 0.75 0.375 -vt 0.75 0 -vt 0.6875 0 -vt 0.625 0.375 -vt 0.6875 0.375 -vt 0.6875 0 -vt 0.625 0 -vt 0.8125 0.375 -vt 0.875 0.375 -vt 0.875 0 -vt 0.8125 0 -vt 0.75 0.375 -vt 0.8125 0.375 -vt 0.8125 0 -vt 0.75 0 -vt 0.75 0.375 -vt 0.6875 0.375 -vt 0.6875 0.5 +o /left_arm +v 0.25 0.75 0.0625 +v 0.25 0.75 -0.0625 +v 0.25 0.375 0.0625 +v 0.25 0.375 -0.0625 +v 0.125 0.75 -0.0625 +v 0.125 0.75 0.0625 +v 0.125 0.375 -0.0625 +v 0.125 0.375 0.0625 +vt 0.6875 0.6875 +vt 0.75 0.6875 vt 0.75 0.5 +vt 0.6875 0.5 +vt 0.625 0.6875 +vt 0.6875 0.6875 +vt 0.6875 0.5 +vt 0.625 0.5 +vt 0.8125 0.6875 +vt 0.875 0.6875 +vt 0.875 0.5 +vt 0.8125 0.5 +vt 0.75 0.6875 +vt 0.8125 0.6875 vt 0.8125 0.5 vt 0.75 0.5 -vt 0.75 0.375 -vt 0.8125 0.375 +vt 0.75 0.6875 +vt 0.6875 0.6875 +vt 0.6875 0.75 +vt 0.75 0.75 +vt 0.75 0.75 +vt 0.8125 0.75 +vt 0.8125 0.6875 +vt 0.75 0.6875 vn 0 0 -1 vn 1 0 0 vn 0 0 1 vn -1 0 0 vn 0 1 0 vn 0 -1 0 -usemtl m_9eb5cf2e-0212-52a4-6070-8cb3b67f2e24 +usemtl none f 28/76/19 31/75/19 29/74/19 26/73/19 f 27/80/20 28/79/20 26/78/20 25/77/20 f 32/84/21 27/83/21 25/82/21 30/81/21 f 31/88/22 32/87/22 30/86/22 29/85/22 f 30/92/23 25/91/23 26/90/23 29/89/23 f 31/96/24 28/95/24 27/94/24 32/93/24 -o LeftArm -v -0.25 1.5 0.125 -v -0.25 1.5 -0.125 -v -0.25 0.75 0.125 -v -0.25 0.75 -0.125 -v -0.5 1.5 -0.125 -v -0.5 1.5 0.125 -v -0.5 0.75 -0.125 -v -0.5 0.75 0.125 -vt 0.75 0.375 -vt 0.6875 0.375 -vt 0.6875 0 -vt 0.75 0 -vt 0.8125 0.375 -vt 0.75 0.375 -vt 0.75 0 -vt 0.8125 0 -vt 0.875 0.375 -vt 0.8125 0.375 -vt 0.8125 0 -vt 0.875 0 -vt 0.6875 0.375 -vt 0.625 0.375 -vt 0.625 0 -vt 0.6875 0 -vt 0.6875 0.375 -vt 0.75 0.375 -vt 0.75 0.5 -vt 0.6875 0.5 -vt 0.75 0.5 -vt 0.8125 0.5 -vt 0.8125 0.375 -vt 0.75 0.375 +o /right_leg +v 0.0031250000000000444 0.375 0.0625 +v 0.0031250000000000444 0.375 -0.0625 +v 0.0031250000000000444 0 0.0625 +v 0.0031250000000000444 0 -0.0625 +v -0.12187500000000001 0.375 -0.0625 +v -0.12187500000000001 0.375 0.0625 +v -0.12187500000000001 0 -0.0625 +v -0.12187500000000001 0 0.0625 +vt 0.0625 0.6875 +vt 0.125 0.6875 +vt 0.125 0.5 +vt 0.0625 0.5 +vt 0 0.6875 +vt 0.0625 0.6875 +vt 0.0625 0.5 +vt 0 0.5 +vt 0.1875 0.6875 +vt 0.25 0.6875 +vt 0.25 0.5 +vt 0.1875 0.5 +vt 0.125 0.6875 +vt 0.1875 0.6875 +vt 0.1875 0.5 +vt 0.125 0.5 +vt 0.125 0.6875 +vt 0.0625 0.6875 +vt 0.0625 0.75 +vt 0.125 0.75 +vt 0.125 0.75 +vt 0.1875 0.75 +vt 0.1875 0.6875 +vt 0.125 0.6875 vn 0 0 -1 vn 1 0 0 vn 0 0 1 vn -1 0 0 vn 0 1 0 vn 0 -1 0 -usemtl m_9eb5cf2e-0212-52a4-6070-8cb3b67f2e24 +usemtl none f 36/100/25 39/99/25 37/98/25 34/97/25 f 35/104/26 36/103/26 34/102/26 33/101/26 f 40/108/27 35/107/27 33/106/27 38/105/27 f 39/112/28 40/111/28 38/110/28 37/109/28 f 38/116/29 33/115/29 34/114/29 37/113/29 f 39/120/30 36/119/30 35/118/30 40/117/30 -o RightLeg -v 0.24375000000000002 0.75 0.125 -v 0.24375000000000002 0.75 -0.125 -v 0.24375000000000002 0 0.125 -v 0.24375000000000002 0 -0.125 -v -0.006249999999999978 0.75 -0.125 -v -0.006249999999999978 0.75 0.125 -v -0.006249999999999978 0 -0.125 -v -0.006249999999999978 0 0.125 -vt 0.0625 0.375 -vt 0.125 0.375 -vt 0.125 0 -vt 0.0625 0 -vt 0 0.375 -vt 0.0625 0.375 -vt 0.0625 0 -vt 0 0 -vt 0.1875 0.375 -vt 0.25 0.375 -vt 0.25 0 -vt 0.1875 0 -vt 0.125 0.375 -vt 0.1875 0.375 -vt 0.1875 0 -vt 0.125 0 -vt 0.125 0.375 -vt 0.0625 0.375 -vt 0.0625 0.5 -vt 0.125 0.5 -vt 0.1875 0.5 -vt 0.125 0.5 -vt 0.125 0.375 -vt 0.1875 0.375 +o /hat +v 0.140625 1.015625 0.140625 +v 0.140625 1.015625 -0.140625 +v 0.140625 0.734375 0.140625 +v 0.140625 0.734375 -0.140625 +v -0.140625 1.015625 -0.140625 +v -0.140625 1.015625 0.140625 +v -0.140625 0.734375 -0.140625 +v -0.140625 0.734375 0.140625 +vt 0.625 0.875 +vt 0.75 0.875 +vt 0.75 0.75 +vt 0.625 0.75 +vt 0.5 0.875 +vt 0.625 0.875 +vt 0.625 0.75 +vt 0.5 0.75 +vt 0.875 0.875 +vt 1 0.875 +vt 1 0.75 +vt 0.875 0.75 +vt 0.75 0.875 +vt 0.875 0.875 +vt 0.875 0.75 +vt 0.75 0.75 +vt 0.75 0.875 +vt 0.625 0.875 +vt 0.625 1 +vt 0.75 1 +vt 0.75 1 +vt 0.875 1 +vt 0.875 0.875 +vt 0.75 0.875 vn 0 0 -1 vn 1 0 0 vn 0 0 1 vn -1 0 0 vn 0 1 0 vn 0 -1 0 -usemtl m_9eb5cf2e-0212-52a4-6070-8cb3b67f2e24 +usemtl none f 44/124/31 47/123/31 45/122/31 42/121/31 f 43/128/32 44/127/32 42/126/32 41/125/32 f 48/132/33 43/131/33 41/130/33 46/129/33 f 47/136/34 48/135/34 46/134/34 45/133/34 f 46/140/35 41/139/35 42/138/35 45/137/35 f 47/144/36 44/143/36 43/142/36 48/141/36 -o LeftLeg -v 0.006249999999999978 0.75 0.125 -v 0.006249999999999978 0.75 -0.125 -v 0.006249999999999978 0 0.125 -v 0.006249999999999978 0 -0.125 -v -0.24375000000000002 0.75 -0.125 -v -0.24375000000000002 0.75 0.125 -v -0.24375000000000002 0 -0.125 -v -0.24375000000000002 0 0.125 -vt 0.125 0.375 -vt 0.0625 0.375 -vt 0.0625 0 -vt 0.125 0 -vt 0.1875 0.375 -vt 0.125 0.375 -vt 0.125 0 -vt 0.1875 0 -vt 0.25 0.375 -vt 0.1875 0.375 -vt 0.1875 0 -vt 0.25 0 -vt 0.0625 0.375 -vt 0 0.375 -vt 0 0 -vt 0.0625 0 -vt 0.0625 0.375 -vt 0.125 0.375 -vt 0.125 0.5 -vt 0.0625 0.5 -vt 0.125 0.5 -vt 0.1875 0.5 -vt 0.1875 0.375 -vt 0.125 0.375 +o /body +v 0.125 0.75 0.0625 +v 0.125 0.75 -0.0625 +v 0.125 0.375 0.0625 +v 0.125 0.375 -0.0625 +v -0.125 0.75 -0.0625 +v -0.125 0.75 0.0625 +v -0.125 0.375 -0.0625 +v -0.125 0.375 0.0625 +vt 0.3125 0.6875 +vt 0.4375 0.6875 +vt 0.4375 0.5 +vt 0.3125 0.5 +vt 0.25 0.6875 +vt 0.3125 0.6875 +vt 0.3125 0.5 +vt 0.25 0.5 +vt 0.5 0.6875 +vt 0.625 0.6875 +vt 0.625 0.5 +vt 0.5 0.5 +vt 0.4375 0.6875 +vt 0.5 0.6875 +vt 0.5 0.5 +vt 0.4375 0.5 +vt 0.4375 0.6875 +vt 0.3125 0.6875 +vt 0.3125 0.75 +vt 0.4375 0.75 +vt 0.4375 0.75 +vt 0.5625 0.75 +vt 0.5625 0.6875 +vt 0.4375 0.6875 vn 0 0 -1 vn 1 0 0 vn 0 0 1 vn -1 0 0 vn 0 1 0 vn 0 -1 0 -usemtl m_9eb5cf2e-0212-52a4-6070-8cb3b67f2e24 +usemtl none f 52/148/37 55/147/37 53/146/37 50/145/37 f 51/152/38 52/151/38 50/150/38 49/149/38 f 56/156/39 51/155/39 49/154/39 54/153/39 From 9bac681c293da03e364a6c49eab7fed92ed1fbf7 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 12 Sep 2024 23:38:45 +0300 Subject: [PATCH 063/834] use correct zombie model --- .../viewer/lib/entity/EntityMesh.js | 14 +- .../viewer/lib/entity/models/zombie.obj | 174 +++++++++--------- 2 files changed, 98 insertions(+), 90 deletions(-) diff --git a/prismarine-viewer/viewer/lib/entity/EntityMesh.js b/prismarine-viewer/viewer/lib/entity/EntityMesh.js index 1387d2cc..a518171f 100644 --- a/prismarine-viewer/viewer/lib/entity/EntityMesh.js +++ b/prismarine-viewer/viewer/lib/entity/EntityMesh.js @@ -2,6 +2,7 @@ import * as THREE from 'three' import { OBJLoader } from 'three-stdlib' import huskPng from 'mc-assets/dist/other-textures/latest/entity/zombie/husk.png' +import { Vec3 } from 'vec3' import entities from './entities.json' import { externalModels } from './objModels' import externalTexturesJson from './externalTextures.json' @@ -292,9 +293,15 @@ const getEntity = (name) => { // } const scaleEntity = { - zombie: 1.9, - husk: 1.9 + zombie: 1.85, + husk: 1.85 } +const offsetEntity = { + zombie: new Vec3(0, 1, 0), + husk: new Vec3(0, 1, 0), + boat: new Vec3(0, -1, 0), +} + // eslint-disable-next-line @typescript-eslint/no-extraneous-class export class EntityMesh { constructor(version, type, scene, /** @type {{textures?, rotation?: Record}} */overrides = {}) { @@ -335,7 +342,8 @@ export class EntityMesh { const obj = objLoader.parse(externalModels[type]) const scale = scaleEntity[originalType] if (scale) obj.scale.set(scale, scale, scale) - if (type === 'boat') obj.position.y = -1 // todo, should not be hardcoded + const offset = offsetEntity[originalType] + if (offset) obj.position.set(offset.x, offset.y, offset.z) obj.traverse((child) => { if (child instanceof THREE.Mesh) { child.material = material diff --git a/prismarine-viewer/viewer/lib/entity/models/zombie.obj b/prismarine-viewer/viewer/lib/entity/models/zombie.obj index 42f1e722..fa5d8f4d 100644 --- a/prismarine-viewer/viewer/lib/entity/models/zombie.obj +++ b/prismarine-viewer/viewer/lib/entity/models/zombie.obj @@ -2,14 +2,14 @@ mtllib materials.mtl o /head -v 0.125 1 0.125 -v 0.125 1 -0.125 -v 0.125 0.75 0.125 -v 0.125 0.75 -0.125 -v -0.125 1 -0.125 -v -0.125 1 0.125 -v -0.125 0.75 -0.125 -v -0.125 0.75 0.125 +v 0.125 0.5 0.125 +v 0.125 0.5 -0.125 +v 0.125 0.25 0.125 +v 0.125 0.25 -0.125 +v -0.125 0.5 -0.125 +v -0.125 0.5 0.125 +v -0.125 0.25 -0.125 +v -0.125 0.25 0.125 vt 0.125 0.875 vt 0.25 0.875 vt 0.25 0.75 @@ -48,38 +48,38 @@ f 7/16/4 8/15/4 6/14/4 5/13/4 f 6/20/5 1/19/5 2/18/5 5/17/5 f 7/24/6 4/23/6 3/22/6 8/21/6 o /right_arm -v -0.125 0.75 0.0625 -v -0.125 0.75 -0.0625 -v -0.125 0.375 0.0625 -v -0.125 0.375 -0.0625 -v -0.25 0.75 -0.0625 -v -0.25 0.75 0.0625 -v -0.25 0.375 -0.0625 -v -0.25 0.375 0.0625 -vt 0.6875 0.6875 +v -0.125 0.25 0.0625 +v -0.125 0.25 -0.3125 +v -0.125 0.125 0.0625 +v -0.125 0.125 -0.3125 +v -0.25 0.25 -0.3125 +v -0.25 0.25 0.0625 +v -0.25 0.125 -0.3125 +v -0.25 0.125 0.0625 +vt 0.8125 0.6875 vt 0.75 0.6875 -vt 0.75 0.5 -vt 0.6875 0.5 -vt 0.625 0.6875 +vt 0.75 0.75 +vt 0.8125 0.75 vt 0.6875 0.6875 vt 0.6875 0.5 vt 0.625 0.5 -vt 0.8125 0.6875 -vt 0.875 0.6875 -vt 0.875 0.5 -vt 0.8125 0.5 -vt 0.75 0.6875 -vt 0.8125 0.6875 -vt 0.8125 0.5 -vt 0.75 0.5 +vt 0.625 0.6875 vt 0.75 0.6875 vt 0.6875 0.6875 vt 0.6875 0.75 vt 0.75 0.75 -vt 0.75 0.75 -vt 0.8125 0.75 -vt 0.8125 0.6875 +vt 0.75 0.5 vt 0.75 0.6875 +vt 0.8125 0.6875 +vt 0.8125 0.5 +vt 0.75 0.5 +vt 0.6875 0.5 +vt 0.6875 0.6875 +vt 0.75 0.6875 +vt 0.8125 0.6875 +vt 0.875 0.6875 +vt 0.875 0.5 +vt 0.8125 0.5 vn 0 0 -1 vn 1 0 0 vn 0 0 1 @@ -94,14 +94,14 @@ f 15/40/10 16/39/10 14/38/10 13/37/10 f 14/44/11 9/43/11 10/42/11 13/41/11 f 15/48/12 12/47/12 11/46/12 16/45/12 o /left_leg -v 0.12187499999999996 0.375 0.0625 -v 0.12187499999999996 0.375 -0.0625 -v 0.12187499999999996 0 0.0625 -v 0.12187499999999996 0 -0.0625 -v -0.003124999999999989 0.375 -0.0625 -v -0.003124999999999989 0.375 0.0625 -v -0.003124999999999989 0 -0.0625 -v -0.003124999999999989 0 0.0625 +v 0.12187499999999996 -0.125 0.0625 +v 0.12187499999999996 -0.125 -0.0625 +v 0.12187499999999996 -0.5 0.0625 +v 0.12187499999999996 -0.5 -0.0625 +v -0.0031250000000000444 -0.125 -0.0625 +v -0.0031250000000000444 -0.125 0.0625 +v -0.0031250000000000444 -0.5 -0.0625 +v -0.0031250000000000444 -0.5 0.0625 vt 0.0625 0.6875 vt 0.125 0.6875 vt 0.125 0.5 @@ -140,38 +140,38 @@ f 23/64/16 24/63/16 22/62/16 21/61/16 f 22/68/17 17/67/17 18/66/17 21/65/17 f 23/72/18 20/71/18 19/70/18 24/69/18 o /left_arm -v 0.25 0.75 0.0625 -v 0.25 0.75 -0.0625 -v 0.25 0.375 0.0625 -v 0.25 0.375 -0.0625 -v 0.125 0.75 -0.0625 -v 0.125 0.75 0.0625 -v 0.125 0.375 -0.0625 -v 0.125 0.375 0.0625 -vt 0.6875 0.6875 +v 0.25 0.25 0.0625 +v 0.25 0.25 -0.3125 +v 0.25 0.125 0.0625 +v 0.25 0.125 -0.3125 +v 0.125 0.25 -0.3125 +v 0.125 0.25 0.0625 +v 0.125 0.125 -0.3125 +v 0.125 0.125 0.0625 +vt 0.8125 0.6875 vt 0.75 0.6875 -vt 0.75 0.5 -vt 0.6875 0.5 -vt 0.625 0.6875 +vt 0.75 0.75 +vt 0.8125 0.75 vt 0.6875 0.6875 vt 0.6875 0.5 vt 0.625 0.5 -vt 0.8125 0.6875 -vt 0.875 0.6875 -vt 0.875 0.5 -vt 0.8125 0.5 -vt 0.75 0.6875 -vt 0.8125 0.6875 -vt 0.8125 0.5 -vt 0.75 0.5 +vt 0.625 0.6875 vt 0.75 0.6875 vt 0.6875 0.6875 vt 0.6875 0.75 vt 0.75 0.75 -vt 0.75 0.75 -vt 0.8125 0.75 -vt 0.8125 0.6875 +vt 0.75 0.5 vt 0.75 0.6875 +vt 0.8125 0.6875 +vt 0.8125 0.5 +vt 0.75 0.5 +vt 0.6875 0.5 +vt 0.6875 0.6875 +vt 0.75 0.6875 +vt 0.8125 0.6875 +vt 0.875 0.6875 +vt 0.875 0.5 +vt 0.8125 0.5 vn 0 0 -1 vn 1 0 0 vn 0 0 1 @@ -186,14 +186,14 @@ f 31/88/22 32/87/22 30/86/22 29/85/22 f 30/92/23 25/91/23 26/90/23 29/89/23 f 31/96/24 28/95/24 27/94/24 32/93/24 o /right_leg -v 0.0031250000000000444 0.375 0.0625 -v 0.0031250000000000444 0.375 -0.0625 -v 0.0031250000000000444 0 0.0625 -v 0.0031250000000000444 0 -0.0625 -v -0.12187500000000001 0.375 -0.0625 -v -0.12187500000000001 0.375 0.0625 -v -0.12187500000000001 0 -0.0625 -v -0.12187500000000001 0 0.0625 +v 0.0031250000000000444 -0.125 0.0625 +v 0.0031250000000000444 -0.125 -0.0625 +v 0.0031250000000000444 -0.5 0.0625 +v 0.0031250000000000444 -0.5 -0.0625 +v -0.12187499999999996 -0.125 -0.0625 +v -0.12187499999999996 -0.125 0.0625 +v -0.12187499999999996 -0.5 -0.0625 +v -0.12187499999999996 -0.5 0.0625 vt 0.0625 0.6875 vt 0.125 0.6875 vt 0.125 0.5 @@ -232,14 +232,14 @@ f 39/112/28 40/111/28 38/110/28 37/109/28 f 38/116/29 33/115/29 34/114/29 37/113/29 f 39/120/30 36/119/30 35/118/30 40/117/30 o /hat -v 0.140625 1.015625 0.140625 -v 0.140625 1.015625 -0.140625 -v 0.140625 0.734375 0.140625 -v 0.140625 0.734375 -0.140625 -v -0.140625 1.015625 -0.140625 -v -0.140625 1.015625 0.140625 -v -0.140625 0.734375 -0.140625 -v -0.140625 0.734375 0.140625 +v 0.140625 0.515625 0.140625 +v 0.140625 0.515625 -0.140625 +v 0.140625 0.234375 0.140625 +v 0.140625 0.234375 -0.140625 +v -0.140625 0.515625 -0.140625 +v -0.140625 0.515625 0.140625 +v -0.140625 0.234375 -0.140625 +v -0.140625 0.234375 0.140625 vt 0.625 0.875 vt 0.75 0.875 vt 0.75 0.75 @@ -278,14 +278,14 @@ f 47/136/34 48/135/34 46/134/34 45/133/34 f 46/140/35 41/139/35 42/138/35 45/137/35 f 47/144/36 44/143/36 43/142/36 48/141/36 o /body -v 0.125 0.75 0.0625 -v 0.125 0.75 -0.0625 -v 0.125 0.375 0.0625 -v 0.125 0.375 -0.0625 -v -0.125 0.75 -0.0625 -v -0.125 0.75 0.0625 -v -0.125 0.375 -0.0625 -v -0.125 0.375 0.0625 +v 0.125 0.25 0.0625 +v 0.125 0.25 -0.0625 +v 0.125 -0.125 0.0625 +v 0.125 -0.125 -0.0625 +v -0.125 0.25 -0.0625 +v -0.125 0.25 0.0625 +v -0.125 -0.125 -0.0625 +v -0.125 -0.125 0.0625 vt 0.3125 0.6875 vt 0.4375 0.6875 vt 0.4375 0.5 @@ -322,4 +322,4 @@ f 51/152/38 52/151/38 50/150/38 49/149/38 f 56/156/39 51/155/39 49/154/39 54/153/39 f 55/160/40 56/159/40 54/158/40 53/157/40 f 54/164/41 49/163/41 50/162/41 53/161/41 -f 55/168/42 52/167/42 51/166/42 56/165/42 \ No newline at end of file +f 55/168/42 52/167/42 51/166/42 56/165/42 From 3ea95d509ac436236dc9113292396b1791512dbf Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 19 Sep 2024 02:34:45 +0300 Subject: [PATCH 064/834] fix: fix github pages main deploy --- prismarine-viewer/rsbuild.config.ts | 1 - prismarine-viewer/rsbuildSharedConfig.ts | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/prismarine-viewer/rsbuild.config.ts b/prismarine-viewer/rsbuild.config.ts index fa666d99..cea001db 100644 --- a/prismarine-viewer/rsbuild.config.ts +++ b/prismarine-viewer/rsbuild.config.ts @@ -24,7 +24,6 @@ export default mergeRsbuildConfig( distPath: { root: join(__dirname, './dist'), }, - assetPrefix: './', }, server: { port: 9090, diff --git a/prismarine-viewer/rsbuildSharedConfig.ts b/prismarine-viewer/rsbuildSharedConfig.ts index 3fba1ef1..0c248f6c 100644 --- a/prismarine-viewer/rsbuildSharedConfig.ts +++ b/prismarine-viewer/rsbuildSharedConfig.ts @@ -17,7 +17,8 @@ export const appAndRendererSharedConfig = () => defineConfig({ output: { polyfill: 'usage', // 50kb limit for data uri - dataUriLimit: 50 * 1024 + dataUriLimit: 50 * 1024, + assetPrefix: './', }, source: { alias: { From 40f81d84cd7c337bd661b15e74358a7c80c6bf59 Mon Sep 17 00:00:00 2001 From: Valery-a <83373303+Valery-a@users.noreply.github.com> Date: Fri, 27 Sep 2024 15:43:47 -0700 Subject: [PATCH 065/834] server change (#207) --- config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.json b/config.json index e4f86060..33095707 100644 --- a/config.json +++ b/config.json @@ -10,9 +10,9 @@ "description": "Chaos and destruction server. Free for everyone." }, { - "ip": "go.mineberry.org", + "ip": "play.applemc.fun", "version": "1.18.2", - "description": "One of the best servers here. Join now!" + "description": "Very nice server. Try it now!" }, { "ip": "sus.shhnowisnottheti.me", From 00150dda1ddb9cb27426e68cebdc0f4b44529554 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 28 Sep 2024 02:57:18 +0300 Subject: [PATCH 066/834] fix: inventory UI crash in some cases with some specific window titles fix: client messages were not displayed on the latest version --- src/botUtils.ts | 20 ++++++++++++++++++++ src/builtinCommands.ts | 5 ++--- src/inventoryWindows.ts | 23 ++++++++++++++++------- src/worldInteractions.ts | 7 ++----- 4 files changed, 40 insertions(+), 15 deletions(-) diff --git a/src/botUtils.ts b/src/botUtils.ts index 79b10118..e98d1e84 100644 --- a/src/botUtils.ts +++ b/src/botUtils.ts @@ -2,6 +2,7 @@ import { fromFormattedString, TextComponent } from '@xmcl/text-component' import type { IndexedData } from 'minecraft-data' +import { versionToNumber } from 'prismarine-viewer/viewer/prepare/utils' export type MessageFormatPart = Pick & { text: string @@ -120,3 +121,22 @@ export const getItemFromBlock = (block: import('prismarine-block').Block) => { const item = global.loadedData.itemsByName[blockToItemRemaps[block.name] ?? block.name] return item } + +export const displayClientChat = (text: string) => { + const message = { + text + } + if (versionToNumber(bot.version) >= versionToNumber('1.19')) { + bot._client.emit('systemChat', { + formattedMessage: JSON.stringify(message), + position: 0, + sender: 'minecraft:chat' + }) + return + } + bot._client.write('chat', { + message: JSON.stringify(message), + position: 0, + sender: 'minecraft:chat' + }) +} diff --git a/src/builtinCommands.ts b/src/builtinCommands.ts index 21b06ca6..ede5480e 100644 --- a/src/builtinCommands.ts +++ b/src/builtinCommands.ts @@ -6,6 +6,7 @@ import { closeWan, openToWanAndCopyJoinLink } from './localServerMultiplayer' import { copyFilesAsync, uniqueFileNameFromWorldName } from './browserfs' import { saveServer } from './flyingSquidUtils' import { setLoadingScreenStatus } from './utils' +import { displayClientChat } from './botUtils' const notImplemented = () => { return 'Not implemented yet' @@ -75,9 +76,7 @@ const exportLoadedWorld = async () => { window.exportWorld = exportLoadedWorld const writeText = (text) => { - bot._client.emit('chat', { - message: JSON.stringify({ text }) - }) + displayClientChat(text) } const commands: Array<{ diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index 23e898a5..0665b7a6 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -11,12 +11,13 @@ import PItem, { Item } from 'prismarine-item' import { ItemsRenderer } from 'mc-assets/dist/itemsRenderer' import { versionToNumber } from 'prismarine-viewer/viewer/prepare/utils' import { getRenamedData } from 'flying-squid/dist/blockRenames' +import PrismarineChatLoader from 'prismarine-chat' import Generic95 from '../assets/generic_95.png' import { appReplacableResources } from './generated/resources' import { activeModalStack, hideCurrentModal, hideModal, miscUiState, showModal } from './globalState' import { options } from './optionsStorage' import { assertDefined, inGameError } from './utils' -import { MessageFormatPart } from './botUtils' +import { displayClientChat, MessageFormatPart } from './botUtils' import { currentScaling } from './scaleInterface' import { getItemDescription } from './itemsDescriptions' @@ -59,11 +60,7 @@ export const onGameLoad = (onLoad) => { openWindow('ChestWin') } else { // todo format - bot._client.emit('chat', { - message: JSON.stringify({ - text: `[client error] cannot open unimplemented window ${win.id} (${win.type}). Slots: ${win.slots.map(item => getItemName(item)).filter(Boolean).join(', ')}` - }) - }) + displayClientChat(`[client error] cannot open unimplemented window ${win.id} (${win.type}). Slots: ${win.slots.map(item => getItemName(item)).filter(Boolean).join(', ')}`) bot.currentWindow?.['close']() } }) @@ -288,6 +285,7 @@ const implementedContainersGuiMap = { 'minecraft:furnace': 'FurnaceWin', 'minecraft:smoker': 'FurnaceWin', 'minecraft:crafting': 'CraftingWin', + 'minecraft:crafting3x3': 'CraftingWin', // todo different result slot 'minecraft:anvil': 'AnvilWin', // enchant 'minecraft:enchanting_table': 'EnchantingWin', @@ -365,7 +363,18 @@ const openWindow = (type: string | undefined) => { cleanLoadedImagesCache() const inv = openItemsCanvas(type) inv.canvasManager.children[0].mobileHelpers = miscUiState.currentTouch - inv.canvasManager.children[0].customTitleText = bot.currentWindow?.title ? fromFormattedString(bot.currentWindow.title).text : undefined + const title = bot.currentWindow?.title + const PrismarineChat = PrismarineChatLoader(bot.version) + try { + inv.canvasManager.children[0].customTitleText = title ? + typeof title === 'string' ? + fromFormattedString(title).text : + new PrismarineChat(title).toString() : + undefined + } catch (err) { + reportError?.(err) + inv.canvasManager.children[0].customTitleText = undefined + } // todo inv.canvasManager.setScale(currentScaling.scale === 1 ? 1.5 : currentScaling.scale) inv.canvas.style.zIndex = '10' diff --git a/src/worldInteractions.ts b/src/worldInteractions.ts index dd5f0aec..dcd5dc3d 100644 --- a/src/worldInteractions.ts +++ b/src/worldInteractions.ts @@ -22,6 +22,7 @@ import { assertDefined } from './utils' import { options } from './optionsStorage' import { itemBeingUsed } from './react/Crosshair' import { isCypress } from './standaloneUtils' +import { displayClientChat } from './botUtils' function getViewDirection (pitch, yaw) { const csPitch = Math.cos(pitch) @@ -263,11 +264,7 @@ class WorldInteraction { hideCurrentModal() } // if (e.message === 'bot is not sleeping') return - bot._client.emit('chat', { - message: JSON.stringify({ - text: e.message, - }) - }) + displayClientChat(e.message) }) setTimeout(() => { cancelSleep = false From 2953554c5302dce09922352cc48c3fb3cdbd3772 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 28 Sep 2024 03:28:27 +0300 Subject: [PATCH 067/834] fix(regression): player walking animation was broken --- package.json | 2 +- pnpm-lock.yaml | 37 ++++++++++++++++++++++++++++++------- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 4b41660a..f8bd1557 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@floating-ui/react": "^0.26.1", "@mui/base": "5.0.0-beta.40", "@nxg-org/mineflayer-auto-jump": "^0.7.12", - "@nxg-org/mineflayer-tracker": "^1.2.3", + "@nxg-org/mineflayer-tracker": "1.2.1", "@react-oauth/google": "^0.12.1", "@stylistic/eslint-plugin": "^2.6.1", "@types/gapi": "^0.0.47", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 05b83f3c..6aca0d12 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,8 +48,8 @@ importers: specifier: ^0.7.12 version: 0.7.12 '@nxg-org/mineflayer-tracker': - specifier: ^1.2.3 - version: 1.2.3 + specifier: 1.2.1 + version: 1.2.1 '@react-oauth/google': specifier: ^0.12.1 version: 0.12.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -2116,8 +2116,8 @@ packages: '@nxg-org/mineflayer-physics-util@1.5.8': resolution: {integrity: sha512-KmCkAqpUo8BbuRdIBs6+V2hWHehz++PRz3lRwIsb47CuG0u4sgLYh37RY3ifAznC6uWvmPK+q3B4ZXwJzPy1MQ==} - '@nxg-org/mineflayer-tracker@1.2.3': - resolution: {integrity: sha512-E7Ik/scU117Rr6kQUHHMBk8qOGh63YlTCGN33jMfeP7L8xmLeSHN3JtV/fbog8Y+R+HgO99yfZiRAaV7z1T6gQ==} + '@nxg-org/mineflayer-tracker@1.2.1': + resolution: {integrity: sha512-SI1ffF8zvg3/ZNE021Ja2W0FZPN+WbQDZf8yFqOcXtPRXAtM9W6HvoACdzXep8BZid7WYgYLIgjKpB+9RqvCNQ==} '@nxg-org/mineflayer-trajectories@1.1.1': resolution: {integrity: sha512-X103KXlX8+L3uMeK4jQxMUdTizv01sQRSfBizAF/iOAdfQZehRLXr3CYKeJzfwPYGLN0X0JCl++cMEcZVn4vbg==} @@ -5372,7 +5372,7 @@ packages: resolution: {integrity: sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==} engines: {node: '>= 4.0'} os: [darwin] - deprecated: The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2 + deprecated: Upgrade to fsevents v2 to mitigate potential security issues fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} @@ -7747,6 +7747,7 @@ packages: range@0.0.3: resolution: {integrity: sha512-OxK2nY2bmeEB4NxoBraQIBOOeOIxoBvm6yt8MA1kLappgkG3SyLf173iOtT5woWycrtESDD2g0Nl2yt8YPoUnw==} engines: {node: '>=0.8'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. raw-body@2.5.1: resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} @@ -8217,6 +8218,10 @@ packages: resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} engines: {node: '>= 0.8.0'} + send@0.19.0: + resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} + engines: {node: '>= 0.8.0'} + sentence-case@3.0.4: resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==} @@ -11523,7 +11528,7 @@ snapshots: dependencies: '@nxg-org/mineflayer-util-plugin': 1.8.3 - '@nxg-org/mineflayer-tracker@1.2.3': + '@nxg-org/mineflayer-tracker@1.2.1': dependencies: '@nxg-org/mineflayer-trajectories': 1.1.1 '@nxg-org/mineflayer-util-plugin': 1.8.3 @@ -17188,7 +17193,7 @@ snapshots: object-assign: 4.1.1 opn: 6.0.0 proxy-middleware: 0.15.0 - send: 0.18.0 + send: 0.19.0 serve-index: 1.9.1 transitivePeerDependencies: - supports-color @@ -19502,6 +19507,24 @@ snapshots: transitivePeerDependencies: - supports-color + send@0.19.0: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + sentence-case@3.0.4: dependencies: no-case: 3.0.4 From ab5f6ab448377cf9cf01ee0b000dc53e83e68578 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 1 Oct 2024 01:34:42 +0300 Subject: [PATCH 068/834] fix: add fallback peerjs discovery server to bypass geo restrictions and because sometimes official server is down feat: allow to use custom peerjs server via config --- config.json | 2 + prismarine-viewer/viewer/lib/entities.ts | 1 + src/connect.ts | 1 + src/entities.ts | 7 +- src/globalState.ts | 3 + src/index.ts | 11 ++- src/localServerMultiplayer.ts | 86 +++++++++++++++++++++--- src/react/PauseScreen.tsx | 9 ++- 8 files changed, 101 insertions(+), 19 deletions(-) diff --git a/config.json b/config.json index 33095707..7813b591 100644 --- a/config.json +++ b/config.json @@ -3,6 +3,8 @@ "defaultHost": "", "defaultProxy": "proxy.mcraft.fun", "mapsProvider": "https://maps.mcraft.fun/", + "peerJsServer": "", + "peerJsServerFallback": "https://p2p.mcraft.fun", "promoteServers": [ { "ip": "kaboom.pw", diff --git a/prismarine-viewer/viewer/lib/entities.ts b/prismarine-viewer/viewer/lib/entities.ts index ee659263..c7476f6c 100644 --- a/prismarine-viewer/viewer/lib/entities.ts +++ b/prismarine-viewer/viewer/lib/entities.ts @@ -340,6 +340,7 @@ export class Entities extends EventEmitter { } update (entity: import('prismarine-entity').Entity & { delete?; pos }, overrides) { + console.log('entity', entity) const isPlayerModel = entity.name === 'player' if (entity.name === 'zombie' || entity.name === 'zombie_villager' || entity.name === 'husk') { overrides.texture = `textures/1.16.4/entity/${entity.name === 'zombie_villager' ? 'zombie_villager/zombie_villager.png' : `zombie/${entity.name}.png`}` diff --git a/src/connect.ts b/src/connect.ts index 12a1fc5b..40a47669 100644 --- a/src/connect.ts +++ b/src/connect.ts @@ -15,4 +15,5 @@ export type ConnectOptions = { serverIndex?: string /** If true, will show a UI to authenticate with a new account */ authenticatedAccount?: AuthenticatedAccount | true + peerOptions?: any } diff --git a/src/entities.ts b/src/entities.ts index de97fb57..26641f5a 100644 --- a/src/entities.ts +++ b/src/entities.ts @@ -75,10 +75,9 @@ customEvents.on('gameLoaded', () => { const isWalking = Math.abs(speed.x) > WALKING_SPEED || Math.abs(speed.z) > WALKING_SPEED const isSprinting = Math.abs(speed.x) > SPRINTING_SPEED || Math.abs(speed.z) > SPRINTING_SPEED const newAnimation = isWalking ? (isSprinting ? 'running' : 'walking') : 'idle' - const username = e.username! - if (newAnimation !== playerPerAnimation[username]) { + if (newAnimation !== playerPerAnimation[id]) { viewer.entities.playAnimation(e.id, newAnimation) - playerPerAnimation[username] = newAnimation + playerPerAnimation[id] = newAnimation } } }) @@ -122,7 +121,7 @@ customEvents.on('gameLoaded', () => { } viewer.entities.addListener('remove', (e) => { loadedSkinEntityIds.delete(e.id) - playerPerAnimation[e.username] = '' + playerPerAnimation[e.id] = '' bot.tracker.stopTrackingEntity(e, true) }) diff --git a/src/globalState.ts b/src/globalState.ts index b0a447f2..cefa7810 100644 --- a/src/globalState.ts +++ b/src/globalState.ts @@ -109,6 +109,8 @@ export type AppConfig = { defaultProxy?: string // defaultProxySave?: string // defaultVersion?: string + peerJsServer?: string + peerJsServerFallback?: string promoteServers?: Array<{ ip, description, version? }> mapsProvider?: string } @@ -120,6 +122,7 @@ export const miscUiState = proxy({ singleplayer: false, flyingSquid: false, wanOpened: false, + wanOpening: false, /** wether game hud is shown (in playing state) */ gameLoaded: false, showUI: true, diff --git a/src/index.ts b/src/index.ts index db855960..b26619c3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -72,7 +72,7 @@ import defaultServerOptions from './defaultLocalServerOptions' import dayCycle from './dayCycle' import { onAppLoad, resourcepackReload } from './resourcePack' -import { connectToPeer } from './localServerMultiplayer' +import { ConnectPeerOptions, connectToPeer } from './localServerMultiplayer' import CustomChannelClient from './customClient' import { loadScript } from 'prismarine-viewer/viewer/lib/utils' import { registerServiceWorker } from './serviceWorker' @@ -486,7 +486,7 @@ async function connect (connectOptions: ConnectOptions) { port: server.port ? +server.port : undefined, version: connectOptions.botVersion || false, ...p2pMultiplayer ? { - stream: await connectToPeer(connectOptions.peerId!), + stream: await connectToPeer(connectOptions.peerId!, connectOptions.peerOptions), } : {}, ...singleplayer || p2pMultiplayer ? { keepAlive: false, @@ -1022,6 +1022,10 @@ downloadAndOpenFile().then((downloadAction) => { void Promise.resolve().then(() => { // try to connect to peer const peerId = qs.get('connectPeer') + const peerOptions = {} as ConnectPeerOptions + if (qs.get('server')) { + peerOptions.server = qs.get('server')! + } const version = qs.get('peerVersion') if (peerId) { let username: string | null = options.guestUsername @@ -1031,7 +1035,8 @@ downloadAndOpenFile().then((downloadAction) => { void connect({ username, botVersion: version || undefined, - peerId + peerId, + peerOptions }) } }) diff --git a/src/localServerMultiplayer.ts b/src/localServerMultiplayer.ts index c8e4bc5e..7d147d0d 100644 --- a/src/localServerMultiplayer.ts +++ b/src/localServerMultiplayer.ts @@ -19,6 +19,8 @@ class CustomDuplex extends Duplex { let peerInstance: Peer | undefined +let overridePeerJsServer = null as string | null + export const getJoinLink = () => { if (!peerInstance) return const url = new URL(window.location.href) @@ -27,6 +29,11 @@ export const getJoinLink = () => { } url.searchParams.set('connectPeer', peerInstance.id) url.searchParams.set('peerVersion', localServer!.options.version) + const host = (overridePeerJsServer ?? miscUiState.appConfig?.peerJsServer) ?? undefined + if (host) { + // TODO! use miscUiState.appConfig.peerJsServer + url.searchParams.set('server', host) + } return url.toString() } @@ -46,8 +53,12 @@ export const openToWanAndCopyJoinLink = async (writeText: (text) => void, doCopy if (doCopy) await copyJoinLink() return 'Already opened to wan. Join link copied' } + miscUiState.wanOpening = true + const host = (overridePeerJsServer ?? miscUiState.appConfig?.peerJsServer) || undefined + const params = host ? parseUrl(host) : undefined const peer = new Peer({ debug: 3, + ...params }) peerInstance = peer peer.on('connection', (connection) => { @@ -83,34 +94,91 @@ export const openToWanAndCopyJoinLink = async (writeText: (text) => void, doCopy connection.on('close', disconnected) connection.on('error', disconnected) }) + const fallbackServer = miscUiState.appConfig?.peerJsServerFallback + const hasFallback = fallbackServer && peer.options.host !== fallbackServer + let hadErrorReported = false peer.on('error', (error) => { - console.error(error) - writeText(error.message) + console.error('peerJS error', error) + if (error.type === 'server-error' && hasFallback) { + return + } + hadErrorReported = true + writeText(error.message || JSON.stringify(error)) }) - return new Promise(resolve => { + let timeout + const destroy = () => { + clearTimeout(timeout) + timeout = undefined + peer.destroy() + peerInstance = undefined + } + + const result = await new Promise(resolve => { peer.on('open', async () => { await copyJoinLink() resolve('Copied join link to clipboard') }) - setTimeout(() => { + timeout = setTimeout(() => { + if (!hadErrorReported && timeout !== undefined) { + writeText('timeout') + } resolve('Failed to open to wan (timeout)') - }, 5000) + }, 6000) + + // fallback + peer.on('error', async (error) => { + if (!peer.open) { + if (hasFallback) { + destroy() + + overridePeerJsServer = fallbackServer + console.log('Trying fallback server', fallbackServer) + resolve((await openToWanAndCopyJoinLink(writeText, doCopy))!) + } + } + }) }) + if (!peerInstance.open) { + destroy() + } + miscUiState.wanOpening = false + return result +} + +const parseUrl = (url: string) => { + // peerJS does this internally for some reason: const url = new URL(`${protocol}://${host}:${port}${path}${key}/${method}`) + if (!url.startsWith('http')) url = `${location.protocol}//${url}` + const urlObj = new URL(url) + const key = urlObj.searchParams.get('key') + return { + host: urlObj.hostname, + path: urlObj.pathname, + protocol: urlObj.protocol.slice(0, -1), + ...urlObj.port ? { port: +urlObj.port } : {}, + ...key ? { key } : {}, + } } export const closeWan = () => { - if (!peerInstance) return - peerInstance.destroy() + peerInstance?.destroy() peerInstance = undefined miscUiState.wanOpened = false - return 'Closed to wan' + return 'Closed WAN' } -export const connectToPeer = async (peerId: string) => { +export type ConnectPeerOptions = { + server?: string +} + +export const connectToPeer = async (peerId: string, options: ConnectPeerOptions = {}) => { setLoadingScreenStatus('Connecting to peer server') // todo destroy connection on error + // TODO! use miscUiState.appConfig.peerJsServer + const host = options.server + const params = host ? parseUrl(host) : undefined const peer = new Peer({ debug: 3, + ...params }) await resolveTimeout(new Promise(resolve => { peer.once('open', resolve) diff --git a/src/react/PauseScreen.tsx b/src/react/PauseScreen.tsx index 1c4fdcd4..19f1385c 100644 --- a/src/react/PauseScreen.tsx +++ b/src/react/PauseScreen.tsx @@ -151,7 +151,7 @@ export default () => { const isModalActive = useIsModalActive('pause-screen') const fsStateSnap = useSnapshot(fsState) const activeModalStackSnap = useSnapshot(activeModalStack) - const { singleplayer, wanOpened } = useSnapshot(miscUiState) + const { singleplayer, wanOpened, wanOpening } = useSnapshot(miscUiState) const handlePointerLockChange = () => { if (!pointerLock.hasPointerLock && activeModalStack.length === 0) { @@ -188,7 +188,10 @@ export default () => { return } if (!wanOpened || !qr) { - await openToWanAndCopyJoinLink(() => { }, !qr) + await openToWanAndCopyJoinLink((err) => { + if (!miscUiState.wanOpening) return + alert(`Something went wrong: ${err}`) + }, !qr) } if (qr) { const joinLink = getJoinLink() @@ -230,7 +233,7 @@ export default () => { {singleplayer ? (
{(navigator.share as typeof navigator.share | undefined) ? ( {(navigator.share as typeof navigator.share | undefined) ? ( ) + } else if (button.type === 'url' && button.text) { + rowButtons.push() + } + } + pauseLinks.push(
{rowButtons}
) + } + } + return -
- - -
+ {pauseLinks} {singleplayer ? (
From 52ae41a78d24b1bb95e26eb24d9b4b3b5cb977f0 Mon Sep 17 00:00:00 2001 From: Max Lee Date: Wed, 26 Feb 2025 20:33:50 +0100 Subject: [PATCH 370/834] Add better chat link prompt screen (#290) --- src/react/MessageFormatted.tsx | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/react/MessageFormatted.tsx b/src/react/MessageFormatted.tsx index 95204b26..554d5a9b 100644 --- a/src/react/MessageFormatted.tsx +++ b/src/react/MessageFormatted.tsx @@ -6,6 +6,7 @@ import { openURL } from 'renderer/viewer/lib/simpleUtils' import { MessageFormatPart } from '../chatUtils' import { chatInputValueGlobal } from './Chat' import './MessageFormatted.css' +import { showOptionsModal } from './SelectOption' const hoverItemToText = (hoverEvent: MessageFormatPart['hoverEvent']) => { try { @@ -42,17 +43,21 @@ const clickEventToProps = (clickEvent: MessageFormatPart['clickEvent']) => { } } } - if (clickEvent.action === 'open_url') { + if (clickEvent.action === 'open_url' || clickEvent.action === 'open_file') { return { - onClick () { - const confirm = window.confirm(`Open ${clickEvent.value}?`) - if (confirm) { + async onClick () { + const promptMessageText = `Open "${clickEvent.value}"?` + const confirm = await showOptionsModal(promptMessageText, ['Open', 'Copy'], { + cancel: true + }) + if (confirm === 'Open') { openURL(clickEvent.value) + } else if (confirm === 'Copy') { + void navigator.clipboard.writeText(clickEvent.value) } } } } - //@ts-expect-error todo if (clickEvent.action === 'copy_to_clipboard') { return { onClick () { @@ -71,6 +76,7 @@ export const MessagePart = ({ part, ...props }: { part: MessageFormatPart } & Co const hoverItemText = hoverMessageRaw && typeof hoverMessageRaw !== 'string' ? render(hoverMessageRaw).children.map(child => child.component.text).join('') : hoverMessageRaw const applyStyles = [ + clickProps && messageFormatStylesMap.clickEvent, color ? colorF(color.toLowerCase()) + `; text-shadow: 1px 1px 0px ${getColorShadow(colorF(color.toLowerCase()).replace('color:', ''))}` : messageFormatStylesMap.white, italic && messageFormatStylesMap.italic, bold && messageFormatStylesMap.bold, @@ -80,7 +86,7 @@ export const MessagePart = ({ part, ...props }: { part: MessageFormatPart } & Co obfuscated && messageFormatStylesMap.obfuscated ].filter(a => a !== false && a !== undefined).filter(Boolean) - return {text} + return {text} } export default ({ parts, className }: { parts: readonly MessageFormatPart[], className?: string }) => { @@ -138,4 +144,5 @@ export const messageFormatStylesMap = { underlined: 'text-decoration:underline', italic: 'font-style:italic', obfuscated: 'filter:blur(2px)', + clickEvent: 'cursor:pointer', } From edad57a225c98809c7877039ffee01fafa1df516 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 26 Feb 2025 22:56:02 +0300 Subject: [PATCH 371/834] feat: allow to load client without free space on device (or no write permissions) --- src/browserfs.ts | 25 ++++++++++++++++++++++--- src/dragndrop.ts | 2 +- src/globalState.ts | 1 + src/react/MainMenu.tsx | 7 +++++-- src/react/MainMenuRenderApp.tsx | 3 ++- 5 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/browserfs.ts b/src/browserfs.ts index d38b13a2..41608e30 100644 --- a/src/browserfs.ts +++ b/src/browserfs.ts @@ -14,22 +14,41 @@ import { VALID_REPLAY_EXTENSIONS, openFile } from './packetsReplay/replayPackets import { getFixedFilesize } from './downloadAndOpenFile' import { packetsReplayState } from './react/state/packetsReplayState' import { createFullScreenProgressReporter } from './core/progressReporter' +import { showNotification } from './react/NotificationProvider' const { GoogleDriveFileSystem } = require('google-drive-browserfs/src/backends/GoogleDrive') browserfs.install(window) const defaultMountablePoints = { - '/world': { fs: 'LocalStorage' }, // will be removed in future '/data': { fs: 'IndexedDB' }, '/resourcepack': { fs: 'InMemory' }, // temporary storage for currently loaded resource pack + '/temp': { fs: 'InMemory' } +} +const fallbackMountablePoints = { + '/resourcepack': { fs: 'InMemory' }, // temporary storage for downloaded server resource pack + '/temp': { fs: 'InMemory' } } browserfs.configure({ fs: 'MountableFileSystem', options: defaultMountablePoints, }, async (e) => { - // todo disable singleplayer button - if (e) throw e + if (e) { + browserfs.configure({ + fs: 'MountableFileSystem', + options: fallbackMountablePoints, + }, async (e2) => { + if (e2) { + showNotification('Unknown FS error, cannot continue', e2.message, true) + throw e2 + } + showNotification('Failed to access device storage', `Check you have free space. ${e.message}`, true) + miscUiState.appLoaded = true + miscUiState.singleplayerAvailable = false + }) + return + } await updateTexturePackInstalledState() miscUiState.appLoaded = true + miscUiState.singleplayerAvailable = true }) export const forceCachedDataPaths = {} diff --git a/src/dragndrop.ts b/src/dragndrop.ts index 6c8af856..6be90551 100644 --- a/src/dragndrop.ts +++ b/src/dragndrop.ts @@ -64,7 +64,7 @@ async function handleDroppedFile (file: File) { return } if (file.name.endsWith('.mca')) { - const tempPath = '/data/temp.mca' + const tempPath = '/temp/temp.mca' try { await fs.promises.writeFile(tempPath, Buffer.from(await file.arrayBuffer()) as any) const region = new RegionFile(tempPath) diff --git a/src/globalState.ts b/src/globalState.ts index cc9cd127..74e6c3ff 100644 --- a/src/globalState.ts +++ b/src/globalState.ts @@ -145,6 +145,7 @@ export const miscUiState = proxy({ /** currently trying to load or loaded mc version, after all data is loaded */ loadedDataVersion: null as string | null, appLoaded: false, + singleplayerAvailable: false, usingGamepadInput: false, appConfig: null as AppConfig | null, displaySearchInput: false, diff --git a/src/react/MainMenu.tsx b/src/react/MainMenu.tsx index fc770ad1..09214af2 100644 --- a/src/react/MainMenu.tsx +++ b/src/react/MainMenu.tsx @@ -24,6 +24,7 @@ interface Props { bottomRightLinks?: string versionText?: string onVersionTextClick?: () => void + singleplayerAvailable?: boolean } const httpsRegex = /^https?:\/\// @@ -41,7 +42,8 @@ export default ({ versionStatus, versionTitle, onVersionStatusClick, - bottomRightLinks + bottomRightLinks, + singleplayerAvailable = true }: Props) => { if (!bottomRightLinks?.trim()) bottomRightLinks = undefined // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion @@ -107,6 +109,7 @@ export default ({ style={{ width: 150 }} {...singleplayerLongPress} data-test-id='singleplayer-button' + disabled={!singleplayerAvailable} initialTooltip={{ content: 'Create worlds and play offline', placement: 'left', @@ -183,7 +186,7 @@ export default ({
})}
- A Minecraft client in the browser! + A Minecraft client clone in the browser!
diff --git a/src/react/MainMenuRenderApp.tsx b/src/react/MainMenuRenderApp.tsx index b12d9ba8..e06ced52 100644 --- a/src/react/MainMenuRenderApp.tsx +++ b/src/react/MainMenuRenderApp.tsx @@ -75,7 +75,7 @@ export const mainMenuState = proxy({ let disableAnimation = false export default () => { const haveModals = useSnapshot(activeModalStack).length - const { gameLoaded, appLoaded, appConfig } = useSnapshot(miscUiState) + const { gameLoaded, appLoaded, appConfig, singleplayerAvailable } = useSnapshot(miscUiState) const noDisplay = haveModals || gameLoaded || !appLoaded @@ -118,6 +118,7 @@ export default () => { return {(state) =>
showModal({ reactType: 'serversList' })} singleplayerAction={async () => { const oldFormatSave = fs.existsSync('./world/level.dat') From 2414111b9cf24ebbe75948234ed33c20cc4c6e50 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 26 Feb 2025 23:29:18 +0300 Subject: [PATCH 372/834] feat: add packets recording control to pause menu, display packets view after recording was started for in real time server packets debug, fix auto captured packets display --- package.json | 2 +- pnpm-lock.yaml | 12 ++--- src/mineflayer/plugins/localRelay.ts | 59 +++++++++++++++++++++--- src/packetsReplay/packetsReplayLegacy.ts | 10 ++-- src/react/AppStatusProvider.tsx | 2 +- src/react/PauseScreen.tsx | 25 ++++++++-- 6 files changed, 86 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index f3f2f644..b67a2b03 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "mojangson": "^2.0.4", "net-browserify": "github:zardoy/prismarinejs-net-browserify", "node-gzip": "^1.1.2", - "mcraft-fun-mineflayer": "^0.1.6", + "mcraft-fun-mineflayer": "^0.1.7", "peerjs": "^1.5.0", "pixelarticons": "^1.8.1", "pretty-bytes": "^6.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7060521d..3386ac03 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -135,8 +135,8 @@ importers: specifier: ^4.17.21 version: 4.17.21 mcraft-fun-mineflayer: - specifier: ^0.1.6 - version: 0.1.6(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13)) + specifier: ^0.1.7 + version: 0.1.7(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13)) minecraft-data: specifier: 3.83.1 version: 3.83.1 @@ -6252,9 +6252,9 @@ packages: resolution: {integrity: sha512-49tk3shwxsDoV0PrrbORZEKg613vUQPULgusWjXNl8JEma5y41LEo57D6q4aHliXsV3Gb9ThrkFf6hIb0WlY1Q==} engines: {node: '>=18.0.0'} - mcraft-fun-mineflayer@0.1.6: - resolution: {integrity: sha512-ifaIL//NJtkGcYasEULy0KcJjwUA8BwcmC/KoIpTTj6Xmk5o8AYEVdUnR9jrir4kpiLBOIbHgG1QhY1Wbofl1g==} - version: 0.1.6 + mcraft-fun-mineflayer@0.1.7: + resolution: {integrity: sha512-DJPpp1YFwztoscdIOwfqBV9lbotva621F9GEep3BlqG3l06UdTzX2ElkvwyTR0IurFFBX/YKsNfxwL5WtLytgw==} + version: 0.1.7 engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: '@roamhq/wrtc': '*' @@ -16691,7 +16691,7 @@ snapshots: apl-image-packer: 1.1.0 zod: 3.24.1 - mcraft-fun-mineflayer@0.1.6(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13)): + mcraft-fun-mineflayer@0.1.7(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13)): dependencies: '@zardoy/flying-squid': 0.0.49(encoding@0.1.13) exit-hook: 2.2.1 diff --git a/src/mineflayer/plugins/localRelay.ts b/src/mineflayer/plugins/localRelay.ts index e6c8cfe5..ab8527e5 100644 --- a/src/mineflayer/plugins/localRelay.ts +++ b/src/mineflayer/plugins/localRelay.ts @@ -3,9 +3,22 @@ import { PACKETS_REPLAY_FILE_EXTENSION, WORLD_STATE_FILE_EXTENSION } from 'mcraf import { Bot } from 'mineflayer' import CircularBuffer from 'flying-squid/dist/circularBuffer' import { PacketsLogger } from 'mcraft-fun-mineflayer/build/packetsLogger' +import { subscribe } from 'valtio' import { lastConnectOptions } from '../../react/AppStatusProvider' +import { packetsReplaceSessionState } from '../../packetsReplay/packetsReplayLegacy' +import { packetsReplayState } from '../../react/state/packetsReplayState' + +const AUTO_CAPTURE_PACKETS_COUNT = 30 +let circularBuffer: CircularBuffer | undefined +let lastConnectVersion = '' export const localRelayServerPlugin = (bot: Bot) => { + lastConnectVersion = bot.version + let ended = false + bot.on('end', () => { + ended = true + }) + bot.loadPlugin( viewerConnector({ tcpEnabled: false, @@ -28,28 +41,60 @@ export const localRelayServerPlugin = (bot: Bot) => { } circularBuffer = new CircularBuffer(AUTO_CAPTURE_PACKETS_COUNT) + let position = 0 bot._client.on('writePacket' as any, (name, params) => { - circularBuffer!.add({ name, params, isFromServer: false }) + circularBuffer!.add({ name, state: bot._client.state, params, isFromServer: false, timestamp: Date.now() }) + if (packetsReplaceSessionState.active) { + packetsReplayState.packetsPlayback.push({ + name, + data: params, + isFromClient: true, + isUpcoming: false, + position: position++, + timestamp: Date.now(), + }) + } }) bot._client.on('packet', (data, { name }) => { - circularBuffer!.add({ name, params: data, isFromServer: true }) + if (name === 'map_chunk') data = { x: data.x, z: data.z } + circularBuffer!.add({ name, state: bot._client.state, params: data, isFromServer: true, timestamp: Date.now() }) + if (packetsReplaceSessionState.active) { + packetsReplayState.packetsPlayback.push({ + name, + data, + isFromClient: false, + isUpcoming: false, + position: position++, + timestamp: Date.now(), + }) + } }) + + upPacketsReplayPanel() } +const upPacketsReplayPanel = () => { + if (packetsReplaceSessionState.active && bot) { + packetsReplayState.isOpen = true + packetsReplayState.replayName = 'Recording all packets for ' + bot.username + } +} + +subscribe(packetsReplaceSessionState, () => { + upPacketsReplayPanel() +}) + declare module 'mineflayer' { interface Bot { downloadCurrentWorldState: () => void } } -const AUTO_CAPTURE_PACKETS_COUNT = 30 -let circularBuffer: CircularBuffer | undefined - export const getLastAutoCapturedPackets = () => circularBuffer?.size export const downloadAutoCapturedPackets = () => { - const logger = new PacketsLogger() + const logger = new PacketsLogger({ minecraftVersion: lastConnectVersion }) for (const packet of circularBuffer?.getLastElements() ?? []) { - logger.log(packet.isFromServer, packet.name, packet.params) + logger.log(packet.isFromServer, { name: packet.name, state: packet.state, time: packet.timestamp }, packet.params) } const textContents = logger.contents const blob = new Blob([textContents], { type: 'text/plain' }) diff --git a/src/packetsReplay/packetsReplayLegacy.ts b/src/packetsReplay/packetsReplayLegacy.ts index 0db97012..478bbc5d 100644 --- a/src/packetsReplay/packetsReplayLegacy.ts +++ b/src/packetsReplay/packetsReplayLegacy.ts @@ -7,7 +7,8 @@ export const packetsReplaceSessionState = proxy({ hasRecordedPackets: false }) -export const replayLogger = new PacketsLogger() +// eslint-disable-next-line import/no-mutable-exports +export let replayLogger: PacketsLogger | undefined const isBufferData = (data: any): boolean => { if (Buffer.isBuffer(data) || data instanceof Uint8Array) return true @@ -35,13 +36,14 @@ const processPacketData = (data: any): any => { export default () => { customEvents.on('mineflayerBotCreated', () => { + replayLogger = new PacketsLogger({ minecraftVersion: bot.version }) replayLogger.contents = '' packetsReplaceSessionState.hasRecordedPackets = false const handleServerPacket = (data, { name, state = bot._client.state }) => { if (!packetsReplaceSessionState.active) { return } - replayLogger.log(true, { name, state }, processPacketData(data)) + replayLogger!.log(true, { name, state }, processPacketData(data)) packetsReplaceSessionState.hasRecordedPackets = true } bot._client.on('packet', handleServerPacket) @@ -53,7 +55,7 @@ export default () => { if (!packetsReplaceSessionState.active) { return } - replayLogger.log(false, { name, state: bot._client.state }, processPacketData(data)) + replayLogger!.log(false, { name, state: bot._client.state }, processPacketData(data)) packetsReplaceSessionState.hasRecordedPackets = true }) }) @@ -61,7 +63,7 @@ export default () => { export const downloadPacketsReplay = async () => { const a = document.createElement('a') - a.href = `data:text/plain;charset=utf-8,${encodeURIComponent(replayLogger.contents)}` + a.href = `data:text/plain;charset=utf-8,${encodeURIComponent(replayLogger!.contents)}` a.download = `packets-replay-${new Date().toISOString()}.txt` a.click() } diff --git a/src/react/AppStatusProvider.tsx b/src/react/AppStatusProvider.tsx index a3ef4c9d..e83416c1 100644 --- a/src/react/AppStatusProvider.tsx +++ b/src/react/AppStatusProvider.tsx @@ -147,7 +147,7 @@ export default () => { <> {displayAuthButton &&
error
}>
From dffadbb06c56ef9288d0ed61758f63724dd3d7e6 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 26 Feb 2025 23:33:13 +0300 Subject: [PATCH 373/834] wip jei channel --- src/customChannels.ts | 36 +++++++++++++++++++++++++++++- src/packetsReplay/replayPackets.ts | 2 +- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/customChannels.ts b/src/customChannels.ts index 71dc7067..35922101 100644 --- a/src/customChannels.ts +++ b/src/customChannels.ts @@ -8,7 +8,10 @@ customEvents.on('mineflayerBotCreated', async () => { resolve(true) }) }) + registerBlockModelsChannel() +}) +const registerBlockModelsChannel = () => { const CHANNEL_NAME = 'minecraft-web-client:blockmodels' const packetStructure = [ @@ -72,4 +75,35 @@ customEvents.on('mineflayerBotCreated', async () => { }) console.debug(`registered custom channel ${CHANNEL_NAME} channel`) -}) +} + +const registeredJeiChannel = () => { + const CHANNEL_NAME = 'minecraft-web-client:jei' + // id - string, categoryTitle - string, items - string (json array) + const packetStructure = [ + 'container', + [ + { + name: 'id', + type: 'pstring', + }, + { + name: 'categoryTitle', + type: 'pstring', + }, + { + name: 'items', + type: 'pstring', + }, + ] + ] + + bot._client.registerChannel(CHANNEL_NAME, packetStructure, true) + + bot._client.on(CHANNEL_NAME as any, (data) => { + const { id, categoryTitle, items } = data + // ... + }) + + console.debug(`registered custom channel ${CHANNEL_NAME} channel`) +} diff --git a/src/packetsReplay/replayPackets.ts b/src/packetsReplay/replayPackets.ts index e3935258..9e777b38 100644 --- a/src/packetsReplay/replayPackets.ts +++ b/src/packetsReplay/replayPackets.ts @@ -1,7 +1,7 @@ /* eslint-disable no-await-in-loop */ import { createServer, ServerClient } from 'minecraft-protocol' import { ParsedReplayPacket, parseReplayContents } from 'mcraft-fun-mineflayer/build/packetsLogger' -import { WorldStateHeader, PACKETS_REPLAY_FILE_EXTENSION, WORLD_STATE_FILE_EXTENSION } from 'mcraft-fun-mineflayer/build/worldState' +import { PACKETS_REPLAY_FILE_EXTENSION, WORLD_STATE_FILE_EXTENSION } from 'mcraft-fun-mineflayer/build/worldState' import MinecraftData from 'minecraft-data' import { LocalServer } from '../customServer' import { UserError } from '../mineflayer/userError' From d348a44bb86cb1678f7727b92c2018acbc0eaf51 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 27 Feb 2025 04:48:03 +0300 Subject: [PATCH 374/834] add a way to disable recording button on pause menu, refactor --- src/index.ts | 2 +- .../{localRelay.ts => packetsRecording.ts} | 10 +++--- src/optionsGuiScheme.tsx | 8 ++--- src/optionsStorage.ts | 2 +- src/packetsReplay/packetsReplayLegacy.ts | 12 +++---- src/react/AppStatusProvider.tsx | 6 ++-- src/react/Chat.tsx | 1 + src/react/PauseScreen.tsx | 34 +++++++++++-------- 8 files changed, 41 insertions(+), 34 deletions(-) rename src/mineflayer/plugins/{localRelay.ts => packetsRecording.ts} (92%) diff --git a/src/index.ts b/src/index.ts index 01912e94..249fbe9d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -115,7 +115,7 @@ import { UserError } from './mineflayer/userError' import ping from './mineflayer/plugins/ping' import { LocalServer } from './customServer' import { startLocalReplayServer } from './packetsReplay/replayPackets' -import { localRelayServerPlugin } from './mineflayer/plugins/localRelay' +import { localRelayServerPlugin } from './mineflayer/plugins/packetsRecording' import { createFullScreenProgressReporter } from './core/progressReporter' window.debug = debug diff --git a/src/mineflayer/plugins/localRelay.ts b/src/mineflayer/plugins/packetsRecording.ts similarity index 92% rename from src/mineflayer/plugins/localRelay.ts rename to src/mineflayer/plugins/packetsRecording.ts index ab8527e5..f1ff18cf 100644 --- a/src/mineflayer/plugins/localRelay.ts +++ b/src/mineflayer/plugins/packetsRecording.ts @@ -5,7 +5,7 @@ import CircularBuffer from 'flying-squid/dist/circularBuffer' import { PacketsLogger } from 'mcraft-fun-mineflayer/build/packetsLogger' import { subscribe } from 'valtio' import { lastConnectOptions } from '../../react/AppStatusProvider' -import { packetsReplaceSessionState } from '../../packetsReplay/packetsReplayLegacy' +import { packetsRecordingState } from '../../packetsReplay/packetsReplayLegacy' import { packetsReplayState } from '../../react/state/packetsReplayState' const AUTO_CAPTURE_PACKETS_COUNT = 30 @@ -44,7 +44,7 @@ export const localRelayServerPlugin = (bot: Bot) => { let position = 0 bot._client.on('writePacket' as any, (name, params) => { circularBuffer!.add({ name, state: bot._client.state, params, isFromServer: false, timestamp: Date.now() }) - if (packetsReplaceSessionState.active) { + if (packetsRecordingState.active) { packetsReplayState.packetsPlayback.push({ name, data: params, @@ -58,7 +58,7 @@ export const localRelayServerPlugin = (bot: Bot) => { bot._client.on('packet', (data, { name }) => { if (name === 'map_chunk') data = { x: data.x, z: data.z } circularBuffer!.add({ name, state: bot._client.state, params: data, isFromServer: true, timestamp: Date.now() }) - if (packetsReplaceSessionState.active) { + if (packetsRecordingState.active) { packetsReplayState.packetsPlayback.push({ name, data, @@ -74,13 +74,13 @@ export const localRelayServerPlugin = (bot: Bot) => { } const upPacketsReplayPanel = () => { - if (packetsReplaceSessionState.active && bot) { + if (packetsRecordingState.active && bot) { packetsReplayState.isOpen = true packetsReplayState.replayName = 'Recording all packets for ' + bot.username } } -subscribe(packetsReplaceSessionState, () => { +subscribe(packetsRecordingState, () => { upPacketsReplayPanel() }) diff --git a/src/optionsGuiScheme.tsx b/src/optionsGuiScheme.tsx index 6f4d4fea..7032e644 100644 --- a/src/optionsGuiScheme.tsx +++ b/src/optionsGuiScheme.tsx @@ -11,7 +11,7 @@ import { getScreenRefreshRate } from './utils' import { setLoadingScreenStatus } from './appStatus' import { openFilePicker, resetLocalStorageWithoutWorld } from './browserfs' import { completeResourcepackPackInstall, getResourcePackNames, resourcePackState, uninstallResourcePack } from './resourcePack' -import { downloadPacketsReplay, packetsReplaceSessionState } from './packetsReplay/packetsReplayLegacy' +import { downloadPacketsReplay, packetsRecordingState } from './packetsReplay/packetsReplayLegacy' import { showOptionsModal } from './react/SelectOption' import supportedVersions from './supportedVersions.mjs' import { getVersionAutoSelect } from './connect' @@ -462,18 +462,18 @@ export const guiOptionsScheme: { }, { custom () { - const { active } = useSnapshot(packetsReplaceSessionState) + const { active } = useSnapshot(packetsRecordingState) return }, }, { custom () { - const { active, hasRecordedPackets } = useSnapshot(packetsReplaceSessionState) + const { active, hasRecordedPackets } = useSnapshot(packetsRecordingState) return
error
}> From dec93c2b64c185b251ebb092c3b3519d206fb890 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 27 Feb 2025 04:48:16 +0300 Subject: [PATCH 375/834] fix react warning --- src/react/PauseScreen.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/react/PauseScreen.tsx b/src/react/PauseScreen.tsx index d7aa2201..4c4dec03 100644 --- a/src/react/PauseScreen.tsx +++ b/src/react/PauseScreen.tsx @@ -227,19 +227,19 @@ export default () => { if (!isModalActive) return null - const pauseLinks: any[] = [] + const pauseLinks: React.ReactNode[] = [] const pauseLinksConfig = miscUiState.appConfig?.pauseLinks if (pauseLinksConfig) { - for (const row of pauseLinksConfig) { - const rowButtons: any[] = [] + for (const [i, row] of pauseLinksConfig.entries()) { + const rowButtons: React.ReactNode[] = [] for (const button of row) { const style = { width: (204 / row.length - (row.length > 1 ? 4 : 0)) + 'px' } if (button.type === 'discord') { - rowButtons.push() + rowButtons.push() } else if (button.type === 'github') { - rowButtons.push() + rowButtons.push() } else if (button.type === 'url' && button.text) { - rowButtons.push() + rowButtons.push() } } pauseLinks.push(
{rowButtons}
) From fa9c0813c3214cceea51de13ce844b6b86006260 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 27 Feb 2025 05:17:18 +0300 Subject: [PATCH 376/834] fix: seagrass and kelp are always waterlogged --- renderer/viewer/lib/mesher/models.ts | 15 ++++++++++----- renderer/viewer/lib/mesher/world.ts | 13 ------------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/renderer/viewer/lib/mesher/models.ts b/renderer/viewer/lib/mesher/models.ts index 0dec4015..dd2952fb 100644 --- a/renderer/viewer/lib/mesher/models.ts +++ b/renderer/viewer/lib/mesher/models.ts @@ -43,10 +43,6 @@ function prepareTints (tints) { }) } -function mod (x: number, n: number) { - return ((x % n) + n) % n -} - const calculatedBlocksEntries = Object.entries(legacyJson.clientCalculatedBlocks) export function preflatBlockCalculation (block: Block, world: World, position: Vec3) { const type = calculatedBlocksEntries.find(([name, blocks]) => blocks.includes(block.name))?.[0] @@ -439,7 +435,16 @@ function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO: } } -const isBlockWaterlogged = (block: Block) => block.getProperties().waterlogged === true || block.getProperties().waterlogged === 'true' +const ALWAYS_WATERLOGGED = new Set([ + 'seagrass', + 'tall_seagrass', + 'kelp', + 'kelp_plant', + 'bubble_column' +]) +const isBlockWaterlogged = (block: Block) => { + return block.getProperties().waterlogged === true || block.getProperties().waterlogged === 'true' || ALWAYS_WATERLOGGED.has(block.name) +} let unknownBlockModel: BlockModelPartsResolved export function getSectionGeometry (sx, sy, sz, world: World) { diff --git a/renderer/viewer/lib/mesher/world.ts b/renderer/viewer/lib/mesher/world.ts index e13bf760..f2757ae6 100644 --- a/renderer/viewer/lib/mesher/world.ts +++ b/renderer/viewer/lib/mesher/world.ts @@ -10,14 +10,6 @@ import { INVISIBLE_BLOCKS } from './worldConstants' const ignoreAoBlocks = Object.keys(moreBlockDataGeneratedJson.noOcclusions) -const ALWAYS_WATERLOGGED = new Set([ - 'seagrass', - 'tall_seagrass', - 'kelp', - 'kelp_plant', - 'bubble_column' -]) - function columnKey (x, z) { return `${x},${z}` } @@ -178,11 +170,6 @@ export class World { if (!attr) throw new Error('attr is required') const props = block.getProperties() - // Patch waterlogged property for ocean plants - if (ALWAYS_WATERLOGGED.has(block.name)) { - props.waterlogged = 'true' - } - try { // fixme if (this.preflat) { From ceb4cb0b66644962da2afbbf31d4faf6ec35cd76 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Thu, 27 Feb 2025 15:26:38 +0300 Subject: [PATCH 377/834] feat: Refactor mouse controls, fixing all false entity/item interaction issues (#286) --- package.json | 1 + pnpm-lock.yaml | 858 ++++++++++++++++++++++++++- src/cameraRotationControls.ts | 14 +- src/devtools.ts | 5 +- src/globals.d.ts | 2 +- src/index.ts | 11 +- src/mineflayer/plugins/mouse.ts | 204 +++++++ src/optionsStorage.ts | 1 + src/react/GameInteractionOverlay.tsx | 3 +- src/react/TouchAreasControls.tsx | 10 +- src/worldInteractions.ts | 551 ----------------- 11 files changed, 1066 insertions(+), 594 deletions(-) create mode 100644 src/mineflayer/plugins/mouse.ts delete mode 100644 src/worldInteractions.ts diff --git a/package.json b/package.json index b67a2b03..b0c5ee20 100644 --- a/package.json +++ b/package.json @@ -145,6 +145,7 @@ "http-browserify": "^1.7.0", "http-server": "^14.1.1", "https-browserify": "^1.0.0", + "mineflayer-mouse": "^0.0.4", "mc-assets": "^0.2.37", "minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next", "mineflayer": "github:zardoy/mineflayer", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3386ac03..be7ce2e5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -282,7 +282,7 @@ importers: version: 7.4.6(encoding@0.1.13)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.5.4) '@storybook/react-vite': specifier: ^7.4.6 - version: 7.4.6(encoding@0.1.13)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(rollup@2.79.1)(typescript@5.5.4)(vite@4.5.3(@types/node@22.8.1)(terser@5.31.3)) + version: 7.4.6(encoding@0.1.13)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(rollup@2.79.1)(typescript@5.5.4)(vite@6.2.0(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1)) '@types/diff-match-patch': specifier: ^1.0.36 version: 1.0.36 @@ -358,6 +358,9 @@ importers: mineflayer: specifier: github:zardoy/mineflayer version: https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13) + mineflayer-mouse: + specifier: ^0.0.4 + version: 0.0.4(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1) mineflayer-pathfinder: specifier: ^2.4.4 version: 2.4.4 @@ -1265,6 +1268,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.25.0': + resolution: {integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.18.20': resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} engines: {node: '>=12'} @@ -1277,6 +1286,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.25.0': + resolution: {integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.18.20': resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} engines: {node: '>=12'} @@ -1289,6 +1304,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.25.0': + resolution: {integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.18.20': resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} engines: {node: '>=12'} @@ -1301,6 +1322,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.25.0': + resolution: {integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.18.20': resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} engines: {node: '>=12'} @@ -1313,6 +1340,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.25.0': + resolution: {integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.18.20': resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} engines: {node: '>=12'} @@ -1325,6 +1358,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.25.0': + resolution: {integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.18.20': resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} engines: {node: '>=12'} @@ -1337,6 +1376,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.25.0': + resolution: {integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.18.20': resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} engines: {node: '>=12'} @@ -1349,6 +1394,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.25.0': + resolution: {integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.18.20': resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} engines: {node: '>=12'} @@ -1361,6 +1412,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.25.0': + resolution: {integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.18.20': resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} engines: {node: '>=12'} @@ -1373,6 +1430,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.25.0': + resolution: {integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.18.20': resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} engines: {node: '>=12'} @@ -1385,6 +1448,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.25.0': + resolution: {integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.18.20': resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} engines: {node: '>=12'} @@ -1397,6 +1466,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.25.0': + resolution: {integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.18.20': resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} engines: {node: '>=12'} @@ -1409,6 +1484,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.25.0': + resolution: {integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.18.20': resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} engines: {node: '>=12'} @@ -1421,6 +1502,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.25.0': + resolution: {integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.18.20': resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} engines: {node: '>=12'} @@ -1433,6 +1520,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.25.0': + resolution: {integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.18.20': resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} engines: {node: '>=12'} @@ -1445,6 +1538,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.25.0': + resolution: {integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.18.20': resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} engines: {node: '>=12'} @@ -1457,6 +1556,18 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.25.0': + resolution: {integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.0': + resolution: {integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.18.20': resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} engines: {node: '>=12'} @@ -1469,6 +1580,18 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.25.0': + resolution: {integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.0': + resolution: {integrity: sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.18.20': resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} engines: {node: '>=12'} @@ -1481,6 +1604,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.25.0': + resolution: {integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/sunos-x64@0.18.20': resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} engines: {node: '>=12'} @@ -1493,6 +1622,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.25.0': + resolution: {integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.18.20': resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} engines: {node: '>=12'} @@ -1505,6 +1640,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.25.0': + resolution: {integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.18.20': resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} engines: {node: '>=12'} @@ -1517,6 +1658,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.25.0': + resolution: {integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.18.20': resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} engines: {node: '>=12'} @@ -1529,6 +1676,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.25.0': + resolution: {integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.4.0': resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1813,6 +1966,9 @@ packages: '@jridgewell/sourcemap-codec@1.4.15': resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} @@ -2244,6 +2400,101 @@ packages: rollup: optional: true + '@rollup/rollup-android-arm-eabi@4.34.8': + resolution: {integrity: sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.34.8': + resolution: {integrity: sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.34.8': + resolution: {integrity: sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.34.8': + resolution: {integrity: sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.34.8': + resolution: {integrity: sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.34.8': + resolution: {integrity: sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.34.8': + resolution: {integrity: sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.34.8': + resolution: {integrity: sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.34.8': + resolution: {integrity: sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.34.8': + resolution: {integrity: sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.34.8': + resolution: {integrity: sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.34.8': + resolution: {integrity: sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.34.8': + resolution: {integrity: sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.34.8': + resolution: {integrity: sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.34.8': + resolution: {integrity: sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.34.8': + resolution: {integrity: sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.34.8': + resolution: {integrity: sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.34.8': + resolution: {integrity: sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.34.8': + resolution: {integrity: sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==} + cpu: [x64] + os: [win32] + '@rsbuild/core@1.0.1-beta.9': resolution: {integrity: sha512-F9npL47TFmNVhPBqoE6jBvKGxXEKNszBA7skhbi3opskmX7Ako9vfXvtgi2W2jQjq837/WUL8gG/ua9zRqKFEQ==} engines: {node: '>=16.7.0'} @@ -2722,6 +2973,9 @@ packages: '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/express-serve-static-core@4.17.37': resolution: {integrity: sha512-ZohaCYTgGFcOP7u6aJOhY9uIZQgZ2vxC2yWoArY+FeDXlqeH66ZVBjgvg+RLVAS/DWNq4Ap9ZXu1+SUQiiWYMg==} @@ -3023,18 +3277,47 @@ packages: '@vitest/expect@0.34.6': resolution: {integrity: sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw==} + '@vitest/expect@3.0.7': + resolution: {integrity: sha512-QP25f+YJhzPfHrHfYHtvRn+uvkCFCqFtW9CktfBxmB+25QqWsx7VB2As6f4GmwllHLDhXNHvqedwhvMmSnNmjw==} + + '@vitest/mocker@3.0.7': + resolution: {integrity: sha512-qui+3BLz9Eonx4EAuR/i+QlCX6AUZ35taDQgwGkK/Tw6/WgwodSrjN1X2xf69IA/643ZX5zNKIn2svvtZDrs4w==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.0.7': + resolution: {integrity: sha512-CiRY0BViD/V8uwuEzz9Yapyao+M9M008/9oMOSQydwbwb+CMokEq3XVaF3XK/VWaOK0Jm9z7ENhybg70Gtxsmg==} + '@vitest/runner@0.34.6': resolution: {integrity: sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ==} + '@vitest/runner@3.0.7': + resolution: {integrity: sha512-WeEl38Z0S2ZcuRTeyYqaZtm4e26tq6ZFqh5y8YD9YxfWuu0OFiGFUbnxNynwLjNRHPsXyee2M9tV7YxOTPZl2g==} + '@vitest/snapshot@0.34.6': resolution: {integrity: sha512-B3OZqYn6k4VaN011D+ve+AA4whM4QkcwcrwaKwAbyyvS/NB1hCWjFIBQxAQQSQir9/RtyAAGuq+4RJmbn2dH4w==} + '@vitest/snapshot@3.0.7': + resolution: {integrity: sha512-eqTUryJWQN0Rtf5yqCGTQWsCFOQe4eNz5Twsu21xYEcnFJtMU5XvmG0vgebhdLlrHQTSq5p8vWHJIeJQV8ovsA==} + '@vitest/spy@0.34.6': resolution: {integrity: sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ==} + '@vitest/spy@3.0.7': + resolution: {integrity: sha512-4T4WcsibB0B6hrKdAZTM37ekuyFZt2cGbEGd2+L0P8ov15J1/HUsUaqkXEQPNAWr4BtPPe1gI+FYfMHhEKfR8w==} + '@vitest/utils@0.34.6': resolution: {integrity: sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==} + '@vitest/utils@3.0.7': + resolution: {integrity: sha512-xePVpCRfooFX3rANQjwoditoXgWb1MaFbzmGuPP59MK6i13mrnDw/yEIyJudLeW6/38mCNcwCiJIGmpDPibAIg==} + '@webassemblyjs/ast@1.12.1': resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==} @@ -3418,6 +3701,10 @@ packages: assertion-error@1.1.0: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + assign-symbols@1.0.0: resolution: {integrity: sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==} engines: {node: '>=0.10.0'} @@ -3777,6 +4064,10 @@ packages: resolution: {integrity: sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==} engines: {node: '>=4'} + chai@5.2.0: + resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} + engines: {node: '>=12'} + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -3795,6 +4086,9 @@ packages: change-case@5.1.2: resolution: {integrity: sha512-CAtbGEDulyjzs05RXy3uKcwqeztz/dMEuAc1Xu9NQBsbrhuGMneL0u9Dj5SoutLKBFYun8txxYIwhjtLNfUmCA==} + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + character-entities@2.0.2: resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} @@ -3804,6 +4098,10 @@ packages: check-error@1.0.3: resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + check-more-types@2.24.0: resolution: {integrity: sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==} engines: {node: '>= 0.8.0'} @@ -4220,6 +4518,10 @@ packages: resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} engines: {node: '>=6'} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} @@ -4541,6 +4843,9 @@ packages: es-module-lexer@1.5.4: resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} + es-module-lexer@1.6.0: + resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -4586,6 +4891,11 @@ packages: engines: {node: '>=12'} hasBin: true + esbuild@0.25.0: + resolution: {integrity: sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==} + engines: {node: '>=18'} + hasBin: true + escalade@3.1.2: resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} @@ -4794,6 +5104,9 @@ packages: estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -4849,6 +5162,10 @@ packages: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} + expect-type@1.1.0: + resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} + engines: {node: '>=12.0.0'} + exponential-backoff@3.1.1: resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==} @@ -6161,6 +6478,9 @@ packages: resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==} deprecated: Please upgrade to 2.3.7 which fixes GHSA-4q6p-r6v2-jvc5 + loupe@3.1.3: + resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} + lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} @@ -6189,6 +6509,9 @@ packages: resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} engines: {node: '>=12'} + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + magic-string@0.30.4: resolution: {integrity: sha512-Q/TKtsC5BPm0kGqgBIF9oXAs/xEf2vRKiIB4wCRQTJOQIByZ1d+NnUOotvJOvNpi5RNIgVOMC3pOuaP1ZTDlVg==} engines: {node: '>=12'} @@ -6478,6 +6801,10 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824} version: 1.2.0 + mineflayer-mouse@0.0.4: + resolution: {integrity: sha512-55GqQhJWCXnOnm30uOjtI7nsawPb0kA3cAv6a5n1NJjTWFR6hzMkiRT6xGLYrvYhdf6Er3nsE2Ok/Aysa/jtFQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + mineflayer-pathfinder@2.4.4: resolution: {integrity: sha512-HAXakZrJRb1UC+5dv8EaDrqjW3ZnBnBk3nkb6x/YWyhHCUKn/E7VU0FO+UN9whuqPlkSaVumEdXJdydE6lSYxQ==} @@ -6615,6 +6942,11 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + nanomatch@1.2.13: resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==} engines: {node: '>=0.10.0'} @@ -7006,9 +7338,16 @@ packages: pathe@1.1.1: resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pathval@1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + pause-stream@0.0.11: resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} @@ -7122,6 +7461,10 @@ packages: resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} engines: {node: ^10 || ^12 || >=14} + postcss@8.5.3: + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} + engines: {node: ^10 || ^12 || >=14} + potpack@1.0.2: resolution: {integrity: sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==} @@ -7810,6 +8153,11 @@ packages: engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true + rollup@4.34.8: + resolution: {integrity: sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + rope-sequence@1.3.4: resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} @@ -8199,6 +8547,9 @@ packages: std-env@3.4.3: resolution: {integrity: sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==} + std-env@3.8.0: + resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} + store2@2.14.2: resolution: {integrity: sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==} @@ -8441,17 +8792,35 @@ packages: tinybench@2.5.1: resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinycolor2@1.6.0: resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinypool@0.7.0: resolution: {integrity: sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==} engines: {node: '>=14.0.0'} + tinypool@1.0.2: + resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + tinyspy@2.2.0: resolution: {integrity: sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==} engines: {node: '>=14.0.0'} + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + title-case@3.0.3: resolution: {integrity: sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==} @@ -8906,6 +9275,11 @@ packages: engines: {node: '>=v14.18.0'} hasBin: true + vite-node@3.0.7: + resolution: {integrity: sha512-2fX0QwX4GkkkpULXdT1Pf4q0tC1i1lFOyseKoonavXUNlQ77KpW2XqBGGNIm/J4Ows4KxgGJzDguYVPKwG/n5A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + vite@4.5.3: resolution: {integrity: sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==} engines: {node: ^14.18.0 || >=16.0.0} @@ -8934,6 +9308,46 @@ packages: terser: optional: true + vite@6.2.0: + resolution: {integrity: sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + vitest@0.34.6: resolution: {integrity: sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==} engines: {node: '>=v14.18.0'} @@ -8965,6 +9379,34 @@ packages: webdriverio: optional: true + vitest@3.0.7: + resolution: {integrity: sha512-IP7gPK3LS3Fvn44x30X1dM9vtawm0aesAa2yBIZ9vQf+qB69NXC5776+Qmcr7ohUXIQuLhk7xQR0aSUIDPqavg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.0.7 + '@vitest/ui': 3.0.7 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vm-browserify@1.1.2: resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==} @@ -9067,6 +9509,11 @@ packages: engines: {node: '>=8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + wide-align@1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} @@ -10255,138 +10702,213 @@ snapshots: '@esbuild/aix-ppc64@0.19.11': optional: true + '@esbuild/aix-ppc64@0.25.0': + optional: true + '@esbuild/android-arm64@0.18.20': optional: true '@esbuild/android-arm64@0.19.11': optional: true + '@esbuild/android-arm64@0.25.0': + optional: true + '@esbuild/android-arm@0.18.20': optional: true '@esbuild/android-arm@0.19.11': optional: true + '@esbuild/android-arm@0.25.0': + optional: true + '@esbuild/android-x64@0.18.20': optional: true '@esbuild/android-x64@0.19.11': optional: true + '@esbuild/android-x64@0.25.0': + optional: true + '@esbuild/darwin-arm64@0.18.20': optional: true '@esbuild/darwin-arm64@0.19.11': optional: true + '@esbuild/darwin-arm64@0.25.0': + optional: true + '@esbuild/darwin-x64@0.18.20': optional: true '@esbuild/darwin-x64@0.19.11': optional: true + '@esbuild/darwin-x64@0.25.0': + optional: true + '@esbuild/freebsd-arm64@0.18.20': optional: true '@esbuild/freebsd-arm64@0.19.11': optional: true + '@esbuild/freebsd-arm64@0.25.0': + optional: true + '@esbuild/freebsd-x64@0.18.20': optional: true '@esbuild/freebsd-x64@0.19.11': optional: true + '@esbuild/freebsd-x64@0.25.0': + optional: true + '@esbuild/linux-arm64@0.18.20': optional: true '@esbuild/linux-arm64@0.19.11': optional: true + '@esbuild/linux-arm64@0.25.0': + optional: true + '@esbuild/linux-arm@0.18.20': optional: true '@esbuild/linux-arm@0.19.11': optional: true + '@esbuild/linux-arm@0.25.0': + optional: true + '@esbuild/linux-ia32@0.18.20': optional: true '@esbuild/linux-ia32@0.19.11': optional: true + '@esbuild/linux-ia32@0.25.0': + optional: true + '@esbuild/linux-loong64@0.18.20': optional: true '@esbuild/linux-loong64@0.19.11': optional: true + '@esbuild/linux-loong64@0.25.0': + optional: true + '@esbuild/linux-mips64el@0.18.20': optional: true '@esbuild/linux-mips64el@0.19.11': optional: true + '@esbuild/linux-mips64el@0.25.0': + optional: true + '@esbuild/linux-ppc64@0.18.20': optional: true '@esbuild/linux-ppc64@0.19.11': optional: true + '@esbuild/linux-ppc64@0.25.0': + optional: true + '@esbuild/linux-riscv64@0.18.20': optional: true '@esbuild/linux-riscv64@0.19.11': optional: true + '@esbuild/linux-riscv64@0.25.0': + optional: true + '@esbuild/linux-s390x@0.18.20': optional: true '@esbuild/linux-s390x@0.19.11': optional: true + '@esbuild/linux-s390x@0.25.0': + optional: true + '@esbuild/linux-x64@0.18.20': optional: true '@esbuild/linux-x64@0.19.11': optional: true + '@esbuild/linux-x64@0.25.0': + optional: true + + '@esbuild/netbsd-arm64@0.25.0': + optional: true + '@esbuild/netbsd-x64@0.18.20': optional: true '@esbuild/netbsd-x64@0.19.11': optional: true + '@esbuild/netbsd-x64@0.25.0': + optional: true + + '@esbuild/openbsd-arm64@0.25.0': + optional: true + '@esbuild/openbsd-x64@0.18.20': optional: true '@esbuild/openbsd-x64@0.19.11': optional: true + '@esbuild/openbsd-x64@0.25.0': + optional: true + '@esbuild/sunos-x64@0.18.20': optional: true '@esbuild/sunos-x64@0.19.11': optional: true + '@esbuild/sunos-x64@0.25.0': + optional: true + '@esbuild/win32-arm64@0.18.20': optional: true '@esbuild/win32-arm64@0.19.11': optional: true + '@esbuild/win32-arm64@0.25.0': + optional: true + '@esbuild/win32-ia32@0.18.20': optional: true '@esbuild/win32-ia32@0.19.11': optional: true + '@esbuild/win32-ia32@0.25.0': + optional: true + '@esbuild/win32-x64@0.18.20': optional: true '@esbuild/win32-x64@0.19.11': optional: true + '@esbuild/win32-x64@0.25.0': + optional: true + '@eslint-community/eslint-utils@4.4.0(eslint@8.50.0)': dependencies: eslint: 8.50.0 @@ -10814,13 +11336,13 @@ snapshots: regenerator-runtime: 0.13.11 optional: true - '@joshwooding/vite-plugin-react-docgen-typescript@0.2.1(typescript@5.5.4)(vite@4.5.3(@types/node@22.8.1)(terser@5.31.3))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.2.1(typescript@5.5.4)(vite@6.2.0(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1))': dependencies: glob: 7.2.3 glob-promise: 4.2.2(glob@7.2.3) magic-string: 0.27.0 react-docgen-typescript: 2.2.2(typescript@5.5.4) - vite: 4.5.3(@types/node@22.8.1)(terser@5.31.3) + vite: 6.2.0(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1) optionalDependencies: typescript: 5.5.4 @@ -10841,6 +11363,8 @@ snapshots: '@jridgewell/sourcemap-codec@1.4.15': {} + '@jridgewell/sourcemap-codec@1.5.0': {} + '@jridgewell/trace-mapping@0.3.25': dependencies: '@jridgewell/resolve-uri': 3.1.1 @@ -11299,6 +11823,63 @@ snapshots: optionalDependencies: rollup: 2.79.1 + '@rollup/rollup-android-arm-eabi@4.34.8': + optional: true + + '@rollup/rollup-android-arm64@4.34.8': + optional: true + + '@rollup/rollup-darwin-arm64@4.34.8': + optional: true + + '@rollup/rollup-darwin-x64@4.34.8': + optional: true + + '@rollup/rollup-freebsd-arm64@4.34.8': + optional: true + + '@rollup/rollup-freebsd-x64@4.34.8': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.34.8': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.34.8': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.34.8': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.34.8': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.34.8': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.34.8': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.34.8': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.34.8': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.34.8': + optional: true + + '@rollup/rollup-linux-x64-musl@4.34.8': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.34.8': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.34.8': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.34.8': + optional: true + '@rsbuild/core@1.0.1-beta.9': dependencies: '@rspack/core': 1.0.0-beta.1(@swc/helpers@0.5.11) @@ -11696,7 +12277,7 @@ snapshots: - encoding - supports-color - '@storybook/builder-vite@7.4.6(encoding@0.1.13)(typescript@5.5.4)(vite@4.5.3(@types/node@22.8.1)(terser@5.31.3))': + '@storybook/builder-vite@7.4.6(encoding@0.1.13)(typescript@5.5.4)(vite@6.2.0(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1))': dependencies: '@storybook/channels': 7.4.6 '@storybook/client-logger': 7.4.6 @@ -11717,7 +12298,7 @@ snapshots: remark-external-links: 8.0.0 remark-slug: 6.1.0 rollup: 3.29.4 - vite: 4.5.3(@types/node@22.8.1)(terser@5.31.3) + vite: 6.2.0(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1) optionalDependencies: typescript: 5.5.4 transitivePeerDependencies: @@ -12003,19 +12584,19 @@ snapshots: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - '@storybook/react-vite@7.4.6(encoding@0.1.13)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(rollup@2.79.1)(typescript@5.5.4)(vite@4.5.3(@types/node@22.8.1)(terser@5.31.3))': + '@storybook/react-vite@7.4.6(encoding@0.1.13)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(rollup@2.79.1)(typescript@5.5.4)(vite@6.2.0(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.2.1(typescript@5.5.4)(vite@4.5.3(@types/node@22.8.1)(terser@5.31.3)) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.2.1(typescript@5.5.4)(vite@6.2.0(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1)) '@rollup/pluginutils': 5.0.5(rollup@2.79.1) - '@storybook/builder-vite': 7.4.6(encoding@0.1.13)(typescript@5.5.4)(vite@4.5.3(@types/node@22.8.1)(terser@5.31.3)) + '@storybook/builder-vite': 7.4.6(encoding@0.1.13)(typescript@5.5.4)(vite@6.2.0(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1)) '@storybook/react': 7.4.6(encoding@0.1.13)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.5.4) - '@vitejs/plugin-react': 3.1.0(vite@4.5.3(@types/node@22.8.1)(terser@5.31.3)) + '@vitejs/plugin-react': 3.1.0(vite@6.2.0(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1)) ast-types: 0.14.2 magic-string: 0.30.4 react: 18.2.0 react-docgen: 6.0.0-alpha.3 react-dom: 18.2.0(react@18.2.0) - vite: 4.5.3(@types/node@22.8.1)(terser@5.31.3) + vite: 6.2.0(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1) transitivePeerDependencies: - '@preact/preset-vite' - encoding @@ -12237,6 +12818,8 @@ snapshots: '@types/estree@1.0.5': {} + '@types/estree@1.0.6': {} + '@types/express-serve-static-core@4.17.37': dependencies: '@types/node': 22.8.1 @@ -12582,14 +13165,14 @@ snapshots: '@typescript-eslint/types': 8.0.0 eslint-visitor-keys: 3.4.3 - '@vitejs/plugin-react@3.1.0(vite@4.5.3(@types/node@22.8.1)(terser@5.31.3))': + '@vitejs/plugin-react@3.1.0(vite@6.2.0(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1))': dependencies: '@babel/core': 7.22.11 '@babel/plugin-transform-react-jsx-self': 7.22.5(@babel/core@7.22.11) '@babel/plugin-transform-react-jsx-source': 7.22.5(@babel/core@7.22.11) magic-string: 0.27.0 react-refresh: 0.14.2 - vite: 4.5.3(@types/node@22.8.1)(terser@5.31.3) + vite: 6.2.0(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1) transitivePeerDependencies: - supports-color @@ -12599,28 +13182,68 @@ snapshots: '@vitest/utils': 0.34.6 chai: 4.3.10 + '@vitest/expect@3.0.7': + dependencies: + '@vitest/spy': 3.0.7 + '@vitest/utils': 3.0.7 + chai: 5.2.0 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.0.7(vite@6.2.0(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1))': + dependencies: + '@vitest/spy': 3.0.7 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 6.2.0(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1) + + '@vitest/pretty-format@3.0.7': + dependencies: + tinyrainbow: 2.0.0 + '@vitest/runner@0.34.6': dependencies: '@vitest/utils': 0.34.6 p-limit: 4.0.0 pathe: 1.1.1 + '@vitest/runner@3.0.7': + dependencies: + '@vitest/utils': 3.0.7 + pathe: 2.0.3 + '@vitest/snapshot@0.34.6': dependencies: magic-string: 0.30.4 pathe: 1.1.1 pretty-format: 29.7.0 + '@vitest/snapshot@3.0.7': + dependencies: + '@vitest/pretty-format': 3.0.7 + magic-string: 0.30.17 + pathe: 2.0.3 + '@vitest/spy@0.34.6': dependencies: tinyspy: 2.2.0 + '@vitest/spy@3.0.7': + dependencies: + tinyspy: 3.0.2 + '@vitest/utils@0.34.6': dependencies: diff-sequences: 29.6.3 loupe: 2.3.6 pretty-format: 29.7.0 + '@vitest/utils@3.0.7': + dependencies: + '@vitest/pretty-format': 3.0.7 + loupe: 3.1.3 + tinyrainbow: 2.0.0 + '@webassemblyjs/ast@1.12.1': dependencies: '@webassemblyjs/helper-numbers': 1.11.6 @@ -13123,6 +13746,8 @@ snapshots: assertion-error@1.1.0: {} + assertion-error@2.0.1: {} + assign-symbols@1.0.0: {} ast-types@0.14.2: @@ -13603,6 +14228,14 @@ snapshots: pathval: 1.1.1 type-detect: 4.0.8 + chai@5.2.0: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.3 + pathval: 2.0.0 + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 @@ -13633,6 +14266,8 @@ snapshots: change-case@5.1.2: {} + change-case@5.4.4: {} + character-entities@2.0.2: {} charenc@0.0.2: {} @@ -13641,6 +14276,8 @@ snapshots: dependencies: get-func-name: 2.0.2 + check-error@2.1.1: {} + check-more-types@2.24.0: optional: true @@ -14154,6 +14791,8 @@ snapshots: dependencies: type-detect: 4.0.8 + deep-eql@5.0.2: {} + deep-extend@0.6.0: {} deep-is@0.1.4: {} @@ -14600,6 +15239,8 @@ snapshots: es-module-lexer@1.5.4: {} + es-module-lexer@1.6.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -14693,6 +15334,34 @@ snapshots: '@esbuild/win32-ia32': 0.19.11 '@esbuild/win32-x64': 0.19.11 + esbuild@0.25.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.0 + '@esbuild/android-arm': 0.25.0 + '@esbuild/android-arm64': 0.25.0 + '@esbuild/android-x64': 0.25.0 + '@esbuild/darwin-arm64': 0.25.0 + '@esbuild/darwin-x64': 0.25.0 + '@esbuild/freebsd-arm64': 0.25.0 + '@esbuild/freebsd-x64': 0.25.0 + '@esbuild/linux-arm': 0.25.0 + '@esbuild/linux-arm64': 0.25.0 + '@esbuild/linux-ia32': 0.25.0 + '@esbuild/linux-loong64': 0.25.0 + '@esbuild/linux-mips64el': 0.25.0 + '@esbuild/linux-ppc64': 0.25.0 + '@esbuild/linux-riscv64': 0.25.0 + '@esbuild/linux-s390x': 0.25.0 + '@esbuild/linux-x64': 0.25.0 + '@esbuild/netbsd-arm64': 0.25.0 + '@esbuild/netbsd-x64': 0.25.0 + '@esbuild/openbsd-arm64': 0.25.0 + '@esbuild/openbsd-x64': 0.25.0 + '@esbuild/sunos-x64': 0.25.0 + '@esbuild/win32-arm64': 0.25.0 + '@esbuild/win32-ia32': 0.25.0 + '@esbuild/win32-x64': 0.25.0 + escalade@3.1.2: {} escape-html@1.0.3: {} @@ -14968,6 +15637,10 @@ snapshots: estree-walker@2.0.2: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.6 + esutils@2.0.3: {} etag@1.8.1: {} @@ -15045,6 +15718,8 @@ snapshots: expand-template@2.0.3: {} + expect-type@1.1.0: {} + exponential-backoff@3.1.1: optional: true @@ -16586,6 +17261,8 @@ snapshots: dependencies: get-func-name: 2.0.2 + loupe@3.1.3: {} + lower-case@2.0.2: dependencies: tslib: 2.6.2 @@ -16613,6 +17290,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + magic-string@0.30.4: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 @@ -17061,6 +17742,33 @@ snapshots: - encoding - supports-color + mineflayer-mouse@0.0.4(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1): + dependencies: + change-case: 5.4.4 + debug: 4.4.0(supports-color@8.1.1) + prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c + vitest: 3.0.7(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1) + transitivePeerDependencies: + - '@edge-runtime/vm' + - '@types/debug' + - '@types/node' + - '@vitest/browser' + - '@vitest/ui' + - happy-dom + - jiti + - jsdom + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + mineflayer-pathfinder@2.4.4: dependencies: minecraft-data: 3.83.1 @@ -17254,6 +17962,8 @@ snapshots: nanoid@3.3.7: {} + nanoid@3.3.8: {} + nanomatch@1.2.13: dependencies: arr-diff: 4.0.0 @@ -17712,8 +18422,12 @@ snapshots: pathe@1.1.1: {} + pathe@2.0.3: {} + pathval@1.1.1: {} + pathval@2.0.0: {} + pause-stream@0.0.11: dependencies: through: 2.3.8 @@ -17821,6 +18535,12 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postcss@8.5.3: + dependencies: + nanoid: 3.3.8 + picocolors: 1.1.1 + source-map-js: 1.2.1 + potpack@1.0.2: {} prebuild-install@7.1.1: @@ -18703,6 +19423,31 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + rollup@4.34.8: + dependencies: + '@types/estree': 1.0.6 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.34.8 + '@rollup/rollup-android-arm64': 4.34.8 + '@rollup/rollup-darwin-arm64': 4.34.8 + '@rollup/rollup-darwin-x64': 4.34.8 + '@rollup/rollup-freebsd-arm64': 4.34.8 + '@rollup/rollup-freebsd-x64': 4.34.8 + '@rollup/rollup-linux-arm-gnueabihf': 4.34.8 + '@rollup/rollup-linux-arm-musleabihf': 4.34.8 + '@rollup/rollup-linux-arm64-gnu': 4.34.8 + '@rollup/rollup-linux-arm64-musl': 4.34.8 + '@rollup/rollup-linux-loongarch64-gnu': 4.34.8 + '@rollup/rollup-linux-powerpc64le-gnu': 4.34.8 + '@rollup/rollup-linux-riscv64-gnu': 4.34.8 + '@rollup/rollup-linux-s390x-gnu': 4.34.8 + '@rollup/rollup-linux-x64-gnu': 4.34.8 + '@rollup/rollup-linux-x64-musl': 4.34.8 + '@rollup/rollup-win32-arm64-msvc': 4.34.8 + '@rollup/rollup-win32-ia32-msvc': 4.34.8 + '@rollup/rollup-win32-x64-msvc': 4.34.8 + fsevents: 2.3.3 + rope-sequence@1.3.4: {} rtl-css-js@1.16.1: @@ -19251,6 +19996,8 @@ snapshots: std-env@3.4.3: {} + std-env@3.8.0: {} + store2@2.14.2: {} storybook@7.4.6(encoding@0.1.13): @@ -19537,13 +20284,23 @@ snapshots: tinybench@2.5.1: {} + tinybench@2.9.0: {} + tinycolor2@1.6.0: optional: true + tinyexec@0.3.2: {} + tinypool@0.7.0: {} + tinypool@1.0.2: {} + + tinyrainbow@2.0.0: {} + tinyspy@2.2.0: {} + tinyspy@3.0.2: {} + title-case@3.0.3: dependencies: tslib: 2.6.2 @@ -20014,6 +20771,27 @@ snapshots: - supports-color - terser + vite-node@3.0.7(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1): + dependencies: + cac: 6.7.14 + debug: 4.4.0(supports-color@8.1.1) + es-module-lexer: 1.6.0 + pathe: 2.0.3 + vite: 6.2.0(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + vite@4.5.3(@types/node@22.8.1)(terser@5.31.3): dependencies: esbuild: 0.18.20 @@ -20024,6 +20802,18 @@ snapshots: fsevents: 2.3.3 terser: 5.31.3 + vite@6.2.0(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1): + dependencies: + esbuild: 0.25.0 + postcss: 8.5.3 + rollup: 4.34.8 + optionalDependencies: + '@types/node': 22.8.1 + fsevents: 2.3.3 + terser: 5.31.3 + tsx: 4.7.0 + yaml: 2.4.1 + vitest@0.34.6(terser@5.31.3): dependencies: '@types/chai': 4.3.6 @@ -20059,6 +20849,45 @@ snapshots: - supports-color - terser + vitest@3.0.7(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1): + dependencies: + '@vitest/expect': 3.0.7 + '@vitest/mocker': 3.0.7(vite@6.2.0(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1)) + '@vitest/pretty-format': 3.0.7 + '@vitest/runner': 3.0.7 + '@vitest/snapshot': 3.0.7 + '@vitest/spy': 3.0.7 + '@vitest/utils': 3.0.7 + chai: 5.2.0 + debug: 4.4.0(supports-color@8.1.1) + expect-type: 1.1.0 + magic-string: 0.30.17 + pathe: 2.0.3 + std-env: 3.8.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.0.2 + tinyrainbow: 2.0.0 + vite: 6.2.0(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1) + vite-node: 3.0.7(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/debug': 4.1.12 + '@types/node': 22.8.1 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + vm-browserify@1.1.2: {} w3c-keyname@2.2.8: {} @@ -20212,6 +21041,11 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + wide-align@1.1.5: dependencies: string-width: 4.2.3 diff --git a/src/cameraRotationControls.ts b/src/cameraRotationControls.ts index b9bc5fe9..0c222dc6 100644 --- a/src/cameraRotationControls.ts +++ b/src/cameraRotationControls.ts @@ -3,20 +3,10 @@ import { activeModalStack, isGameActive, miscUiState, showModal } from './global import { options } from './optionsStorage' import { hideNotification, notificationProxy } from './react/NotificationProvider' import { pointerLock } from './utils' -import worldInteractions from './worldInteractions' import { updateMotion, initMotionTracking } from './react/uiMotion' let lastMouseMove: number -const MOTION_DAMPING = 0.92 -const MAX_MOTION_OFFSET = 30 -const motionVelocity = { x: 0, y: 0 } -const lastUpdate = performance.now() - -export const updateCursor = () => { - worldInteractions.update() -} - export type CameraMoveEvent = { movementX: number movementY: number @@ -30,7 +20,7 @@ export function onCameraMove (e: MouseEvent | CameraMoveEvent) { e.stopPropagation?.() const now = performance.now() // todo: limit camera movement for now to avoid unexpected jumps - if (now - lastMouseMove < 4) return + if (now - lastMouseMove < 4 && !options.preciseMouseInput) return lastMouseMove = now let { mouseSensX, mouseSensY } = options if (mouseSensY === -1) mouseSensY = mouseSensX @@ -38,7 +28,7 @@ export function onCameraMove (e: MouseEvent | CameraMoveEvent) { x: e.movementX * mouseSensX * 0.0001, y: e.movementY * mouseSensY * 0.0001 }) - updateCursor() + bot.mouse.update() updateMotion() } diff --git a/src/devtools.ts b/src/devtools.ts index 5d0be20b..617a4440 100644 --- a/src/devtools.ts +++ b/src/devtools.ts @@ -3,7 +3,6 @@ import fs from 'fs' import { WorldRendererThree } from 'renderer/viewer/lib/worldrendererThree' import { enable, disable, enabled } from 'debug' -import { getEntityCursor } from './worldInteractions' window.cursorBlockRel = (x = 0, y = 0, z = 0) => { const newPos = bot.blockAtCursor(5)?.position.offset(x, y, z) @@ -11,8 +10,8 @@ window.cursorBlockRel = (x = 0, y = 0, z = 0) => { return bot.world.getBlock(newPos) } -window.cursorEntity = () => { - return getEntityCursor() +window.entityCursor = () => { + return bot.mouse.getCursorState().entity } // wanderer diff --git a/src/globals.d.ts b/src/globals.d.ts index 96b32916..6b2c6640 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -21,7 +21,7 @@ declare const loadedData: import('minecraft-data').IndexedData & { sounds: Recor declare const customEvents: import('typed-emitter').default<{ /** Singleplayer load requested */ singleplayer (): void - digStart () + digStart (): void gameLoaded (): void mineflayerBotCreated (): void search (q: string): void diff --git a/src/index.ts b/src/index.ts index 249fbe9d..beb86aea 100644 --- a/src/index.ts +++ b/src/index.ts @@ -40,8 +40,6 @@ import { WorldDataEmitter, Viewer } from 'renderer/viewer' import pathfinder from 'mineflayer-pathfinder' import { Vec3 } from 'vec3' -import worldInteractions from './worldInteractions' - import * as THREE from 'three' import MinecraftData from 'minecraft-data' import debug from 'debug' @@ -106,13 +104,12 @@ import { parseFormattedMessagePacket } from './botUtils' import { getViewerVersionData, getWsProtocolStream, handleCustomChannel } from './viewerConnector' import { getWebsocketStream } from './mineflayer/websocket-core' import { appQueryParams, appQueryParamsArray } from './appParams' -import { updateCursor } from './cameraRotationControls' -import { pingServerVersion } from './mineflayer/minecraft-protocol-extra' import { playerState, PlayerStateManager } from './mineflayer/playerState' import { states } from 'minecraft-protocol' import { initMotionTracking } from './react/uiMotion' import { UserError } from './mineflayer/userError' import ping from './mineflayer/plugins/ping' +import mouse from './mineflayer/plugins/mouse' import { LocalServer } from './customServer' import { startLocalReplayServer } from './packetsReplay/replayPackets' import { localRelayServerPlugin } from './mineflayer/plugins/packetsRecording' @@ -120,7 +117,6 @@ import { createFullScreenProgressReporter } from './core/progressReporter' window.debug = debug window.THREE = THREE -window.worldInteractions = worldInteractions window.beforeRenderFrame = [] // ACTUAL CODE @@ -705,6 +701,7 @@ export async function connect (connectOptions: ConnectOptions) { if (connectOptions.server) { bot.loadPlugin(ping) } + bot.loadPlugin(mouse) if (!localReplaySession) { bot.loadPlugin(localRelayServerPlugin) } @@ -754,8 +751,6 @@ export async function connect (connectOptions: ConnectOptions) { onBotCreate() bot.once('login', () => { - worldInteractions.initBot() - setLoadingScreenStatus('Loading world') const mcData = MinecraftData(bot.version) @@ -815,8 +810,6 @@ export async function connect (connectOptions: ConnectOptions) { const worldView = window.worldView = new WorldDataEmitter(bot.world, renderDistance, center) watchOptionsAfterWorldViewInit() - bot.on('physicsTick', () => updateCursor()) - void initVR() initMotionTracking() diff --git a/src/mineflayer/plugins/mouse.ts b/src/mineflayer/plugins/mouse.ts new file mode 100644 index 00000000..e5b5e283 --- /dev/null +++ b/src/mineflayer/plugins/mouse.ts @@ -0,0 +1,204 @@ +import { createMouse } from 'mineflayer-mouse' +import * as THREE from 'three' +import { Bot } from 'mineflayer' +import { Block } from 'prismarine-block' +import { Vec3 } from 'vec3' +import { LineMaterial } from 'three-stdlib' +import { subscribeKey } from 'valtio/utils' +import { disposeObject } from 'renderer/viewer/lib/threeJsUtils' +import { isGameActive, showModal } from '../../globalState' + +// wouldn't better to create atlas instead? +import destroyStage0 from '../../../assets/destroy_stage_0.png' +import destroyStage1 from '../../../assets/destroy_stage_1.png' +import destroyStage2 from '../../../assets/destroy_stage_2.png' +import destroyStage3 from '../../../assets/destroy_stage_3.png' +import destroyStage4 from '../../../assets/destroy_stage_4.png' +import destroyStage5 from '../../../assets/destroy_stage_5.png' +import destroyStage6 from '../../../assets/destroy_stage_6.png' +import destroyStage7 from '../../../assets/destroy_stage_7.png' +import destroyStage8 from '../../../assets/destroy_stage_8.png' +import destroyStage9 from '../../../assets/destroy_stage_9.png' +import { options } from '../../optionsStorage' +import { isCypress } from '../../standaloneUtils' +import { playerState } from '../playerState' + +function createDisplayManager (bot: Bot, scene: THREE.Scene, renderer: THREE.WebGLRenderer) { + // State + const state = { + blockBreakMesh: null as THREE.Mesh | null, + breakTextures: [] as THREE.Texture[], + } + + // Initialize break mesh and textures + const loader = new THREE.TextureLoader() + const destroyStagesImages = [ + destroyStage0, destroyStage1, destroyStage2, destroyStage3, destroyStage4, + destroyStage5, destroyStage6, destroyStage7, destroyStage8, destroyStage9 + ] + + for (let i = 0; i < 10; i++) { + const texture = loader.load(destroyStagesImages[i]) + texture.magFilter = THREE.NearestFilter + texture.minFilter = THREE.NearestFilter + state.breakTextures.push(texture) + } + + const breakMaterial = new THREE.MeshBasicMaterial({ + transparent: true, + blending: THREE.MultiplyBlending, + alphaTest: 0.5, + }) + state.blockBreakMesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), breakMaterial) + state.blockBreakMesh.visible = false + state.blockBreakMesh.renderOrder = 999 + state.blockBreakMesh.name = 'blockBreakMesh' + scene.add(state.blockBreakMesh) + + // Update functions + function updateLineMaterial () { + const inCreative = bot.game.gameMode === 'creative' + const pixelRatio = viewer.renderer.getPixelRatio() + + viewer.world.threejsCursorLineMaterial = new LineMaterial({ + color: (() => { + switch (options.highlightBlockColor) { + case 'blue': + return 0x40_80_ff + case 'classic': + return 0x00_00_00 + default: + return inCreative ? 0x40_80_ff : 0x00_00_00 + } + })(), + linewidth: Math.max(pixelRatio * 0.7, 1) * 2, + // dashed: true, + // dashSize: 5, + }) + } + + function updateDisplay () { + if (viewer.world.threejsCursorLineMaterial) { + const { renderer } = viewer + viewer.world.threejsCursorLineMaterial.resolution.set(renderer.domElement.width, renderer.domElement.height) + viewer.world.threejsCursorLineMaterial.dashOffset = performance.now() / 750 + } + } + beforeRenderFrame.push(updateDisplay) + + // Update cursor line material on game mode change + bot.on('game', updateLineMaterial) + // Update material when highlight color setting changes + subscribeKey(options, 'highlightBlockColor', updateLineMaterial) + + function updateBreakAnimation (block: Block | undefined, stage: number | null) { + hideBreakAnimation() + if (!state.blockBreakMesh) return // todo + if (stage === null || !block) return + + const mergedShape = bot.mouse.getMergedCursorShape(block) + if (!mergedShape) return + const { position, width, height, depth } = bot.mouse.getDataFromShape(mergedShape) + state.blockBreakMesh.scale.set(width * 1.001, height * 1.001, depth * 1.001) + position.add(block.position) + state.blockBreakMesh.position.set(position.x, position.y, position.z) + state.blockBreakMesh.visible = true + + //@ts-expect-error + state.blockBreakMesh.material.map = state.breakTextures[stage] ?? state.breakTextures.at(-1) + //@ts-expect-error + state.blockBreakMesh.material.needsUpdate = true + } + + function hideBreakAnimation () { + if (state.blockBreakMesh) { + state.blockBreakMesh.visible = false + } + } + + function updateCursorBlock (data?: { block: Block }) { + if (!data?.block) { + viewer.world.setHighlightCursorBlock(null) + return + } + + const { block } = data + viewer.world.setHighlightCursorBlock(block.position, bot.mouse.getBlockCursorShapes(block).map(shape => { + return bot.mouse.getDataFromShape(shape) + })) + } + + bot.on('highlightCursorBlock', updateCursorBlock) + + bot.on('blockBreakProgressStage', updateBreakAnimation) + + bot.on('end', () => { + disposeObject(state.blockBreakMesh!, true) + scene.remove(state.blockBreakMesh!) + viewer.world.setHighlightCursorBlock(null) + }) +} + +export default (bot: Bot) => { + bot.loadPlugin(createMouse({})) + + domListeners(bot) + createDisplayManager(bot, viewer.scene, viewer.renderer) + + otherListeners() +} + +const otherListeners = () => { + bot.on('startDigging', (block) => { + customEvents.emit('digStart') + }) + + bot.on('goingToSleep', () => { + showModal({ reactType: 'bed' }) + }) + + bot.on('botArmSwingStart', (hand) => { + viewer.world.changeHandSwingingState(true, hand === 'left') + }) + + bot.on('botArmSwingEnd', (hand) => { + viewer.world.changeHandSwingingState(false, hand === 'left') + }) + + bot.on('startUsingItem', (item, slot, isOffhand, duration) => { + customEvents.emit('activateItem', item, isOffhand ? 45 : bot.quickBarSlot, isOffhand) + playerState.startUsingItem() + }) + + bot.on('stopUsingItem', () => { + playerState.stopUsingItem() + }) +} + +const domListeners = (bot: Bot) => { + document.addEventListener('mousedown', (e) => { + if (e.isTrusted && !document.pointerLockElement && !isCypress()) return + if (!isGameActive(true)) return + + if (e.button === 0) { + bot.leftClickStart() + } else if (e.button === 2) { + bot.rightClickStart() + } + }) + + document.addEventListener('mouseup', (e) => { + if (e.button === 0) { + bot.leftClickEnd() + } else if (e.button === 2) { + bot.rightClickEnd() + } + }) + + bot.mouse.beforeUpdateChecks = () => { + if (!document.hasFocus()) { + // deactive all buttons + bot.mouse.buttons.fill(false) + } + } +} diff --git a/src/optionsStorage.ts b/src/optionsStorage.ts index 47109979..b88b71c4 100644 --- a/src/optionsStorage.ts +++ b/src/optionsStorage.ts @@ -60,6 +60,7 @@ const defaultOptions = { serversAutoVersionSelect: 'auto' as 'auto' | 'latest' | '1.20.4' | string, customChannels: false, packetsReplayAutoStart: false, + preciseMouseInput: false, // todo ui setting, maybe enable by default? waitForChunksRender: 'sp-only' as 'sp-only' | boolean, diff --git a/src/react/GameInteractionOverlay.tsx b/src/react/GameInteractionOverlay.tsx index 503ca8b4..cb7a39f8 100644 --- a/src/react/GameInteractionOverlay.tsx +++ b/src/react/GameInteractionOverlay.tsx @@ -3,7 +3,6 @@ import { subscribe, useSnapshot } from 'valtio' import { useUtilsEffect } from '@zardoy/react-util' import { options } from '../optionsStorage' import { activeModalStack, isGameActive, miscUiState } from '../globalState' -import worldInteractions from '../worldInteractions' import { onCameraMove, CameraMoveEvent } from '../cameraRotationControls' import { pointerLock, isInRealGameSession } from '../utils' import { handleMovementStickDelta, joystickPointer } from './TouchAreasControls' @@ -152,7 +151,7 @@ function GameInteractionOverlayInner ({ virtualClickActive = false } else if (!capturedPointer.active.activateCameraMove && (Date.now() - capturedPointer.active.time < touchStartBreakingBlockMs)) { document.dispatchEvent(new MouseEvent('mousedown', { button: 2 })) - worldInteractions.update() + bot.mouse.update() document.dispatchEvent(new MouseEvent('mouseup', { button: 2 })) } diff --git a/src/react/TouchAreasControls.tsx b/src/react/TouchAreasControls.tsx index 981ebebb..6b34f9bc 100644 --- a/src/react/TouchAreasControls.tsx +++ b/src/react/TouchAreasControls.tsx @@ -1,7 +1,6 @@ import { CSSProperties, PointerEvent, useEffect, useRef } from 'react' import { proxy, ref, useSnapshot } from 'valtio' import { contro } from '../controls' -import worldInteractions from '../worldInteractions' import { options } from '../optionsStorage' import PixelartIcon from './PixelartIcon' import Button from './Button' @@ -73,8 +72,9 @@ export default ({ setupActive, closeButtonsSetup, foregroundGameActive }: Props) }[name] const holdDown = { action () { + if (!bot) return document.dispatchEvent(new MouseEvent('mousedown', { button: 2 })) - worldInteractions.update() + bot.mouse.update() }, sneak () { void contro.emit('trigger', { @@ -84,8 +84,9 @@ export default ({ setupActive, closeButtonsSetup, foregroundGameActive }: Props) active = bot?.getControlState('sneak') }, break () { + if (!bot) return document.dispatchEvent(new MouseEvent('mousedown', { button: 0 })) - worldInteractions.update() + bot.mouse.update() active = true }, jump () { @@ -108,8 +109,9 @@ export default ({ setupActive, closeButtonsSetup, foregroundGameActive }: Props) active = bot?.getControlState('sneak') }, break () { + if (!bot) return document.dispatchEvent(new MouseEvent('mouseup', { button: 0 })) - worldInteractions.update() + bot.mouse.update() active = false }, jump () { diff --git a/src/worldInteractions.ts b/src/worldInteractions.ts deleted file mode 100644 index 5b03184c..00000000 --- a/src/worldInteractions.ts +++ /dev/null @@ -1,551 +0,0 @@ -//@ts-check - -import * as THREE from 'three' - -// wouldn't better to create atlas instead? -import { Vec3 } from 'vec3' -import { LineMaterial } from 'three-stdlib' -import { Entity } from 'prismarine-entity' -import { Block } from 'prismarine-block' -import { subscribeKey } from 'valtio/utils' -import destroyStage0 from '../assets/destroy_stage_0.png' -import destroyStage1 from '../assets/destroy_stage_1.png' -import destroyStage2 from '../assets/destroy_stage_2.png' -import destroyStage3 from '../assets/destroy_stage_3.png' -import destroyStage4 from '../assets/destroy_stage_4.png' -import destroyStage5 from '../assets/destroy_stage_5.png' -import destroyStage6 from '../assets/destroy_stage_6.png' -import destroyStage7 from '../assets/destroy_stage_7.png' -import destroyStage8 from '../assets/destroy_stage_8.png' -import destroyStage9 from '../assets/destroy_stage_9.png' - -import { hideCurrentModal, isGameActive, showModal } from './globalState' -import { assertDefined } from './utils' -import { options } from './optionsStorage' -import { itemBeingUsed } from './react/Crosshair' -import { isCypress } from './standaloneUtils' -import { displayClientChat } from './botUtils' -import { playerState } from './mineflayer/playerState' - -function getViewDirection (pitch, yaw) { - const csPitch = Math.cos(pitch) - const snPitch = Math.sin(pitch) - const csYaw = Math.cos(yaw) - const snYaw = Math.sin(yaw) - return new Vec3(-snYaw * csPitch, snPitch, -csYaw * csPitch) -} - -class WorldInteraction { - ready = false - cursorBlock: Block | null = null - prevBreakState: number | null = null - currentDigTime: number | null = null - prevOnGround: boolean | null = null - lastBlockPlaced: number - lastSwing = 0 - buttons = [false, false, false] - lastButtons = [false, false, false] - breakStartTime: number | undefined = 0 - lastDugBlock: Vec3 | null = null - blockBreakMesh: THREE.Mesh - breakTextures: THREE.Texture[] - lastDigged: number - debugDigStatus: string - currentBreakBlock: { block: any, stage: number } | null = null - swingTimeout: any = null - - oneTimeInit () { - const loader = new THREE.TextureLoader() - this.breakTextures = [] - const destroyStagesImages = [ - destroyStage0, - destroyStage1, - destroyStage2, - destroyStage3, - destroyStage4, - destroyStage5, - destroyStage6, - destroyStage7, - destroyStage8, - destroyStage9 - ] - for (let i = 0; i < 10; i++) { - const texture = loader.load(destroyStagesImages[i]) - texture.magFilter = THREE.NearestFilter - texture.minFilter = THREE.NearestFilter - this.breakTextures.push(texture) - } - const breakMaterial = new THREE.MeshBasicMaterial({ - transparent: true, - blending: THREE.MultiplyBlending, - alphaTest: 0.5, - }) - this.blockBreakMesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), breakMaterial) - this.blockBreakMesh.visible = false - this.blockBreakMesh.renderOrder = 999 - this.blockBreakMesh.name = 'blockBreakMesh' - viewer.scene.add(this.blockBreakMesh) - - // Setup events - document.addEventListener('mouseup', (e) => { - this.buttons[e.button] = false - }) - - this.lastBlockPlaced = 4 // ticks since last placed - document.addEventListener('mousedown', (e) => { - if (e.isTrusted && !document.pointerLockElement && !isCypress()) return - if (!isGameActive(true)) return - this.buttons[e.button] = true - - const entity = getEntityCursor() - - if (entity) { - if (e.button === 0) { // left click - bot.attack(entity) - } else if (e.button === 2) { // right click - this.activateEntity(entity) - } - } - }) - document.addEventListener('blur', (e) => { - this.buttons = [false, false, false] - }) - - beforeRenderFrame.push(() => { - if (viewer.world.threejsCursorLineMaterial) { - const { renderer } = viewer - viewer.world.threejsCursorLineMaterial.resolution.set(renderer.domElement.width, renderer.domElement.height) - viewer.world.threejsCursorLineMaterial.dashOffset = performance.now() / 750 - } - }) - } - - initBot () { - if (!this.ready) { - this.ready = true - this.oneTimeInit() - } - assertDefined(viewer) - bot.on('physicsTick', () => { if (this.lastBlockPlaced < 4) this.lastBlockPlaced++ }) - bot.on('diggingCompleted', (block) => { - this.breakStartTime = undefined - this.lastDugBlock = block.position - // TODO: If the tool and enchantments immediately exceed the hardness times 30, the block breaks with no delay; SO WE NEED TO CHECK THAT - // TODO: Any blocks with a breaking time of 0.05 - this.lastDigged = Date.now() - this.debugDigStatus = 'done' - this.stopBreakAnimation() - }) - bot.on('diggingAborted', (block) => { - if (!viewer.world.cursorBlock?.equals(block.position)) return - this.debugDigStatus = 'aborted' - this.breakStartTime = undefined - if (this.buttons[0]) { - this.buttons[0] = false - this.update() - this.buttons[0] = true // trigger again - } - this.lastDugBlock = null - this.stopBreakAnimation() - }) - bot.on('heldItemChanged' as any, () => { - itemBeingUsed.name = null - }) - - // Add new event listeners for block breaking and swinging - bot.on('entitySwingArm', (entity: Entity) => { - if (entity.id === bot.entity.id) { - if (this.swingTimeout) { - clearTimeout(this.swingTimeout) - } - bot.swingArm('right') - viewer.world.changeHandSwingingState(true, false) - this.swingTimeout = setTimeout(() => { - viewer.world.changeHandSwingingState(false, false) - this.swingTimeout = null - }, 250) - } - }) - - //@ts-expect-error mineflayer types are wrong - bot.on('blockBreakProgressObserved', (block: Block, destroyStage: number, entity: Entity) => { - if (this.cursorBlock?.position.equals(block.position) && entity.id === bot.entity.id) { - if (!this.buttons[0]) { - // Simulate left mouse button press - this.buttons[0] = true - this.update() - } - // this.setBreakState(block, destroyStage) - } - }) - - //@ts-expect-error mineflayer types are wrong - bot.on('blockBreakProgressEnd', (block: Block, entity: Entity) => { - if (this.currentBreakBlock?.block.position.equals(block.position) && entity.id === bot.entity.id) { - if (!this.buttons[0]) { - // Simulate left mouse button press - this.buttons[0] = false - this.update() - } - // this.stopBreakAnimation() - } - }) - - // Handle acknowledge_player_digging packet - bot._client.on('acknowledge_player_digging', (data: { location: { x: number, y: number, z: number }, block: number, status: number, successful: boolean } | { sequenceId: number }) => { - if ('location' in data && !data.successful) { - const packetPos = new Vec3(data.location.x, data.location.y, data.location.z) - if (this.cursorBlock?.position.equals(packetPos)) { - this.buttons[0] = false - this.update() - this.stopBreakAnimation() - } - } - }) - - const upLineMaterial = () => { - const inCreative = bot.game.gameMode === 'creative' - const pixelRatio = viewer.renderer.getPixelRatio() - viewer.world.threejsCursorLineMaterial = new LineMaterial({ - color: (() => { - switch (options.highlightBlockColor) { - case 'blue': - return 0x40_80_ff - case 'classic': - return 0x00_00_00 - default: - return inCreative ? 0x40_80_ff : 0x00_00_00 - } - })(), - linewidth: Math.max(pixelRatio * 0.7, 1) * 2, - // dashed: true, - // dashSize: 5, - }) - } - upLineMaterial() - // todo use gamemode update only - bot.on('game', upLineMaterial) - // Update material when highlight color setting changes - subscribeKey(options, 'highlightBlockColor', upLineMaterial) - } - - activateEntity (entity: Entity) { - // mineflayer has completely wrong implementation of this action - if (bot.supportFeature('armAnimationBeforeUse')) { - bot.swingArm('right') - } - bot._client.write('use_entity', { - target: entity.id, - mouse: 2, - // todo do not fake - x: 0.581_012_585_759_162_9, - y: 0.581_012_585_759_162_9, - z: 0.581_012_585_759_162_9, - // x: raycastPosition.x - entity.position.x, - // y: raycastPosition.y - entity.position.y, - // z: raycastPosition.z - entity.position.z - sneaking: bot.getControlState('sneak'), - hand: 0 - }) - bot._client.write('use_entity', { - target: entity.id, - mouse: 0, - sneaking: bot.getControlState('sneak'), - hand: 0 - }) - if (!bot.supportFeature('armAnimationBeforeUse')) { - bot.swingArm('right') - } - } - - beforeUpdateChecks () { - if (!document.hasFocus()) { - // deactive all buttson - this.buttons.fill(false) - } - } - - // todo this shouldnt be done in the render loop, migrate the code to dom events to avoid delays on lags - update () { - this.beforeUpdateChecks() - const inSpectator = bot.game.gameMode === 'spectator' - const inAdventure = bot.game.gameMode === 'adventure' - const entity = getEntityCursor() - let _cursorBlock = inSpectator && !options.showCursorBlockInSpectator ? null : bot.blockAtCursor(5) - if (entity) { - _cursorBlock = null - } - this.cursorBlock = _cursorBlock - const { cursorBlock } = this - - let cursorBlockDiggable = cursorBlock - if (cursorBlock && (!bot.canDigBlock(cursorBlock) || inAdventure) && bot.game.gameMode !== 'creative') cursorBlockDiggable = null - - const cursorChanged = cursorBlock && viewer.world.cursorBlock ? !viewer.world.cursorBlock.equals(cursorBlock.position) : viewer.world.cursorBlock !== cursorBlock - - // Place / interact / activate - if (this.buttons[2] && this.lastBlockPlaced >= 4) { - const activatableItems = (itemName: string) => { - return ['egg', 'fishing_rod', 'firework_rocket', - 'fire_charge', 'snowball', 'ender_pearl', 'experience_bottle', 'potion', - 'glass_bottle', 'bucket', 'water_bucket', 'lava_bucket', 'milk_bucket', - 'minecart', 'boat', 'tnt_minecart', 'chest_minecart', 'hopper_minecart', - 'command_block_minecart', 'armor_stand', 'lead', 'name_tag', - // - 'writable_book', 'written_book', 'compass', 'clock', 'filled_map', 'empty_map', 'map', - 'shears', 'carrot_on_a_stick', 'warped_fungus_on_a_stick', - 'spawn_egg', 'trident', 'crossbow', 'elytra', 'shield', 'turtle_helmet', 'bow', 'crossbow', 'bucket_of_cod', - ...loadedData.foodsArray.map((f) => f.name), - ].includes(itemName) - } - const activate = bot.heldItem && activatableItems(bot.heldItem.name) - let stop = false - if (!bot.controlState.sneak) { - if (cursorBlock?.name === 'bed' || cursorBlock?.name.endsWith('_bed')) { - stop = true - showModal({ reactType: 'bed' }) - let cancelSleep = true - void bot.sleep(cursorBlock).catch((e) => { - if (cancelSleep) { - hideCurrentModal() - } - // if (e.message === 'bot is not sleeping') return - displayClientChat(e.message) - }) - setTimeout(() => { - cancelSleep = false - }) - } - } - // todo placing with offhand - if (cursorBlock && !activate && !stop) { - 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)] - //@ts-expect-error - const delta = cursorBlock.intersect.minus(cursorBlock.position) - - if (bot.heldItem) { - //@ts-expect-error todo - bot._placeBlockWithOptions(cursorBlock, vecArray[cursorBlock.face], { delta, forceLook: 'ignore' }).catch(console.warn) - } else { - // https://discord.com/channels/413438066984747026/413438150594265099/1198724637572477098 - const oldLookAt = bot.lookAt - //@ts-expect-error - bot.lookAt = (pos) => { } - //@ts-expect-error - // TODO it still must 1. fire block place 2. swing arm (right) - bot.activateBlock(cursorBlock, vecArray[cursorBlock.face], delta).finally(() => { - bot.lookAt = oldLookAt - }).catch(console.warn) - } - 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 - const item = offhand ? bot.inventory.slots[45] : bot.heldItem - if (item) { - customEvents.emit('activateItem', item, offhand ? 45 : bot.quickBarSlot, offhand) - } - playerState.startUsingItem() - itemBeingUsed.name = (offhand ? bot.inventory.slots[45]?.name : bot.heldItem?.name) ?? null - itemBeingUsed.hand = offhand ? 1 : 0 - } - this.lastBlockPlaced = 0 - } - // stop using activated item (cancel) - if (itemBeingUsed.name && !this.buttons[2]) { - itemBeingUsed.name = null - // "only foods and bow can be deactivated" - not true, shields also can be deactivated and client always sends this - // if (bot.heldItem && (loadedData.foodsArray.map((f) => f.name).includes(bot.heldItem.name) || bot.heldItem.name === 'bow')) { - bot.deactivateItem() - playerState.stopUsingItem() - // } - } - - // 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 - } - // We stopped breaking - if ((!this.buttons[0] && this.lastButtons[0])) { - this.lastDugBlock = null - this.breakStartTime = undefined - this.debugDigStatus = 'cancelled' - this.stopBreakAnimation() - } - - 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] - ) { - if (cursorBlockDiggable - && (!this.lastButtons[0] || ((cursorChanged || (this.lastDugBlock && !this.lastDugBlock.equals(cursorBlock!.position))) && Date.now() - (this.lastDigged ?? 0) > 300) || onGround !== this.prevOnGround) - && onGround) { - this.lastDugBlock = null - this.debugDigStatus = 'breaking' - 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') - this.lastDigged = Date.now() - 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, false) - } - this.prevOnGround = onGround - - // Show cursor - const allShapes = [...cursorBlock?.shapes ?? [], ...cursorBlock?.['interactionShapes'] ?? []] - if (cursorBlock) { - // BREAK MESH - // 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) - } - - // 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) - if (state !== this.prevBreakState) { - this.setBreakState(cursorBlockDiggable, Math.min(state, 9)) - } - this.prevBreakState = state - } else { - this.blockBreakMesh.visible = false - } - - // Update state - if (cursorChanged) { - viewer.world.setHighlightCursorBlock(cursorBlock?.position ?? null, allShapes.map(shape => { - return getDataFromShape(shape) - })) - } - this.lastButtons[0] = this.buttons[0] - this.lastButtons[1] = this.buttons[1] - this.lastButtons[2] = this.buttons[2] - } - - setBreakState (block: Block, stage: number) { - this.currentBreakBlock = { block, stage } - this.blockBreakMesh.visible = true - //@ts-expect-error - this.blockBreakMesh.material.map = this.breakTextures[stage] ?? this.breakTextures.at(-1) - //@ts-expect-error - this.blockBreakMesh.material.needsUpdate = true - } - - stopBreakAnimation () { - this.currentBreakBlock = null - this.blockBreakMesh.visible = false - } -} - -const getDataFromShape = (shape) => { - const width = shape[3] - shape[0] - const height = shape[4] - shape[1] - const depth = shape[5] - shape[2] - const centerX = (shape[3] + shape[0]) / 2 - const centerY = (shape[4] + shape[1]) / 2 - const centerZ = (shape[5] + shape[2]) / 2 - const position = new Vec3(centerX, centerY, centerZ) - return { position, width, height, depth } -} - -// Blocks that can be interacted with in adventure mode -const activatableBlockPatterns = [ - // Containers - /^(chest|barrel|hopper|dispenser|dropper)$/, - /^.*shulker_box$/, - /^.*(furnace|smoker)$/, - /^(brewing_stand|beacon)$/, - // Crafting - /^.*table$/, - /^(grindstone|stonecutter|loom)$/, - /^.*anvil$/, - // Redstone - /^(lever|repeater|comparator|daylight_detector|observer|note_block|jukebox|bell)$/, - // Buttons - /^.*button$/, - // Doors and trapdoors - /^.*door$/, - /^.*trapdoor$/, - // Functional blocks - /^(enchanting_table|lectern|composter|respawn_anchor|lodestone|conduit)$/, - /^.*bee.*$/, - // Beds - /^.*bed$/, - // Misc - /^(cake|decorated_pot|crafter|trial_spawner|vault)$/ -] - -function isBlockActivatable (blockName: string) { - return activatableBlockPatterns.some(pattern => pattern.test(blockName)) -} - -function isLookingAtActivatableBlock (block: Block) { - return isBlockActivatable(block.name) -} - -export const getEntityCursor = () => { - const entity = bot.nearestEntity((e) => { - if (e.position.distanceTo(bot.entity.position) <= (bot.game.gameMode === 'creative' ? 5 : 3)) { - const dir = getViewDirection(bot.entity.pitch, bot.entity.yaw) - const { width, height } = e - const { x: eX, y: eY, z: eZ } = e.position - const { x: bX, y: bY, z: bZ } = bot.entity.position - const box = new THREE.Box3( - new THREE.Vector3(eX - width / 2, eY, eZ - width / 2), - new THREE.Vector3(eX + width / 2, eY + height, eZ + width / 2) - ) - - const r = new THREE.Raycaster( - new THREE.Vector3(bX, bY + 1.52, bZ), - new THREE.Vector3(dir.x, dir.y, dir.z) - ) - const int = r.ray.intersectBox(box, new THREE.Vector3(eX, eY, eZ)) - return int !== null - } - - return false - }) - return entity -} - -const worldInteraction = new WorldInteraction() -globalThis.worldInteraction = worldInteraction -export default worldInteraction From 10f17063c06a5c45106c1cec01af9b0a4727ac51 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 3 Mar 2025 14:19:38 +0300 Subject: [PATCH 378/834] fix: fix whole pipeline of rendering custom items from rp: add them to atlas and update texture propertly. align behavior blocks vs items and gui vs hand/floor --- renderer/viewer/lib/viewer.ts | 7 +---- renderer/viewer/lib/worldrendererCommon.ts | 12 ++++++-- src/index.ts | 17 ++++-------- src/inventoryWindows.ts | 32 +++++++++------------- src/mineflayer/items.ts | 1 + src/resourcePack.ts | 23 +++++++++++----- src/resourcesManager.ts | 23 ++++++++++++++++ 7 files changed, 70 insertions(+), 45 deletions(-) create mode 100644 src/resourcesManager.ts diff --git a/renderer/viewer/lib/viewer.ts b/renderer/viewer/lib/viewer.ts index 7b951438..941f2182 100644 --- a/renderer/viewer/lib/viewer.ts +++ b/renderer/viewer/lib/viewer.ts @@ -86,12 +86,7 @@ export class Viewer { console.log('[viewer] Using version:', userVersion, 'textures:', texturesVersion) this.entities.clear() // this.primitives.clear() - return this.world.setVersion(userVersion, texturesVersion).then(async () => { - return new THREE.TextureLoader().loadAsync(this.world.itemsAtlasParser!.latestImage) - }).then((texture) => { - this.entities.itemsTexture = texture - this.world.renderUpdateEmitter.emit('itemsTextureDownloaded') - }) + return this.world.setVersion(userVersion, texturesVersion) } addColumn (x, z, chunk, isLightUpdate = false) { diff --git a/renderer/viewer/lib/worldrendererCommon.ts b/renderer/viewer/lib/worldrendererCommon.ts index 3ca100a2..a54f9fe8 100644 --- a/renderer/viewer/lib/worldrendererCommon.ts +++ b/renderer/viewer/lib/worldrendererCommon.ts @@ -365,6 +365,7 @@ export abstract class WorldRendererCommon } const customBlockTextures = Object.keys(this.customTextures.blocks?.textures ?? {}) + const customItemTextures = Object.keys(this.customTextures.items?.textures ?? {}) console.time('createBlocksAtlas') const { atlas: blocksAtlas, canvas: blocksCanvas } = await blocksAssetsParser.makeNewAtlas(this.texturesVersion ?? this.version ?? 'latest', (textureName) => { const texture = this.customTextures?.blocks?.textures[textureName] @@ -376,7 +377,7 @@ export abstract class WorldRendererCommon const texture = this.customTextures?.items?.textures[textureName] if (!texture) return return texture - }, this.customTextures?.items?.tileSize) + }, this.customTextures?.items?.tileSize, undefined, customItemTextures) console.timeEnd('createItemsAtlas') this.blocksAtlasParser = new AtlasParser({ latest: blocksAtlas }, blocksCanvas.toDataURL()) this.itemsAtlasParser = new AtlasParser({ latest: itemsAtlas }, itemsCanvas.toDataURL()) @@ -417,8 +418,15 @@ export abstract class WorldRendererCommon config: this.mesherConfig, }) } + const itemsTexture = await new THREE.TextureLoader().loadAsync(this.itemsAtlasParser.latestImage) + itemsTexture.magFilter = THREE.NearestFilter + itemsTexture.minFilter = THREE.NearestFilter + itemsTexture.flipY = false + viewer.entities.itemsTexture = itemsTexture + this.renderUpdateEmitter.emit('textureDownloaded') - console.log('texture loaded') + this.renderUpdateEmitter.emit('itemsTextureDownloaded') + console.log('textures loaded') } async downloadDebugAtlas (isItems = false) { diff --git a/src/index.ts b/src/index.ts index beb86aea..8e586a8f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,7 +14,7 @@ import './mineflayer/java-tester/index' import './external' import { getServerInfo } from './mineflayer/mc-protocol' import { onGameLoad, renderSlot } from './inventoryWindows' -import { RenderItem } from './mineflayer/items' +import { GeneralInputItem, RenderItem } from './mineflayer/items' import initCollisionShapes from './getCollisionInteractionShapes' import protocolMicrosoftAuth from 'minecraft-protocol/src/client/microsoftAuth' import microsoftAuthflow from './microsoftAuthflow' @@ -114,6 +114,7 @@ import { LocalServer } from './customServer' import { startLocalReplayServer } from './packetsReplay/replayPackets' import { localRelayServerPlugin } from './mineflayer/plugins/packetsRecording' import { createFullScreenProgressReporter } from './core/progressReporter' +import { getItemModelName } from './resourcesManager' window.debug = debug window.THREE = THREE @@ -181,19 +182,13 @@ viewer.entities.getItemUv = (item, specificProps) => { const name = typeof idOrName === 'number' ? loadedData.items[idOrName]?.name : idOrName if (!name) throw new Error(`Item not found: ${idOrName}`) - const itemSelector = playerState.getItemSelector({ - ...specificProps - }) - const model = getItemDefinition(viewer.world.itemsDefinitionsStore, { + const model = getItemModelName({ + ...item, name, - version: viewer.world.texturesVersion!, - properties: itemSelector - })?.model ?? name + } as GeneralInputItem, specificProps) const renderInfo = renderSlot({ - ...item, - nbt: null, - name: model, + modelName: model, }, false, true) if (!renderInfo) throw new Error(`Failed to get render info for item ${name}`) diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index 471f5be4..d473f5be 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -20,6 +20,7 @@ import { currentScaling } from './scaleInterface' import { getItemDescription } from './itemsDescriptions' import { MessageFormatPart } from './chatUtils' import { GeneralInputItem, getItemMetadata, getItemNameRaw, RenderItem } from './mineflayer/items' +import { getItemModelName } from './resourcesManager' const loadedImagesCache = new Map() const cleanLoadedImagesCache = () => { @@ -174,14 +175,18 @@ const getImage = ({ path = undefined as string | undefined, texture = undefined return loadedImagesCache.get(loadPath) } -export const renderSlot = (slot: GeneralInputItem, debugIsQuickbar = false, fullBlockModelSupport = false): { +export type ResolvedItemModelRender = { + modelName: string, +} + +export const renderSlot = (model: ResolvedItemModelRender, debugIsQuickbar = false, fullBlockModelSupport = false): { texture: string, blockData?: Record & { resolvedModel: BlockModel }, scale?: number, slice?: number[], modelName?: string } | undefined => { - let itemModelName = slot.name + let itemModelName = model.modelName const originalItemName = itemModelName const isItem = loadedData.itemsByName[itemModelName] @@ -190,15 +195,12 @@ export const renderSlot = (slot: GeneralInputItem, debugIsQuickbar = false, full // #endregion - const { customModel } = getItemMetadata(slot) - if (customModel) { - itemModelName = customModel - } - let itemTexture try { assertDefined(viewer.world.itemsRenderer) - itemTexture = viewer.world.itemsRenderer.getItemTexture(itemModelName, {}, false, fullBlockModelSupport) ?? viewer.world.itemsRenderer.getItemTexture('item/missing_texture')! + itemTexture = + viewer.world.itemsRenderer.getItemTexture(itemModelName, {}, false, fullBlockModelSupport) + ?? viewer.world.itemsRenderer.getItemTexture('item/missing_texture')! } catch (err) { inGameError(`Failed to render item ${itemModelName} (original: ${originalItemName}) on ${bot.version} (resourcepack: ${options.enabledResourcepack}): ${err.stack}`) itemTexture = viewer.world.itemsRenderer!.getItemTexture('block/errored')! @@ -228,23 +230,15 @@ const getItemName = (slot: Item | RenderItem | null) => { return text.join('') } -export const renderSlotExternal = (slot) => { - const data = renderSlot(slot) - if (!data) return - return { - imageDataUrl: data.texture === 'invsprite' ? undefined : getImage({ path: data.texture })?.src, - sprite: data.slice && data.texture !== 'invsprite' ? data.slice.map(x => x * 2) : data.slice, - displayName: getItemName(slot) ?? slot.displayName, - } -} - const mapSlots = (slots: Array) => { return slots.map((slot, i) => { // todo stateid if (!slot) return try { - const slotCustomProps = renderSlot(slot, i === bot.inventory.hotbarStart + bot.quickBarSlot) + const debugIsQuickbar = i === bot.inventory.hotbarStart + bot.quickBarSlot + const modelName = getItemModelName(slot, { 'minecraft:display_context': 'gui', }) + const slotCustomProps = renderSlot({ modelName }, debugIsQuickbar) const itemCustomName = getItemName(slot) Object.assign(slot, { ...slotCustomProps, displayName: itemCustomName ?? slot.displayName }) //@ts-expect-error diff --git a/src/mineflayer/items.ts b/src/mineflayer/items.ts index 2f1b2f8d..bb437ef4 100644 --- a/src/mineflayer/items.ts +++ b/src/mineflayer/items.ts @@ -19,6 +19,7 @@ export type RenderItem = Pick & { components?: RenderSlotComponent[], displayName?: string + modelResolved?: boolean } type JsonString = string diff --git a/src/resourcePack.ts b/src/resourcePack.ts index aef23e8f..6e9c28a4 100644 --- a/src/resourcePack.ts +++ b/src/resourcePack.ts @@ -314,7 +314,8 @@ export const getResourcepackTiles = async (type: 'blocks' | 'items' | 'armor', e const prepareBlockstatesAndModels = async (progressReporter: ProgressReporter) => { viewer.world.customBlockStates = {} viewer.world.customModels = {} - const usedTextures = new Set() + const usedBlockTextures = new Set() + const usedItemTextures = new Set() const basePath = await getActiveResourcepackBasePath() if (!basePath) return progressReporter.beginStage('read-resource-pack-blockstates-and-models', 'Reading resource pack blockstates and models') @@ -328,8 +329,9 @@ const prepareBlockstatesAndModels = async (progressReporter: ProgressReporter) = if (file.endsWith('.json')) { const contents = await fs.promises.readFile(filePath, 'utf8') let name = file.replace('.json', '') + const isBlock = path.endsWith('block') if (type === 'models') { - name = `${path.endsWith('block') ? 'block' : 'item'}/${name}` + name = `${isBlock ? 'block' : 'item'}/${name}` } const parsed = JSON.parse(contents) if (namespaceDir === 'minecraft') { @@ -341,7 +343,11 @@ const prepareBlockstatesAndModels = async (progressReporter: ProgressReporter) = if (typeof texturePath !== 'string') continue if (texturePath.startsWith('#')) continue if (!texturePath.includes(':')) texturePath = `minecraft:${texturePath}` - usedTextures.add(texturePath as string) + if (isBlock) { + usedBlockTextures.add(texturePath as string) + } else { + usedItemTextures.add(texturePath as string) + } } } } @@ -369,7 +375,10 @@ const prepareBlockstatesAndModels = async (progressReporter: ProgressReporter) = viewer.world.customBlockStates = undefined viewer.world.customModels = undefined } - return { usedTextures } + return { + usedBlockTextures, + usedItemTextures + } } const downloadAndUseResourcePack = async (url: string, progressReporter: ProgressReporter): Promise => { @@ -517,9 +526,9 @@ const updateTextures = async (progressReporter = createConsoleLogProgressReporte const origBlocksFiles = Object.keys(viewer.world.sourceData.blocksAtlases.latest.textures) const origItemsFiles = Object.keys(viewer.world.sourceData.itemsAtlases.latest.textures) const origArmorFiles = Object.keys(armorTextures) - const { usedTextures: extraBlockTextures = new Set() } = await prepareBlockstatesAndModels(progressReporter) ?? {} - const blocksData = await getResourcepackTiles('blocks', [...origBlocksFiles, ...extraBlockTextures], progressReporter) - const itemsData = await getResourcepackTiles('items', origItemsFiles, progressReporter) + const { usedBlockTextures, usedItemTextures } = await prepareBlockstatesAndModels(progressReporter) ?? {} + const blocksData = await getResourcepackTiles('blocks', [...origBlocksFiles, ...usedBlockTextures ?? []], progressReporter) + const itemsData = await getResourcepackTiles('items', [...origItemsFiles, ...usedItemTextures ?? []], progressReporter) const armorData = await getResourcepackTiles('armor', origArmorFiles, progressReporter) await updateAllReplacableTextures() viewer.world.customTextures = {} diff --git a/src/resourcesManager.ts b/src/resourcesManager.ts new file mode 100644 index 00000000..fd92e58b --- /dev/null +++ b/src/resourcesManager.ts @@ -0,0 +1,23 @@ +import { Item } from 'prismarine-item' +import { ItemSpecificContextProperties } from 'renderer/viewer/lib/basePlayerState' +import { getItemDefinition } from 'mc-assets/dist/itemDefinitions' +import { playerState } from './mineflayer/playerState' +import { GeneralInputItem, getItemMetadata } from './mineflayer/items' + +export const getItemModelName = (item: GeneralInputItem, specificProps: ItemSpecificContextProperties) => { + let itemModelName = item.name + const { customModel } = getItemMetadata(item) + if (customModel) { + itemModelName = customModel + } + + const itemSelector = playerState.getItemSelector({ + ...specificProps + }) + const model = getItemDefinition(viewer.world.itemsDefinitionsStore, { + name: itemModelName, + version: viewer.world.texturesVersion!, + properties: itemSelector + })?.model ?? itemModelName + return model +} From b0da1e41d690d081636d320b8d29a1a80da58991 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 3 Mar 2025 15:31:25 +0300 Subject: [PATCH 379/834] fix: fix crashes on packets logging recording fix: make replay panel minmizable --- package.json | 4 +- pnpm-lock.yaml | 22 ++-- src/appParams.ts | 3 +- src/core/progressReporter.ts | 1 + src/index.ts | 3 +- src/optionsStorage.ts | 2 +- src/react/PauseScreen.tsx | 9 +- src/react/ReplayPanel.tsx | 119 +++++++++++++++------ src/react/components/replay/PacketList.tsx | 8 +- 9 files changed, 114 insertions(+), 57 deletions(-) diff --git a/package.json b/package.json index b0c5ee20..7cacae56 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "mojangson": "^2.0.4", "net-browserify": "github:zardoy/prismarinejs-net-browserify", "node-gzip": "^1.1.2", - "mcraft-fun-mineflayer": "^0.1.7", + "mcraft-fun-mineflayer": "^0.1.8", "peerjs": "^1.5.0", "pixelarticons": "^1.8.1", "pretty-bytes": "^6.1.1", @@ -145,7 +145,7 @@ "http-browserify": "^1.7.0", "http-server": "^14.1.1", "https-browserify": "^1.0.0", - "mineflayer-mouse": "^0.0.4", + "mineflayer-mouse": "^0.0.5", "mc-assets": "^0.2.37", "minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next", "mineflayer": "github:zardoy/mineflayer", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index be7ce2e5..dbbac60e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -135,8 +135,8 @@ importers: specifier: ^4.17.21 version: 4.17.21 mcraft-fun-mineflayer: - specifier: ^0.1.7 - version: 0.1.7(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13)) + specifier: ^0.1.8 + version: 0.1.8(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13)) minecraft-data: specifier: 3.83.1 version: 3.83.1 @@ -359,8 +359,8 @@ importers: specifier: github:zardoy/mineflayer version: https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13) mineflayer-mouse: - specifier: ^0.0.4 - version: 0.0.4(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1) + specifier: ^0.0.5 + version: 0.0.5(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1) mineflayer-pathfinder: specifier: ^2.4.4 version: 2.4.4 @@ -6575,9 +6575,9 @@ packages: resolution: {integrity: sha512-49tk3shwxsDoV0PrrbORZEKg613vUQPULgusWjXNl8JEma5y41LEo57D6q4aHliXsV3Gb9ThrkFf6hIb0WlY1Q==} engines: {node: '>=18.0.0'} - mcraft-fun-mineflayer@0.1.7: - resolution: {integrity: sha512-DJPpp1YFwztoscdIOwfqBV9lbotva621F9GEep3BlqG3l06UdTzX2ElkvwyTR0IurFFBX/YKsNfxwL5WtLytgw==} - version: 0.1.7 + mcraft-fun-mineflayer@0.1.8: + resolution: {integrity: sha512-jyJTihNHfeToBPwVs3QMKBlVcaCABJ25YN2eoIBQEVTRVFzaXh13XRpElphLzTMj1Q5XFYqufHtMoR4tsb08qQ==} + version: 0.1.8 engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: '@roamhq/wrtc': '*' @@ -6801,8 +6801,8 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824} version: 1.2.0 - mineflayer-mouse@0.0.4: - resolution: {integrity: sha512-55GqQhJWCXnOnm30uOjtI7nsawPb0kA3cAv6a5n1NJjTWFR6hzMkiRT6xGLYrvYhdf6Er3nsE2Ok/Aysa/jtFQ==} + mineflayer-mouse@0.0.5: + resolution: {integrity: sha512-0r/AOGTq+wZH9vrBcW93jH2dGRSlwlO6xc1Z67VJUFlZZ8oBefAOpiZq7LIGc7ROVbpcKEKjROdNv/iCFmzXYA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} mineflayer-pathfinder@2.4.4: @@ -17372,7 +17372,7 @@ snapshots: apl-image-packer: 1.1.0 zod: 3.24.1 - mcraft-fun-mineflayer@0.1.7(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13)): + mcraft-fun-mineflayer@0.1.8(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13)): dependencies: '@zardoy/flying-squid': 0.0.49(encoding@0.1.13) exit-hook: 2.2.1 @@ -17742,7 +17742,7 @@ snapshots: - encoding - supports-color - mineflayer-mouse@0.0.4(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1): + mineflayer-mouse@0.0.5(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1): dependencies: change-case: 5.4.4 debug: 4.4.0(supports-color@8.1.1) diff --git a/src/appParams.ts b/src/appParams.ts index 50fbf055..962eb168 100644 --- a/src/appParams.ts +++ b/src/appParams.ts @@ -63,7 +63,8 @@ type AppQsParamsArrayTransformed = { [k in keyof AppQsParamsArray]: string[] } -const initialAppConfig = process.env.INLINED_APP_CONFIG as AppConfig ?? {} +globalThis.process ??= {} as any +const initialAppConfig = process?.env?.INLINED_APP_CONFIG as AppConfig ?? {} export const appQueryParams = new Proxy({} as AppQsParams, { get (target, property) { diff --git a/src/core/progressReporter.ts b/src/core/progressReporter.ts index db1958da..e2bdf5bc 100644 --- a/src/core/progressReporter.ts +++ b/src/core/progressReporter.ts @@ -138,6 +138,7 @@ export const createFullScreenProgressReporter = (): ProgressReporter => { setLoadingScreenStatus(message) }, end () { + if (appStatusState.isError) return fullScreenReporters.splice(fullScreenReporters.indexOf(reporter), 1) if (fullScreenReporters.length === 0) { setLoadingScreenStatus(undefined) diff --git a/src/index.ts b/src/index.ts index 8e586a8f..7269781d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -322,6 +322,7 @@ export async function connect (connectOptions: ConnectOptions) { if (ended) return ended = true viewer.resetAll() + progress.end() localServer = window.localServer = window.server = undefined gameAdditionalState.viewerConnection = false @@ -692,6 +693,7 @@ export async function connect (connectOptions: ConnectOptions) { } catch (err) { handleError(err) } + if (!bot) return if (connectOptions.server) { bot.loadPlugin(ping) @@ -700,7 +702,6 @@ export async function connect (connectOptions: ConnectOptions) { if (!localReplaySession) { bot.loadPlugin(localRelayServerPlugin) } - if (!bot) return const p2pConnectTimeout = p2pMultiplayer ? setTimeout(() => { throw new UserError('Spawn timeout. There might be error on the other side, check console.') }, 20_000) : undefined diff --git a/src/optionsStorage.ts b/src/optionsStorage.ts index b88b71c4..904b0a29 100644 --- a/src/optionsStorage.ts +++ b/src/optionsStorage.ts @@ -8,7 +8,7 @@ import { appQueryParamsArray } from './appParams' import type { AppConfig } from './globalState' const isDev = process.env.NODE_ENV === 'development' -const initialAppConfig = process.env.INLINED_APP_CONFIG as AppConfig ?? {} +const initialAppConfig = process.env?.INLINED_APP_CONFIG as AppConfig ?? {} const defaultOptions = { renderDistance: 3, keepChunksDistance: 1, diff --git a/src/react/PauseScreen.tsx b/src/react/PauseScreen.tsx index 4c4dec03..35d873ea 100644 --- a/src/react/PauseScreen.tsx +++ b/src/react/PauseScreen.tsx @@ -232,14 +232,15 @@ export default () => { if (pauseLinksConfig) { for (const [i, row] of pauseLinksConfig.entries()) { const rowButtons: React.ReactNode[] = [] - for (const button of row) { + for (const [k, button] of row.entries()) { + const key = `${i}-${k}` const style = { width: (204 / row.length - (row.length > 1 ? 4 : 0)) + 'px' } if (button.type === 'discord') { - rowButtons.push() + rowButtons.push() } else if (button.type === 'github') { - rowButtons.push() + rowButtons.push() } else if (button.type === 'url' && button.text) { - rowButtons.push() + rowButtons.push() } } pauseLinks.push(
{rowButtons}
) diff --git a/src/react/ReplayPanel.tsx b/src/react/ReplayPanel.tsx index 59950555..fd4082af 100644 --- a/src/react/ReplayPanel.tsx +++ b/src/react/ReplayPanel.tsx @@ -41,31 +41,100 @@ export default function ReplayPanel ({ style }: Props) { const [filter, setFilter] = useState(defaultFilter) + const [isMinimized, setIsMinimized] = useState(false) const { filtered: filteredPackets, hiddenCount } = filterPackets(packets.slice(-500), filter) useEffect(() => { onFilterChange(filter) }, [filter, onFilterChange]) + const handlePlayPauseClick = () => { + if (isMinimized) { + setIsMinimized(false) + } else { + onPlayPause?.(!isPlaying) + } + } + + const playPauseButton = ( + + ) + + const baseContainerStyle = { + position: 'fixed', + top: 18, + right: 0, + zIndex: 1000, + background: DARK_COLORS.bg, + padding: '16px', + borderRadius: '0 0 8px 0', + boxShadow: '0 2px 8px rgba(0,0,0,0.3)', + display: 'flex', + flexDirection: 'column', + gap: '12px', + color: DARK_COLORS.text, + ...style + } as const + + if (isMinimized) { + return ( +
+ {playPauseButton} +
+ ) + } + return (
-
{replayName || 'Unnamed Replay'}
+
+
{replayName || 'Unnamed Replay'}
+ +
+
Integrated server emulation. Testing client...
- - + {playPauseButton}
diff --git a/src/react/components/replay/PacketList.tsx b/src/react/components/replay/PacketList.tsx index 484c42c9..5183bf3f 100644 --- a/src/react/components/replay/PacketList.tsx +++ b/src/react/components/replay/PacketList.tsx @@ -1,4 +1,5 @@ import { useRef, useState } from 'react' +import { processPacketDataForLogging } from 'mcraft-fun-mineflayer/build/packetsLogger' import { PacketData } from '../../ReplayPanel' import { useScrollBehavior } from '../../hooks/useScrollBehavior' import { ClientOnMap } from '../../../generatedServerPackets' @@ -12,6 +13,7 @@ const formatters: Record string> = { const blockEntitiesCount = data.blockEntities?.length return `x:${data.x} z:${data.z} C:${sizeOfChunk} E:${blockEntitiesCount}` }, + default: (data) => processPacketDataForLogging(data) } const getPacketIcon = (name: string): string => { @@ -115,7 +117,7 @@ export default function PacketList ({ packets, filter, maxHeight = 300 }: Props) {packet.name} - {formatters[packet.name]?.(packet.data) ?? JSON.stringify(packet.data)} + {formatters[packet.name]?.(packet.data) ?? formatters.default(packet.data)}
{expandedPacket === packet.position && ( @@ -123,14 +125,14 @@ export default function PacketList ({ packets, filter, maxHeight = 300 }: Props)
Data:
-                        {JSON.stringify(packet.data, null, 2)}
+                        {JSON.stringify(JSON.parse(formatters.default(packet.data)), null, 2)}
                       
{packet.actualVersion && (
Actual Version:
-                          {JSON.stringify(packet.actualVersion, null, 2)}
+                          {JSON.stringify(JSON.parse(formatters.default(packet.actualVersion)), null, 2)}
                         
)} From 2619e5da89fc270297d3cc379758a95e13e01e0a Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 3 Mar 2025 17:42:01 +0300 Subject: [PATCH 380/834] fix: was not possible to click notification, make error routing more strict & obvious --- src/core/progressReporter.ts | 25 ++++++++++++++++++++++++- src/react/NotificationProvider.tsx | 11 +++++++---- src/reactUi.tsx | 2 +- src/resourcePack.ts | 4 +++- 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/core/progressReporter.ts b/src/core/progressReporter.ts index e2bdf5bc..6ef6044f 100644 --- a/src/core/progressReporter.ts +++ b/src/core/progressReporter.ts @@ -13,12 +13,14 @@ export interface ProgressReporter { setMessage (message: string): void - end (): void + end(): void + error(message: string): void } interface ReporterDisplayImplementation { setMessage (message: string): void end (): void + error(message: string): void } interface StageInfo { @@ -124,6 +126,10 @@ const createProgressReporter = (implementation: ReporterDisplayImplementation): get currentMessage () { return currentMessage + }, + + error (message: string): void { + implementation.error(message) } } @@ -145,6 +151,11 @@ export const createFullScreenProgressReporter = (): ProgressReporter => { } else { setLoadingScreenStatus(fullScreenReporters.at(-1)!.currentMessage) } + }, + + error (message: string): void { + if (appStatusState.isError) return + setLoadingScreenStatus(message, true) } }) fullScreenReporters.push(reporter) @@ -162,6 +173,10 @@ export const createNotificationProgressReporter = (endMessage?: string): Progres } else { hideNotification() } + }, + + error (message: string): void { + showNotification(message, '', true, '', undefined, true) } }) } @@ -173,6 +188,10 @@ export const createConsoleLogProgressReporter = (): ProgressReporter => { }, end () { console.log('done') + }, + + error (message: string): void { + console.error(message) } }) } @@ -191,6 +210,10 @@ export const createWrappedProgressReporter = (reporter: ProgressReporter, messag if (message) { reporter.endStage(stage) } + }, + + error (message: string): void { + reporter.error(message) } }) } diff --git a/src/react/NotificationProvider.tsx b/src/react/NotificationProvider.tsx index 4c9661e0..6460c0e9 100644 --- a/src/react/NotificationProvider.tsx +++ b/src/react/NotificationProvider.tsx @@ -60,10 +60,13 @@ export default () => { // }, []) const scale = useAppScale() - return
+ return
{ +
@@ -214,7 +215,6 @@ const App = () => {
-
diff --git a/src/resourcePack.ts b/src/resourcePack.ts index 6e9c28a4..fd01168a 100644 --- a/src/resourcePack.ts +++ b/src/resourcePack.ts @@ -392,7 +392,7 @@ const downloadAndUseResourcePack = async (url: string, progressReporter: Progres const response = await fetch(url).catch((err) => { console.log(`Ensure server on ${url} support CORS which is not required for regular client, but is required for the web client`) console.error(err) - showNotification('Failed to download resource pack: ' + err.message) + progressReporter.error('Failed to download resource pack: ' + err.message) }) console.timeEnd('downloadServerResourcePack') if (!response) return @@ -425,6 +425,8 @@ const downloadAndUseResourcePack = async (url: string, progressReporter: Progres console.error(err) showNotification('Failed to install resource pack: ' + err.message) }) + } catch (err) { + progressReporter.error('Could not install resource pack: ' + err.message) } finally { progressReporter.endStage('download-resource-pack') resourcePackState.isServerInstalling = false From 2a8f5140957be1e832a8b480c262cec5963f514e Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 3 Mar 2025 18:24:06 +0300 Subject: [PATCH 381/834] add build zip workflow --- .github/workflows/build-zip.yml | 43 +++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/build-zip.yml diff --git a/.github/workflows/build-zip.yml b/.github/workflows/build-zip.yml new file mode 100644 index 00000000..9f8a6292 --- /dev/null +++ b/.github/workflows/build-zip.yml @@ -0,0 +1,43 @@ +name: Build and Bundle + +on: + workflow_dispatch: + +jobs: + build-and-bundle: + runs-on: ubuntu-latest + permissions: write-all + steps: + - name: Checkout repository + uses: actions/checkout@master + + - uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Install dependencies + run: pnpm install + + - name: Build project + run: pnpm build + + - name: Bundle server.js + run: | + pnpm esbuild server.js --bundle --platform=node --outfile=bundled-server.js + + - name: Create distribution package + run: | + mkdir -p package + cp -r dist package/ + cp bundled-server.js package/server.js + cd package + zip -r ../dist-package.zip . + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: dist-package + path: dist-package.zip From 874cafc75e605688e9a7d745eed09859ff820154 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 3 Mar 2025 18:42:08 +0300 Subject: [PATCH 382/834] add self host zip publishing with release --- .github/workflows/build-zip.yml | 10 +++++----- .github/workflows/publish.yml | 31 ++++++++++++++++++++++++------- package.json | 3 +++ server.js | 2 +- 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build-zip.yml b/.github/workflows/build-zip.yml index 9f8a6292..e6ed334c 100644 --- a/.github/workflows/build-zip.yml +++ b/.github/workflows/build-zip.yml @@ -1,4 +1,4 @@ -name: Build and Bundle +name: Make Self Host Zip on: workflow_dispatch: @@ -26,7 +26,7 @@ jobs: - name: Bundle server.js run: | - pnpm esbuild server.js --bundle --platform=node --outfile=bundled-server.js + pnpm esbuild server.js --bundle --platform=node --outfile=bundled-server.js --define=process.env.NODE_ENV="production" - name: Create distribution package run: | @@ -34,10 +34,10 @@ jobs: cp -r dist package/ cp bundled-server.js package/server.js cd package - zip -r ../dist-package.zip . + zip -r ../self-host.zip . - name: Upload artifact uses: actions/upload-artifact@v4 with: - name: dist-package - path: dist-package.zip + name: self-host + path: self-host.zip diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index eb765648..958aa50e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -43,19 +43,36 @@ jobs: with: run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }} --prod id: deploy - - run: | - pnpx zardoy-release node --footer "This release URL: ${{ steps.deploy.outputs.stdout }}" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # has possible output: tag - id: release - # has output + # publish to github - run: cp vercel.json .vercel/output/static/vercel.json - uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: .vercel/output/static force_orphan: true + + - name: Build self-host version + run: pnpm build + - name: Bundle server.js + run: | + pnpm esbuild server.js --bundle --platform=node --outfile=bundled-server.js --define=process.env.NODE_ENV="production" + + - name: Create zip package + run: | + mkdir -p package + cp -r dist package/ + cp bundled-server.js package/server.js + cd package + zip -r ../self-host.zip . + + - run: | + pnpx zardoy-release node --footer "This release URL: ${{ steps.deploy.outputs.stdout }}" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # has possible output: tag + id: release + + # has output - name: Set publishing config run: pnpm config set '//registry.npmjs.org/:_authToken' "${NODE_AUTH_TOKEN}" env: diff --git a/package.json b/package.json index 7cacae56..0e0aa6a8 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,9 @@ "web", "client" ], + "release": { + "attachReleaseFiles": "self-host.zip" + }, "publish": { "preset": { "publishOnlyIfChanged": true, diff --git a/server.js b/server.js index 2dbb05b3..20e66051 100644 --- a/server.js +++ b/server.js @@ -15,7 +15,7 @@ try { // Create our app const app = express() -const isProd = process.argv.includes('--prod') +const isProd = process.argv.includes('--prod') || process.env.NODE_ENV === 'production' app.use(compression()) app.use(cors()) app.use(netApi({ allowOrigin: '*' })) From 1d4dc0ddaa8fe203197c6c819f0223b551bcaeb9 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 3 Mar 2025 18:45:25 +0300 Subject: [PATCH 383/834] fix define in arg build --- .github/workflows/build-zip.yml | 2 +- .github/workflows/publish.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-zip.yml b/.github/workflows/build-zip.yml index e6ed334c..2e7e886d 100644 --- a/.github/workflows/build-zip.yml +++ b/.github/workflows/build-zip.yml @@ -26,7 +26,7 @@ jobs: - name: Bundle server.js run: | - pnpm esbuild server.js --bundle --platform=node --outfile=bundled-server.js --define=process.env.NODE_ENV="production" + pnpm esbuild server.js --bundle --platform=node --outfile=bundled-server.js --define:process.env.NODE_ENV="production" - name: Create distribution package run: | diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 958aa50e..7e0af8d2 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -55,7 +55,7 @@ jobs: run: pnpm build - name: Bundle server.js run: | - pnpm esbuild server.js --bundle --platform=node --outfile=bundled-server.js --define=process.env.NODE_ENV="production" + pnpm esbuild server.js --bundle --platform=node --outfile=bundled-server.js --define:process.env.NODE_ENV="production" - name: Create zip package run: | From 4b54be637d1917b87e51f8c8c03938141fbc3bfe Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 3 Mar 2025 18:48:27 +0300 Subject: [PATCH 384/834] ci: adjust esbuild build arg syntax for prod --- .github/workflows/build-zip.yml | 2 +- .github/workflows/publish.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-zip.yml b/.github/workflows/build-zip.yml index 2e7e886d..cc472476 100644 --- a/.github/workflows/build-zip.yml +++ b/.github/workflows/build-zip.yml @@ -26,7 +26,7 @@ jobs: - name: Bundle server.js run: | - pnpm esbuild server.js --bundle --platform=node --outfile=bundled-server.js --define:process.env.NODE_ENV="production" + pnpm esbuild server.js --bundle --platform=node --outfile=bundled-server.js --define:process.env.NODE_ENV="'production'" - name: Create distribution package run: | diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7e0af8d2..4fbff15d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -55,7 +55,7 @@ jobs: run: pnpm build - name: Bundle server.js run: | - pnpm esbuild server.js --bundle --platform=node --outfile=bundled-server.js --define:process.env.NODE_ENV="production" + pnpm esbuild server.js --bundle --platform=node --outfile=bundled-server.js --define:process.env.NODE_ENV="'production'" - name: Create zip package run: | From 1c700aac1e3b7d8592c3a0a60e1f27ad5ad80756 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Tue, 4 Mar 2025 19:00:20 +0300 Subject: [PATCH 385/834] feat(config-json): Only either bundle or load from remote (#291) --- .github/workflows/next-deploy.yml | 2 ++ .github/workflows/preview.yml | 2 ++ .github/workflows/publish.yml | 2 ++ Dockerfile | 5 +-- rsbuild.config.ts | 12 +++++-- scripts/dockerPrepare.mjs | 22 ++++++++++-- src/appConfig.ts | 59 +++++++++++++++++++++++++++++++ src/appParams.ts | 2 +- src/browserfs.ts | 4 +-- src/customChannels.ts | 6 ++-- src/globalState.ts | 41 ++------------------- src/index.ts | 17 +++------ src/optionsStorage.ts | 19 +++++++--- src/panorama.ts | 2 +- src/react/MainMenuRenderApp.tsx | 6 ++-- 15 files changed, 127 insertions(+), 74 deletions(-) create mode 100644 src/appConfig.ts diff --git a/.github/workflows/next-deploy.yml b/.github/workflows/next-deploy.yml index 665abb30..042302a4 100644 --- a/.github/workflows/next-deploy.yml +++ b/.github/workflows/next-deploy.yml @@ -32,6 +32,8 @@ jobs: echo "{\"latestTag\": \"$(git rev-parse --short $GITHUB_SHA)\", \"isCommit\": true}" > assets/release.json - name: Build Project Artifacts run: vercel build --token=${{ secrets.VERCEL_TOKEN }} + env: + CONFIG_JSON_SOURCE: BUNDLED - run: pnpm build-storybook - name: Copy playground files run: | diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 18c80e8c..6408c86a 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -61,6 +61,8 @@ jobs: echo "{\"latestTag\": \"$(git rev-parse --short ${{ github.event.pull_request.head.sha }})\", \"isCommit\": true}" > assets/release.json - name: Build Project Artifacts run: vercel build --token=${{ secrets.VERCEL_TOKEN }} + env: + CONFIG_JSON_SOURCE: BUNDLED - run: pnpm build-storybook - name: Copy playground files run: | diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 4fbff15d..5af8abab 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -30,6 +30,8 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: vercel build --token=${{ secrets.VERCEL_TOKEN }} --prod + env: + CONFIG_JSON_SOURCE: BUNDLED - run: pnpm build-storybook - name: Copy playground files run: | diff --git a/Dockerfile b/Dockerfile index 4769141f..34641353 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,6 +9,7 @@ RUN npm i -g pnpm@9.0.4 # Build arguments ARG DOWNLOAD_SOUNDS=false ARG DISABLE_SERVICE_WORKER=false +ARG CONFIG_JSON_SOURCE=REMOTE # TODO need flat --no-root-optional RUN node ./scripts/dockerPrepare.mjs RUN pnpm i @@ -22,8 +23,8 @@ RUN if [ "$DOWNLOAD_SOUNDS" = "true" ] ; then node scripts/downloadSoundsMap.mjs # ENTRYPOINT ["pnpm", "run", "run-all"] # only for prod -RUN GITHUB_REPOSITORY=zardoy/minecraft-web-client \ - DISABLE_SERVICE_WORKER=$DISABLE_SERVICE_WORKER \ +RUN DISABLE_SERVICE_WORKER=$DISABLE_SERVICE_WORKER \ + CONFIG_JSON_SOURCE=$CONFIG_JSON_SOURCE \ pnpm run build # ---- Run Stage ---- diff --git a/rsbuild.config.ts b/rsbuild.config.ts index 4147ce7d..875d0e0c 100644 --- a/rsbuild.config.ts +++ b/rsbuild.config.ts @@ -25,12 +25,14 @@ const disableServiceWorker = process.env.DISABLE_SERVICE_WORKER === 'true' let releaseTag let releaseLink let releaseChangelog +let githubRepositoryFallback if (fs.existsSync('./assets/release.json')) { const releaseJson = JSON.parse(fs.readFileSync('./assets/release.json', 'utf8')) releaseTag = releaseJson.latestTag releaseLink = releaseJson.isCommit ? `/commit/${releaseJson.latestTag}` : `/releases/${releaseJson.latestTag}` releaseChangelog = releaseJson.changelog?.replace(//, '') + githubRepositoryFallback = releaseJson.repository } const configJson = JSON.parse(fs.readFileSync('./config.json', 'utf8')) @@ -41,6 +43,8 @@ if (dev) { configJson.defaultProxy = ':8080' } +const configSource = process.env.CONFIG_JSON_SOURCE || 'REMOTE' + // base options are in ./renderer/rsbuildSharedConfig.ts const appConfig = defineConfig({ html: { @@ -66,13 +70,13 @@ const appConfig = defineConfig({ 'process.env.BUILD_VERSION': JSON.stringify(!dev ? buildingVersion : 'undefined'), 'process.env.MAIN_MENU_LINKS': JSON.stringify(process.env.MAIN_MENU_LINKS), 'process.env.GITHUB_URL': - JSON.stringify(`https://github.com/${process.env.GITHUB_REPOSITORY || `${process.env.VERCEL_GIT_REPO_OWNER}/${process.env.VERCEL_GIT_REPO_SLUG}`}`), + JSON.stringify(`https://github.com/${process.env.GITHUB_REPOSITORY || `${process.env.VERCEL_GIT_REPO_OWNER}/${process.env.VERCEL_GIT_REPO_SLUG}` || githubRepositoryFallback}`), 'process.env.DEPS_VERSIONS': JSON.stringify({}), 'process.env.RELEASE_TAG': JSON.stringify(releaseTag), 'process.env.RELEASE_LINK': JSON.stringify(releaseLink), 'process.env.RELEASE_CHANGELOG': JSON.stringify(releaseChangelog), 'process.env.DISABLE_SERVICE_WORKER': JSON.stringify(disableServiceWorker), - 'process.env.INLINED_APP_CONFIG': JSON.stringify(configJson), + 'process.env.INLINED_APP_CONFIG': JSON.stringify(configSource === 'BUNDLED' ? configJson : null), }, }, server: { @@ -109,7 +113,9 @@ const appConfig = defineConfig({ fs.copyFileSync('./assets/release.json', './dist/release.json') } - fs.writeFileSync('./dist/config.json', JSON.stringify(configJson), 'utf8') + if (configSource === 'REMOTE') { + fs.writeFileSync('./dist/config.json', JSON.stringify(configJson), 'utf8') + } if (fs.existsSync('./generated/sounds.js')) { fs.copyFileSync('./generated/sounds.js', './dist/sounds.js') } diff --git a/scripts/dockerPrepare.mjs b/scripts/dockerPrepare.mjs index 37e57d01..62a4f5e4 100644 --- a/scripts/dockerPrepare.mjs +++ b/scripts/dockerPrepare.mjs @@ -4,9 +4,27 @@ import path from 'path' import { fileURLToPath } from 'url' import { execSync } from 'child_process' -// write release tag +// Get repository from git config +const getGitRepository = () => { + try { + const gitConfig = fs.readFileSync('.git/config', 'utf8') + const originUrlMatch = gitConfig.match(/\[remote "origin"\][\s\S]*?url = .*?github\.com[:/](.*?)(\.git)?\n/m) + if (originUrlMatch) { + return originUrlMatch[1] + } + } catch (err) { + console.warn('Failed to read git repository from config:', err) + } + return null +} + +// write release tag and repository info const commitShort = execSync('git rev-parse --short HEAD').toString().trim() -fs.writeFileSync('./assets/release.json', JSON.stringify({ latestTag: `${commitShort} (docker)` }), 'utf8') +const repository = getGitRepository() +fs.writeFileSync('./assets/release.json', JSON.stringify({ + latestTag: `${commitShort} (docker)`, + repository +}), 'utf8') const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8')) delete packageJson.optionalDependencies diff --git a/src/appConfig.ts b/src/appConfig.ts new file mode 100644 index 00000000..3d6d8f93 --- /dev/null +++ b/src/appConfig.ts @@ -0,0 +1,59 @@ +import { disabledSettings, options, qsOptions } from './optionsStorage' +import { miscUiState } from './globalState' +import { setLoadingScreenStatus } from './appStatus' + +export type AppConfig = { + // defaultHost?: string + // defaultHostSave?: string + defaultProxy?: string + // defaultProxySave?: string + // defaultVersion?: string + peerJsServer?: string + peerJsServerFallback?: string + promoteServers?: Array<{ ip, description, version? }> + mapsProvider?: string + + appParams?: Record // query string params + + defaultSettings?: Record + forceSettings?: Record + // hideSettings?: Record + allowAutoConnect?: boolean + pauseLinks?: Array>> +} + +export const loadAppConfig = (appConfig: AppConfig) => { + if (miscUiState.appConfig) { + Object.assign(miscUiState.appConfig, appConfig) + } else { + miscUiState.appConfig = appConfig + } + + if (appConfig.forceSettings) { + for (const [key, value] of Object.entries(appConfig.forceSettings)) { + if (value) { + disabledSettings.value.add(key) + // since the setting is forced, we need to set it to that value + if (appConfig.defaultSettings?.[key] && !qsOptions[key]) { + options[key] = appConfig.defaultSettings[key] + } + } else { + disabledSettings.value.delete(key) + } + } + } +} + +export const isBundledConfigUsed = !!process.env.INLINED_APP_CONFIG + +if (isBundledConfigUsed) { + loadAppConfig(process.env.INLINED_APP_CONFIG as AppConfig ?? {}) +} else { + void window.fetch('config.json').then(async res => res.json()).then(c => c, (error) => { + // console.warn('Failed to load optional app config.json', error) + // return {} + setLoadingScreenStatus('Failed to load app config.json', true) + }).then((config: AppConfig) => { + loadAppConfig(config) + }) +} diff --git a/src/appParams.ts b/src/appParams.ts index 962eb168..98d6ff62 100644 --- a/src/appParams.ts +++ b/src/appParams.ts @@ -1,4 +1,4 @@ -import type { AppConfig } from './globalState' +import type { AppConfig } from './appConfig' const qsParams = new URLSearchParams(window.location?.search ?? '') diff --git a/src/browserfs.ts b/src/browserfs.ts index 41608e30..0f4579b8 100644 --- a/src/browserfs.ts +++ b/src/browserfs.ts @@ -41,13 +41,13 @@ browserfs.configure({ throw e2 } showNotification('Failed to access device storage', `Check you have free space. ${e.message}`, true) - miscUiState.appLoaded = true + miscUiState.fsReady = true miscUiState.singleplayerAvailable = false }) return } await updateTexturePackInstalledState() - miscUiState.appLoaded = true + miscUiState.fsReady = true miscUiState.singleplayerAvailable = true }) diff --git a/src/customChannels.ts b/src/customChannels.ts index 35922101..ff0f8a32 100644 --- a/src/customChannels.ts +++ b/src/customChannels.ts @@ -85,15 +85,15 @@ const registeredJeiChannel = () => { [ { name: 'id', - type: 'pstring', + type: ['pstring', { countType: 'i16' }] }, { name: 'categoryTitle', - type: 'pstring', + type: ['pstring', { countType: 'i16' }] }, { name: 'items', - type: 'pstring', + type: ['pstring', { countType: 'i16' }] }, ] ] diff --git a/src/globalState.ts b/src/globalState.ts index 74e6c3ff..0ee8671d 100644 --- a/src/globalState.ts +++ b/src/globalState.ts @@ -5,6 +5,7 @@ import type { WorldWarp } from 'flying-squid/dist/lib/modules/warps' import type { OptionsGroupType } from './optionsGuiScheme' import { appQueryParams } from './appParams' import { options, disabledSettings } from './optionsStorage' +import { AppConfig } from './appConfig' // todo: refactor structure with support of hideNext=false @@ -110,26 +111,6 @@ export const showContextmenu = (items: ContextMenuItem[], { clientX, clientY }) // --- -export type AppConfig = { - // defaultHost?: string - // defaultHostSave?: string - defaultProxy?: string - // defaultProxySave?: string - // defaultVersion?: string - peerJsServer?: string - peerJsServerFallback?: string - promoteServers?: Array<{ ip, description, version? }> - mapsProvider?: string - - appParams?: Record // query string params - - defaultSettings?: Record - forceSettings?: Record - // hideSettings?: Record - allowAutoConnect?: boolean - pauseLinks?: Array>> -} - export const miscUiState = proxy({ currentDisplayQr: null as string | null, currentTouch: null as boolean | null, @@ -144,7 +125,7 @@ export const miscUiState = proxy({ loadedServerIndex: '', /** currently trying to load or loaded mc version, after all data is loaded */ loadedDataVersion: null as string | null, - appLoaded: false, + fsReady: false, singleplayerAvailable: false, usingGamepadInput: false, appConfig: null as AppConfig | null, @@ -152,24 +133,6 @@ export const miscUiState = proxy({ displayFullmap: false }) -export const loadAppConfig = (appConfig: AppConfig) => { - if (miscUiState.appConfig) { - Object.assign(miscUiState.appConfig, appConfig) - } else { - miscUiState.appConfig = appConfig - } - - if (appConfig.forceSettings) { - for (const [key, value] of Object.entries(appConfig.forceSettings)) { - if (value) { - disabledSettings.value.delete(key) - } else { - disabledSettings.value.add(key) - } - } - } -} - export const isGameActive = (foregroundCheck: boolean) => { if (foregroundCheck && activeModalStack.length) return false return miscUiState.gameLoaded diff --git a/src/index.ts b/src/index.ts index 7269781d..b8727395 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,6 +12,7 @@ import './mineflayer/cameraShake' import './shims/patchShims' import './mineflayer/java-tester/index' import './external' +import './appConfig' import { getServerInfo } from './mineflayer/mc-protocol' import { onGameLoad, renderSlot } from './inventoryWindows' import { GeneralInputItem, RenderItem } from './mineflayer/items' @@ -48,7 +49,6 @@ import initializePacketsReplay from './packetsReplay/packetsReplayLegacy' import { initVR } from './vr' import { - AppConfig, activeModalStack, activeModalStacks, hideModal, @@ -57,7 +57,6 @@ import { miscUiState, showModal, gameAdditionalState, - loadAppConfig } from './globalState' import { parseServerAddress } from './parseServerAddress' @@ -904,8 +903,9 @@ export async function connect (connectOptions: ConnectOptions) { const reconnectOptions = sessionStorage.getItem('reconnectOptions') ? JSON.parse(sessionStorage.getItem('reconnectOptions')!) : undefined listenGlobalEvents() -watchValue(miscUiState, async s => { - if (s.appLoaded) { // fs ready +const unsubscribe = watchValue(miscUiState, async s => { + if (s.fsReady && s.appConfig) { + unsubscribe() if (reconnectOptions) { sessionStorage.removeItem('reconnectOptions') if (Date.now() - reconnectOptions.timestamp < 1000 * 60 * 2) { @@ -966,15 +966,6 @@ document.body.addEventListener('touchstart', (e) => { }, { passive: false }) // #endregion -loadAppConfig(process.env.INLINED_APP_CONFIG as AppConfig ?? {}) -// load maybe updated config on the server with updated params (just in case) -void window.fetch('config.json').then(async res => res.json()).then(c => c, (error) => { - console.warn('Failed to load optional app config.json', error) - return {} -}).then((config: AppConfig | {}) => { - loadAppConfig(config) -}) - // qs open actions if (!reconnectOptions) { downloadAndOpenFile().then((downloadAction) => { diff --git a/src/optionsStorage.ts b/src/optionsStorage.ts index 904b0a29..79748679 100644 --- a/src/optionsStorage.ts +++ b/src/optionsStorage.ts @@ -5,7 +5,7 @@ import { proxy, subscribe } from 'valtio/vanilla' import { subscribeKey } from 'valtio/utils' import { omitObj } from '@zardoy/utils' import { appQueryParamsArray } from './appParams' -import type { AppConfig } from './globalState' +import type { AppConfig } from './appConfig' const isDev = process.env.NODE_ENV === 'development' const initialAppConfig = process.env?.INLINED_APP_CONFIG as AppConfig ?? {} @@ -188,7 +188,7 @@ subscribe(options, () => { localStorage.options = JSON.stringify(saveOptions) }) -type WatchValue = >(proxy: T, callback: (p: T, isChanged: boolean) => void) => void +type WatchValue = >(proxy: T, callback: (p: T, isChanged: boolean) => void) => () => void export const watchValue: WatchValue = (proxy, callback) => { const watchedProps = new Set() @@ -198,10 +198,19 @@ export const watchValue: WatchValue = (proxy, callback) => { return Reflect.get(target, p, receiver) }, }), false) + const unsubscribes = [] as Array<() => void> for (const prop of watchedProps) { - subscribeKey(proxy, prop, () => { - callback(proxy, true) - }) + unsubscribes.push( + subscribeKey(proxy, prop, () => { + callback(proxy, true) + }) + ) + } + + return () => { + for (const unsubscribe of unsubscribes) { + unsubscribe() + } } } diff --git a/src/panorama.ts b/src/panorama.ts index efc06e16..3c888246 100644 --- a/src/panorama.ts +++ b/src/panorama.ts @@ -48,7 +48,7 @@ const updateResourcePackSupportPanorama = async () => { } watchValue(miscUiState, m => { - if (m.appLoaded) { + if (m.fsReady) { // Also adds panorama on app load here watchValue(resourcePackState, async (s) => { const oldState = panoramaUsesResourcePack diff --git a/src/react/MainMenuRenderApp.tsx b/src/react/MainMenuRenderApp.tsx index e06ced52..30fd3047 100644 --- a/src/react/MainMenuRenderApp.tsx +++ b/src/react/MainMenuRenderApp.tsx @@ -75,12 +75,12 @@ export const mainMenuState = proxy({ let disableAnimation = false export default () => { const haveModals = useSnapshot(activeModalStack).length - const { gameLoaded, appLoaded, appConfig, singleplayerAvailable } = useSnapshot(miscUiState) + const { gameLoaded, fsReady, appConfig, singleplayerAvailable } = useSnapshot(miscUiState) - const noDisplay = haveModals || gameLoaded || !appLoaded + const noDisplay = haveModals || gameLoaded || !fsReady useEffect(() => { - if (noDisplay && appLoaded) disableAnimation = true + if (noDisplay && fsReady) disableAnimation = true }, [noDisplay]) const [versionStatus, setVersionStatus] = useState('') From 465ce35e8314a0ddda93a39e733243b7763c49f3 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 5 Mar 2025 13:02:55 +0300 Subject: [PATCH 386/834] feat: display motd/players info for ws servers (still no icon sadly) add new server --- config.json | 4 ++++ src/mineflayer/minecraft-protocol-extra.ts | 2 +- src/react/ServersListProvider.tsx | 6 +++++- src/react/Singleplayer.tsx | 4 +++- src/react/singleplayer.module.css | 3 +++ 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/config.json b/config.json index 6aa86397..e48d758b 100644 --- a/config.json +++ b/config.json @@ -6,6 +6,10 @@ "peerJsServer": "", "peerJsServerFallback": "https://p2p.mcraft.fun", "promoteServers": [ + { + "ip": "ws://mcraft.ryzyn.xyz", + "version": "1.19.4" + }, { "ip": "ws://play.mcraft.fun" }, diff --git a/src/mineflayer/minecraft-protocol-extra.ts b/src/mineflayer/minecraft-protocol-extra.ts index ef1bfbf2..e8216a00 100644 --- a/src/mineflayer/minecraft-protocol-extra.ts +++ b/src/mineflayer/minecraft-protocol-extra.ts @@ -13,7 +13,7 @@ export const pingServerVersion = async (ip: string, port?: number, mergeOptions: ...mergeOptions, } let latency = 0 - let fullInfo = null + let fullInfo: any = null fakeClient.autoVersionHooks = [(res) => { latency = res.latency fullInfo = res diff --git a/src/react/ServersListProvider.tsx b/src/react/ServersListProvider.tsx index 16c5ae8b..56ec75b6 100644 --- a/src/react/ServersListProvider.tsx +++ b/src/react/ServersListProvider.tsx @@ -17,6 +17,7 @@ import { useCopyKeybinding } from './simpleHooks' import { AuthenticatedAccount, getInitialServersList, getServerConnectionHistory, setNewServersList, StoreServerItem } from './serversStorage' type AdditionalDisplayData = { + textNameRightGrayed: string formattedText: string textNameRight: string icon?: string @@ -143,9 +144,11 @@ const Inner = ({ hidden, customServersList }: { hidden?: boolean, customServersL let data if (isWebSocket) { const pingResult = await getServerInfo(server.ip, undefined, undefined, true) + console.log('pingResult.fullInfo.description', pingResult.fullInfo.description) data = { - formattedText: `${pingResult.version} server with a direct websocket connection`, + formattedText: pingResult.fullInfo.description, textNameRight: `ws ${pingResult.latency}ms`, + textNameRightGrayed: `${pingResult.fullInfo.players?.online ?? '??'}/${pingResult.fullInfo.players?.max ?? '??'}`, offline: false } } else { @@ -364,6 +367,7 @@ const Inner = ({ hidden, customServersList }: { hidden?: boolean, customServersL detail: (server.versionOverride ?? '') + ' ' + (server.usernameOverride ?? ''), formattedTextOverride: additional?.formattedText, worldNameRight: additional?.textNameRight ?? '', + worldNameRightGrayed: additional?.textNameRightGrayed ?? '', iconSrc: additional?.icon, offline: additional?.offline } diff --git a/src/react/Singleplayer.tsx b/src/react/Singleplayer.tsx index 5effc269..6d7d6b0e 100644 --- a/src/react/Singleplayer.tsx +++ b/src/react/Singleplayer.tsx @@ -24,13 +24,14 @@ export interface WorldProps { detail?: string formattedTextOverride?: string worldNameRight?: string + worldNameRightGrayed?: string onFocus?: (name: string) => void onInteraction?(interaction: 'enter' | 'space') elemRef?: React.Ref offline?: boolean } -const World = ({ name, isFocused, title, lastPlayed, size, detail = '', onFocus, onInteraction, iconSrc, formattedTextOverride, worldNameRight, elemRef, offline }: WorldProps & { ref?: React.Ref }) => { +const World = ({ name, isFocused, title, lastPlayed, size, detail = '', onFocus, onInteraction, iconSrc, formattedTextOverride, worldNameRight, worldNameRightGrayed, elemRef, offline }: WorldProps & { ref?: React.Ref }) => { const timeRelativeFormatted = useMemo(() => { if (!lastPlayed) return '' const formatter = new Intl.RelativeTimeFormat('en', { numeric: 'auto' }) @@ -63,6 +64,7 @@ const World = ({ name, isFocused, title, lastPlayed, size, detail = '', onFocus,
{title}
+ {worldNameRightGrayed && {worldNameRightGrayed}} {offline ? ( diff --git a/src/react/singleplayer.module.css b/src/react/singleplayer.module.css index 0e69ea46..0de91735 100644 --- a/src/react/singleplayer.module.css +++ b/src/react/singleplayer.module.css @@ -36,6 +36,9 @@ .world_title_right { color: #999; font-size: 9px; + display: flex; + align-items: end; + gap: 1px; } .world_info { margin-left: 3px; From 998f0f0a85cd82cd3455b45d9866279000504298 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 5 Mar 2025 13:07:21 +0300 Subject: [PATCH 387/834] fix: fix sentry #6092213276 DataCloneError: Cannot decode detached ArrayBuffer --- src/basicSounds.ts | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/basicSounds.ts b/src/basicSounds.ts index 6c2b5f4f..40428c6b 100644 --- a/src/basicSounds.ts +++ b/src/basicSounds.ts @@ -17,17 +17,30 @@ const convertedSounds = [] as string[] export async function loadSound (path: string, contents = path) { if (loadingSounds.includes(path)) return true loadingSounds.push(path) - const res = await window.fetch(contents) - if (!res.ok) { - const error = `Failed to load sound ${path}` - if (isCypress()) throw new Error(error) - else console.warn(error) - return - } - const data = await res.arrayBuffer() - sounds[path] = data - loadingSounds.splice(loadingSounds.indexOf(path), 1) + try { + audioContext ??= new window.AudioContext() + + const res = await window.fetch(contents) + if (!res.ok) { + const error = `Failed to load sound ${path}` + if (isCypress()) throw new Error(error) + else console.warn(error) + return + } + const arrayBuffer = await res.arrayBuffer() + + // Decode the audio data immediately + const audioBuffer = await audioContext.decodeAudioData(arrayBuffer) + sounds[path] = audioBuffer + convertedSounds.push(path) // Mark as converted immediately + + loadingSounds.splice(loadingSounds.indexOf(path), 1) + } catch (err) { + console.warn(`Failed to load sound ${path}:`, err) + loadingSounds.splice(loadingSounds.indexOf(path), 1) + if (isCypress()) throw err + } } export const loadOrPlaySound = async (url, soundVolume = 1, loadTimeout = 500) => { @@ -53,13 +66,6 @@ export async function playSound (url, soundVolume = 1) { return } - for (const [soundName, sound] of Object.entries(sounds)) { - if (convertedSounds.includes(soundName)) continue - // eslint-disable-next-line no-await-in-loop - sounds[soundName] = await audioContext.decodeAudioData(sound) - convertedSounds.push(soundName) - } - const soundBuffer = sounds[url] if (!soundBuffer) { console.warn(`Sound ${url} not loaded yet`) From 0db49e78799021d90223f989abec11a99ff87f86 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 5 Mar 2025 15:11:42 +0300 Subject: [PATCH 388/834] feat: Full support for rendering blocks in inventory GUI powered by deeplsate (#292) --- package.json | 2 +- pnpm-lock.yaml | 22 +- renderer/viewer/lib/guiRenderer.ts | 275 +++++++++++++++++++++ renderer/viewer/lib/worldrendererCommon.ts | 13 + src/inventoryWindows.ts | 34 ++- src/react/EnterFullscreenButton.tsx | 9 +- 6 files changed, 336 insertions(+), 19 deletions(-) create mode 100644 renderer/viewer/lib/guiRenderer.ts diff --git a/package.json b/package.json index 0e0aa6a8..1a195884 100644 --- a/package.json +++ b/package.json @@ -148,8 +148,8 @@ "http-browserify": "^1.7.0", "http-server": "^14.1.1", "https-browserify": "^1.0.0", + "mc-assets": "^0.2.42", "mineflayer-mouse": "^0.0.5", - "mc-assets": "^0.2.37", "minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next", "mineflayer": "github:zardoy/mineflayer", "mineflayer-pathfinder": "^2.4.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dbbac60e..d5c2446f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -350,8 +350,8 @@ importers: specifier: ^1.0.0 version: 1.0.0 mc-assets: - specifier: ^0.2.37 - version: 0.2.37 + specifier: ^0.2.42 + version: 0.2.42 minecraft-inventory-gui: specifier: github:zardoy/minecraft-inventory-gui#next version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/75e940a4cd50d89e0ba03db3733d5d704917a3c8(@types/react@18.2.20)(react@18.2.0) @@ -3589,9 +3589,6 @@ packages: resolution: {integrity: sha512-FCAJojipPn0bXjuEpjOOOMN8FZDkxfWWp4JGN9mifU2IhxvKyXZYqpzPHdnTSUpmPDy+tsslB6Z1g+Vg6nVbYA==} engines: {node: '>=8'} - apl-image-packer@1.1.0: - resolution: {integrity: sha512-Pb1Q76cp8xpY8X4OqVrnk5v1/tB5kOtCzwgOnx8IxMNeekFh/eNUiUKeX5fvGNViZiLmuKAAQc5ICuBDspZ4cA==} - app-root-dir@1.0.2: resolution: {integrity: sha512-jlpIfsOoNoafl92Sz//64uQHGSyMrD2vYG5d8o2a4qGvyNCvXur7bzIsWtAC/6flI2RYAp3kv8rsfBtaLm7w0g==} @@ -6571,8 +6568,11 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - mc-assets@0.2.37: - resolution: {integrity: sha512-49tk3shwxsDoV0PrrbORZEKg613vUQPULgusWjXNl8JEma5y41LEo57D6q4aHliXsV3Gb9ThrkFf6hIb0WlY1Q==} + maxrects-packer@2.7.3: + resolution: {integrity: sha512-bG6qXujJ1QgttZVIH4WDanhoJtvbud/xP/XPyf6A69C9RdA61BM4TomFALCq2nrTa+tARRIBB4LuIFsnUQU2wA==} + + mc-assets@0.2.42: + resolution: {integrity: sha512-j2D1RNYtB5Z9gFu9MVjyDBbiALI0mWZ3xW/A3PPefVAHm3HJ2T1vH+1XBov1spBGPl7u+Zo7mRXza3X0egbeOg==} engines: {node: '>=18.0.0'} mcraft-fun-mineflayer@0.1.8: @@ -13609,8 +13609,6 @@ snapshots: apache-md5@1.1.8: {} - apl-image-packer@1.1.0: {} - app-root-dir@1.0.2: {} aproba@2.0.0: @@ -17367,9 +17365,11 @@ snapshots: math-intrinsics@1.1.0: {} - mc-assets@0.2.37: + maxrects-packer@2.7.3: {} + + mc-assets@0.2.42: dependencies: - apl-image-packer: 1.1.0 + maxrects-packer: 2.7.3 zod: 3.24.1 mcraft-fun-mineflayer@0.1.8(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13)): diff --git a/renderer/viewer/lib/guiRenderer.ts b/renderer/viewer/lib/guiRenderer.ts new file mode 100644 index 00000000..b197040e --- /dev/null +++ b/renderer/viewer/lib/guiRenderer.ts @@ -0,0 +1,275 @@ +// Import placeholders - replace with actual imports for your environment +import { ItemRenderer, Identifier, ItemStack, NbtString, Structure, StructureRenderer, ItemRendererResources, BlockDefinition, BlockModel, TextureAtlas, Resources, ItemModel } from 'deepslate' +import { mat4, vec3 } from 'gl-matrix' +import { AssetsParser } from 'mc-assets/dist/assetsParser' +import { getLoadedImage } from 'mc-assets/dist/utils' +import { BlockModel as BlockModelMcAssets, AtlasParser } from 'mc-assets' +import { getLoadedBlockstatesStore, getLoadedModelsStore } from 'mc-assets/dist/stores' +import { makeTextureAtlas } from 'mc-assets/dist/atlasCreator' +import { proxy, ref } from 'valtio' +import { getItemDefinition } from 'mc-assets/dist/itemDefinitions' + +export const activeGuiAtlas = proxy({ + atlas: null as null | { json, image }, +}) + +export const getNonFullBlocksModels = () => { + const version = viewer.world.texturesVersion ?? 'latest' + const itemsDefinitions = viewer.world.itemsDefinitionsStore.data.latest + const blockModelsResolved = {} as Record + const itemsModelsResolved = {} as Record + const fullBlocksWithNonStandardDisplay = [] as string[] + const handledItemsWithDefinitions = new Set() + const assetsParser = new AssetsParser(version, getLoadedBlockstatesStore(viewer.world.blockstatesModels), getLoadedModelsStore(viewer.world.blockstatesModels)) + + const standardGuiDisplay = { + 'rotation': [ + 30, + 225, + 0 + ], + 'translation': [ + 0, + 0, + 0 + ], + 'scale': [ + 0.625, + 0.625, + 0.625 + ] + } + + const arrEqual = (a: number[], b: number[]) => a.length === b.length && a.every((x, i) => x === b[i]) + const addModelIfNotFullblock = (name: string, model: BlockModelMcAssets) => { + if (blockModelsResolved[name]) return + if (!model?.elements?.length) return + const isFullBlock = model.elements.length === 1 && arrEqual(model.elements[0].from, [0, 0, 0]) && arrEqual(model.elements[0].to, [16, 16, 16]) + if (isFullBlock) return + model['display'] ??= {} + model['display']['gui'] ??= standardGuiDisplay + blockModelsResolved[name] = model + } + + for (const [name, definition] of Object.entries(itemsDefinitions)) { + const item = getItemDefinition(viewer.world.itemsDefinitionsStore, { + version, + name, + properties: { + 'minecraft:display_context': 'gui', + }, + }) + if (item) { + const { resolvedModel } = assetsParser.getResolvedModelsByModel((item.special ? name : item.model).replace('minecraft:', '')) ?? {} + if (resolvedModel) { + handledItemsWithDefinitions.add(name) + } + if (resolvedModel?.elements) { + + let hasStandardDisplay = true + if (resolvedModel['display']?.gui) { + hasStandardDisplay = + arrEqual(resolvedModel['display'].gui.rotation, standardGuiDisplay.rotation) + && arrEqual(resolvedModel['display'].gui.translation, standardGuiDisplay.translation) + && arrEqual(resolvedModel['display'].gui.scale, standardGuiDisplay.scale) + } + + addModelIfNotFullblock(name, resolvedModel) + + if (!blockModelsResolved[name] && !hasStandardDisplay) { + fullBlocksWithNonStandardDisplay.push(name) + } + const notSideLight = resolvedModel['gui_light'] && resolvedModel['gui_light'] !== 'side' + if (!hasStandardDisplay || notSideLight) { + blockModelsResolved[name] = resolvedModel + } + } + if (!blockModelsResolved[name] && item.tints && resolvedModel) { + resolvedModel['tints'] = item.tints + if (resolvedModel.elements) { + blockModelsResolved[name] = resolvedModel + } else { + itemsModelsResolved[name] = resolvedModel + } + } + } + } + + for (const [name, blockstate] of Object.entries(viewer.world.blockstatesModels.blockstates.latest)) { + if (handledItemsWithDefinitions.has(name)) { + continue + } + const resolvedModel = assetsParser.getResolvedModelFirst({ name: name.replace('minecraft:', ''), properties: {} }, true) + if (resolvedModel) { + addModelIfNotFullblock(name, resolvedModel[0]) + } + } + + return { + blockModelsResolved, + itemsModelsResolved + } +} + +// customEvents.on('gameLoaded', () => { +// const res = getNonFullBlocksModels() +// }) + +const RENDER_SIZE = 64 + +const generateItemsGui = async (models: Record, isItems = false) => { + const img = await getLoadedImage(isItems ? viewer.world.itemsAtlasParser!.latestImage : viewer.world.blocksAtlasParser!.latestImage) + const canvasTemp = document.createElement('canvas') + canvasTemp.width = img.width + canvasTemp.height = img.height + canvasTemp.style.imageRendering = 'pixelated' + const ctx = canvasTemp.getContext('2d')! + ctx.imageSmoothingEnabled = false + ctx.drawImage(img, 0, 0) + + const atlasParser = isItems ? viewer.world.itemsAtlasParser! : viewer.world.blocksAtlasParser! + const textureAtlas = new TextureAtlas( + ctx.getImageData(0, 0, img.width, img.height), + Object.fromEntries(Object.entries(atlasParser.atlas.latest.textures).map(([key, value]) => { + return [key, [ + value.u, + value.v, + (value.u + (value.su ?? atlasParser.atlas.latest.suSv)), + (value.v + (value.sv ?? atlasParser.atlas.latest.suSv)), + ]] as [string, [number, number, number, number]] + })) + ) + + const PREVIEW_ID = Identifier.parse('preview:preview') + const PREVIEW_DEFINITION = new BlockDefinition({ '': { model: PREVIEW_ID.toString() } }, undefined) + + let modelData: any + let currentModelName: string | undefined + const resources: ItemRendererResources = { + getBlockModel (id) { + if (id.equals(PREVIEW_ID)) { + return BlockModel.fromJson(modelData ?? {}) + } + return null + }, + getTextureUV (texture) { + return textureAtlas.getTextureUV(texture.toString().slice(1).split('/').slice(1).join('/') as any) + }, + getTextureAtlas () { + return textureAtlas.getTextureAtlas() + }, + getItemComponents (id) { + return new Map() + }, + getItemModel (id) { + // const isSpecial = currentModelName === 'shield' || currentModelName === 'conduit' || currentModelName === 'trident' + const isSpecial = false + if (id.equals(PREVIEW_ID)) { + return ItemModel.fromJson({ + type: isSpecial ? 'minecraft:special' : 'minecraft:model', + model: isSpecial ? { + type: currentModelName, + } : PREVIEW_ID.toString(), + base: PREVIEW_ID.toString(), + tints: modelData?.tints, + }) + } + return null + }, + } + + const canvas = document.createElement('canvas') + canvas.width = RENDER_SIZE + canvas.height = RENDER_SIZE + const gl = canvas.getContext('webgl2', { preserveDrawingBuffer: true }) + if (!gl) { + throw new Error('Cannot get WebGL2 context') + } + + function resetGLContext (gl) { + gl.clearColor(0, 0, 0, 0) + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT) + } + + // const includeOnly = ['powered_repeater', 'wooden_door'] + const includeOnly = [] as string[] + + const images: Record = {} + const item = new ItemStack(PREVIEW_ID, 1, new Map(Object.entries({ + 'minecraft:item_model': new NbtString(PREVIEW_ID.toString()), + }))) + const renderer = new ItemRenderer(gl, item, resources, { display_context: 'gui' }) + const missingTextures = new Set() + for (const [modelName, model] of Object.entries(models)) { + if (includeOnly.length && !includeOnly.includes(modelName)) continue + + const patchMissingTextures = () => { + for (const element of model.elements ?? []) { + for (const [faceName, face] of Object.entries(element.faces)) { + if (face.texture.startsWith('#')) { + missingTextures.add(`${modelName} ${faceName}: ${face.texture}`) + face.texture = 'block/unknown' + } + } + } + } + patchMissingTextures() + // TODO eggs + + modelData = model + currentModelName = modelName + resetGLContext(gl) + if (!modelData) continue + renderer.setItem(item, { display_context: 'gui' }) + renderer.drawItem() + const url = canvas.toDataURL() + // eslint-disable-next-line no-await-in-loop + const img = await getLoadedImage(url) + images[modelName] = img + } + + if (missingTextures.size) { + console.warn(`[guiRenderer] Missing textures in ${[...missingTextures].join(', ')}`) + } + + return images +} + +const generateAtlas = async (images: Record) => { + const atlas = makeTextureAtlas({ + input: Object.keys(images), + tileSize: RENDER_SIZE, + getLoadedImage (name) { + return { + image: images[name], + } + }, + }) + + // const atlasParser = new AtlasParser({ latest: atlas.json }, atlas.canvas.toDataURL()) + // const a = document.createElement('a') + // a.href = await atlasParser.createDebugImage(true) + // a.download = 'blocks_atlas.png' + // a.click() + + activeGuiAtlas.atlas = { + json: atlas.json, + image: ref(await getLoadedImage(atlas.canvas.toDataURL())), + } + + return atlas +} + +export const generateGuiAtlas = async () => { + const { blockModelsResolved, itemsModelsResolved } = getNonFullBlocksModels() + + // Generate blocks atlas + console.time('generate blocks gui atlas') + const blockImages = await generateItemsGui(blockModelsResolved, false) + console.timeEnd('generate blocks gui atlas') + console.time('generate items gui atlas') + const itemImages = await generateItemsGui(itemsModelsResolved, true) + console.timeEnd('generate items gui atlas') + await generateAtlas({ ...blockImages, ...itemImages }) + // await generateAtlas(blockImages) +} diff --git a/renderer/viewer/lib/worldrendererCommon.ts b/renderer/viewer/lib/worldrendererCommon.ts index a54f9fe8..261b18e6 100644 --- a/renderer/viewer/lib/worldrendererCommon.ts +++ b/renderer/viewer/lib/worldrendererCommon.ts @@ -24,6 +24,7 @@ import { chunkPos } from './simpleUtils' import { HandItemBlock } from './holdingBlock' import { updateStatText } from './ui/newStats' import { WorldRendererThree } from './worldrendererThree' +import { generateGuiAtlas } from './guiRenderer' function mod (x, n) { return ((x % n) + n) % n @@ -354,6 +355,10 @@ export abstract class WorldRendererCommon } } + async generateGuiTextures () { + await generateGuiAtlas() + } + async updateAssetsData (resourcePackUpdate = false, prioritizeBlockTextures?: string[]) { const blocksAssetsParser = new AtlasParser(this.sourceData.blocksAtlases, blocksAtlasLatest, blocksAtlasLegacy) const itemsAssetsParser = new AtlasParser(this.sourceData.itemsAtlases, itemsAtlasLatest, itemsAtlasLegacy) @@ -379,6 +384,7 @@ export abstract class WorldRendererCommon return texture }, this.customTextures?.items?.tileSize, undefined, customItemTextures) console.timeEnd('createItemsAtlas') + this.blocksAtlasParser = new AtlasParser({ latest: blocksAtlas }, blocksCanvas.toDataURL()) this.itemsAtlasParser = new AtlasParser({ latest: itemsAtlas }, itemsCanvas.toDataURL()) @@ -418,13 +424,20 @@ export abstract class WorldRendererCommon config: this.mesherConfig, }) } + if (!this.itemsAtlasParser) return const itemsTexture = await new THREE.TextureLoader().loadAsync(this.itemsAtlasParser.latestImage) itemsTexture.magFilter = THREE.NearestFilter itemsTexture.minFilter = THREE.NearestFilter itemsTexture.flipY = false viewer.entities.itemsTexture = itemsTexture + if (!this.itemsAtlasParser) return this.renderUpdateEmitter.emit('textureDownloaded') + + console.time('generateGuiTextures') + await this.generateGuiTextures() + console.timeEnd('generateGuiTextures') + if (!this.itemsAtlasParser) return this.renderUpdateEmitter.emit('itemsTextureDownloaded') console.log('textures loaded') } diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index d473f5be..4e4616cd 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -10,6 +10,7 @@ import { versionToNumber } from 'renderer/viewer/prepare/utils' import { getRenamedData } from 'flying-squid/dist/blockRenames' import PrismarineChatLoader from 'prismarine-chat' import { BlockModel } from 'mc-assets' +import { activeGuiAtlas } from 'renderer/viewer/lib/guiRenderer' import Generic95 from '../assets/generic_95.png' import { appReplacableResources } from './generated/resources' import { activeModalStack, hideCurrentModal, hideModal, miscUiState, showModal } from './globalState' @@ -155,7 +156,10 @@ const getImageSrc = (path): string | HTMLImageElement => { return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=' } -const getImage = ({ path = undefined as string | undefined, texture = undefined as string | undefined, blockData = undefined as any }, onLoad = () => { }) => { +const getImage = ({ path = undefined as string | undefined, texture = undefined as string | undefined, blockData = undefined as any, image = undefined as HTMLImageElement | undefined }, onLoad = () => { }) => { + if (image) { + return image + } if (!path && !texture) throw new Error('Either pass path or texture') const loadPath = (blockData ? 'blocks' : path ?? texture)! if (loadedImagesCache.has(loadPath)) { @@ -184,7 +188,8 @@ export const renderSlot = (model: ResolvedItemModelRender, debugIsQuickbar = fal blockData?: Record & { resolvedModel: BlockModel }, scale?: number, slice?: number[], - modelName?: string + modelName?: string, + image?: HTMLImageElement } | undefined => { let itemModelName = model.modelName const originalItemName = itemModelName @@ -196,6 +201,23 @@ export const renderSlot = (model: ResolvedItemModelRender, debugIsQuickbar = fal let itemTexture + + if (!fullBlockModelSupport) { + const atlas = activeGuiAtlas.atlas?.json + // todo atlas holds all rendered blocks, not all possibly rendered item/block models, need to request this on demand instead (this is how vanilla works) + const item = atlas?.textures[itemModelName.replace('minecraft:', '').replace('block/', '').replace('blocks/', '').replace('item/', '').replace('items/', '')] + if (item) { + const x = item.u * atlas.width + const y = item.v * atlas.height + return { + texture: 'gui', + image: activeGuiAtlas.atlas!.image, + slice: [x, y, atlas.tileSize, atlas.tileSize], + scale: 0.25, + } + } + } + try { assertDefined(viewer.world.itemsRenderer) itemTexture = @@ -205,6 +227,8 @@ export const renderSlot = (model: ResolvedItemModelRender, debugIsQuickbar = fal inGameError(`Failed to render item ${itemModelName} (original: ${originalItemName}) on ${bot.version} (resourcepack: ${options.enabledResourcepack}): ${err.stack}`) itemTexture = viewer.world.itemsRenderer!.getItemTexture('block/errored')! } + + if ('type' in itemTexture) { // is item return { @@ -230,13 +254,13 @@ const getItemName = (slot: Item | RenderItem | null) => { return text.join('') } -const mapSlots = (slots: Array) => { +const mapSlots = (slots: Array, isJei = false) => { return slots.map((slot, i) => { // todo stateid if (!slot) return try { - const debugIsQuickbar = i === bot.inventory.hotbarStart + bot.quickBarSlot + const debugIsQuickbar = !isJei && i === bot.inventory.hotbarStart + bot.quickBarSlot const modelName = getItemModelName(slot, { 'minecraft:display_context': 'gui', }) const slotCustomProps = renderSlot({ modelName }, debugIsQuickbar) const itemCustomName = getItemName(slot) @@ -305,7 +329,7 @@ const upJei = (search: string) => { return new PrismarineItem(x.id, 1) }).filter(a => a !== null) lastWindow.pwindow.win.jeiSlotsPage = 0 - lastWindow.pwindow.win.jeiSlots = mapSlots(matchedSlots) + lastWindow.pwindow.win.jeiSlots = mapSlots(matchedSlots, true) } export const openItemsCanvas = (type, _bot = bot as typeof bot | null) => { diff --git a/src/react/EnterFullscreenButton.tsx b/src/react/EnterFullscreenButton.tsx index 3901b9ae..0bc41632 100644 --- a/src/react/EnterFullscreenButton.tsx +++ b/src/react/EnterFullscreenButton.tsx @@ -4,6 +4,7 @@ import { activeModalStack, miscUiState } from '../globalState' import Button from './Button' import { useUsingTouch } from './utilsApp' import { pixelartIcons } from './PixelartIcon' +import { showNotification } from './NotificationProvider' const hideOnModals = new Set(['chat']) @@ -33,8 +34,12 @@ export default () => { left: inMainMenu ? 35 : 5, width: 22, }} - onClick={() => { - void document.documentElement.requestFullscreen() + onClick={async () => { + try { + await document.documentElement.requestFullscreen() + } catch (err) { + showNotification(`${err.message ?? err}`, undefined, true) + } }} /> } From b9df1bcf9ec4b099eede64fde721327c082a95b1 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 5 Mar 2025 15:12:05 +0300 Subject: [PATCH 389/834] fix enabling lighting falsey when load for chunks is enabled --- src/watchOptions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/watchOptions.ts b/src/watchOptions.ts index 724fb2ba..3e607f49 100644 --- a/src/watchOptions.ts +++ b/src/watchOptions.ts @@ -75,7 +75,7 @@ export const watchOptionsAfterViewerInit = () => { viewer.world.mesherConfig.enableLighting = !bot.supportFeature('blockStateId') || options.newVersionsLighting; (viewer.world as WorldRendererThree).rerenderAllChunks() }) - customEvents.on('gameLoaded', () => { + customEvents.on('mineflayerBotCreated', () => { viewer.world.mesherConfig.enableLighting = !bot.supportFeature('blockStateId') || options.newVersionsLighting }) From 6fb18d44382b729163fa5c7ca2fb96313ea6455b Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 5 Mar 2025 15:26:59 +0300 Subject: [PATCH 390/834] fixes & workarounds rendering items in inventory (some were broken since last commit) --- src/inventoryWindows.ts | 2 +- src/resourcesManager.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index 4e4616cd..6a5ab0d6 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -205,7 +205,7 @@ export const renderSlot = (model: ResolvedItemModelRender, debugIsQuickbar = fal if (!fullBlockModelSupport) { const atlas = activeGuiAtlas.atlas?.json // todo atlas holds all rendered blocks, not all possibly rendered item/block models, need to request this on demand instead (this is how vanilla works) - const item = atlas?.textures[itemModelName.replace('minecraft:', '').replace('block/', '').replace('blocks/', '').replace('item/', '').replace('items/', '')] + const item = atlas?.textures[itemModelName.replace('minecraft:', '').replace('block/', '').replace('blocks/', '').replace('item/', '').replace('items/', '').replace('_inventory', '').replace('_bottom', '')] if (item) { const x = item.u * atlas.width const y = item.v * atlas.height diff --git a/src/resourcesManager.ts b/src/resourcesManager.ts index fd92e58b..43c3882e 100644 --- a/src/resourcesManager.ts +++ b/src/resourcesManager.ts @@ -14,10 +14,11 @@ export const getItemModelName = (item: GeneralInputItem, specificProps: ItemSpec const itemSelector = playerState.getItemSelector({ ...specificProps }) - const model = getItemDefinition(viewer.world.itemsDefinitionsStore, { + const modelFromDef = getItemDefinition(viewer.world.itemsDefinitionsStore, { name: itemModelName, version: viewer.world.texturesVersion!, properties: itemSelector - })?.model ?? itemModelName + })?.model + const model = (modelFromDef === 'minecraft:special' ? undefined : modelFromDef) ?? itemModelName return model } From a846eb4500912578742518878c57903d22a28cf4 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 5 Mar 2025 20:45:34 +0300 Subject: [PATCH 391/834] hotfix: fix world interaction crashes --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 1a195884..1bc36f9c 100644 --- a/package.json +++ b/package.json @@ -149,7 +149,7 @@ "http-server": "^14.1.1", "https-browserify": "^1.0.0", "mc-assets": "^0.2.42", - "mineflayer-mouse": "^0.0.5", + "mineflayer-mouse": "^0.0.7", "minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next", "mineflayer": "github:zardoy/mineflayer", "mineflayer-pathfinder": "^2.4.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d5c2446f..491ef376 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -359,8 +359,8 @@ importers: specifier: github:zardoy/mineflayer version: https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13) mineflayer-mouse: - specifier: ^0.0.5 - version: 0.0.5(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1) + specifier: ^0.0.7 + version: 0.0.7(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1) mineflayer-pathfinder: specifier: ^2.4.4 version: 2.4.4 @@ -6801,8 +6801,8 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824} version: 1.2.0 - mineflayer-mouse@0.0.5: - resolution: {integrity: sha512-0r/AOGTq+wZH9vrBcW93jH2dGRSlwlO6xc1Z67VJUFlZZ8oBefAOpiZq7LIGc7ROVbpcKEKjROdNv/iCFmzXYA==} + mineflayer-mouse@0.0.7: + resolution: {integrity: sha512-/cSDsc2ZPlvakc3BX+/K9VD64HAIa+LGiz34RpQvUy7hwx3nXdZjJHDjzEdn86BBzRF5pZOxIoXm8hlZKCYeeQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} mineflayer-pathfinder@2.4.4: @@ -17742,7 +17742,7 @@ snapshots: - encoding - supports-color - mineflayer-mouse@0.0.5(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1): + mineflayer-mouse@0.0.7(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1): dependencies: change-case: 5.4.4 debug: 4.4.0(supports-color@8.1.1) From c6b8efe4e8b4885ecaad7403b41bcbc33675e3a4 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 5 Mar 2025 22:22:35 +0300 Subject: [PATCH 392/834] hotfix: should fix edge case when canvas was out of viewport bounds on ios --- src/react/DebugEdges.tsx | 10 +++++++++- src/styles.css | 14 ++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/react/DebugEdges.tsx b/src/react/DebugEdges.tsx index a65b9f92..150e1f16 100644 --- a/src/react/DebugEdges.tsx +++ b/src/react/DebugEdges.tsx @@ -2,7 +2,7 @@ import { useState } from 'react' import { useIsHashActive } from './simpleHooks' export default () => { - const MODES_COUNT = 4 + const MODES_COUNT = 5 const [mode, setMode] = useState(0) const isHashActive = useIsHashActive('#edges') @@ -41,6 +41,14 @@ export default () => { styles.height = '100dvh' text = 'top 0 fixed 100dvh' } + if (mode === 4) { + styles.position = 'fixed' + styles.top = 0 + styles.left = 0 + styles.right = 0 + styles.height = '100dvh' + text = 'top 0 bottom 0 fixed 100dvh' + } return
Date: Wed, 5 Mar 2025 22:49:36 +0300 Subject: [PATCH 393/834] feat: Add interaction hint for touch-based entity targeting --- src/react/GameInteractionOverlay.tsx | 8 +++-- src/react/InteractionHint.module.css | 19 ++++++++++ src/react/InteractionHint.module.css.d.ts | 10 ++++++ src/react/InteractionHint.tsx | 44 +++++++++++++++++++++++ src/reactUi.tsx | 2 ++ 5 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 src/react/InteractionHint.module.css create mode 100644 src/react/InteractionHint.module.css.d.ts create mode 100644 src/react/InteractionHint.tsx diff --git a/src/react/GameInteractionOverlay.tsx b/src/react/GameInteractionOverlay.tsx index cb7a39f8..7c406e8d 100644 --- a/src/react/GameInteractionOverlay.tsx +++ b/src/react/GameInteractionOverlay.tsx @@ -150,9 +150,13 @@ function GameInteractionOverlayInner ({ document.dispatchEvent(new MouseEvent('mouseup', { button: 0 })) virtualClickActive = false } else if (!capturedPointer.active.activateCameraMove && (Date.now() - capturedPointer.active.time < touchStartBreakingBlockMs)) { - document.dispatchEvent(new MouseEvent('mousedown', { button: 2 })) + // single click action + const MOUSE_BUTTON_RIGHT = 2 + const MOUSE_BUTTON_LEFT = 0 + const gonnaAttack = !!bot.mouse.getCursorState().entity + document.dispatchEvent(new MouseEvent('mousedown', { button: gonnaAttack ? MOUSE_BUTTON_LEFT : MOUSE_BUTTON_RIGHT })) bot.mouse.update() - document.dispatchEvent(new MouseEvent('mouseup', { button: 2 })) + document.dispatchEvent(new MouseEvent('mouseup', { button: gonnaAttack ? MOUSE_BUTTON_LEFT : MOUSE_BUTTON_RIGHT })) } if (screenTouches > 0) { diff --git a/src/react/InteractionHint.module.css b/src/react/InteractionHint.module.css new file mode 100644 index 00000000..026b49c8 --- /dev/null +++ b/src/react/InteractionHint.module.css @@ -0,0 +1,19 @@ +.hint_container { + position: fixed; + top: 20%; + left: 0; + right: 0; + margin: 0 auto; + width: fit-content; + display: flex; + align-items: center; + gap: 8px; + pointer-events: none; + z-index: 1000; + text-shadow: 1px 1px 8px rgba(0, 0, 0, 1); +} + +.hint_text { + color: white; + font-size: 10px; +} diff --git a/src/react/InteractionHint.module.css.d.ts b/src/react/InteractionHint.module.css.d.ts new file mode 100644 index 00000000..45bdf30b --- /dev/null +++ b/src/react/InteractionHint.module.css.d.ts @@ -0,0 +1,10 @@ +// This file is automatically generated. +// Please do not change this file! +interface CssExports { + hintContainer: string; + hintText: string; + hint_container: string; + hint_text: string; +} +declare const cssExports: CssExports; +export default cssExports; diff --git a/src/react/InteractionHint.tsx b/src/react/InteractionHint.tsx new file mode 100644 index 00000000..9121249e --- /dev/null +++ b/src/react/InteractionHint.tsx @@ -0,0 +1,44 @@ +import { useEffect, useState } from 'react' +import { useSnapshot } from 'valtio' +import { options } from '../optionsStorage' +import PixelartIcon, { pixelartIcons } from './PixelartIcon' +import styles from './InteractionHint.module.css' +import { useUsingTouch } from './utilsApp' + +export default () => { + const usingTouch = useUsingTouch() + const { touchInteractionType } = useSnapshot(options) + const [hintText, setHintText] = useState(null) + + useEffect(() => { + const update = () => { + const cursorState = bot.mouse.getCursorState() + if (cursorState.entity) { + const entityName = cursorState.entity.displayName ?? cursorState.entity.name + setHintText(`Attack ${entityName}`) + } else { + setHintText(null) + } + } + + // Initial update + update() + + // Subscribe to physics ticks + bot.on('physicsTick', update) + + return () => { + bot.removeListener('physicsTick', update) + } + }, []) + + if (!usingTouch || touchInteractionType !== 'classic') return null + if (!hintText) return null + + return ( +
+ + {hintText} +
+ ) +} diff --git a/src/reactUi.tsx b/src/reactUi.tsx index ac2dbe74..f07c01ce 100644 --- a/src/reactUi.tsx +++ b/src/reactUi.tsx @@ -52,6 +52,7 @@ import MineflayerPluginConsole from './react/MineflayerPluginConsole' import { UIProvider } from './react/UIProvider' import { useAppScale } from './scaleInterface' import PacketsReplayProvider from './react/PacketsReplayProvider' +import InteractionHint from './react/InteractionHint' const RobustPortal = ({ children, to }) => { return createPortal({children}, to) @@ -146,6 +147,7 @@ const InGameUi = () => { + {showUI && }
{!disabledUiParts.includes('xp-bar') && } {!disabledUiParts.includes('hud-bars') && } From e7b012c08d270d813e365adb55fe3d99251c96f0 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 5 Mar 2025 22:58:11 +0300 Subject: [PATCH 394/834] feat: Display players list on long chat button hold --- src/react/InteractionHint.tsx | 4 ++- src/react/MobileTopButtons.tsx | 33 +++++++++++++++++++------ src/react/PlayerListOverlayProvider.tsx | 4 +++ 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/react/InteractionHint.tsx b/src/react/InteractionHint.tsx index 9121249e..5c60cdc8 100644 --- a/src/react/InteractionHint.tsx +++ b/src/react/InteractionHint.tsx @@ -1,12 +1,14 @@ import { useEffect, useState } from 'react' import { useSnapshot } from 'valtio' import { options } from '../optionsStorage' +import { activeModalStack } from '../globalState' import PixelartIcon, { pixelartIcons } from './PixelartIcon' import styles from './InteractionHint.module.css' import { useUsingTouch } from './utilsApp' export default () => { const usingTouch = useUsingTouch() + const modalStack = useSnapshot(activeModalStack) const { touchInteractionType } = useSnapshot(options) const [hintText, setHintText] = useState(null) @@ -32,7 +34,7 @@ export default () => { } }, []) - if (!usingTouch || touchInteractionType !== 'classic') return null + if (!usingTouch || touchInteractionType !== 'classic' || modalStack.length > 0) return null if (!hintText) return null return ( diff --git a/src/react/MobileTopButtons.tsx b/src/react/MobileTopButtons.tsx index f686d8af..4d18f817 100644 --- a/src/react/MobileTopButtons.tsx +++ b/src/react/MobileTopButtons.tsx @@ -33,6 +33,28 @@ export default () => { } const longPressEvent = useLongPress(onLongPress, () => {}, defaultOptions) + + const onChatLongPress = () => { + document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab' })) + } + + const onChatClick = () => { + if (activeModalStack.at(-1)?.reactType === 'chat') { + hideCurrentModal() + } else { + showModal({ reactType: 'chat' }) + } + } + + const chatLongPressEvent = useLongPress( + onChatLongPress, + onChatClick, + { + shouldPreventDefault: true, + delay: 300, + } + ) + // ios note: just don't use
From 09cd2c3f644c0cd52d53778aaa167a3c050e06ad Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 14 Mar 2025 01:50:54 +0300 Subject: [PATCH 423/834] fix(guiRenderer): dont break textures with custom namespaces rendering --- renderer/viewer/lib/guiRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/renderer/viewer/lib/guiRenderer.ts b/renderer/viewer/lib/guiRenderer.ts index f4af3d20..d2987ce6 100644 --- a/renderer/viewer/lib/guiRenderer.ts +++ b/renderer/viewer/lib/guiRenderer.ts @@ -155,7 +155,7 @@ const generateItemsGui = async (models: Record, isIt return null }, getTextureUV (texture) { - return textureAtlas.getTextureUV(texture.toString().slice(1).split('/').slice(1).join('/') as any) + return textureAtlas.getTextureUV(texture.toString().replace('minecraft:', '').replace('block/', '').replace('item/', '').replace('blocks/', '').replace('items/', '') as any) }, getTextureAtlas () { return textureAtlas.getTextureAtlas() From 518d6ad8661079eb84d948917a1fbb9a80a14130 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 14 Mar 2025 02:13:04 +0300 Subject: [PATCH 424/834] fix always display reconnect and better last packets display (time) --- package.json | 2 +- pnpm-lock.yaml | 12 ++++++------ src/index.ts | 4 ++-- src/mineflayer/plugins/packetsRecording.ts | 2 ++ src/packetsReplay/replayPackets.ts | 11 +++++++++++ src/react/ServersListProvider.tsx | 2 +- 6 files changed, 23 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index a1fcff61..f6ae254e 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "mojangson": "^2.0.4", "net-browserify": "github:zardoy/prismarinejs-net-browserify", "node-gzip": "^1.1.2", - "mcraft-fun-mineflayer": "^0.1.8", + "mcraft-fun-mineflayer": "^0.1.10", "peerjs": "^1.5.0", "pixelarticons": "^1.8.1", "pretty-bytes": "^6.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 02a0a671..2b09f583 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -135,8 +135,8 @@ importers: specifier: ^4.17.21 version: 4.17.21 mcraft-fun-mineflayer: - specifier: ^0.1.8 - version: 0.1.8(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13)) + specifier: ^0.1.10 + version: 0.1.10(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13)) minecraft-data: specifier: 3.83.1 version: 3.83.1 @@ -6690,9 +6690,9 @@ packages: resolution: {integrity: sha512-j2D1RNYtB5Z9gFu9MVjyDBbiALI0mWZ3xW/A3PPefVAHm3HJ2T1vH+1XBov1spBGPl7u+Zo7mRXza3X0egbeOg==} engines: {node: '>=18.0.0'} - mcraft-fun-mineflayer@0.1.8: - resolution: {integrity: sha512-jyJTihNHfeToBPwVs3QMKBlVcaCABJ25YN2eoIBQEVTRVFzaXh13XRpElphLzTMj1Q5XFYqufHtMoR4tsb08qQ==} - version: 0.1.8 + mcraft-fun-mineflayer@0.1.10: + resolution: {integrity: sha512-KHzPts82I39nTDZlGwqJo1JXLwaIUHphBbmGWv7oYztUrq3iPiJDEIFgst0ROO/apjtHjzbCM9eb19qWw1JM3Q==} + version: 0.1.10 engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: '@roamhq/wrtc': '*' @@ -17579,7 +17579,7 @@ snapshots: maxrects-packer: 2.7.3 zod: 3.24.1 - mcraft-fun-mineflayer@0.1.8(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13)): + mcraft-fun-mineflayer@0.1.10(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13)): dependencies: '@zardoy/flying-squid': 0.0.49(encoding@0.1.13) exit-hook: 2.2.1 diff --git a/src/index.ts b/src/index.ts index 67187c1c..83f9f289 100644 --- a/src/index.ts +++ b/src/index.ts @@ -362,8 +362,8 @@ export async function connect (connectOptions: ConnectOptions) { miscUiState.hasErrors = true if (miscUiState.gameLoaded) return - appStatusState.showReconnect = true setLoadingScreenStatus(`Error encountered. ${err}`, true) + appStatusState.showReconnect = true onPossibleErrorDisconnect() destroyAll() } @@ -712,8 +712,8 @@ export async function connect (connectOptions: ConnectOptions) { bot.on('kicked', (kickReason) => { console.log('You were kicked!', kickReason) const { formatted: kickReasonFormatted, plain: kickReasonString } = parseFormattedMessagePacket(kickReason) - appStatusState.showReconnect = true setLoadingScreenStatus(`The Minecraft server kicked you. Kick reason: ${kickReasonString}`, true, undefined, undefined, kickReasonFormatted) + appStatusState.showReconnect = true destroyAll() }) diff --git a/src/mineflayer/plugins/packetsRecording.ts b/src/mineflayer/plugins/packetsRecording.ts index f1ff18cf..a6b761e7 100644 --- a/src/mineflayer/plugins/packetsRecording.ts +++ b/src/mineflayer/plugins/packetsRecording.ts @@ -93,6 +93,8 @@ declare module 'mineflayer' { export const getLastAutoCapturedPackets = () => circularBuffer?.size export const downloadAutoCapturedPackets = () => { const logger = new PacketsLogger({ minecraftVersion: lastConnectVersion }) + logger.relativeTime = false + logger.formattedTime = true for (const packet of circularBuffer?.getLastElements() ?? []) { logger.log(packet.isFromServer, { name: packet.name, state: packet.state, time: packet.timestamp }, packet.params) } diff --git a/src/packetsReplay/replayPackets.ts b/src/packetsReplay/replayPackets.ts index 9e777b38..13891899 100644 --- a/src/packetsReplay/replayPackets.ts +++ b/src/packetsReplay/replayPackets.ts @@ -188,6 +188,10 @@ const mainPacketsReplayer = async (client: ServerClient, packets: ParsedReplayPa } if (packet.isFromServer) { + if (packet.params === null) { + console.warn('packet.params is null', packet) + continue + } playServerPacket(packet.name, packet.params) await new Promise(resolve => { setTimeout(resolve, packet.diff * packetsReplayState.speed + ADDITIONAL_DELAY * (packetsReplayState.customButtons.packetsSenderDelay.state ? 1 : 0)) @@ -216,6 +220,7 @@ const mainPacketsReplayer = async (client: ServerClient, packets: ParsedReplayPa setTimeout(resolve, 1000) })] : []) ]) + clientsPacketsWaiter.stopWaiting() clientPackets = [] } } @@ -236,6 +241,7 @@ interface PacketsWaiterOptions { interface PacketsWaiter { addPacket(name: string, params: any): void waitForPackets(packets: string[]): Promise + stopWaiting(): void } const createPacketsWaiter = (options: PacketsWaiterOptions = {}): PacketsWaiter => { @@ -296,6 +302,11 @@ const createPacketsWaiter = (options: PacketsWaiterOptions = {}): PacketsWaiter isWaiting = false packetHandler = null } + }, + stopWaiting () { + isWaiting = false + packetHandler = null + queuedPackets.length = 0 } } } diff --git a/src/react/ServersListProvider.tsx b/src/react/ServersListProvider.tsx index f0543e21..bf440c69 100644 --- a/src/react/ServersListProvider.tsx +++ b/src/react/ServersListProvider.tsx @@ -407,7 +407,7 @@ const Inner = ({ hidden, customServersList }: { hidden?: boolean, customServersL worldNameRightGrayed: additional?.textNameRightGrayed ?? '', iconSrc: additional?.icon, offline: additional?.offline, - group: 'Custom Servers' + group: 'Your Servers' } })} initialProxies={{ From a67b9d7aa215bb8de6fd5c23b2cfd58e0ac14fea Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 14 Mar 2025 19:11:14 +0300 Subject: [PATCH 425/834] active back all vanilla mechanics like hotbar wheel when replay window is minimized --- src/react/ReplayPanel.tsx | 8 +++++--- src/react/state/packetsReplayState.ts | 1 + src/utils.ts | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/react/ReplayPanel.tsx b/src/react/ReplayPanel.tsx index fd4082af..3b709882 100644 --- a/src/react/ReplayPanel.tsx +++ b/src/react/ReplayPanel.tsx @@ -1,9 +1,11 @@ import { useState, useEffect } from 'react' +import { useSnapshot } from 'valtio' import { filterPackets } from './packetsFilter' import { DARK_COLORS } from './components/replay/constants' import FilterInput from './components/replay/FilterInput' import PacketList from './components/replay/PacketList' import ProgressBar from './components/replay/ProgressBar' +import { packetsReplayState } from './state/packetsReplayState' interface Props { replayName: string @@ -41,7 +43,7 @@ export default function ReplayPanel ({ style }: Props) { const [filter, setFilter] = useState(defaultFilter) - const [isMinimized, setIsMinimized] = useState(false) + const { isMinimized } = useSnapshot(packetsReplayState) const { filtered: filteredPackets, hiddenCount } = filterPackets(packets.slice(-500), filter) useEffect(() => { @@ -50,7 +52,7 @@ export default function ReplayPanel ({ const handlePlayPauseClick = () => { if (isMinimized) { - setIsMinimized(false) + packetsReplayState.isMinimized = false } else { onPlayPause?.(!isPlaying) } @@ -113,7 +115,7 @@ export default function ReplayPanel ({
{replayName || 'Unnamed Replay'}
- - GitHub - - {linksButton} +
diff --git a/src/react/MainMenuRenderApp.tsx b/src/react/MainMenuRenderApp.tsx index cdcfc096..c112cb0e 100644 --- a/src/react/MainMenuRenderApp.tsx +++ b/src/react/MainMenuRenderApp.tsx @@ -8,7 +8,6 @@ import { setLoadingScreenStatus } from '../appStatus' import { openFilePicker, copyFilesAsync, mkdirRecursive, openWorldDirectory, removeFileRecursiveAsync } from '../browserfs' import MainMenu from './MainMenu' -import { DiscordButton } from './DiscordButton' const isMainMenu = () => { return activeModalStack.length === 0 && !miscUiState.gameLoaded @@ -145,7 +144,6 @@ export default () => { }} githubAction={() => openGithub()} optionsAction={() => openOptionsMenu('main')} - linksButton={} bottomRightLinks={process.env.MAIN_MENU_LINKS} openFileAction={e => { if (!!window.showDirectoryPicker && !e.shiftKey) { diff --git a/src/react/PauseLinkButtons.tsx b/src/react/PauseLinkButtons.tsx new file mode 100644 index 00000000..18331f2d --- /dev/null +++ b/src/react/PauseLinkButtons.tsx @@ -0,0 +1,50 @@ +import { useSnapshot } from 'valtio' +import { openURL } from 'renderer/viewer/lib/simpleUtils' +import { ErrorBoundary } from '@zardoy/react-util' +import { miscUiState } from '../globalState' +import { openGithub } from '../utils' +import Button from './Button' +import { DiscordButton } from './DiscordButton' +import styles from './PauseScreen.module.css' + +function PauseLinkButtonsInner () { + const { appConfig } = useSnapshot(miscUiState) + const pauseLinksConfig = appConfig?.pauseLinks + + if (!pauseLinksConfig) return null + + const renderButton = (button: Record, style: React.CSSProperties, key: number) => { + if (button.type === 'discord') { + return + } + if (button.type === 'github') { + return + } + if (button.type === 'url' && button.text) { + return + } + return null + } + + return ( + <> + {pauseLinksConfig.map((row, i) => { + const style = { width: (204 / row.length - (row.length > 1 ? 4 : 0)) + 'px' } + return ( +
+ {row.map((button, k) => renderButton(button, style, k))} +
+ ) + })} + + ) +} + +export default () => { + return { + console.error(error) + return null + }}> + + +} diff --git a/src/react/PauseScreen.tsx b/src/react/PauseScreen.tsx index 35d873ea..55ffe47f 100644 --- a/src/react/PauseScreen.tsx +++ b/src/react/PauseScreen.tsx @@ -34,6 +34,7 @@ import { DiscordButton } from './DiscordButton' import { showNotification } from './NotificationProvider' import { appStatusState, reconnectReload } from './AppStatusProvider' import NetworkStatus from './NetworkStatus' +import PauseLinkButtons from './PauseLinkButtons' const waitForPotentialRender = async () => { return new Promise(resolve => { @@ -227,26 +228,6 @@ export default () => { if (!isModalActive) return null - const pauseLinks: React.ReactNode[] = [] - const pauseLinksConfig = miscUiState.appConfig?.pauseLinks - if (pauseLinksConfig) { - for (const [i, row] of pauseLinksConfig.entries()) { - const rowButtons: React.ReactNode[] = [] - for (const [k, button] of row.entries()) { - const key = `${i}-${k}` - const style = { width: (204 / row.length - (row.length > 1 ? 4 : 0)) + 'px' } - if (button.type === 'discord') { - rowButtons.push() - } else if (button.type === 'github') { - rowButtons.push() - } else if (button.type === 'url' && button.text) { - rowButtons.push() - } - } - pauseLinks.push(
{rowButtons}
) - } - } - return
- {pauseLinks} + {singleplayer ? (
From 72028d925d4f01be0094ceb2badbe4a839006046 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 14 Mar 2025 23:25:13 +0300 Subject: [PATCH 428/834] feat: revamp right click experience by reworking block placing prediction and extending activatble items list --- package.json | 2 +- pnpm-lock.yaml | 35 ++++++++++++++++++++++------------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index f6ae254e..6485c25e 100644 --- a/package.json +++ b/package.json @@ -151,7 +151,7 @@ "http-server": "^14.1.1", "https-browserify": "^1.0.0", "mc-assets": "^0.2.42", - "mineflayer-mouse": "^0.0.9", + "mineflayer-mouse": "^0.1.0", "minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next", "mineflayer": "github:zardoy/mineflayer", "mineflayer-pathfinder": "^2.4.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2b09f583..dc4b1555 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -136,7 +136,7 @@ importers: version: 4.17.21 mcraft-fun-mineflayer: specifier: ^0.1.10 - version: 0.1.10(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13)) + version: 0.1.10(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/06e3050ddf4d9aa655fea6e2bed182937a81705d(encoding@0.1.13)) minecraft-data: specifier: 3.83.1 version: 3.83.1 @@ -360,10 +360,10 @@ importers: version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/75e940a4cd50d89e0ba03db3733d5d704917a3c8(@types/react@18.2.20)(react@18.2.0) mineflayer: specifier: github:zardoy/mineflayer - version: https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13) + version: https://codeload.github.com/zardoy/mineflayer/tar.gz/06e3050ddf4d9aa655fea6e2bed182937a81705d(encoding@0.1.13) mineflayer-mouse: - specifier: ^0.0.9 - version: 0.0.9(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1) + specifier: ^0.1.0 + version: 0.1.0(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1) mineflayer-pathfinder: specifier: ^2.4.4 version: 2.4.4 @@ -4129,6 +4129,10 @@ packages: resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} engines: {node: '>= 0.4'} + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -6916,8 +6920,8 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824} version: 1.2.0 - mineflayer-mouse@0.0.9: - resolution: {integrity: sha512-oViJrou2tziPuox/ZFJWZJMCnaF5+KPEsrbBgKmXVr3eK35iPohdhYwoKgqgBY8uXS/bNaFnkCR0K7ZDqyBF8g==} + mineflayer-mouse@0.1.0: + resolution: {integrity: sha512-NFfHASMo3iZOECoYOHVqGBFROVapzDJRlgANMiWnynO/oPUZkOJF6oRrcE33FCVHGlwMCT2S6N7TcqCYqF21Uw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} mineflayer-pathfinder@2.4.4: @@ -6927,8 +6931,8 @@ packages: resolution: {integrity: sha512-q7cmpZFaSI6sodcMJxc2GkV8IO84HbsUP+xNipGKfGg+FMISKabzdJ838Axb60qRtZrp6ny7LluQE7lesHvvxQ==} engines: {node: '>=18'} - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49: - resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49} + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/06e3050ddf4d9aa655fea6e2bed182937a81705d: + resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/06e3050ddf4d9aa655fea6e2bed182937a81705d} version: 4.25.0 engines: {node: '>=18'} @@ -14365,6 +14369,11 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + callsites@3.1.0: {} camel-case@4.1.2: @@ -15424,7 +15433,7 @@ snapshots: es-iterator-helpers@1.2.1: dependencies: call-bind: 1.0.8 - call-bound: 1.0.3 + call-bound: 1.0.4 define-properties: 1.2.1 es-abstract: 1.23.9 es-errors: 1.3.0 @@ -17579,12 +17588,12 @@ snapshots: maxrects-packer: 2.7.3 zod: 3.24.1 - mcraft-fun-mineflayer@0.1.10(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13)): + mcraft-fun-mineflayer@0.1.10(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/06e3050ddf4d9aa655fea6e2bed182937a81705d(encoding@0.1.13)): dependencies: '@zardoy/flying-squid': 0.0.49(encoding@0.1.13) exit-hook: 2.2.1 minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/5ec3dd4b367fcc039fbcb3edd214fe3cf8178a6d(patch_hash=dkeyukcqlupmk563gwxsmjr3yu)(encoding@0.1.13) - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/06e3050ddf4d9aa655fea6e2bed182937a81705d(encoding@0.1.13) prismarine-item: 1.16.0 ws: 8.18.0 transitivePeerDependencies: @@ -17949,7 +17958,7 @@ snapshots: - encoding - supports-color - mineflayer-mouse@0.0.9(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1): + mineflayer-mouse@0.1.0(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1): dependencies: change-case: 5.4.4 debug: 4.4.0(supports-color@8.1.1) @@ -18010,7 +18019,7 @@ snapshots: - encoding - supports-color - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13): + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/06e3050ddf4d9aa655fea6e2bed182937a81705d(encoding@0.1.13): dependencies: minecraft-data: 3.83.1 minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/5ec3dd4b367fcc039fbcb3edd214fe3cf8178a6d(patch_hash=dkeyukcqlupmk563gwxsmjr3yu)(encoding@0.1.13) From 3e056946ec07c45e30182a7d012604564f5ab188 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 15 Mar 2025 02:20:47 +0300 Subject: [PATCH 429/834] add world download button --- src/react/PauseScreen.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/react/PauseScreen.tsx b/src/react/PauseScreen.tsx index 55ffe47f..f051f9e1 100644 --- a/src/react/PauseScreen.tsx +++ b/src/react/PauseScreen.tsx @@ -35,6 +35,7 @@ import { showNotification } from './NotificationProvider' import { appStatusState, reconnectReload } from './AppStatusProvider' import NetworkStatus from './NetworkStatus' import PauseLinkButtons from './PauseLinkButtons' +import { pixelartIcons } from './PixelartIcon' const waitForPotentialRender = async () => { return new Promise(resolve => { @@ -161,7 +162,7 @@ export default () => { const { singleplayer, wanOpened, wanOpening } = useSnapshot(miscUiState) const { noConnection } = useSnapshot(gameAdditionalState) const { active: packetsReplaceActive, hasRecordedPackets: packetsReplaceHasRecordedPackets } = useSnapshot(packetsRecordingState) - const { displayRecordButton } = useSnapshot(options) + const { displayRecordButton: displayPacketsButtons } = useSnapshot(options) const handlePointerLockChange = () => { if (!pointerLock.hasPointerLock && activeModalStack.length === 0) { @@ -234,7 +235,7 @@ export default () => { icon="pixelarticons:folder" onClick={async () => openWorldActions()} /> - {displayRecordButton && ( + {displayPacketsButtons && ( <>
From da35cfb8a23aa3978ad53bdca0e6bcf45e697a03 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 15 Mar 2025 16:46:51 +0300 Subject: [PATCH 430/834] up mouse --- package.json | 4 ++-- pnpm-lock.yaml | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 6485c25e..9dd43eaf 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "mojangson": "^2.0.4", "net-browserify": "github:zardoy/prismarinejs-net-browserify", "node-gzip": "^1.1.2", - "mcraft-fun-mineflayer": "^0.1.10", + "mcraft-fun-mineflayer": "^0.1.12", "peerjs": "^1.5.0", "pixelarticons": "^1.8.1", "pretty-bytes": "^6.1.1", @@ -151,7 +151,7 @@ "http-server": "^14.1.1", "https-browserify": "^1.0.0", "mc-assets": "^0.2.42", - "mineflayer-mouse": "^0.1.0", + "mineflayer-mouse": "^0.1.1", "minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next", "mineflayer": "github:zardoy/mineflayer", "mineflayer-pathfinder": "^2.4.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc4b1555..d8d28545 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -135,8 +135,8 @@ importers: specifier: ^4.17.21 version: 4.17.21 mcraft-fun-mineflayer: - specifier: ^0.1.10 - version: 0.1.10(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/06e3050ddf4d9aa655fea6e2bed182937a81705d(encoding@0.1.13)) + specifier: ^0.1.12 + version: 0.1.12(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/06e3050ddf4d9aa655fea6e2bed182937a81705d(encoding@0.1.13)) minecraft-data: specifier: 3.83.1 version: 3.83.1 @@ -362,8 +362,8 @@ importers: specifier: github:zardoy/mineflayer version: https://codeload.github.com/zardoy/mineflayer/tar.gz/06e3050ddf4d9aa655fea6e2bed182937a81705d(encoding@0.1.13) mineflayer-mouse: - specifier: ^0.1.0 - version: 0.1.0(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1) + specifier: ^0.1.1 + version: 0.1.1(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1) mineflayer-pathfinder: specifier: ^2.4.4 version: 2.4.4 @@ -6694,9 +6694,9 @@ packages: resolution: {integrity: sha512-j2D1RNYtB5Z9gFu9MVjyDBbiALI0mWZ3xW/A3PPefVAHm3HJ2T1vH+1XBov1spBGPl7u+Zo7mRXza3X0egbeOg==} engines: {node: '>=18.0.0'} - mcraft-fun-mineflayer@0.1.10: - resolution: {integrity: sha512-KHzPts82I39nTDZlGwqJo1JXLwaIUHphBbmGWv7oYztUrq3iPiJDEIFgst0ROO/apjtHjzbCM9eb19qWw1JM3Q==} - version: 0.1.10 + mcraft-fun-mineflayer@0.1.12: + resolution: {integrity: sha512-BhfkagVJX+QmD/dt3qNQS5f7g3/7NI//OfSW4VnRolCnZtrLU8ekr59bLRcNmUWsvtTjkg+wbMeXwclHshSWOA==} + version: 0.1.12 engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: '@roamhq/wrtc': '*' @@ -6920,8 +6920,8 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824} version: 1.2.0 - mineflayer-mouse@0.1.0: - resolution: {integrity: sha512-NFfHASMo3iZOECoYOHVqGBFROVapzDJRlgANMiWnynO/oPUZkOJF6oRrcE33FCVHGlwMCT2S6N7TcqCYqF21Uw==} + mineflayer-mouse@0.1.1: + resolution: {integrity: sha512-7jKN+6pIGtQVfYxEIm4tA9CYwTS8Mgn/qJ2wyhrAoIEW8smCHUu0kj5Sdo0TwTCdlOQClKt8aEBZ13E7MGqOhg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} mineflayer-pathfinder@2.4.4: @@ -17588,7 +17588,7 @@ snapshots: maxrects-packer: 2.7.3 zod: 3.24.1 - mcraft-fun-mineflayer@0.1.10(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/06e3050ddf4d9aa655fea6e2bed182937a81705d(encoding@0.1.13)): + mcraft-fun-mineflayer@0.1.12(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/06e3050ddf4d9aa655fea6e2bed182937a81705d(encoding@0.1.13)): dependencies: '@zardoy/flying-squid': 0.0.49(encoding@0.1.13) exit-hook: 2.2.1 @@ -17958,7 +17958,7 @@ snapshots: - encoding - supports-color - mineflayer-mouse@0.1.0(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1): + mineflayer-mouse@0.1.1(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1): dependencies: change-case: 5.4.4 debug: 4.4.0(supports-color@8.1.1) From 36bf18b02f616b900852016a93ab22f79061f39b Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 17 Mar 2025 16:05:04 +0300 Subject: [PATCH 431/834] feat: refactor all app storage managment (#310) --- src/appConfig.ts | 3 + src/browserfs.ts | 20 +--- src/controls.ts | 5 +- src/optionsGuiScheme.tsx | 112 ++++++++++++++++++- src/optionsStorage.ts | 33 ++++-- src/react/AddServerOrConnect.tsx | 18 +-- src/react/Chat.css | 5 +- src/react/Input.tsx | 18 ++- src/react/MainMenuRenderApp.tsx | 18 --- src/react/SelectOption.tsx | 131 +++++++++++++++++++--- src/react/ServersList.tsx | 79 +++++++------ src/react/ServersListProvider.tsx | 177 +++++++++--------------------- src/react/appStorageProvider.ts | 135 +++++++++++++++++++++++ src/react/serversStorage.ts | 62 +++-------- src/react/storageProvider.ts | 13 --- 15 files changed, 525 insertions(+), 304 deletions(-) create mode 100644 src/react/appStorageProvider.ts delete mode 100644 src/react/storageProvider.ts diff --git a/src/appConfig.ts b/src/appConfig.ts index 3d6d8f93..b8f83ad1 100644 --- a/src/appConfig.ts +++ b/src/appConfig.ts @@ -1,6 +1,7 @@ import { disabledSettings, options, qsOptions } from './optionsStorage' import { miscUiState } from './globalState' import { setLoadingScreenStatus } from './appStatus' +import { setStorageDataOnAppConfigLoad } from './react/appStorageProvider' export type AppConfig = { // defaultHost?: string @@ -42,6 +43,8 @@ export const loadAppConfig = (appConfig: AppConfig) => { } } } + + setStorageDataOnAppConfigLoad() } export const isBundledConfigUsed = !!process.env.INLINED_APP_CONFIG diff --git a/src/browserfs.ts b/src/browserfs.ts index 0f4579b8..9fb0771f 100644 --- a/src/browserfs.ts +++ b/src/browserfs.ts @@ -15,6 +15,7 @@ import { getFixedFilesize } from './downloadAndOpenFile' import { packetsReplayState } from './react/state/packetsReplayState' import { createFullScreenProgressReporter } from './core/progressReporter' import { showNotification } from './react/NotificationProvider' +import { resetAppStorage } from './react/appStorageProvider' const { GoogleDriveFileSystem } = require('google-drive-browserfs/src/backends/GoogleDrive') browserfs.install(window) @@ -620,24 +621,13 @@ export const openWorldZip = async (...args: Parameters } } -export const resetLocalStorageWorld = () => { - for (const key of Object.keys(localStorage)) { - if (/^[\da-fA-F]{8}(?:\b-[\da-fA-F]{4}){3}\b-[\da-fA-F]{12}$/g.test(key) || key === '/') { - localStorage.removeItem(key) - } - } -} - -export const resetLocalStorageWithoutWorld = () => { - for (const key of Object.keys(localStorage)) { - if (!/^[\da-fA-F]{8}(?:\b-[\da-fA-F]{4}){3}\b-[\da-fA-F]{12}$/g.test(key) && key !== '/') { - localStorage.removeItem(key) - } - } +export const resetLocalStorage = () => { resetOptions() + resetAppStorage() } -window.resetLocalStorageWorld = resetLocalStorageWorld +window.resetLocalStorage = resetLocalStorage + export const openFilePicker = (specificCase?: 'resourcepack') => { // create and show input picker let picker: HTMLInputElement = document.body.querySelector('input#file-zip-picker')! diff --git a/src/controls.ts b/src/controls.ts index a3a54ffb..69b94636 100644 --- a/src/controls.ts +++ b/src/controls.ts @@ -25,11 +25,12 @@ import { showNotification } from './react/NotificationProvider' import { lastConnectOptions } from './react/AppStatusProvider' import { onCameraMove, onControInit } from './cameraRotationControls' import { createNotificationProgressReporter } from './core/progressReporter' +import { appStorage } from './react/appStorageProvider' -export const customKeymaps = proxy(JSON.parse(localStorage.keymap || '{}')) as UserOverridesConfig +export const customKeymaps = proxy(appStorage.keybindings) subscribe(customKeymaps, () => { - localStorage.keymap = JSON.stringify(customKeymaps) + appStorage.keybindings = customKeymaps }) const controlOptions = { diff --git a/src/optionsGuiScheme.tsx b/src/optionsGuiScheme.tsx index 7032e644..5851dd8d 100644 --- a/src/optionsGuiScheme.tsx +++ b/src/optionsGuiScheme.tsx @@ -3,19 +3,21 @@ import { useSnapshot } from 'valtio' import { openURL } from 'renderer/viewer/lib/simpleUtils' import { noCase } from 'change-case' import { gameAdditionalState, miscUiState, openOptionsMenu, showModal } from './globalState' -import { AppOptions, options } from './optionsStorage' +import { AppOptions, getChangedSettings, options, resetOptions } from './optionsStorage' import Button from './react/Button' import { OptionMeta, OptionSlider } from './react/OptionsItems' import Slider from './react/Slider' import { getScreenRefreshRate } from './utils' import { setLoadingScreenStatus } from './appStatus' -import { openFilePicker, resetLocalStorageWithoutWorld } from './browserfs' +import { openFilePicker, resetLocalStorage } from './browserfs' import { completeResourcepackPackInstall, getResourcePackNames, resourcePackState, uninstallResourcePack } from './resourcePack' import { downloadPacketsReplay, packetsRecordingState } from './packetsReplay/packetsReplayLegacy' -import { showOptionsModal } from './react/SelectOption' +import { showInputsModal, showOptionsModal } from './react/SelectOption' import supportedVersions from './supportedVersions.mjs' import { getVersionAutoSelect } from './connect' import { createNotificationProgressReporter } from './core/progressReporter' +import { customKeymaps } from './controls' +import { appStorage } from './react/appStorageProvider' export const guiOptionsScheme: { [t in OptionsGroupType]: Array<{ [K in keyof AppOptions]?: Partial> } & { custom? }> @@ -450,9 +452,19 @@ export const guiOptionsScheme: { return + >Reset settings + }, + }, + { + custom () { + return }, }, { @@ -460,6 +472,11 @@ export const guiOptionsScheme: { return Developer }, }, + { + custom () { + return + } + }, + { + custom () { + return + } + }, + { + custom () { + return + } + }, + { + custom () { + return + } + } + ], } -export type OptionsGroupType = 'main' | 'render' | 'interface' | 'controls' | 'sound' | 'advanced' | 'VR' +export type OptionsGroupType = 'main' | 'render' | 'interface' | 'controls' | 'sound' | 'advanced' | 'VR' | 'export-import' const Category = ({ children }) =>
>) => { export type AppOptions = typeof defaultOptions -// when opening html file locally in browser, localStorage is shared between all ever opened html files, so we try to avoid conflicts -const localStorageKey = process.env?.SINGLE_FILE_BUILD ? 'minecraftWebClientOptions' : 'options' +const isDeepEqual = (a: any, b: any): boolean => { + if (a === b) return true + if (typeof a !== typeof b) return false + if (typeof a !== 'object') return false + if (a === null || b === null) return a === b + if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) return false + return a.every((item, index) => isDeepEqual(item, b[index])) + } + const keysA = Object.keys(a) + const keysB = Object.keys(b) + if (keysA.length !== keysB.length) return false + return keysA.every(key => isDeepEqual(a[key], b[key])) +} + +export const getChangedSettings = () => { + return Object.fromEntries( + Object.entries(options).filter(([key, value]) => !isDeepEqual(defaultOptions[key], value)) + ) +} + export const options: AppOptions = proxy({ ...defaultOptions, ...initialAppConfig.defaultSettings, - ...migrateOptions(JSON.parse(localStorage[localStorageKey] || '{}')), + ...migrateOptions(appStorage.options), ...qsOptions }) @@ -181,14 +198,14 @@ export const resetOptions = () => { Object.defineProperty(window, 'debugChangedOptions', { get () { - return Object.fromEntries(Object.entries(options).filter(([key, v]) => defaultOptions[key] !== v)) + return getChangedSettings() }, }) subscribe(options, () => { // Don't save disabled settings to localStorage const saveOptions = omitObj(options, [...disabledSettings.value] as any) - localStorage[localStorageKey] = JSON.stringify(saveOptions) + appStorage.options = saveOptions }) type WatchValue = >(proxy: T, callback: (p: T, isChanged: boolean) => void) => () => void diff --git a/src/react/AddServerOrConnect.tsx b/src/react/AddServerOrConnect.tsx index 1186fd9a..c25db793 100644 --- a/src/react/AddServerOrConnect.tsx +++ b/src/react/AddServerOrConnect.tsx @@ -3,7 +3,7 @@ import { appQueryParams } from '../appParams' import { fetchServerStatus, isServerValid } from '../api/mcStatusApi' import { parseServerAddress } from '../parseServerAddress' import Screen from './Screen' -import Input from './Input' +import Input, { INPUT_LABEL_WIDTH, InputWithLabel } from './Input' import Button from './Button' import SelectGameVersion from './SelectGameVersion' import { usePassesScaledDimensions } from './UIProvider' @@ -32,8 +32,6 @@ interface Props { allowAutoConnect?: boolean } -const ELEMENTS_WIDTH = 190 - export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQs, onQsConnect, placeholders, accounts, versions, allowAutoConnect }: Props) => { const isSmallHeight = !usePassesScaledDimensions(null, 350) const qsParamName = parseQs ? appQueryParams.name : undefined @@ -256,20 +254,8 @@ export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQ const ButtonWrapper = ({ ...props }: React.ComponentProps) => { props.style ??= {} - props.style.width = ELEMENTS_WIDTH + props.style.width = INPUT_LABEL_WIDTH return
} + +export default Input + +export const INPUT_LABEL_WIDTH = 190 + +export const InputWithLabel = ({ label, span, ...props }: React.ComponentProps & { label, span? }) => { + return
+ + +
+} diff --git a/src/react/MainMenuRenderApp.tsx b/src/react/MainMenuRenderApp.tsx index c112cb0e..f678e7f7 100644 --- a/src/react/MainMenuRenderApp.tsx +++ b/src/react/MainMenuRenderApp.tsx @@ -122,24 +122,6 @@ export default () => { singleplayerAvailable={singleplayerAvailable} connectToServerAction={() => showModal({ reactType: 'serversList' })} singleplayerAction={async () => { - const oldFormatSave = fs.existsSync('./world/level.dat') - if (oldFormatSave) { - setLoadingScreenStatus('Migrating old save, don\'t close the page') - try { - await mkdirRecursive('/data/worlds/local') - await copyFilesAsync('/world/', '/data/worlds/local') - try { - await removeFileRecursiveAsync('/world/') - } catch (err) { - console.error(err) - } - } catch (err) { - console.warn(err) - alert('Failed to migrate world from localStorage') - } finally { - setLoadingScreenStatus(undefined) - } - } showModal({ reactType: 'singleplayer' }) }} githubAction={() => openGithub()} diff --git a/src/react/SelectOption.tsx b/src/react/SelectOption.tsx index 9db20c86..4df471c0 100644 --- a/src/react/SelectOption.tsx +++ b/src/react/SelectOption.tsx @@ -1,10 +1,14 @@ import { proxy, useSnapshot } from 'valtio' +import { useEffect, useRef } from 'react' +import { noCase } from 'change-case' +import { titleCase } from 'title-case' import { hideCurrentModal, showModal } from '../globalState' import { parseFormattedMessagePacket } from '../botUtils' import Screen from './Screen' import { useIsModalActive } from './utilsApp' import Button from './Button' import MessageFormattedString from './MessageFormattedString' +import Input, { InputWithLabel } from './Input' const state = proxy({ title: '', @@ -12,6 +16,8 @@ const state = proxy({ showCancel: true, minecraftJsonMessage: null as null | Record, behavior: 'resolve-close' as 'resolve-close' | 'close-resolve', + inputs: {} as Record, + inputsConfirmButton: '' }) let resolve @@ -35,17 +41,63 @@ export const showOptionsModal = async ( title, options, showCancel: cancel, - minecraftJsonMessage: minecraftJsonMessageParsed + minecraftJsonMessage: minecraftJsonMessageParsed, + inputs: {}, + inputsConfirmButton: '' + }) + }) +} + +type InputOption = { + type: 'text' | 'checkbox' + defaultValue?: string | boolean + label?: string +} +export const showInputsModal = async >( + title: string, + inputs: T, + { cancel = true, minecraftJsonMessage }: { cancel?: boolean, minecraftJsonMessage? } = {} +): Promise<{ + [K in keyof T]: T[K] extends { type: 'text' } + ? string + : T[K] extends { type: 'checkbox' } + ? boolean + : never +}> => { + showModal({ reactType: 'general-select' }) + let minecraftJsonMessageParsed + if (minecraftJsonMessage) { + const parseResult = parseFormattedMessagePacket(minecraftJsonMessage) + minecraftJsonMessageParsed = parseResult.formatted + if (parseResult.plain) { + title += ` (${parseResult.plain})` + } + } + return new Promise((_resolve) => { + resolve = _resolve + Object.assign(state, { + title, + inputs, + showCancel: cancel, + minecraftJsonMessage: minecraftJsonMessageParsed, + options: [], + inputsConfirmButton: 'Confirm' }) }) } export default () => { - const { title, options, showCancel, minecraftJsonMessage } = useSnapshot(state) + const { title, options, showCancel, minecraftJsonMessage, inputs, inputsConfirmButton } = useSnapshot(state) const isModalActive = useIsModalActive('general-select') + const inputValues = useRef({}) + + useEffect(() => { + inputValues.current = Object.fromEntries(Object.entries(inputs).map(([key, input]) => [key, input.defaultValue ?? (input.type === 'checkbox' ? false : '')])) + }, [inputs]) + if (!isModalActive) return - const resolveClose = (value: string | undefined) => { + const resolveClose = (value: any) => { if (state.behavior === 'resolve-close') { resolve(value) hideCurrentModal() @@ -59,17 +111,66 @@ export default () => { {minecraftJsonMessage &&
} - {options.map(option => )} - {showCancel && } +
+ {options.length > 0 &&
+ {options.map(option => )} +
} +
+ {Object.entries(inputs).map(([key, input]) => { + const label = input.label ?? titleCase(noCase(key)) + return
+ {input.type === 'text' && ( + { + inputValues.current[key] = e.target.value + }} + /> + )} + {input.type === 'checkbox' && ( + + )} +
+ })} +
+ {inputs && inputsConfirmButton && ( + + )} + {showCancel && ( + + )} +
} diff --git a/src/react/ServersList.tsx b/src/react/ServersList.tsx index 46541af6..7b34f016 100644 --- a/src/react/ServersList.tsx +++ b/src/react/ServersList.tsx @@ -1,64 +1,61 @@ -import React from 'react' +import React, { useMemo } from 'react' +import { useSnapshot } from 'valtio' +import { miscUiState } from '../globalState' +import { appQueryParams } from '../appParams' import Singleplayer from './Singleplayer' import Input from './Input' import Button from './Button' import PixelartIcon, { pixelartIcons } from './PixelartIcon' - import Select from './Select' import { BaseServerInfo } from './AddServerOrConnect' import { useIsSmallWidth } from './simpleHooks' +import { appStorage, SavedProxiesData, ServerHistoryEntry } from './appStorageProvider' + +const getInitialProxies = () => { + const proxies = [] as string[] + if (miscUiState.appConfig?.defaultProxy) { + proxies.push(miscUiState.appConfig.defaultProxy) + } + return proxies +} + +export const getCurrentProxy = (): string | undefined => { + return appQueryParams.proxy ?? appStorage.proxiesData?.selected ?? getInitialProxies()[0] +} + +export const getCurrentUsername = () => { + return appQueryParams.username ?? appStorage.username +} interface Props extends React.ComponentProps { joinServer: (info: BaseServerInfo | string, additional: { shouldSave?: boolean index?: number }) => void - initialProxies: SavedProxiesLocalStorage - updateProxies: (proxies: SavedProxiesLocalStorage) => void - username: string - setUsername: (username: string) => void onProfileClick?: () => void setQuickConnectIp?: (ip: string) => void - serverHistory?: Array<{ - ip: string - versionOverride?: string - numConnects: number - }> -} - -export interface SavedProxiesLocalStorage { - proxies: readonly string[] - selected: string -} - -type ProxyStatusResult = { - time: number - ping: number - status: 'success' | 'error' | 'unknown' } export default ({ - initialProxies, - updateProxies: updateProxiesProp, joinServer, - username, - setUsername, onProfileClick, setQuickConnectIp, - serverHistory, ...props }: Props) => { - const [proxies, setProxies] = React.useState(initialProxies) - - const updateProxies = (newData: SavedProxiesLocalStorage) => { - setProxies(newData) - updateProxiesProp(newData) - } - + const snap = useSnapshot(appStorage) + const username = useMemo(() => getCurrentUsername(), [appQueryParams.username, appStorage.username]) const [serverIp, setServerIp] = React.useState('') const [save, setSave] = React.useState(true) const [activeHighlight, setActiveHighlight] = React.useState(undefined as 'quick-connect' | 'server-list' | undefined) + const updateProxies = (newData: SavedProxiesData) => { + appStorage.proxiesData = newData + } + + const setUsername = (username: string) => { + appStorage.username = username + } + const getActiveHighlightStyles = (type: typeof activeHighlight) => { const styles: React.CSSProperties = { transition: 'filter 0.2s', @@ -71,6 +68,8 @@ export default ({ const isSmallWidth = useIsSmallWidth() + const initialProxies = getInitialProxies() + const proxiesData = snap.proxiesData ?? { proxies: initialProxies, selected: initialProxies[0] } return setActiveHighlight('quick-connect')} onMouseLeave={() => setActiveHighlight(undefined)} > - {/* todo history */} - {serverHistory?.map((server) => ( -