From 245300ff84e8db42298ef38622887a25a858ab5b Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 18 Feb 2025 21:48:18 +0300 Subject: [PATCH 01/98] init --- package.json | 3 ++- renderer/viewer/lib/lightEngine.ts | 33 +++++++++++++++++++++++++ renderer/viewer/lib/worldDataEmitter.ts | 18 +++++++++++--- 3 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 renderer/viewer/lib/lightEngine.ts diff --git a/package.json b/package.json index 79e2a9ba..0afa4bcf 100644 --- a/package.json +++ b/package.json @@ -162,7 +162,8 @@ "timers-browserify": "^2.0.12", "typescript": "5.5.4", "vitest": "^0.34.6", - "yaml": "^2.3.2" + "yaml": "^2.3.2", + "minecraft-lighting": "file:../minecraft-lighting" }, "optionalDependencies": { "cypress": "^10.11.0", diff --git a/renderer/viewer/lib/lightEngine.ts b/renderer/viewer/lib/lightEngine.ts new file mode 100644 index 00000000..25d0bbd0 --- /dev/null +++ b/renderer/viewer/lib/lightEngine.ts @@ -0,0 +1,33 @@ +import { createLightEngineForSyncWorld, convertPrismarineBlockToWorldBlock } from 'minecraft-lighting/src/prismarineShim' +import { LightWorld } from 'minecraft-lighting/src/engine' +import { world } from 'prismarine-world' +import { Chunk } from 'prismarine-world/types/world' +import { fillColumnWithZeroLight } from 'minecraft-lighting/src/testDebug' + +let lightEngine: LightWorld | null = null +export const getLightEngine = () => { + if (!lightEngine) throw new Error('Light engine not initialized') + return lightEngine +} + +export const createLightEngine = () => { + lightEngine = createLightEngineForSyncWorld(worldView!.world as world.WorldSync, loadedData, { + minY: viewer.world.worldConfig.minY, + height: viewer.world.worldConfig.worldHeight, + enableSkyLight: false, + }) + globalThis.lightEngine = lightEngine +} + +export const processLightChunk = async (x: number, z: number) => { + const chunkX = Math.floor(x / 16) + const chunkZ = Math.floor(z / 16) + const engine = getLightEngine() + fillColumnWithZeroLight(engine.externalWorld, chunkX, chunkZ) + return engine.receiveUpdateColumn(chunkX, chunkZ) +} + +export const updateBlockLight = (x: number, y: number, z: number, stateId: number) => { + const engine = getLightEngine() + engine.setBlock(x, y, z, convertPrismarineBlockToWorldBlock(mcData.blocks[stateId], mcData)) +} diff --git a/renderer/viewer/lib/worldDataEmitter.ts b/renderer/viewer/lib/worldDataEmitter.ts index 7c2be715..fdeb4cb9 100644 --- a/renderer/viewer/lib/worldDataEmitter.ts +++ b/renderer/viewer/lib/worldDataEmitter.ts @@ -5,10 +5,10 @@ import { EventEmitter } from 'events' import { generateSpiralMatrix, ViewRect } from 'flying-squid/dist/utils' import { Vec3 } from 'vec3' import { BotEvents } from 'mineflayer' -import { getItemFromBlock } from '../../../src/chatUtils' import { delayedIterator } from '../../playground/shared' import { playerState } from '../../../src/mineflayer/playerState' import { chunkPos } from './simpleUtils' +import { createLightEngine, processLightChunk, updateBlockLight } from './lightEngine' export type ChunkPosKey = string type ChunkPos = { x: number, z: number } @@ -52,6 +52,7 @@ export class WorldDataEmitter extends EventEmitter { return } + updateBlockLight(position.x, position.y, position.z, stateId) this.emit('blockUpdate', { pos: position, stateId }) } @@ -145,6 +146,7 @@ export class WorldDataEmitter extends EventEmitter { } async init (pos: Vec3) { + createLightEngine() this.updateViewDistance(this.viewDistance) this.emitter.emit('chunkPosUpdate', { pos }) const [botX, botZ] = chunkPos(pos) @@ -177,12 +179,22 @@ export class WorldDataEmitter extends EventEmitter { async loadChunk (pos: ChunkPos, isLightUpdate = false) { const [botX, botZ] = chunkPos(this.lastPos) - const dx = Math.abs(botX - Math.floor(pos.x / 16)) - const dz = Math.abs(botZ - Math.floor(pos.z / 16)) + const chunkX = Math.floor(pos.x / 16) + const chunkZ = Math.floor(pos.z / 16) + const dx = Math.abs(botX - chunkX) + const dz = Math.abs(botZ - chunkZ) if (dx <= this.viewDistance && dz <= this.viewDistance) { // eslint-disable-next-line @typescript-eslint/await-thenable -- todo allow to use async world provider but not sure if needed const column = await this.world.getColumnAt(pos['y'] ? pos as Vec3 : new Vec3(pos.x, 0, pos.z)) if (column) { + const result = await processLightChunk(pos.x, pos.z) + if (!result) return + for (const affectedChunk of result) { + if (affectedChunk.x === chunkX && affectedChunk.z === chunkZ) continue + const loadedChunk = this.loadedChunks[`${affectedChunk.x},${affectedChunk.z}`] + if (!loadedChunk) continue + void this.loadChunk(new Vec3(affectedChunk.x * 16, 0, affectedChunk.z * 16), true) + } // const latency = Math.floor(performance.now() - this.lastTime) // this.debugGotChunkLatency.push(latency) // this.lastTime = performance.now() From d5c61d83205f4c87e5009c92a098ecc22bad801c Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 20 Feb 2025 00:30:36 +0300 Subject: [PATCH 02/98] a working light --- renderer/viewer/lib/lightEngine.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/renderer/viewer/lib/lightEngine.ts b/renderer/viewer/lib/lightEngine.ts index 25d0bbd0..bf2f82b4 100644 --- a/renderer/viewer/lib/lightEngine.ts +++ b/renderer/viewer/lib/lightEngine.ts @@ -1,8 +1,7 @@ -import { createLightEngineForSyncWorld, convertPrismarineBlockToWorldBlock } from 'minecraft-lighting/src/prismarineShim' +import { createLightEngineForSyncWorld, convertPrismarineBlockToWorldBlock, fillColumnWithZeroLight } from 'minecraft-lighting/src/prismarineShim' import { LightWorld } from 'minecraft-lighting/src/engine' import { world } from 'prismarine-world' import { Chunk } from 'prismarine-world/types/world' -import { fillColumnWithZeroLight } from 'minecraft-lighting/src/testDebug' let lightEngine: LightWorld | null = null export const getLightEngine = () => { @@ -23,7 +22,7 @@ export const processLightChunk = async (x: number, z: number) => { const chunkX = Math.floor(x / 16) const chunkZ = Math.floor(z / 16) const engine = getLightEngine() - fillColumnWithZeroLight(engine.externalWorld, chunkX, chunkZ) + fillColumnWithZeroLight(worldView!.world as world.WorldSync, viewer.world.worldConfig.minY, viewer.world.worldConfig.worldHeight, chunkX, chunkZ) return engine.receiveUpdateColumn(chunkX, chunkZ) } From 48ead547e3134bd51571b78db0be822281da53f6 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 12 Mar 2025 18:23:37 +0300 Subject: [PATCH 03/98] should work. --- package.json | 2 +- pnpm-lock.yaml | 24 ++++++++++++++++++++---- renderer/viewer/lib/lightEngine.ts | 8 +++----- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 0afa4bcf..40d16f76 100644 --- a/package.json +++ b/package.json @@ -163,7 +163,7 @@ "typescript": "5.5.4", "vitest": "^0.34.6", "yaml": "^2.3.2", - "minecraft-lighting": "file:../minecraft-lighting" + "minecraft-lighting": "^0.0.3" }, "optionalDependencies": { "cypress": "^10.11.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7540c5bb..4ab1d8fc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -354,6 +354,9 @@ importers: 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) + minecraft-lighting: + specifier: ^0.0.3 + version: 0.0.3 mineflayer: specifier: github:zardoy/mineflayer version: https://codeload.github.com/zardoy/mineflayer/tar.gz/4fa1af9964cab91315d8d1ae02615f3039638828(encoding@0.1.13) @@ -6457,6 +6460,10 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/75e940a4cd50d89e0ba03db3733d5d704917a3c8} version: 1.0.1 + minecraft-lighting@0.0.3: + resolution: {integrity: sha512-JDxWOy/9mogn4AjJi8sPvY1BoHSLcTSZ9QntCNTtEtQlXMNRe5Wr+4z+apgOKLXDFaisZr+jPQRY8RyjV7lJyw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/e9eb551ba30ec2e742c49e6927be6402b413bb76: resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/e9eb551ba30ec2e742c49e6927be6402b413bb76} version: 1.54.0 @@ -8883,6 +8890,9 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vec3@0.1.10: + resolution: {integrity: sha512-Sr1U3mYtMqCOonGd3LAN9iqy0qF6C+Gjil92awyK/i2OwiUo9bm7PnLgFpafymun50mOjnDcg4ToTgRssrlTcw==} + vec3@0.1.8: resolution: {integrity: sha512-LfKrP625Bsg/Tj52YdYPsHmpsJuo+tc6fLxZxXjEo9k2xSspKlPvoYTHehykKhp1FvV9nm+XU3Ehej5/9tpDCg==} @@ -14263,7 +14273,7 @@ snapshots: prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1) prismarine-registry: 1.11.0 random-seed: 0.3.0 - vec3: 0.1.8 + vec3: 0.1.10 diff-match-patch@1.0.5: {} @@ -16996,6 +17006,10 @@ snapshots: - '@types/react' - react + minecraft-lighting@0.0.3: + dependencies: + vec3: 0.1.10 + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/e9eb551ba30ec2e742c49e6927be6402b413bb76(patch_hash=dkeyukcqlupmk563gwxsmjr3yu)(encoding@0.1.13): dependencies: '@types/node-rsa': 1.1.4 @@ -17902,7 +17916,7 @@ snapshots: prismarine-chat: 1.10.1 prismarine-item: 1.16.0 prismarine-registry: 1.11.0 - vec3: 0.1.8 + vec3: 0.1.10 prismarine-item@1.16.0: dependencies: @@ -17917,7 +17931,7 @@ snapshots: dependencies: minecraft-data: 3.83.1 prismarine-nbt: 2.5.0 - vec3: 0.1.8 + vec3: 0.1.10 prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.83.1): dependencies: @@ -17964,7 +17978,7 @@ snapshots: prismarine-world@https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c: dependencies: - vec3: 0.1.8 + vec3: 0.1.10 process-nextick-args@2.0.1: {} @@ -19968,6 +19982,8 @@ snapshots: vary@1.1.2: {} + vec3@0.1.10: {} + vec3@0.1.8: {} verror@1.10.0: diff --git a/renderer/viewer/lib/lightEngine.ts b/renderer/viewer/lib/lightEngine.ts index bf2f82b4..578ccf9b 100644 --- a/renderer/viewer/lib/lightEngine.ts +++ b/renderer/viewer/lib/lightEngine.ts @@ -1,7 +1,5 @@ -import { createLightEngineForSyncWorld, convertPrismarineBlockToWorldBlock, fillColumnWithZeroLight } from 'minecraft-lighting/src/prismarineShim' -import { LightWorld } from 'minecraft-lighting/src/engine' +import { LightWorld, createLightEngineForSyncWorld, convertPrismarineBlockToWorldBlock, fillColumnWithZeroLight } from 'minecraft-lighting' import { world } from 'prismarine-world' -import { Chunk } from 'prismarine-world/types/world' let lightEngine: LightWorld | null = null export const getLightEngine = () => { @@ -10,7 +8,7 @@ export const getLightEngine = () => { } export const createLightEngine = () => { - lightEngine = createLightEngineForSyncWorld(worldView!.world as world.WorldSync, loadedData, { + lightEngine = createLightEngineForSyncWorld(worldView!.world as unknown as world.WorldSync, loadedData, { minY: viewer.world.worldConfig.minY, height: viewer.world.worldConfig.worldHeight, enableSkyLight: false, @@ -22,7 +20,7 @@ export const processLightChunk = async (x: number, z: number) => { const chunkX = Math.floor(x / 16) const chunkZ = Math.floor(z / 16) const engine = getLightEngine() - fillColumnWithZeroLight(worldView!.world as world.WorldSync, viewer.world.worldConfig.minY, viewer.world.worldConfig.worldHeight, chunkX, chunkZ) + fillColumnWithZeroLight(engine.externalWorld, chunkX, chunkZ) return engine.receiveUpdateColumn(chunkX, chunkZ) } From ace45a9f87a1e5497cdf3d3c14469e388e6c9327 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 14 Mar 2025 00:03:33 +0300 Subject: [PATCH 04/98] not crash pls --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- renderer/viewer/lib/mesher/world.ts | 4 ++-- src/optionsStorage.ts | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 55dbb43f..fbc8ff69 100644 --- a/package.json +++ b/package.json @@ -169,7 +169,7 @@ "typescript": "5.5.4", "vitest": "^0.34.6", "yaml": "^2.3.2", - "minecraft-lighting": "^0.0.3" + "minecraft-lighting": "^0.0.4" }, "optionalDependencies": { "cypress": "^10.11.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0683e7a4..71a81bc8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -359,8 +359,8 @@ importers: 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) minecraft-lighting: - specifier: ^0.0.3 - version: 0.0.3 + specifier: ^0.0.4 + version: 0.0.4 mineflayer: specifier: github:zardoy/mineflayer version: https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13) @@ -6903,8 +6903,8 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/75e940a4cd50d89e0ba03db3733d5d704917a3c8} version: 1.0.1 - minecraft-lighting@0.0.3: - resolution: {integrity: sha512-JDxWOy/9mogn4AjJi8sPvY1BoHSLcTSZ9QntCNTtEtQlXMNRe5Wr+4z+apgOKLXDFaisZr+jPQRY8RyjV7lJyw==} + minecraft-lighting@0.0.4: + resolution: {integrity: sha512-5p+Dx1SIdQhkKA8Wbm7slN0MR6s7pdnlV2MVSBSmAlR4zW8+FVpsNJfvMQ4XltRqKYyHybNDZEdJocdtdkfhpQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/5ec3dd4b367fcc039fbcb3edd214fe3cf8178a6d: @@ -17899,7 +17899,7 @@ snapshots: - '@types/react' - react - minecraft-lighting@0.0.3: + minecraft-lighting@0.0.4: dependencies: vec3: 0.1.10 diff --git a/renderer/viewer/lib/mesher/world.ts b/renderer/viewer/lib/mesher/world.ts index f2757ae6..69e0363b 100644 --- a/renderer/viewer/lib/mesher/world.ts +++ b/renderer/viewer/lib/mesher/world.ts @@ -63,8 +63,8 @@ export class World { 15, Math.max( column.getBlockLight(posInChunk(pos)), - Math.min(skyLight, column.getSkyLight(posInChunk(pos))) - ) + 2 + // Math.min(skyLight, column.getSkyLight(posInChunk(pos))) + ) ) // lightsCache.set(key, result) if (result === 2 && [this.getBlock(pos)?.name ?? '', curBlockName].some(x => /_stairs|slab|glass_pane/.exec(x)) && !skipMoreChecks) { // todo this is obviously wrong diff --git a/src/optionsStorage.ts b/src/optionsStorage.ts index 1c5c999e..7fe5219a 100644 --- a/src/optionsStorage.ts +++ b/src/optionsStorage.ts @@ -88,7 +88,7 @@ const defaultOptions = { showCursorBlockInSpectator: false, renderEntities: true, smoothLighting: true, - newVersionsLighting: false, + newVersionsLighting: true, chatSelect: true, autoJump: 'auto' as 'auto' | 'always' | 'never', autoParkour: false, From 9f505f81d6dd640754fa7a31f9c7418ac87eba14 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 21 Mar 2025 16:36:29 +0300 Subject: [PATCH 05/98] rm workaround --- renderer/viewer/lib/mesher/world.ts | 33 +++++++++++++++++------------ 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/renderer/viewer/lib/mesher/world.ts b/renderer/viewer/lib/mesher/world.ts index 69e0363b..92fa7d86 100644 --- a/renderer/viewer/lib/mesher/world.ts +++ b/renderer/viewer/lib/mesher/world.ts @@ -51,6 +51,7 @@ export class World { } getLight (pos: Vec3, isNeighbor = false, skipMoreChecks = false, curBlockName = '') { + const IS_USING_SERVER_LIGHTING = false // for easier testing if (!(pos instanceof Vec3)) pos = new Vec3(...pos as [number, number, number]) const { enableLighting, skyLight } = this.config @@ -63,25 +64,29 @@ export class World { 15, Math.max( column.getBlockLight(posInChunk(pos)), - // Math.min(skyLight, column.getSkyLight(posInChunk(pos))) + Math.min(skyLight, column.getSkyLight(posInChunk(pos))) ) ) + const MIN_LIGHT_LEVEL = 2 + result = Math.max(result, MIN_LIGHT_LEVEL) // lightsCache.set(key, result) - if (result === 2 && [this.getBlock(pos)?.name ?? '', curBlockName].some(x => /_stairs|slab|glass_pane/.exec(x)) && !skipMoreChecks) { // todo this is obviously wrong - const lights = [ - this.getLight(pos.offset(0, 1, 0), undefined, true), - this.getLight(pos.offset(0, -1, 0), undefined, true), - this.getLight(pos.offset(0, 0, 1), undefined, true), - this.getLight(pos.offset(0, 0, -1), undefined, true), - this.getLight(pos.offset(1, 0, 0), undefined, true), - this.getLight(pos.offset(-1, 0, 0), undefined, true) - ].filter(x => x !== 2) - if (lights.length) { - const min = Math.min(...lights) - result = min + if (result === 2 && IS_USING_SERVER_LIGHTING) { + if ([this.getBlock(pos)?.name ?? '', curBlockName].some(x => /_stairs|slab|glass_pane/.exec(x)) && !skipMoreChecks) { // todo this is obviously wrong + const lights = [ + this.getLight(pos.offset(0, 1, 0), undefined, true), + this.getLight(pos.offset(0, -1, 0), undefined, true), + this.getLight(pos.offset(0, 0, 1), undefined, true), + this.getLight(pos.offset(0, 0, -1), undefined, true), + this.getLight(pos.offset(1, 0, 0), undefined, true), + this.getLight(pos.offset(-1, 0, 0), undefined, true) + ].filter(x => x !== 2) + if (lights.length) { + const min = Math.min(...lights) + result = min + } } + if (isNeighbor) result = 15 // TODO } - if (isNeighbor && result === 2) result = 15 // TODO return result } From e10f6108989014ad7524520d20d313139564ac3a Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 10 Apr 2025 05:24:53 +0300 Subject: [PATCH 06/98] humble and terrible progress --- package.json | 2 +- pnpm-lock.yaml | 31 +++++++++++----------- renderer/viewer/lib/lightEngine.ts | 17 +++++++----- renderer/viewer/lib/mesher/mesher.ts | 3 +++ renderer/viewer/lib/mesher/world.ts | 16 +++++++++-- renderer/viewer/lib/worldDataEmitter.ts | 4 +-- renderer/viewer/lib/worldrendererCommon.ts | 6 ++++- 7 files changed, 51 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index cd1c877b..dc1b4775 100644 --- a/package.json +++ b/package.json @@ -170,7 +170,7 @@ "typescript": "5.5.4", "vitest": "^0.34.6", "yaml": "^2.3.2", - "minecraft-lighting": "^0.0.4" + "minecraft-lighting": "file:../minecraft-lighting" }, "optionalDependencies": { "cypress": "^10.11.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b906647a..e36135e2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -359,8 +359,8 @@ importers: specifier: github:zardoy/minecraft-inventory-gui#next version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/f57dd78ca8e3b7cdd724d4272d8cbf6743b0cf00(@types/react@18.2.20)(react@18.2.0) minecraft-lighting: - specifier: ^0.0.4 - version: 0.0.4 + specifier: file:../minecraft-lighting + version: file:../minecraft-lighting mineflayer: specifier: github:zardoy/mineflayer version: https://codeload.github.com/zardoy/mineflayer/tar.gz/06e3050ddf4d9aa655fea6e2bed182937a81705d(encoding@0.1.13) @@ -441,7 +441,7 @@ importers: version: 1.3.6 prismarine-block: specifier: github:zardoy/prismarine-block#next-era - version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) prismarine-chunk: specifier: github:zardoy/prismarine-chunk#master version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1) @@ -6919,9 +6919,8 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/f57dd78ca8e3b7cdd724d4272d8cbf6743b0cf00} version: 1.0.1 - minecraft-lighting@0.0.4: - resolution: {integrity: sha512-5p+Dx1SIdQhkKA8Wbm7slN0MR6s7pdnlV2MVSBSmAlR4zW8+FVpsNJfvMQ4XltRqKYyHybNDZEdJocdtdkfhpQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + minecraft-lighting@file:../minecraft-lighting: + resolution: {directory: ../minecraft-lighting, type: directory} minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3bd4dc1b2002cd7badfa5b9cf8dda35cd6cc9ac1: resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3bd4dc1b2002cd7badfa5b9cf8dda35cd6cc9ac1} @@ -17941,7 +17940,7 @@ snapshots: - '@types/react' - react - minecraft-lighting@0.0.4: + minecraft-lighting@file:../minecraft-lighting: dependencies: vec3: 0.1.10 @@ -18084,7 +18083,7 @@ snapshots: mineflayer-pathfinder@2.4.4: dependencies: minecraft-data: 3.83.1 - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) prismarine-entity: 2.3.1 prismarine-item: 1.16.0 prismarine-nbt: 2.5.0 @@ -18096,7 +18095,7 @@ snapshots: 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) prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) prismarine-chat: 1.10.1 prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1) prismarine-entity: 2.3.1 @@ -18119,7 +18118,7 @@ snapshots: 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) prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) prismarine-chat: 1.10.1 prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1) prismarine-entity: 2.3.1 @@ -18908,7 +18907,7 @@ snapshots: minecraft-data: 3.83.1 prismarine-registry: 1.11.0 - prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9: + prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0): dependencies: minecraft-data: 3.83.1 prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0) @@ -18916,6 +18915,8 @@ snapshots: prismarine-item: 1.16.0 prismarine-nbt: 2.5.0 prismarine-registry: 1.11.0 + transitivePeerDependencies: + - prismarine-registry prismarine-chat@1.10.1: dependencies: @@ -18926,7 +18927,7 @@ snapshots: prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1): dependencies: prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) prismarine-nbt: 2.5.0 prismarine-registry: 1.11.0 smart-buffer: 4.2.0 @@ -18964,7 +18965,7 @@ snapshots: prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.83.1): dependencies: - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1) prismarine-nbt: 2.5.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c @@ -18988,13 +18989,13 @@ snapshots: prismarine-registry@1.11.0: dependencies: minecraft-data: 3.83.1 - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) prismarine-nbt: 2.7.0 prismarine-schematic@1.2.3: dependencies: minecraft-data: 3.83.1 - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) prismarine-nbt: 2.5.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c vec3: 0.1.10 diff --git a/renderer/viewer/lib/lightEngine.ts b/renderer/viewer/lib/lightEngine.ts index 578ccf9b..88e2be33 100644 --- a/renderer/viewer/lib/lightEngine.ts +++ b/renderer/viewer/lib/lightEngine.ts @@ -1,18 +1,23 @@ import { LightWorld, createLightEngineForSyncWorld, convertPrismarineBlockToWorldBlock, fillColumnWithZeroLight } from 'minecraft-lighting' import { world } from 'prismarine-world' +import { WorldRendererCommon } from './worldrendererCommon' let lightEngine: LightWorld | null = null export const getLightEngine = () => { if (!lightEngine) throw new Error('Light engine not initialized') return lightEngine } +export const getLightEngineSafe = () => { + return lightEngine +} -export const createLightEngine = () => { - lightEngine = createLightEngineForSyncWorld(worldView!.world as unknown as world.WorldSync, loadedData, { - minY: viewer.world.worldConfig.minY, - height: viewer.world.worldConfig.worldHeight, +export const createLightEngine = (world: WorldRendererCommon) => { + lightEngine = createLightEngineForSyncWorld(world.displayOptions.worldView.world as unknown as world.WorldSync, loadedData, { + minY: world.worldSizeParams.minY, + height: world.worldSizeParams.worldHeight, enableSkyLight: false, }) + lightEngine.PARALLEL_CHUNK_PROCESSING = false globalThis.lightEngine = lightEngine } @@ -20,11 +25,11 @@ export const processLightChunk = async (x: number, z: number) => { const chunkX = Math.floor(x / 16) const chunkZ = Math.floor(z / 16) const engine = getLightEngine() - fillColumnWithZeroLight(engine.externalWorld, chunkX, chunkZ) + // fillColumnWithZeroLight(engine.externalWorld, chunkX, chunkZ) return engine.receiveUpdateColumn(chunkX, chunkZ) } export const updateBlockLight = (x: number, y: number, z: number, stateId: number) => { const engine = getLightEngine() - engine.setBlock(x, y, z, convertPrismarineBlockToWorldBlock(mcData.blocks[stateId], mcData)) + engine.setBlock(x, y, z, convertPrismarineBlockToWorldBlock(mcData.blocks[stateId], loadedData)) } diff --git a/renderer/viewer/lib/mesher/mesher.ts b/renderer/viewer/lib/mesher/mesher.ts index 21e2d8ef..3f06d60d 100644 --- a/renderer/viewer/lib/mesher/mesher.ts +++ b/renderer/viewer/lib/mesher/mesher.ts @@ -107,6 +107,9 @@ const handleMessage = data => { } case 'chunk': { world.addColumn(data.x, data.z, data.chunk) + if (data.lightData) { + world.lightHolder.loadChunk(data.lightData) + } if (data.customBlockModels) { const chunkKey = `${data.x},${data.z}` world.customBlockModels.set(chunkKey, data.customBlockModels) diff --git a/renderer/viewer/lib/mesher/world.ts b/renderer/viewer/lib/mesher/world.ts index 92fa7d86..69eb7c1d 100644 --- a/renderer/viewer/lib/mesher/world.ts +++ b/renderer/viewer/lib/mesher/world.ts @@ -1,3 +1,4 @@ +import { WorldLightHolder } from 'minecraft-lighting/dist/worldLightHolder' import Chunks from 'prismarine-chunk' import mcData from 'minecraft-data' import { Block } from 'prismarine-block' @@ -32,6 +33,7 @@ export type WorldBlock = Omit & { } export class World { + lightHolder = new WorldLightHolder(0, 0) config = defaultMesherConfig Chunk: typeof import('prismarine-chunk/types/index').PCChunk columns = {} as { [key: string]: import('prismarine-chunk/types/index').PCChunk } @@ -63,8 +65,8 @@ export class World { let result = Math.min( 15, Math.max( - column.getBlockLight(posInChunk(pos)), - Math.min(skyLight, column.getSkyLight(posInChunk(pos))) + this.getBlockLight(pos), + Math.min(skyLight, this.getSkyLight(pos)) ) ) const MIN_LIGHT_LEVEL = 2 @@ -90,6 +92,16 @@ export class World { return result } + getBlockLight (pos: Vec3) { + return this.lightHolder.getBlockLight(pos.x, pos.y, pos.z) + // return column.getBlockLight(posInChunk(pos)) + } + + getSkyLight (pos: Vec3) { + return this.lightHolder.getSkyLight(pos.x, pos.y, pos.z) + // return column.getSkyLight(posInChunk(pos)) + } + addColumn (x, z, json) { const chunk = this.Chunk.fromJson(json) this.columns[columnKey(x, z)] = chunk as any diff --git a/renderer/viewer/lib/worldDataEmitter.ts b/renderer/viewer/lib/worldDataEmitter.ts index 42f1e51e..a92599ee 100644 --- a/renderer/viewer/lib/worldDataEmitter.ts +++ b/renderer/viewer/lib/worldDataEmitter.ts @@ -8,9 +8,8 @@ import { BotEvents } from 'mineflayer' import { proxy } from 'valtio' import TypedEmitter from 'typed-emitter' import { delayedIterator } from '../../playground/shared' -import { playerState } from '../../../src/mineflayer/playerState' import { chunkPos } from './simpleUtils' -import { createLightEngine, processLightChunk, updateBlockLight } from './lightEngine' +import { processLightChunk, updateBlockLight } from './lightEngine' export type ChunkPosKey = string type ChunkPos = { x: number, z: number } @@ -178,7 +177,6 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter } constructor (public readonly resourcesManager: ResourcesManager, public displayOptions: DisplayWorldOptions, public initOptions: GraphicsInitOptions) { + createLightEngine(this) + // this.initWorkers(1) // preload script on page load this.snapshotInitialValues() this.worldRendererConfig = displayOptions.inWorldRenderingConfig @@ -577,7 +580,8 @@ export abstract class WorldRendererCommon x, z, chunk, - customBlockModels: customBlockModels || undefined + customBlockModels: customBlockModels || undefined, + lightData: getLightEngineSafe()?.worldLightHolder.dumpChunk(x, z) }) } this.logWorkerWork(`-> chunk ${JSON.stringify({ x, z, chunkLength: chunk.length, customBlockModelsLength: customBlockModels ? Object.keys(customBlockModels).length : 0 })}`) From 1918c68efbc32a280d219b9095bcbda3cbf77617 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 25 Apr 2025 04:49:31 +0300 Subject: [PATCH 07/98] finish lighting --- package.json | 2 +- pnpm-lock.yaml | 11 ++++++----- renderer/viewer/lib/lightEngine.ts | 13 ++++++++++--- renderer/viewer/lib/mesher/mesher.ts | 2 ++ renderer/viewer/lib/mesher/world.ts | 13 ++++++++----- renderer/viewer/lib/worldrendererCommon.ts | 5 +++-- renderer/viewer/three/worldrendererThree.ts | 6 ++++-- src/optionsStorage.ts | 3 ++- src/watchOptions.ts | 12 ++++++++---- 9 files changed, 44 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index 58de8151..a43df849 100644 --- a/package.json +++ b/package.json @@ -170,7 +170,7 @@ "typescript": "5.5.4", "vitest": "^0.34.6", "yaml": "^2.3.2", - "minecraft-lighting": "file:../minecraft-lighting" + "minecraft-lighting": "^0.0.8" }, "optionalDependencies": { "cypress": "^10.11.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index db081014..f04d19e9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -343,8 +343,8 @@ importers: specifier: github:zardoy/minecraft-inventory-gui#next version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/98bc5bb8ee6da8b4b771c05b404cee796318ccd4(@types/react@18.3.18)(react@18.3.1) minecraft-lighting: - specifier: file:../minecraft-lighting - version: file:../minecraft-lighting + specifier: ^0.0.8 + version: 0.0.8 mineflayer: specifier: github:GenerelSchwerz/mineflayer version: https://codeload.github.com/GenerelSchwerz/mineflayer/tar.gz/d459d2ed76a997af1a7c94718ed7d5dee4478b8a(encoding@0.1.13) @@ -6680,8 +6680,9 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/98bc5bb8ee6da8b4b771c05b404cee796318ccd4} version: 1.0.1 - minecraft-lighting@file:../minecraft-lighting: - resolution: {directory: ../minecraft-lighting, type: directory} + minecraft-lighting@0.0.8: + resolution: {integrity: sha512-HEew4qycmHv2r5+FR4vZ81YJkOR9Q8jbpj6nO9nEoiKRFvo9HamcbA6uQmSxJZKkkgt1uOp/EQ61rQXFw3nnGQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284: resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284} @@ -17322,7 +17323,7 @@ snapshots: - '@types/react' - react - minecraft-lighting@file:../minecraft-lighting: + minecraft-lighting@0.0.8: dependencies: vec3: 0.1.10 diff --git a/renderer/viewer/lib/lightEngine.ts b/renderer/viewer/lib/lightEngine.ts index 88e2be33..c5cecfa0 100644 --- a/renderer/viewer/lib/lightEngine.ts +++ b/renderer/viewer/lib/lightEngine.ts @@ -1,4 +1,4 @@ -import { LightWorld, createLightEngineForSyncWorld, convertPrismarineBlockToWorldBlock, fillColumnWithZeroLight } from 'minecraft-lighting' +import { LightWorld, createLightEngineForSyncWorld, convertPrismarineBlockToWorldBlock } from 'minecraft-lighting' import { world } from 'prismarine-world' import { WorldRendererCommon } from './worldrendererCommon' @@ -15,7 +15,7 @@ export const createLightEngine = (world: WorldRendererCommon) => { lightEngine = createLightEngineForSyncWorld(world.displayOptions.worldView.world as unknown as world.WorldSync, loadedData, { minY: world.worldSizeParams.minY, height: world.worldSizeParams.worldHeight, - enableSkyLight: false, + // enableSkyLight: false, }) lightEngine.PARALLEL_CHUNK_PROCESSING = false globalThis.lightEngine = lightEngine @@ -26,7 +26,14 @@ export const processLightChunk = async (x: number, z: number) => { const chunkZ = Math.floor(z / 16) const engine = getLightEngine() // fillColumnWithZeroLight(engine.externalWorld, chunkX, chunkZ) - return engine.receiveUpdateColumn(chunkX, chunkZ) + + const updated = engine.receiveUpdateColumn(chunkX, chunkZ) + return updated +} + +export const dumpLightData = (x: number, z: number) => { + const engine = getLightEngineSafe() + return engine?.worldLightHolder.dumpChunk(Math.floor(x / 16), Math.floor(z / 16)) } export const updateBlockLight = (x: number, y: number, z: number, stateId: number) => { diff --git a/renderer/viewer/lib/mesher/mesher.ts b/renderer/viewer/lib/mesher/mesher.ts index 3f06d60d..4f1885b1 100644 --- a/renderer/viewer/lib/mesher/mesher.ts +++ b/renderer/viewer/lib/mesher/mesher.ts @@ -109,6 +109,8 @@ const handleMessage = data => { world.addColumn(data.x, data.z, data.chunk) if (data.lightData) { world.lightHolder.loadChunk(data.lightData) + } else { + console.warn('no light data', data.x, data.z) } if (data.customBlockModels) { const chunkKey = `${data.x},${data.z}` diff --git a/renderer/viewer/lib/mesher/world.ts b/renderer/viewer/lib/mesher/world.ts index 69eb7c1d..cd8ad335 100644 --- a/renderer/viewer/lib/mesher/world.ts +++ b/renderer/viewer/lib/mesher/world.ts @@ -62,11 +62,14 @@ export class World { // if (lightsCache.has(key)) return lightsCache.get(key) const column = this.getColumnByPos(pos) if (!column || !hasChunkSection(column, pos)) return 15 - let result = Math.min( - 15, - Math.max( - this.getBlockLight(pos), - Math.min(skyLight, this.getSkyLight(pos)) + let result = Math.max( + 3, + Math.min( + 15, + Math.max( + this.getBlockLight(pos), + Math.min(skyLight, this.getSkyLight(pos)) + ) ) ) const MIN_LIGHT_LEVEL = 2 diff --git a/renderer/viewer/lib/worldrendererCommon.ts b/renderer/viewer/lib/worldrendererCommon.ts index aa85aa93..416ede1f 100644 --- a/renderer/viewer/lib/worldrendererCommon.ts +++ b/renderer/viewer/lib/worldrendererCommon.ts @@ -19,7 +19,7 @@ import { chunkPos } from './simpleUtils' import { addNewStat, removeAllStats, removeStat, updatePanesVisibility, updateStatText } from './ui/newStats' import { WorldDataEmitter } from './worldDataEmitter' import { IPlayerState } from './basePlayerState' -import { createLightEngine, getLightEngine, getLightEngineSafe } from './lightEngine' +import { createLightEngine, dumpLightData, getLightEngine, getLightEngineSafe } from './lightEngine' import { MesherLogReader } from './mesherlogReader' function mod (x, n) { @@ -40,6 +40,7 @@ export const defaultWorldRendererConfig = { clipWorldBelowY: undefined as number | undefined, smoothLighting: true, enableLighting: true, + clientSideLighting: false, starfield: true, addChunksBatchWaitTime: 200, vrSupport: true, @@ -614,7 +615,7 @@ export abstract class WorldRendererCommon z, chunk, customBlockModels: customBlockModels || undefined, - lightData: getLightEngineSafe()?.worldLightHolder.dumpChunk(x, z) + lightData: dumpLightData(x, z) }) } this.logWorkerWork(() => `-> chunk ${JSON.stringify({ x, z, chunkLength: chunk.length, customBlockModelsLength: customBlockModels ? Object.keys(customBlockModels).length : 0 })}`) diff --git a/renderer/viewer/three/worldrendererThree.ts b/renderer/viewer/three/worldrendererThree.ts index 14270367..acd44e8b 100644 --- a/renderer/viewer/three/worldrendererThree.ts +++ b/renderer/viewer/three/worldrendererThree.ts @@ -151,11 +151,13 @@ export class WorldRendererThree extends WorldRendererCommon { }) this.onReactiveValueUpdated('ambientLight', (value) => { if (!value) return - this.ambientLight.intensity = value + // this.ambientLight.intensity = value + this.ambientLight.intensity = 1 }) this.onReactiveValueUpdated('directionalLight', (value) => { if (!value) return - this.directionalLight.intensity = value + // this.directionalLight.intensity = value + this.directionalLight.intensity = 1 }) this.onReactiveValueUpdated('lookingAtBlock', (value) => { this.cursorBlock.setHighlightCursorBlock(value ? new Vec3(value.x, value.y, value.z) : null, value?.shapes) diff --git a/src/optionsStorage.ts b/src/optionsStorage.ts index ecfcac9e..27eb520d 100644 --- a/src/optionsStorage.ts +++ b/src/optionsStorage.ts @@ -97,7 +97,8 @@ const defaultOptions = { showCursorBlockInSpectator: false, renderEntities: true, smoothLighting: true, - newVersionsLighting: true, + // lightingStrategy: 'prefer-server' as 'only-server' | 'always-client' | 'prefer-server', + lightingStrategy: 'prefer-server' as 'always-client' | 'prefer-server', chatSelect: true, autoJump: 'auto' as 'auto' | 'always' | 'never', autoParkour: false, diff --git a/src/watchOptions.ts b/src/watchOptions.ts index 9fe55289..d50b3fcb 100644 --- a/src/watchOptions.ts +++ b/src/watchOptions.ts @@ -94,12 +94,16 @@ export const watchOptionsAfterViewerInit = () => { appViewer.inWorldRenderingConfig.smoothLighting = options.smoothLighting }) - subscribeKey(options, 'newVersionsLighting', () => { - appViewer.inWorldRenderingConfig.enableLighting = !bot.supportFeature('blockStateId') || options.newVersionsLighting - }) + const updateLightingStrategy = () => { + const clientSideLighting = options.lightingStrategy === 'always-client' || (options.lightingStrategy === 'prefer-server' && bot.supportFeature('blockStateId')) + appViewer.inWorldRenderingConfig.clientSideLighting = clientSideLighting + appViewer.inWorldRenderingConfig.enableLighting = options.dayCycleAndLighting && (!bot.supportFeature('blockStateId') || clientSideLighting) + } + + subscribeKey(options, 'lightingStrategy', updateLightingStrategy) customEvents.on('mineflayerBotCreated', () => { - appViewer.inWorldRenderingConfig.enableLighting = !bot.supportFeature('blockStateId') || options.newVersionsLighting + updateLightingStrategy() }) watchValue(options, o => { From b4c72dbb36a57bab3a005d4050feb30aa1894fb9 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 25 Apr 2025 05:24:06 +0300 Subject: [PATCH 08/98] fix crash opt --- src/watchOptions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/watchOptions.ts b/src/watchOptions.ts index d50b3fcb..d0ff609f 100644 --- a/src/watchOptions.ts +++ b/src/watchOptions.ts @@ -95,6 +95,7 @@ export const watchOptionsAfterViewerInit = () => { }) const updateLightingStrategy = () => { + if (!bot) return const clientSideLighting = options.lightingStrategy === 'always-client' || (options.lightingStrategy === 'prefer-server' && bot.supportFeature('blockStateId')) appViewer.inWorldRenderingConfig.clientSideLighting = clientSideLighting appViewer.inWorldRenderingConfig.enableLighting = options.dayCycleAndLighting && (!bot.supportFeature('blockStateId') || clientSideLighting) From 1f5b682bee3d743373a293d750996d5980c3029b Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 28 Apr 2025 09:46:59 +0300 Subject: [PATCH 09/98] FINISH OPTIONS, FINISH RECOMPUTE, ADD LIGHT TO WATER --- renderer/viewer/lib/lightEngine.ts | 21 +++++++++++- renderer/viewer/lib/mesher/models.ts | 38 +++++++++++++++++++-- renderer/viewer/lib/mesher/shared.ts | 3 ++ renderer/viewer/lib/mesher/world.ts | 18 ++++++++-- renderer/viewer/lib/worldDataEmitter.ts | 16 ++++++--- renderer/viewer/lib/worldrendererCommon.ts | 3 +- renderer/viewer/three/worldrendererThree.ts | 2 +- src/dayCycle.ts | 8 ++--- src/optionsGuiScheme.tsx | 11 +++--- src/optionsStorage.ts | 2 +- src/react/DebugOverlay.tsx | 7 ++-- src/watchOptions.ts | 9 +++-- 12 files changed, 111 insertions(+), 27 deletions(-) diff --git a/renderer/viewer/lib/lightEngine.ts b/renderer/viewer/lib/lightEngine.ts index c5cecfa0..8b83ab98 100644 --- a/renderer/viewer/lib/lightEngine.ts +++ b/renderer/viewer/lib/lightEngine.ts @@ -17,6 +17,7 @@ export const createLightEngine = (world: WorldRendererCommon) => { height: world.worldSizeParams.worldHeight, // enableSkyLight: false, }) + lightEngine.externalWorld.setBlock = () => {} lightEngine.PARALLEL_CHUNK_PROCESSING = false globalThis.lightEngine = lightEngine } @@ -36,7 +37,25 @@ export const dumpLightData = (x: number, z: number) => { return engine?.worldLightHolder.dumpChunk(Math.floor(x / 16), Math.floor(z / 16)) } +export const getDebugLightValues = (x: number, y: number, z: number) => { + const engine = getLightEngineSafe() + return { + blockLight: engine?.worldLightHolder.getBlockLight(x, y, z) ?? -1, + skyLight: engine?.worldLightHolder.getSkyLight(x, y, z) ?? -1, + } +} + export const updateBlockLight = (x: number, y: number, z: number, stateId: number) => { const engine = getLightEngine() - engine.setBlock(x, y, z, convertPrismarineBlockToWorldBlock(mcData.blocks[stateId], loadedData)) + const affected = engine['affectedChunksTimestamps'] as Map + const noAffected = affected.size === 0 + engine.setBlock(x, y, z, convertPrismarineBlockToWorldBlock(stateId, loadedData)) + + if (affected.size > 0) { + const chunks = [...affected.keys()].map(key => { + return key.split(',').map(Number) as [number, number] + }) + affected.clear() + return chunks + } } diff --git a/renderer/viewer/lib/mesher/models.ts b/renderer/viewer/lib/mesher/models.ts index fc57d2b3..32b805d1 100644 --- a/renderer/viewer/lib/mesher/models.ts +++ b/renderer/viewer/lib/mesher/models.ts @@ -125,6 +125,13 @@ const isCube = (block: Block) => { })) } +const getVec = (v: Vec3, dir: Vec3) => { + for (const coord of ['x', 'y', 'z']) { + if (Math.abs(dir[coord]) > 0) v[coord] = 0 + } + return v.plus(dir) +} + function renderLiquid (world: World, cursor: Vec3, texture: any | undefined, type: number, biome: string, water: boolean, attr: Record, isRealWater: boolean) { const heights: number[] = [] for (let z = -1; z <= 1; z++) { @@ -142,7 +149,7 @@ function renderLiquid (world: World, cursor: Vec3, texture: any | undefined, typ // eslint-disable-next-line guard-for-in for (const face in elemFaces) { - const { dir, corners } = elemFaces[face] + const { dir, corners, mask1, mask2 } = elemFaces[face] const isUp = dir[1] === 1 const neighborPos = cursor.offset(...dir as [number, number, number]) @@ -180,6 +187,9 @@ function renderLiquid (world: World, cursor: Vec3, texture: any | undefined, typ const { su } = texture const { sv } = texture + // Get base light value for the face + const baseLight = world.getLight(neighborPos, undefined, undefined, water ? 'water' : 'lava') / 15 + for (const pos of corners) { const height = cornerHeights[pos[2] * 2 + pos[0]] attr.t_positions.push( @@ -189,7 +199,31 @@ function renderLiquid (world: World, cursor: Vec3, texture: any | undefined, typ ) attr.t_normals.push(...dir) attr.t_uvs.push(pos[3] * su + u, pos[4] * sv * (pos[1] ? 1 : height) + v) - attr.t_colors.push(tint[0], tint[1], tint[2]) + + let cornerLightResult = baseLight + if (world.config.smoothLighting) { + const dx = pos[0] * 2 - 1 + const dy = pos[1] * 2 - 1 + const dz = pos[2] * 2 - 1 + const cornerDir: [number, number, number] = [dx, dy, dz] + const side1Dir: [number, number, number] = [dx * mask1[0], dy * mask1[1], dz * mask1[2]] + const side2Dir: [number, number, number] = [dx * mask2[0], dy * mask2[1], dz * mask2[2]] + + const dirVec = new Vec3(...dir as [number, number, number]) + + const side1LightDir = getVec(new Vec3(...side1Dir), dirVec) + const side1Light = world.getLight(cursor.plus(side1LightDir)) / 15 + const side2DirLight = getVec(new Vec3(...side2Dir), dirVec) + const side2Light = world.getLight(cursor.plus(side2DirLight)) / 15 + const cornerLightDir = getVec(new Vec3(...cornerDir), dirVec) + const cornerLight = world.getLight(cursor.plus(cornerLightDir)) / 15 + // interpolate + const lights = [side1Light, side2Light, cornerLight, baseLight] + cornerLightResult = lights.reduce((acc, cur) => acc + cur, 0) / lights.length + } + + // Apply light value to tint + attr.t_colors.push(tint[0] * cornerLightResult, tint[1] * cornerLightResult, tint[2] * cornerLightResult) } } } diff --git a/renderer/viewer/lib/mesher/shared.ts b/renderer/viewer/lib/mesher/shared.ts index eb1346f4..6fe44e9c 100644 --- a/renderer/viewer/lib/mesher/shared.ts +++ b/renderer/viewer/lib/mesher/shared.ts @@ -3,9 +3,12 @@ import { BlockType } from '../../../playground/shared' // only here for easier testing export const defaultMesherConfig = { version: '', + enableLighting: true, skyLight: 15, smoothLighting: true, + clientSideLighting: false, + outputFormat: 'threeJs' as 'threeJs' | 'webgpu', textureSize: 1024, // for testing debugModelVariant: undefined as undefined | number[], diff --git a/renderer/viewer/lib/mesher/world.ts b/renderer/viewer/lib/mesher/world.ts index cd8ad335..ba7ea616 100644 --- a/renderer/viewer/lib/mesher/world.ts +++ b/renderer/viewer/lib/mesher/world.ts @@ -53,7 +53,9 @@ export class World { } getLight (pos: Vec3, isNeighbor = false, skipMoreChecks = false, curBlockName = '') { - const IS_USING_SERVER_LIGHTING = false + const IS_USING_SERVER_LIGHTING = !this.config.clientSideLighting + // const IS_USING_SERVER_LIGHTING = false + // for easier testing if (!(pos instanceof Vec3)) pos = new Vec3(...pos as [number, number, number]) const { enableLighting, skyLight } = this.config @@ -96,13 +98,23 @@ export class World { } getBlockLight (pos: Vec3) { + if (!this.config.clientSideLighting) { + const column = this.getColumnByPos(pos) + if (!column) return 15 + return column.getBlockLight(posInChunk(pos)) + } + return this.lightHolder.getBlockLight(pos.x, pos.y, pos.z) - // return column.getBlockLight(posInChunk(pos)) } getSkyLight (pos: Vec3) { + if (!this.config.clientSideLighting) { + const column = this.getColumnByPos(pos) + if (!column) return 15 + return column.getSkyLight(posInChunk(pos)) + } + return this.lightHolder.getSkyLight(pos.x, pos.y, pos.z) - // return column.getSkyLight(posInChunk(pos)) } addColumn (x, z, json) { diff --git a/renderer/viewer/lib/worldDataEmitter.ts b/renderer/viewer/lib/worldDataEmitter.ts index 34755c2a..8f569af7 100644 --- a/renderer/viewer/lib/worldDataEmitter.ts +++ b/renderer/viewer/lib/worldDataEmitter.ts @@ -80,8 +80,11 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter TypedEmitter { this.unloadChunk(pos) }, - blockUpdate: (oldBlock: any, newBlock: any) => { + blockUpdate: (oldBlock, newBlock) => { const stateId = newBlock.stateId ?? ((newBlock.type << 4) | newBlock.metadata) - this.emitter.emit('blockUpdate', { pos: oldBlock.position, stateId }) + + const updateChunks = updateBlockLight(newBlock.position.x, newBlock.position.y, newBlock.position.z, stateId) ?? [] + this.emit('blockUpdate', { pos: newBlock.position, stateId }) + for (const chunk of updateChunks) { + void this.loadChunk(new Vec3(chunk[0] * 16, 0, chunk[1] * 16), true, 'setBlockStateId light update') + } }, time: () => { this.emitter.emit('time', bot.time.timeOfDay) @@ -261,7 +269,7 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter textureSize: this.resourcesManager.currentResources!.blocksAtlasParser.atlas.latest.width, debugModelVariant: undefined, clipWorldBelowY: this.worldRendererConfig.clipWorldBelowY, - disableSignsMapsSupport: !this.worldRendererConfig.extraBlockRenderers + disableSignsMapsSupport: !this.worldRendererConfig.extraBlockRenderers, + clientSideLighting: this.worldRendererConfig.clientSideLighting } } diff --git a/renderer/viewer/three/worldrendererThree.ts b/renderer/viewer/three/worldrendererThree.ts index acd44e8b..7aacf61d 100644 --- a/renderer/viewer/three/worldrendererThree.ts +++ b/renderer/viewer/three/worldrendererThree.ts @@ -157,7 +157,7 @@ export class WorldRendererThree extends WorldRendererCommon { this.onReactiveValueUpdated('directionalLight', (value) => { if (!value) return // this.directionalLight.intensity = value - this.directionalLight.intensity = 1 + this.directionalLight.intensity = 0.4 }) this.onReactiveValueUpdated('lookingAtBlock', (value) => { this.cursorBlock.setHighlightCursorBlock(value ? new Vec3(value.x, value.y, value.z) : null, value?.shapes) diff --git a/src/dayCycle.ts b/src/dayCycle.ts index 50e63a21..5b2725e2 100644 --- a/src/dayCycle.ts +++ b/src/dayCycle.ts @@ -35,10 +35,10 @@ export default () => { // todo need to think wisely how to set these values & also move directional light around! const colorInt = Math.max(int, 0.1) updateBackground({ r: dayColor.r * colorInt, g: dayColor.g * colorInt, b: dayColor.b * colorInt }) - if (!options.newVersionsLighting && bot.supportFeature('blockStateId')) { - appViewer.playerState.reactive.ambientLight = Math.max(int, 0.25) - appViewer.playerState.reactive.directionalLight = Math.min(int, 0.5) - } + // if (!options.newVersionsLighting && bot.supportFeature('blockStateId')) { + // appViewer.playerState.reactive.ambientLight = Math.max(int, 0.25) + // appViewer.playerState.reactive.directionalLight = Math.min(int, 0.45) + // } } bot.on('time', timeUpdated) diff --git a/src/optionsGuiScheme.tsx b/src/optionsGuiScheme.tsx index 20514489..0184e9bf 100644 --- a/src/optionsGuiScheme.tsx +++ b/src/optionsGuiScheme.tsx @@ -81,12 +81,12 @@ export const guiOptionsScheme: { custom () { return Experimental }, - dayCycleAndLighting: { - text: 'Day Cycle', - }, smoothLighting: {}, - newVersionsLighting: { - text: 'Lighting in Newer Versions', + lightingStrategy: { + values: [ + ['prefer-server', 'Prefer Server'], + ['always-client', 'Always Client'], + ], }, lowMemoryMode: { text: 'Low Memory Mode', @@ -94,7 +94,6 @@ export const guiOptionsScheme: { disabledDuringGame: true }, starfieldRendering: {}, - renderEntities: {}, keepChunksDistance: { max: 5, unit: '', diff --git a/src/optionsStorage.ts b/src/optionsStorage.ts index 27eb520d..b0cc0068 100644 --- a/src/optionsStorage.ts +++ b/src/optionsStorage.ts @@ -98,7 +98,7 @@ const defaultOptions = { renderEntities: true, smoothLighting: true, // lightingStrategy: 'prefer-server' as 'only-server' | 'always-client' | 'prefer-server', - lightingStrategy: 'prefer-server' as 'always-client' | 'prefer-server', + lightingStrategy: 'prefer-server' as 'always-client' | 'prefer-server' | 'always-server', chatSelect: true, autoJump: 'auto' as 'auto' | 'always' | 'never', autoParkour: false, diff --git a/src/react/DebugOverlay.tsx b/src/react/DebugOverlay.tsx index a7eb068f..2175863d 100644 --- a/src/react/DebugOverlay.tsx +++ b/src/react/DebugOverlay.tsx @@ -1,6 +1,7 @@ import { useEffect, useRef, useState } from 'react' import type { Block } from 'prismarine-block' import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods' +import { getDebugLightValues } from 'renderer/viewer/lib/lightEngine' import { getFixedFilesize } from '../downloadAndOpenFile' import { options } from '../optionsStorage' import { BlockStateModelInfo } from '../../renderer/viewer/lib/mesher/shared' @@ -128,9 +129,11 @@ export default () => { }) const freqUpdateInterval = setInterval(() => { + const lights = getDebugLightValues(Math.floor(bot.entity.position.x), Math.floor(bot.entity.position.y), Math.floor(bot.entity.position.z)) + setPos({ ...bot.entity.position }) - setSkyL(bot.world.getSkyLight(bot.entity.position)) - setBlockL(bot.world.getBlockLight(bot.entity.position)) + setSkyL(lights.skyLight) + setBlockL(lights.blockLight) setBiomeId(bot.world.getBiome(bot.entity.position)) setDimension(bot.game.dimension) setDay(bot.time.day) diff --git a/src/watchOptions.ts b/src/watchOptions.ts index d0ff609f..effd3d87 100644 --- a/src/watchOptions.ts +++ b/src/watchOptions.ts @@ -96,9 +96,14 @@ export const watchOptionsAfterViewerInit = () => { const updateLightingStrategy = () => { if (!bot) return - const clientSideLighting = options.lightingStrategy === 'always-client' || (options.lightingStrategy === 'prefer-server' && bot.supportFeature('blockStateId')) + const serverAvailable = !bot.supportFeature('blockStateId') + + const serverLightingEnabled = serverAvailable && (options.lightingStrategy === 'prefer-server' || options.lightingStrategy === 'always-server') + const clientLightingEnabled = options.lightingStrategy === 'prefer-server' ? !serverAvailable : options.lightingStrategy === 'always-client' + + const clientSideLighting = !serverLightingEnabled appViewer.inWorldRenderingConfig.clientSideLighting = clientSideLighting - appViewer.inWorldRenderingConfig.enableLighting = options.dayCycleAndLighting && (!bot.supportFeature('blockStateId') || clientSideLighting) + appViewer.inWorldRenderingConfig.enableLighting = options.dayCycleAndLighting && (clientLightingEnabled || serverLightingEnabled) } subscribeKey(options, 'lightingStrategy', updateLightingStrategy) From f4eab39f7f3de463a5d47c90d6c0a55e9c68a0fe Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 30 Apr 2025 08:51:46 +0300 Subject: [PATCH 10/98] finish lighting --- pnpm-lock.yaml | 28 +++++++++++----------- renderer/viewer/lib/basePlayerState.ts | 1 + renderer/viewer/lib/mesher/shared.ts | 1 + renderer/viewer/lib/mesher/world.ts | 21 +++++++--------- renderer/viewer/lib/worldrendererCommon.ts | 6 +++-- src/mineflayer/playerState.ts | 18 ++++++++++++++ src/react/ChunksDebugScreen.tsx | 2 +- src/react/DebugOverlay.tsx | 21 +++++++++++----- src/watchOptions.ts | 8 ++++--- 9 files changed, 68 insertions(+), 38 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index be002941..a3f636c4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -148,7 +148,7 @@ importers: version: 2.0.4 net-browserify: specifier: github:zardoy/prismarinejs-net-browserify - version: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/92707300cce08287ed7750f4447be350fc342d07 + version: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/356edb3b061be0753f53fc2cf7fa995d40d05469 node-gzip: specifier: ^1.1.2 version: 1.1.2 @@ -441,7 +441,7 @@ importers: version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chunk: specifier: github:zardoy/prismarine-chunk#master - version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1) + version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5(minecraft-data@3.83.1) prismarine-schematic: specifier: ^1.2.0 version: 1.2.3 @@ -6869,8 +6869,8 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/92707300cce08287ed7750f4447be350fc342d07: - resolution: {tarball: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/92707300cce08287ed7750f4447be350fc342d07} + net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/356edb3b061be0753f53fc2cf7fa995d40d05469: + resolution: {tarball: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/356edb3b061be0753f53fc2cf7fa995d40d05469} version: 0.2.4 nice-try@1.0.5: @@ -7415,8 +7415,8 @@ packages: prismarine-chat@1.11.0: resolution: {integrity: sha512-VJT/MWYB3qoiznUhrgvSQh76YFpzpCZpY85kJKxHLbd3UVoM0wsfs43Eg8dOltiZG92wc5/DTMLlT07TEeoa9w==} - prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374: - resolution: {tarball: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374} + prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5: + resolution: {tarball: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5} version: 1.38.1 engines: {node: '>=14'} @@ -13184,7 +13184,7 @@ snapshots: mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5(minecraft-data@3.83.1) prismarine-entity: 2.5.0 prismarine-item: 1.16.0 prismarine-nbt: 2.7.0 @@ -13220,7 +13220,7 @@ snapshots: mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5(minecraft-data@3.83.1) prismarine-entity: 2.5.0 prismarine-item: 1.16.0 prismarine-nbt: 2.7.0 @@ -14615,7 +14615,7 @@ snapshots: diamond-square@https://codeload.github.com/zardoy/diamond-square/tar.gz/cfaad2d1d5909fdfa63c8cc7bc05fb5e87782d71: dependencies: minecraft-data: 3.83.1 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5(minecraft-data@3.83.1) prismarine-registry: 1.11.0 random-seed: 0.3.0 vec3: 0.1.10 @@ -17430,7 +17430,7 @@ snapshots: prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chat: 1.11.0 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5(minecraft-data@3.83.1) prismarine-entity: 2.5.0 prismarine-item: 1.16.0 prismarine-nbt: 2.7.0 @@ -17454,7 +17454,7 @@ snapshots: prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chat: 1.11.0 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5(minecraft-data@3.83.1) prismarine-entity: 2.5.0 prismarine-item: 1.16.0 prismarine-nbt: 2.7.0 @@ -17644,7 +17644,7 @@ snapshots: neo-async@2.6.2: {} - net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/92707300cce08287ed7750f4447be350fc342d07: + net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/356edb3b061be0753f53fc2cf7fa995d40d05469: dependencies: body-parser: 1.20.3 express: 4.21.2 @@ -18254,7 +18254,7 @@ snapshots: prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 - prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1): + prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5(minecraft-data@3.83.1): dependencies: prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 @@ -18292,7 +18292,7 @@ snapshots: prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.83.1): dependencies: prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5(minecraft-data@3.83.1) prismarine-nbt: 2.7.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c uint4: 0.1.2 diff --git a/renderer/viewer/lib/basePlayerState.ts b/renderer/viewer/lib/basePlayerState.ts index df8cbc61..ff7617ef 100644 --- a/renderer/viewer/lib/basePlayerState.ts +++ b/renderer/viewer/lib/basePlayerState.ts @@ -31,6 +31,7 @@ export interface IPlayerState { getHeldItem?(isLeftHand: boolean): HandItemBlock | undefined username?: string onlineMode?: boolean + lightingDisabled?: boolean events: TypedEmitter diff --git a/renderer/viewer/lib/mesher/shared.ts b/renderer/viewer/lib/mesher/shared.ts index 6fe44e9c..71140096 100644 --- a/renderer/viewer/lib/mesher/shared.ts +++ b/renderer/viewer/lib/mesher/shared.ts @@ -8,6 +8,7 @@ export const defaultMesherConfig = { skyLight: 15, smoothLighting: true, clientSideLighting: false, + flyingSquidWorkarounds: false, outputFormat: 'threeJs' as 'threeJs' | 'webgpu', textureSize: 1024, // for testing diff --git a/renderer/viewer/lib/mesher/world.ts b/renderer/viewer/lib/mesher/world.ts index ba7ea616..22d27129 100644 --- a/renderer/viewer/lib/mesher/world.ts +++ b/renderer/viewer/lib/mesher/world.ts @@ -53,19 +53,19 @@ export class World { } getLight (pos: Vec3, isNeighbor = false, skipMoreChecks = false, curBlockName = '') { - const IS_USING_SERVER_LIGHTING = !this.config.clientSideLighting - // const IS_USING_SERVER_LIGHTING = false - // for easier testing if (!(pos instanceof Vec3)) pos = new Vec3(...pos as [number, number, number]) - const { enableLighting, skyLight } = this.config + + const IS_USING_LOCAL_SERVER_LIGHTING = this.config.flyingSquidWorkarounds + // const IS_USING_SERVER_LIGHTING = false + + const { enableLighting, skyLight, clientSideLighting } = this.config if (!enableLighting) return 15 - // const key = `${pos.x},${pos.y},${pos.z}` - // if (lightsCache.has(key)) return lightsCache.get(key) const column = this.getColumnByPos(pos) - if (!column || !hasChunkSection(column, pos)) return 15 + if (!column) return 15 + if (!clientSideLighting && !hasChunkSection(column, pos)) return 2 let result = Math.max( - 3, + 2, Math.min( 15, Math.max( @@ -74,10 +74,7 @@ export class World { ) ) ) - const MIN_LIGHT_LEVEL = 2 - result = Math.max(result, MIN_LIGHT_LEVEL) - // lightsCache.set(key, result) - if (result === 2 && IS_USING_SERVER_LIGHTING) { + if (result === 2 && IS_USING_LOCAL_SERVER_LIGHTING) { if ([this.getBlock(pos)?.name ?? '', curBlockName].some(x => /_stairs|slab|glass_pane/.exec(x)) && !skipMoreChecks) { // todo this is obviously wrong const lights = [ this.getLight(pos.offset(0, 1, 0), undefined, true), diff --git a/renderer/viewer/lib/worldrendererCommon.ts b/renderer/viewer/lib/worldrendererCommon.ts index b99aa72e..6da45d3f 100644 --- a/renderer/viewer/lib/worldrendererCommon.ts +++ b/renderer/viewer/lib/worldrendererCommon.ts @@ -42,6 +42,7 @@ export const defaultWorldRendererConfig = { smoothLighting: true, enableLighting: true, clientSideLighting: false, + flyingSquidWorkarounds: false, starfield: true, addChunksBatchWaitTime: 200, vrSupport: true, @@ -549,7 +550,7 @@ export abstract class WorldRendererCommon skyLight = Math.floor(skyLight) return { version: this.version, - enableLighting: this.worldRendererConfig.enableLighting, + enableLighting: this.worldRendererConfig.enableLighting && !this.playerState.lightingDisabled, skyLight, smoothLighting: this.worldRendererConfig.smoothLighting, outputFormat: this.outputFormat, @@ -557,7 +558,8 @@ export abstract class WorldRendererCommon debugModelVariant: undefined, clipWorldBelowY: this.worldRendererConfig.clipWorldBelowY, disableSignsMapsSupport: !this.worldRendererConfig.extraBlockRenderers, - clientSideLighting: this.worldRendererConfig.clientSideLighting + clientSideLighting: this.worldRendererConfig.clientSideLighting, + flyingSquidWorkarounds: this.worldRendererConfig.flyingSquidWorkarounds } } diff --git a/src/mineflayer/playerState.ts b/src/mineflayer/playerState.ts index 85f0b00c..d16bedf3 100644 --- a/src/mineflayer/playerState.ts +++ b/src/mineflayer/playerState.ts @@ -24,6 +24,7 @@ export class PlayerStateManager implements IPlayerState { private itemUsageTicks = 0 private isUsingItem = false private ready = false + public lightingDisabled = false onlineMode = false get username () { return bot.username ?? '' @@ -51,6 +52,23 @@ export class PlayerStateManager implements IPlayerState { } private botCreated () { + const handleDimensionData = (data) => { + let hasSkyLight = 1 + try { + hasSkyLight = data.dimension.value.has_skylight.value + } catch (err) { + hasSkyLight = 0 + } + this.lightingDisabled = bot.game.dimension === 'the_nether' || bot.game.dimension === 'the_end' || !hasSkyLight + } + + bot._client.on('login', (packet) => { + handleDimensionData(packet) + }) + bot._client.on('respawn', (packet) => { + handleDimensionData(packet) + }) + // Movement tracking bot.on('move', this.updateState) diff --git a/src/react/ChunksDebugScreen.tsx b/src/react/ChunksDebugScreen.tsx index 80e926d8..9f47d023 100644 --- a/src/react/ChunksDebugScreen.tsx +++ b/src/react/ChunksDebugScreen.tsx @@ -30,7 +30,7 @@ const Inner = () => { state, lines: [String(chunk?.loads.length ?? 0)], sidebarLines: [ - `loads: ${chunk.loads.map(l => `${l.reason} ${l.dataLength} ${l.time}`).join('\n')}`, + `loads: ${chunk.loads?.map(l => `${l.reason} ${l.dataLength} ${l.time}`).join('\n')}`, // `blockUpdates: ${chunk.blockUpdates}`, ], } diff --git a/src/react/DebugOverlay.tsx b/src/react/DebugOverlay.tsx index c187587d..56950db2 100644 --- a/src/react/DebugOverlay.tsx +++ b/src/react/DebugOverlay.tsx @@ -31,8 +31,7 @@ export default () => { const [packetsString, setPacketsString] = useState('') const [showDebug, setShowDebug] = useState(false) const [pos, setPos] = useState<{ x: number, y: number, z: number }>({ x: 0, y: 0, z: 0 }) - const [skyL, setSkyL] = useState(0) - const [blockL, setBlockL] = useState(0) + const [lightInfo, setLightInfo] = useState<{ sky: number, block: number, info: string }>({ sky: 0, block: 0, info: '-' }) const [biomeId, setBiomeId] = useState(0) const [day, setDay] = useState(0) const [timeOfDay, setTimeOfDay] = useState(0) @@ -129,11 +128,21 @@ export default () => { }) const freqUpdateInterval = setInterval(() => { - const lights = getDebugLightValues(Math.floor(bot.entity.position.x), Math.floor(bot.entity.position.y), Math.floor(bot.entity.position.z)) + const lightingEnabled = appViewer.inWorldRenderingConfig.enableLighting + const serverLighting = !appViewer.inWorldRenderingConfig.clientSideLighting + if (!lightingEnabled || serverLighting) { + setLightInfo({ + sky: bot.world.getSkyLight(bot.entity.position), + block: bot.world.getBlockLight(bot.entity.position), + info: lightingEnabled ? 'Server Lighting' : 'Lighting Disabled' + }) + } else { + // client engine is used + const lights = getDebugLightValues(Math.floor(bot.entity.position.x), Math.floor(bot.entity.position.y), Math.floor(bot.entity.position.z)) + setLightInfo({ sky: lights.skyLight, block: lights.blockLight, info: 'Client Light Engine' }) + } setPos({ ...bot.entity.position }) - setSkyL(lights.skyLight) - setBlockL(lights.blockLight) setBiomeId(bot.world.getBiome(bot.entity.position)) setDimension(bot.game.dimension) setDay(bot.time.day) @@ -192,7 +201,7 @@ export default () => {

Client TPS: {clientTps} {serverTps ? `Server TPS: ${serverTps.value} ${serverTps.frozen ? '(frozen)' : ''}` : ''}

Facing (viewer): {bot.entity.yaw.toFixed(3)} {bot.entity.pitch.toFixed(3)}

Facing (minecraft): {quadsDescription[minecraftQuad.current]} ({minecraftYaw.current.toFixed(1)} {(bot.entity.pitch * -180 / Math.PI).toFixed(1)})

-

Light: {blockL} ({skyL} sky)

+

Light: {lightInfo.block} ({lightInfo.sky} sky) ({lightInfo.info})

Biome: minecraft:{loadedData.biomesArray[biomeId]?.name ?? 'unknown biome'}

Day: {day} Time: {timeOfDay}

diff --git a/src/watchOptions.ts b/src/watchOptions.ts index effd3d87..42bb26f6 100644 --- a/src/watchOptions.ts +++ b/src/watchOptions.ts @@ -96,10 +96,12 @@ export const watchOptionsAfterViewerInit = () => { const updateLightingStrategy = () => { if (!bot) return - const serverAvailable = !bot.supportFeature('blockStateId') + // for now ignore saved lighting to allow proper updates and singleplayer created worlds + // appViewer.inWorldRenderingConfig.flyingSquidWorkarounds = miscUiState.flyingSquid + const serverParsingSupported = miscUiState.flyingSquid ? /* !bot.supportFeature('blockStateId') */false : bot.supportFeature('blockStateId') - const serverLightingEnabled = serverAvailable && (options.lightingStrategy === 'prefer-server' || options.lightingStrategy === 'always-server') - const clientLightingEnabled = options.lightingStrategy === 'prefer-server' ? !serverAvailable : options.lightingStrategy === 'always-client' + const serverLightingEnabled = serverParsingSupported && (options.lightingStrategy === 'prefer-server' || options.lightingStrategy === 'always-server') + const clientLightingEnabled = options.lightingStrategy === 'prefer-server' ? !serverParsingSupported : options.lightingStrategy === 'always-client' const clientSideLighting = !serverLightingEnabled appViewer.inWorldRenderingConfig.clientSideLighting = clientSideLighting From 79f0fdd86eef7b5b460a275fa7e83050445d4079 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 30 Apr 2025 08:51:51 +0300 Subject: [PATCH 11/98] fix lava rendering --- renderer/viewer/lib/mesher/models.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/renderer/viewer/lib/mesher/models.ts b/renderer/viewer/lib/mesher/models.ts index 32b805d1..802ecaf0 100644 --- a/renderer/viewer/lib/mesher/models.ts +++ b/renderer/viewer/lib/mesher/models.ts @@ -103,8 +103,8 @@ function tintToGl (tint) { return [r / 255, g / 255, b / 255] } -function getLiquidRenderHeight (world: World, block: WorldBlock | null, type: number, pos: Vec3, isRealWater: boolean) { - if (!isRealWater || (block && isBlockWaterlogged(block))) return 8 / 9 +function getLiquidRenderHeight (world: World, block: WorldBlock | null, type: number, pos: Vec3, isWater: boolean, isRealWater: boolean) { + if ((isWater && !isRealWater) || (block && isBlockWaterlogged(block))) return 8 / 9 if (!block || block.type !== type) return 1 / 9 if (block.metadata === 0) { // source block const blockAbove = world.getBlock(pos.offset(0, 1, 0)) @@ -137,7 +137,7 @@ function renderLiquid (world: World, cursor: Vec3, texture: any | undefined, typ for (let z = -1; z <= 1; z++) { for (let x = -1; x <= 1; x++) { const pos = cursor.offset(x, 0, z) - heights.push(getLiquidRenderHeight(world, world.getBlock(pos), type, pos, isRealWater)) + heights.push(getLiquidRenderHeight(world, world.getBlock(pos), type, pos, water, isRealWater)) } } const cornerHeights = [ From 27c55b1afc5c21b56cc91de3e4b2e2485204ce68 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 30 Apr 2025 09:14:37 +0300 Subject: [PATCH 12/98] finish! --- renderer/viewer/lib/lightEngine.ts | 22 +++++-- renderer/viewer/lib/mesher/mesher.ts | 2 - renderer/viewer/lib/worldDataEmitter.ts | 67 +++++++++++++++------- renderer/viewer/lib/worldrendererCommon.ts | 6 +- src/appViewer.ts | 1 + src/optionsGuiScheme.tsx | 1 + 6 files changed, 67 insertions(+), 32 deletions(-) diff --git a/renderer/viewer/lib/lightEngine.ts b/renderer/viewer/lib/lightEngine.ts index 8b83ab98..84fbc820 100644 --- a/renderer/viewer/lib/lightEngine.ts +++ b/renderer/viewer/lib/lightEngine.ts @@ -1,6 +1,7 @@ import { LightWorld, createLightEngineForSyncWorld, convertPrismarineBlockToWorldBlock } from 'minecraft-lighting' import { world } from 'prismarine-world' import { WorldRendererCommon } from './worldrendererCommon' +import { WorldDataEmitter } from './worldDataEmitter' let lightEngine: LightWorld | null = null export const getLightEngine = () => { @@ -11,10 +12,11 @@ export const getLightEngineSafe = () => { return lightEngine } -export const createLightEngine = (world: WorldRendererCommon) => { - lightEngine = createLightEngineForSyncWorld(world.displayOptions.worldView.world as unknown as world.WorldSync, loadedData, { - minY: world.worldSizeParams.minY, - height: world.worldSizeParams.worldHeight, +export const createLightEngineIfNeeded = (worldView: WorldDataEmitter) => { + if (lightEngine) return + lightEngine = createLightEngineForSyncWorld(worldView.world as unknown as world.WorldSync, loadedData, { + minY: worldView.minY, + height: worldView.worldHeight, // enableSkyLight: false, }) lightEngine.externalWorld.setBlock = () => {} @@ -23,9 +25,11 @@ export const createLightEngine = (world: WorldRendererCommon) => { } export const processLightChunk = async (x: number, z: number) => { + const engine = getLightEngineSafe() + if (!engine) return + const chunkX = Math.floor(x / 16) const chunkZ = Math.floor(z / 16) - const engine = getLightEngine() // fillColumnWithZeroLight(engine.externalWorld, chunkX, chunkZ) const updated = engine.receiveUpdateColumn(chunkX, chunkZ) @@ -46,7 +50,8 @@ export const getDebugLightValues = (x: number, y: number, z: number) => { } export const updateBlockLight = (x: number, y: number, z: number, stateId: number) => { - const engine = getLightEngine() + const engine = getLightEngineSafe() + if (!engine) return const affected = engine['affectedChunksTimestamps'] as Map const noAffected = affected.size === 0 engine.setBlock(x, y, z, convertPrismarineBlockToWorldBlock(stateId, loadedData)) @@ -59,3 +64,8 @@ export const updateBlockLight = (x: number, y: number, z: number, stateId: numbe return chunks } } + +export const destroyLightEngine = () => { + lightEngine = null + globalThis.lightEngine = null +} diff --git a/renderer/viewer/lib/mesher/mesher.ts b/renderer/viewer/lib/mesher/mesher.ts index 4f1885b1..3f06d60d 100644 --- a/renderer/viewer/lib/mesher/mesher.ts +++ b/renderer/viewer/lib/mesher/mesher.ts @@ -109,8 +109,6 @@ const handleMessage = data => { world.addColumn(data.x, data.z, data.chunk) if (data.lightData) { world.lightHolder.loadChunk(data.lightData) - } else { - console.warn('no light data', data.x, data.z) } if (data.customBlockModels) { const chunkKey = `${data.x},${data.z}` diff --git a/renderer/viewer/lib/worldDataEmitter.ts b/renderer/viewer/lib/worldDataEmitter.ts index 9b3da8aa..ceed72f0 100644 --- a/renderer/viewer/lib/worldDataEmitter.ts +++ b/renderer/viewer/lib/worldDataEmitter.ts @@ -9,7 +9,8 @@ import { proxy } from 'valtio' import TypedEmitter from 'typed-emitter' import { delayedIterator } from '../../playground/shared' import { chunkPos } from './simpleUtils' -import { processLightChunk, updateBlockLight } from './lightEngine' +import { createLightEngineIfNeeded, destroyLightEngine, processLightChunk, updateBlockLight } from './lightEngine' +import { WorldRendererConfig } from './worldrendererCommon' export type ChunkPosKey = string // like '16,16' type ChunkPos = { x: number, z: number } // like { x: 16, z: 16 } @@ -36,6 +37,11 @@ export type WorldDataEmitterEvents = { * It's up to the consumer to serialize the data if needed */ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter) { + minY = -64 + worldHeight = 384 + dimensionName = '' + + worldRendererConfig: WorldRendererConfig loadedChunks: Record readonly lastPos: Vec3 private eventListeners: Record = {} @@ -70,22 +76,22 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter | void - if (val) throw new Error('setBlockStateId returned promise (not supported)') - // const chunkX = Math.floor(position.x / 16) - // const chunkZ = Math.floor(position.z / 16) - // if (!this.loadedChunks[`${chunkX},${chunkZ}`] && !this.waitingSpiralChunksLoad[`${chunkX},${chunkZ}`]) { - // void this.loadChunk({ x: chunkX, z: chunkZ }) - // return - // } + // setBlockStateId (position: Vec3, stateId: number) { + // const val = this.world.setBlockStateId(position, stateId) as Promise | void + // if (val) throw new Error('setBlockStateId returned promise (not supported)') + // // const chunkX = Math.floor(position.x / 16) + // // const chunkZ = Math.floor(position.z / 16) + // // if (!this.loadedChunks[`${chunkX},${chunkZ}`] && !this.waitingSpiralChunksLoad[`${chunkX},${chunkZ}`]) { + // // void this.loadChunk({ x: chunkX, z: chunkZ }) + // // return + // // } - const updateChunks = updateBlockLight(position.x, position.y, position.z, stateId) ?? [] - this.emit('blockUpdate', { pos: position, stateId }) - for (const chunk of updateChunks) { - void this.loadChunk(new Vec3(chunk[0] * 16, 0, chunk[1] * 16), true, 'setBlockStateId light update') - } - } + // const updateChunks = this.worldRendererConfig.clientSideLighting ? updateBlockLight(position.x, position.y, position.z, stateId) ?? [] : [] + // this.emit('blockUpdate', { pos: position, stateId }) + // for (const chunk of updateChunks) { + // void this.loadChunk(new Vec3(chunk[0] * 16, 0, chunk[1] * 16), true, 'setBlockStateId light update') + // } + // } updateViewDistance (viewDistance: number) { this.viewDistance = viewDistance @@ -149,7 +155,7 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter { const stateId = newBlock.stateId ?? ((newBlock.type << 4) | newBlock.metadata) - const updateChunks = updateBlockLight(newBlock.position.x, newBlock.position.y, newBlock.position.z, stateId) ?? [] + const updateChunks = this.worldRendererConfig.clientSideLighting ? updateBlockLight(newBlock.position.x, newBlock.position.y, newBlock.position.z, stateId) ?? [] : [] this.emit('blockUpdate', { pos: newBlock.position, stateId }) for (const chunk of updateChunks) { void this.loadChunk(new Vec3(chunk[0] * 16, 0, chunk[1] * 16), true, 'setBlockStateId light update') @@ -162,15 +168,21 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter { - void this.updatePosition(bot.entity.position, true) + login () { + possiblyDimensionChange() }, respawn: () => { - void this.updatePosition(bot.entity.position, true) + possiblyDimensionChange() this.emitter.emit('onWorldSwitch') }, } satisfies Partial + const possiblyDimensionChange = () => { + this.minY = bot.game['minY'] ?? -64 + this.worldHeight = bot.game['height'] ?? 384 + this.dimensionName = bot.game['dimension'] ?? '' + void this.updatePosition(bot.entity.position, true) + } bot._client.on('update_light', ({ chunkX, chunkZ }) => { const chunkPos = new Vec3(chunkX * 16, 0, chunkZ * 16) @@ -218,6 +230,14 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter TypedEmitter TypedEmitter + if (!isLightUpdate && this.worldRendererConfig.clientSideLighting) { + result = await processLightChunk(pos.x, pos.z) ?? [] + } if (!result) return for (const affectedChunk of result) { if (affectedChunk.x === chunkX && affectedChunk.z === chunkZ) continue diff --git a/renderer/viewer/lib/worldrendererCommon.ts b/renderer/viewer/lib/worldrendererCommon.ts index 6da45d3f..e91f8925 100644 --- a/renderer/viewer/lib/worldrendererCommon.ts +++ b/renderer/viewer/lib/worldrendererCommon.ts @@ -19,7 +19,7 @@ import { chunkPos } from './simpleUtils' import { addNewStat, removeAllStats, removeStat, updatePanesVisibility, updateStatText } from './ui/newStats' import { WorldDataEmitter } from './worldDataEmitter' import { IPlayerState } from './basePlayerState' -import { createLightEngine, dumpLightData, getLightEngine, getLightEngineSafe } from './lightEngine' +import { createLightEngineIfNeeded, dumpLightData, getLightEngine, getLightEngineSafe } from './lightEngine' import { MesherLogReader } from './mesherlogReader' import { setSkinsConfig } from './utils/skins' @@ -173,8 +173,6 @@ export abstract class WorldRendererCommon } constructor (public readonly resourcesManager: ResourcesManager, public displayOptions: DisplayWorldOptions, public initOptions: GraphicsInitOptions) { - createLightEngine(this) - this.snapshotInitialValues() this.worldRendererConfig = displayOptions.inWorldRenderingConfig this.playerState = displayOptions.playerState @@ -1003,5 +1001,7 @@ export abstract class WorldRendererCommon this.displayOptions.worldView.removeAllListeners() // todo this.abortController.abort() removeAllStats() + + this.displayOptions.worldView.destroy() } } diff --git a/src/appViewer.ts b/src/appViewer.ts index ca62bd1b..f3969c60 100644 --- a/src/appViewer.ts +++ b/src/appViewer.ts @@ -171,6 +171,7 @@ export class AppViewer { this.currentDisplay = 'world' const startPosition = playerStateSend.getPosition() this.worldView = new WorldDataEmitter(world, renderDistance, startPosition) + this.worldView.worldRendererConfig = this.inWorldRenderingConfig window.worldView = this.worldView watchOptionsAfterWorldViewInit(this.worldView) diff --git a/src/optionsGuiScheme.tsx b/src/optionsGuiScheme.tsx index 39114e3b..2afc7a24 100644 --- a/src/optionsGuiScheme.tsx +++ b/src/optionsGuiScheme.tsx @@ -86,6 +86,7 @@ export const guiOptionsScheme: { values: [ ['prefer-server', 'Prefer Server'], ['always-client', 'Always Client'], + ['always-server', 'Always Server'], ], }, lowMemoryMode: { From f4f5eddce0d66990d76658b51fdd145e1a130da9 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 30 Apr 2025 09:37:25 +0300 Subject: [PATCH 13/98] fix lighting disabling todo do sky light update re-rendering when server: engine skip calc but stores 15 sky levels, it does recalc in worker and merge updated values --- src/mineflayer/playerState.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/mineflayer/playerState.ts b/src/mineflayer/playerState.ts index d16bedf3..1ab33feb 100644 --- a/src/mineflayer/playerState.ts +++ b/src/mineflayer/playerState.ts @@ -56,9 +56,7 @@ export class PlayerStateManager implements IPlayerState { let hasSkyLight = 1 try { hasSkyLight = data.dimension.value.has_skylight.value - } catch (err) { - hasSkyLight = 0 - } + } catch {} this.lightingDisabled = bot.game.dimension === 'the_nether' || bot.game.dimension === 'the_end' || !hasSkyLight } From c97c7e0cc056b3bf48ffe00c444b3dfa085b75a0 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 1 May 2025 15:11:28 +0300 Subject: [PATCH 14/98] finish combined computation, finish settings and strategies todo: worker, horizontal sky fix, FIX SKY LEVEL from time!, fix block light blocking. OTHER done --- README.MD | 3 +- package.json | 4 +- pnpm-lock.yaml | 10 ++-- renderer/viewer/lib/basePlayerState.ts | 1 + renderer/viewer/lib/lightEngine.ts | 41 ++++++++++++----- renderer/viewer/lib/mesher/models.ts | 3 ++ renderer/viewer/lib/mesher/shared.ts | 3 +- renderer/viewer/lib/mesher/world.ts | 35 ++++++++------ renderer/viewer/lib/worldDataEmitter.ts | 13 ++++-- renderer/viewer/lib/worldrendererCommon.ts | 33 +++++++------ renderer/viewer/three/worldrendererThree.ts | 51 ++++++++++++++++++--- src/dayCycle.ts | 2 +- src/mineflayer/playerState.ts | 6 ++- src/optionsGuiScheme.tsx | 4 ++ src/optionsStorage.ts | 12 ++++- src/react/DebugOverlay.tsx | 10 ++-- src/watchOptions.ts | 26 +++++++++-- 17 files changed, 181 insertions(+), 76 deletions(-) diff --git a/README.MD b/README.MD index aa36f7e8..8bc67319 100644 --- a/README.MD +++ b/README.MD @@ -12,9 +12,10 @@ For building the project yourself / contributing, see [Development, Debugging & ### Big Features +- Connect to Java servers running in both offline (cracked) and online mode* (it's possible because of proxy servers, see below) +- Combined Lighting System - Server Parsing + Client Side Engine for block updates - Official Mineflayer [plugin integration](https://github.com/zardoy/mcraft-fun-mineflayer-plugin)! View / Control your bot remotely. - Open any zip world file or even folder in read-write mode! -- Connect to Java servers running in both offline (cracked) and online mode* (it's possible because of proxy servers, see below) - Integrated JS server clone capable of opening Java world saves in any way (folders, zip, web chunks streaming, etc) - Singleplayer mode with simple world generations! - Works offline diff --git a/package.json b/package.json index 1522ed47..5b9f2a3f 100644 --- a/package.json +++ b/package.json @@ -153,6 +153,7 @@ "https-browserify": "^1.0.0", "mc-assets": "^0.2.53", "minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next", + "minecraft-lighting": "^0.0.9", "mineflayer": "github:GenerelSchwerz/mineflayer", "mineflayer-mouse": "^0.1.9", "mineflayer-pathfinder": "^2.4.4", @@ -169,8 +170,7 @@ "timers-browserify": "^2.0.12", "typescript": "5.5.4", "vitest": "^0.34.6", - "yaml": "^2.3.2", - "minecraft-lighting": "^0.0.8" + "yaml": "^2.3.2" }, "optionalDependencies": { "cypress": "^10.11.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a3f636c4..64ab6e27 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -343,8 +343,8 @@ importers: specifier: github:zardoy/minecraft-inventory-gui#next version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/98bc5bb8ee6da8b4b771c05b404cee796318ccd4(@types/react@18.3.18)(react@18.3.1) minecraft-lighting: - specifier: ^0.0.8 - version: 0.0.8 + specifier: ^0.0.9 + version: 0.0.9 mineflayer: specifier: github:GenerelSchwerz/mineflayer version: https://codeload.github.com/GenerelSchwerz/mineflayer/tar.gz/d459d2ed76a997af1a7c94718ed7d5dee4478b8a(encoding@0.1.13) @@ -6680,8 +6680,8 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/98bc5bb8ee6da8b4b771c05b404cee796318ccd4} version: 1.0.1 - minecraft-lighting@0.0.8: - resolution: {integrity: sha512-HEew4qycmHv2r5+FR4vZ81YJkOR9Q8jbpj6nO9nEoiKRFvo9HamcbA6uQmSxJZKkkgt1uOp/EQ61rQXFw3nnGQ==} + minecraft-lighting@0.0.9: + resolution: {integrity: sha512-2ma3m3ia4YmcAFHlZ9BGo4CSVyXL2TltPoXZSeYHmgstGuZwECHzOMQOKjUEzCnQs11X9Rnqz9Dum+Y17bsbyA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284: @@ -17323,7 +17323,7 @@ snapshots: - '@types/react' - react - minecraft-lighting@0.0.8: + minecraft-lighting@0.0.9: dependencies: vec3: 0.1.10 diff --git a/renderer/viewer/lib/basePlayerState.ts b/renderer/viewer/lib/basePlayerState.ts index ff7617ef..856772b1 100644 --- a/renderer/viewer/lib/basePlayerState.ts +++ b/renderer/viewer/lib/basePlayerState.ts @@ -32,6 +32,7 @@ export interface IPlayerState { username?: string onlineMode?: boolean lightingDisabled?: boolean + shouldHideHand?: boolean events: TypedEmitter diff --git a/renderer/viewer/lib/lightEngine.ts b/renderer/viewer/lib/lightEngine.ts index 84fbc820..23a0c1bb 100644 --- a/renderer/viewer/lib/lightEngine.ts +++ b/renderer/viewer/lib/lightEngine.ts @@ -16,7 +16,8 @@ export const createLightEngineIfNeeded = (worldView: WorldDataEmitter) => { if (lightEngine) return lightEngine = createLightEngineForSyncWorld(worldView.world as unknown as world.WorldSync, loadedData, { minY: worldView.minY, - height: worldView.worldHeight, + height: worldView.minY + worldView.worldHeight, + // writeLightToOriginalWorld: true, // enableSkyLight: false, }) lightEngine.externalWorld.setBlock = () => {} @@ -49,20 +50,36 @@ export const getDebugLightValues = (x: number, y: number, z: number) => { } } -export const updateBlockLight = (x: number, y: number, z: number, stateId: number) => { +export const updateBlockLight = async (x: number, y: number, z: number, stateId: number, distance: number) => { + if (distance > 16) return [] + const chunkX = Math.floor(x / 16) * 16 + const chunkZ = Math.floor(z / 16) * 16 const engine = getLightEngineSafe() if (!engine) return - const affected = engine['affectedChunksTimestamps'] as Map - const noAffected = affected.size === 0 - engine.setBlock(x, y, z, convertPrismarineBlockToWorldBlock(stateId, loadedData)) + const result = await engine.setBlockUpdateChunkIfNeeded(x, y, z) + if (!result) return + console.log(`[light engine] updateBlockLight (${x}, ${y}, ${z}) took`, Math.round(result.time), 'ms', result.affectedChunks?.length ?? 0, 'chunks') + return result.affectedChunks - if (affected.size > 0) { - const chunks = [...affected.keys()].map(key => { - return key.split(',').map(Number) as [number, number] - }) - affected.clear() - return chunks - } + // const engine = getLightEngineSafe() + // if (!engine) return + // const affected = engine['affectedChunksTimestamps'] as Map + // const noAffected = affected.size === 0 + // engine.setBlock(x, y, z, convertPrismarineBlockToWorldBlock(stateId, loadedData)) + + // if (affected.size > 0) { + // const chunks = [...affected.keys()].map(key => { + // return key.split(',').map(Number) as [number, number] + // }) + // affected.clear() + // return chunks + // } +} + +export const lightRemoveColumn = (x: number, z: number) => { + const engine = getLightEngineSafe() + if (!engine) return + engine.columnCleanup(Math.floor(x / 16), Math.floor(z / 16)) } export const destroyLightEngine = () => { diff --git a/renderer/viewer/lib/mesher/models.ts b/renderer/viewer/lib/mesher/models.ts index 802ecaf0..f743dfb4 100644 --- a/renderer/viewer/lib/mesher/models.ts +++ b/renderer/viewer/lib/mesher/models.ts @@ -488,6 +488,7 @@ const isBlockWaterlogged = (block: Block) => { let unknownBlockModel: BlockModelPartsResolved export function getSectionGeometry (sx, sy, sz, world: World) { + world.hadSkyLight = false let delayedRender = [] as Array<() => void> const attr: MesherGeometryOutput = { @@ -691,6 +692,8 @@ export function getSectionGeometry (sx, sy, sz, world: World) { delete attr.uvs } + attr.hasSkylight = world.hadSkyLight + return attr } diff --git a/renderer/viewer/lib/mesher/shared.ts b/renderer/viewer/lib/mesher/shared.ts index 71140096..380be30e 100644 --- a/renderer/viewer/lib/mesher/shared.ts +++ b/renderer/viewer/lib/mesher/shared.ts @@ -7,7 +7,7 @@ export const defaultMesherConfig = { enableLighting: true, skyLight: 15, smoothLighting: true, - clientSideLighting: false, + usingCustomLightHolder: false, flyingSquidWorkarounds: false, outputFormat: 'threeJs' as 'threeJs' | 'webgpu', @@ -48,6 +48,7 @@ export type MesherGeometryOutput = { hadErrors: boolean blocksCount: number customBlockModels?: CustomBlockModels + hasSkylight?: boolean } export type HighestBlockInfo = { y: number, stateId: number | undefined, biomeId: number | undefined } diff --git a/renderer/viewer/lib/mesher/world.ts b/renderer/viewer/lib/mesher/world.ts index 22d27129..675ceca0 100644 --- a/renderer/viewer/lib/mesher/world.ts +++ b/renderer/viewer/lib/mesher/world.ts @@ -33,6 +33,7 @@ export type WorldBlock = Omit & { } export class World { + hadSkyLight = false lightHolder = new WorldLightHolder(0, 0) config = defaultMesherConfig Chunk: typeof import('prismarine-chunk/types/index').PCChunk @@ -59,11 +60,11 @@ export class World { const IS_USING_LOCAL_SERVER_LIGHTING = this.config.flyingSquidWorkarounds // const IS_USING_SERVER_LIGHTING = false - const { enableLighting, skyLight, clientSideLighting } = this.config + const { enableLighting, skyLight, usingCustomLightHolder } = this.config if (!enableLighting) return 15 const column = this.getColumnByPos(pos) if (!column) return 15 - if (!clientSideLighting && !hasChunkSection(column, pos)) return 2 + if (!usingCustomLightHolder && !hasChunkSection(column, pos)) return 2 let result = Math.max( 2, Math.min( @@ -95,23 +96,29 @@ export class World { } getBlockLight (pos: Vec3) { - if (!this.config.clientSideLighting) { - const column = this.getColumnByPos(pos) - if (!column) return 15 - return column.getBlockLight(posInChunk(pos)) - } + // if (this.config.clientSideLighting) { + // return this.lightHolder.getBlockLight(pos.x, pos.y, pos.z) + // } - return this.lightHolder.getBlockLight(pos.x, pos.y, pos.z) + const column = this.getColumnByPos(pos) + if (!column) return 15 + return column.getBlockLight(posInChunk(pos)) } getSkyLight (pos: Vec3) { - if (!this.config.clientSideLighting) { - const column = this.getColumnByPos(pos) - if (!column) return 15 - return column.getSkyLight(posInChunk(pos)) - } + const result = this.getSkyLightInner(pos) + if (result > 2) this.hadSkyLight = true + return result + } - return this.lightHolder.getSkyLight(pos.x, pos.y, pos.z) + getSkyLightInner (pos: Vec3) { + // if (this.config.clientSideLighting) { + // return this.lightHolder.getSkyLight(pos.x, pos.y, pos.z) + // } + + const column = this.getColumnByPos(pos) + if (!column) return 15 + return column.getSkyLight(posInChunk(pos)) } addColumn (x, z, json) { diff --git a/renderer/viewer/lib/worldDataEmitter.ts b/renderer/viewer/lib/worldDataEmitter.ts index ceed72f0..4b5039dd 100644 --- a/renderer/viewer/lib/worldDataEmitter.ts +++ b/renderer/viewer/lib/worldDataEmitter.ts @@ -9,7 +9,7 @@ import { proxy } from 'valtio' import TypedEmitter from 'typed-emitter' import { delayedIterator } from '../../playground/shared' import { chunkPos } from './simpleUtils' -import { createLightEngineIfNeeded, destroyLightEngine, processLightChunk, updateBlockLight } from './lightEngine' +import { createLightEngineIfNeeded, destroyLightEngine, lightRemoveColumn, processLightChunk, updateBlockLight } from './lightEngine' import { WorldRendererConfig } from './worldrendererCommon' export type ChunkPosKey = string // like '16,16' @@ -152,13 +152,15 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter { this.unloadChunk(pos) }, - blockUpdate: (oldBlock, newBlock) => { + blockUpdate: async (oldBlock, newBlock) => { + if (typeof newBlock.stateId === 'number' && oldBlock?.stateId === newBlock.stateId) return const stateId = newBlock.stateId ?? ((newBlock.type << 4) | newBlock.metadata) + const distance = newBlock.position.distanceTo(this.lastPos) - const updateChunks = this.worldRendererConfig.clientSideLighting ? updateBlockLight(newBlock.position.x, newBlock.position.y, newBlock.position.z, stateId) ?? [] : [] this.emit('blockUpdate', { pos: newBlock.position, stateId }) + const updateChunks = this.worldRendererConfig.clientSideLighting === 'none' ? [] : await updateBlockLight(newBlock.position.x, newBlock.position.y, newBlock.position.z, stateId, distance) ?? [] for (const chunk of updateChunks) { - void this.loadChunk(new Vec3(chunk[0] * 16, 0, chunk[1] * 16), true, 'setBlockStateId light update') + void this.loadChunk(new Vec3(chunk.x * 16, 0, chunk.z * 16), true, 'setBlockStateId light update') } }, time: () => { @@ -308,7 +310,7 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter - if (!isLightUpdate && this.worldRendererConfig.clientSideLighting) { + if (!isLightUpdate && this.worldRendererConfig.clientSideLighting === 'full') { result = await processLightChunk(pos.x, pos.z) ?? [] } if (!result) return @@ -359,6 +361,7 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter { + skyLight = 15 worldReadyResolvers = Promise.withResolvers() worldReadyPromise = this.worldReadyResolvers.promise timeOfTheDay = 0 @@ -485,6 +487,8 @@ export abstract class WorldRendererCommon timeUpdated? (newTime: number): void + skylightUpdated? (): void + updateViewerPosition (pos: Vec3) { this.viewerPosition = pos for (const [key, value] of Object.entries(this.loadedChunks)) { @@ -532,7 +536,7 @@ export abstract class WorldRendererCommon this.sendMesherMcData() } - getMesherConfig (): MesherConfig { + changeSkyLight () { let skyLight = 15 const timeOfDay = this.timeOfTheDay if (timeOfDay < 0 || timeOfDay > 24_000) { @@ -545,18 +549,21 @@ export abstract class WorldRendererCommon skyLight = ((timeOfDay - 12_000) / 6000) * 15 } - skyLight = Math.floor(skyLight) + this.skyLight = Math.floor(skyLight) + } + + getMesherConfig (): MesherConfig { return { version: this.version, enableLighting: this.worldRendererConfig.enableLighting && !this.playerState.lightingDisabled, - skyLight, + skyLight: this.skyLight, smoothLighting: this.worldRendererConfig.smoothLighting, outputFormat: this.outputFormat, textureSize: this.resourcesManager.currentResources!.blocksAtlasParser.atlas.latest.width, debugModelVariant: undefined, clipWorldBelowY: this.worldRendererConfig.clipWorldBelowY, disableSignsMapsSupport: !this.worldRendererConfig.extraBlockRenderers, - clientSideLighting: this.worldRendererConfig.clientSideLighting, + usingCustomLightHolder: false, flyingSquidWorkarounds: this.worldRendererConfig.flyingSquidWorkarounds } } @@ -789,19 +796,17 @@ export abstract class WorldRendererCommon }) worldEmitter.on('time', (timeOfDay) => { - this.timeUpdated?.(timeOfDay) - if (timeOfDay < 0 || timeOfDay > 24_000) { throw new Error('Invalid time of day. It should be between 0 and 24000.') } + const oldSkyLight = this.skyLight this.timeOfTheDay = timeOfDay - - // if (this.worldRendererConfig.skyLight === skyLight) return - // this.worldRendererConfig.skyLight = skyLight - // if (this instanceof WorldRendererThree) { - // (this).rerenderAllChunks?.() - // } + this.changeSkyLight() + if (oldSkyLight !== this.skyLight) { + this.skylightUpdated?.() + } + this.timeUpdated?.(timeOfDay) }) worldEmitter.emit('listening') @@ -896,7 +901,7 @@ export abstract class WorldRendererCommon this.reactiveState.world.mesherWork = true const distance = this.getDistance(pos) // todo shouldnt we check loadedChunks instead? - if (!this.workers.length || distance[0] > this.viewDistance || distance[1] > this.viewDistance) return + // if (!this.workers.length || distance[0] > this.viewDistance || distance[1] > this.viewDistance) return const key = `${Math.floor(pos.x / 16) * 16},${Math.floor(pos.y / 16) * 16},${Math.floor(pos.z / 16) * 16}` // if (this.sectionsOutstanding.has(key)) return this.renderUpdateEmitter.emit('dirty', pos, value) diff --git a/renderer/viewer/three/worldrendererThree.ts b/renderer/viewer/three/worldrendererThree.ts index 4c031268..9407d20c 100644 --- a/renderer/viewer/three/worldrendererThree.ts +++ b/renderer/viewer/three/worldrendererThree.ts @@ -31,7 +31,7 @@ type SectionKey = string export class WorldRendererThree extends WorldRendererCommon { outputFormat = 'threeJs' as const - sectionObjects: Record = {} + sectionObjects: Record = {} chunkTextures = new Map() signsCache = new Map() starField: StarField @@ -151,13 +151,19 @@ export class WorldRendererThree extends WorldRendererCommon { }) this.onReactiveValueUpdated('ambientLight', (value) => { if (!value) return - // this.ambientLight.intensity = value - this.ambientLight.intensity = 1 + if (this.worldRendererConfig.legacyLighting) { + this.ambientLight.intensity = value + } else { + this.ambientLight.intensity = 1 + } }) this.onReactiveValueUpdated('directionalLight', (value) => { if (!value) return - // this.directionalLight.intensity = value - this.directionalLight.intensity = 0.4 + if (this.worldRendererConfig.legacyLighting) { + this.directionalLight.intensity = value + } else { + this.directionalLight.intensity = 0.4 + } }) this.onReactiveValueUpdated('lookingAtBlock', (value) => { this.cursorBlock.setHighlightCursorBlock(value ? new Vec3(value.x, value.y, value.z) : null, value?.shapes) @@ -238,10 +244,38 @@ export class WorldRendererThree extends WorldRendererCommon { } } + skylightUpdated (): void { + let updated = 0 + for (const sectionKey of Object.keys(this.sectionObjects)) { + if (this.sectionObjects[sectionKey].hasSkylight) { + // set section to be updated + const [x, y, z] = sectionKey.split(',').map(Number) + this.setSectionDirty(new Vec3(x, y, z)) + updated++ + } + } + + console.log(`Skylight changed to ${this.skyLight}. Updated`, updated, 'sections') + } + getItemRenderData (item: Record, specificProps: ItemSpecificContextProperties) { return getItemUv(item, specificProps, this.resourcesManager) } + debugOnlySunlightSections (enable: boolean, state = true) { + for (const sectionKey of Object.keys(this.sectionObjects)) { + if (!enable) { + this.sectionObjects[sectionKey].visible = true + continue + } + if (this.sectionObjects[sectionKey].hasSkylight) { + this.sectionObjects[sectionKey].visible = state + } else { + this.sectionObjects[sectionKey].visible = false + } + } + } + async demoModel () { //@ts-expect-error const pos = cursorBlockRel(0, 1, 0).position @@ -328,7 +362,7 @@ export class WorldRendererThree extends WorldRendererCommon { // debugRecomputedDeletedObjects = 0 handleWorkerMessage (data: { geometry: MesherGeometryOutput, key, type }): void { if (data.type !== 'geometry') return - let object: THREE.Object3D = this.sectionObjects[data.key] + let object = this.sectionObjects[data.key] if (object) { this.scene.remove(object) disposeObject(object) @@ -387,7 +421,10 @@ export class WorldRendererThree extends WorldRendererCommon { object.add(head) } } + + object.hasSkylight = data.geometry.hasSkylight this.sectionObjects[data.key] = object + if (this.displayOptions.inWorldRenderingConfig._renderByChunks) { object.visible = false const chunkKey = `${chunkCoords[0]},${chunkCoords[2]}` @@ -472,7 +509,7 @@ export class WorldRendererThree extends WorldRendererCommon { 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) - if (this.displayOptions.inWorldRenderingConfig.showHand/* && !this.freeFlyMode */) { + if (this.displayOptions.inWorldRenderingConfig.showHand && !this.playerState.shouldHideHand /* && !this.freeFlyMode */) { this.holdingBlock.render(this.camera, this.renderer, this.ambientLight, this.directionalLight) this.holdingBlockLeft.render(this.camera, this.renderer, this.ambientLight, this.directionalLight) } diff --git a/src/dayCycle.ts b/src/dayCycle.ts index 5b2725e2..9e3c731f 100644 --- a/src/dayCycle.ts +++ b/src/dayCycle.ts @@ -10,7 +10,7 @@ export default () => { const night = 13_500 const morningStart = 23_000 const morningEnd = 23_961 - const timeProgress = options.dayCycleAndLighting ? bot.time.timeOfDay : 0 + const timeProgress = options.dayCycle ? bot.time.timeOfDay : 0 // todo check actual colors const dayColorRainy = { r: 111 / 255, g: 156 / 255, b: 236 / 255 } diff --git a/src/mineflayer/playerState.ts b/src/mineflayer/playerState.ts index 1ab33feb..16739c86 100644 --- a/src/mineflayer/playerState.ts +++ b/src/mineflayer/playerState.ts @@ -91,6 +91,10 @@ export class PlayerStateManager implements IPlayerState { this.reactive.gameMode = bot.game?.gameMode } + get shouldHideHand () { + return this.reactive.gameMode === 'spectator' + } + // #region Movement and Physics State private updateState () { if (!bot?.entity || this.disableStateUpdates) return @@ -134,7 +138,7 @@ export class PlayerStateManager implements IPlayerState { } getEyeHeight (): number { - return bot.controlState.sneak ? 1.27 : 1.62 + return bot.controlState.sneak && !this.isFlying() ? 1.27 : 1.62 } isOnGround (): boolean { diff --git a/src/optionsGuiScheme.tsx b/src/optionsGuiScheme.tsx index 2afc7a24..7c9bb7b3 100644 --- a/src/optionsGuiScheme.tsx +++ b/src/optionsGuiScheme.tsx @@ -81,6 +81,10 @@ export const guiOptionsScheme: { custom () { return Experimental }, + experimentalLightingV1: { + text: 'Experimental Lighting', + tooltip: 'Once stable this setting will be removed and always enabled', + }, smoothLighting: {}, lightingStrategy: { values: [ diff --git a/src/optionsStorage.ts b/src/optionsStorage.ts index 962559b0..8aca1da8 100644 --- a/src/optionsStorage.ts +++ b/src/optionsStorage.ts @@ -8,6 +8,9 @@ import { miscUiState } from './globalState' const isDev = process.env.NODE_ENV === 'development' const initialAppConfig = process.env?.INLINED_APP_CONFIG as AppConfig ?? {} +// todo +const IS_BETA_TESTER = location.hostname.startsWith('s.') || location.hostname.startsWith('beta.') + const defaultOptions = { renderDistance: 3, keepChunksDistance: 1, @@ -45,7 +48,9 @@ const defaultOptions = { /** @unstable */ debugLogNotFrequentPackets: false, unimplementedContainers: false, - dayCycleAndLighting: true, + dayCycle: true, + // experimentalLighting: IS_BETA_TESTER, + experimentalLightingV1: location.hostname.startsWith('lighting.'), loadPlayerSkins: true, renderEars: true, lowMemoryMode: false, @@ -170,6 +175,11 @@ export const disabledSettings = proxy({ }) const migrateOptions = (options: Partial>) => { + if (options.dayCycleAndLighting) { + delete options.dayCycleAndLighting + options.dayCycle = options.dayCycleAndLighting + } + if (options.highPerformanceGpu) { options.gpuPreference = 'high-performance' delete options.highPerformanceGpu diff --git a/src/react/DebugOverlay.tsx b/src/react/DebugOverlay.tsx index 56950db2..fb4feb19 100644 --- a/src/react/DebugOverlay.tsx +++ b/src/react/DebugOverlay.tsx @@ -129,17 +129,13 @@ export default () => { const freqUpdateInterval = setInterval(() => { const lightingEnabled = appViewer.inWorldRenderingConfig.enableLighting - const serverLighting = !appViewer.inWorldRenderingConfig.clientSideLighting - if (!lightingEnabled || serverLighting) { + const { clientSideLighting } = appViewer.inWorldRenderingConfig + if (!lightingEnabled) { setLightInfo({ sky: bot.world.getSkyLight(bot.entity.position), block: bot.world.getBlockLight(bot.entity.position), - info: lightingEnabled ? 'Server Lighting' : 'Lighting Disabled' + info: lightingEnabled ? clientSideLighting === 'none' ? 'Server Lighting' : 'Server + Client Engine' : 'Lighting Disabled' }) - } else { - // client engine is used - const lights = getDebugLightValues(Math.floor(bot.entity.position.x), Math.floor(bot.entity.position.y), Math.floor(bot.entity.position.z)) - setLightInfo({ sky: lights.skyLight, block: lights.blockLight, info: 'Client Light Engine' }) } setPos({ ...bot.entity.position }) diff --git a/src/watchOptions.ts b/src/watchOptions.ts index 42bb26f6..24e51a81 100644 --- a/src/watchOptions.ts +++ b/src/watchOptions.ts @@ -96,16 +96,32 @@ export const watchOptionsAfterViewerInit = () => { const updateLightingStrategy = () => { if (!bot) return + if (!options.experimentalLightingV1) { + appViewer.inWorldRenderingConfig.clientSideLighting = 'none' + appViewer.inWorldRenderingConfig.enableLighting = false + appViewer.inWorldRenderingConfig.legacyLighting = true + return + } + + const lightingEnabled = options.dayCycle + if (!lightingEnabled) { + appViewer.inWorldRenderingConfig.clientSideLighting = 'none' + appViewer.inWorldRenderingConfig.enableLighting = false + return + } + + appViewer.inWorldRenderingConfig.legacyLighting = false + // for now ignore saved lighting to allow proper updates and singleplayer created worlds // appViewer.inWorldRenderingConfig.flyingSquidWorkarounds = miscUiState.flyingSquid const serverParsingSupported = miscUiState.flyingSquid ? /* !bot.supportFeature('blockStateId') */false : bot.supportFeature('blockStateId') - const serverLightingEnabled = serverParsingSupported && (options.lightingStrategy === 'prefer-server' || options.lightingStrategy === 'always-server') - const clientLightingEnabled = options.lightingStrategy === 'prefer-server' ? !serverParsingSupported : options.lightingStrategy === 'always-client' + const serverLightingPossible = serverParsingSupported && (options.lightingStrategy === 'prefer-server' || options.lightingStrategy === 'always-server') + const clientLightingPossible = options.lightingStrategy !== 'always-server' - const clientSideLighting = !serverLightingEnabled - appViewer.inWorldRenderingConfig.clientSideLighting = clientSideLighting - appViewer.inWorldRenderingConfig.enableLighting = options.dayCycleAndLighting && (clientLightingEnabled || serverLightingEnabled) + const clientSideLighting = !serverLightingPossible + appViewer.inWorldRenderingConfig.clientSideLighting = serverLightingPossible && clientLightingPossible ? 'partial' : clientSideLighting ? 'full' : 'none' + appViewer.inWorldRenderingConfig.enableLighting = serverLightingPossible || clientLightingPossible } subscribeKey(options, 'lightingStrategy', updateLightingStrategy) From c4b9c33a3bd3f6a12a3741409c967d938ea0ad6a Mon Sep 17 00:00:00 2001 From: Vitaly Date: Thu, 1 May 2025 15:26:22 +0300 Subject: [PATCH 15/98] Update src/optionsStorage.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/optionsStorage.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/optionsStorage.ts b/src/optionsStorage.ts index 8aca1da8..f027ddf3 100644 --- a/src/optionsStorage.ts +++ b/src/optionsStorage.ts @@ -103,7 +103,12 @@ const defaultOptions = { showCursorBlockInSpectator: false, renderEntities: true, smoothLighting: true, - // lightingStrategy: 'prefer-server' as 'only-server' | 'always-client' | 'prefer-server', + /** + * Controls how lighting is calculated and rendered: + * - 'always-client': Always use client-side lighting engine for all light calculations + * - 'prefer-server': Use server lighting data when available, fallback to client-side calculations + * - 'always-server': Only use lighting data from the server, disable client-side calculations + */ lightingStrategy: 'prefer-server' as 'always-client' | 'prefer-server' | 'always-server', chatSelect: true, autoJump: 'auto' as 'auto' | 'always' | 'never', From d6f394fe204f80172a14ed7e74f9773abb188400 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 2 May 2025 12:20:25 +0300 Subject: [PATCH 16/98] hide cursor block in spectator --- src/mineflayer/plugins/mouse.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/mineflayer/plugins/mouse.ts b/src/mineflayer/plugins/mouse.ts index 4e82b770..fc1ce0fd 100644 --- a/src/mineflayer/plugins/mouse.ts +++ b/src/mineflayer/plugins/mouse.ts @@ -10,7 +10,7 @@ import { sendVideoInteraction, videoCursorInteraction } from '../../customChanne function cursorBlockDisplay (bot: Bot) { const updateCursorBlock = (data?: { block: Block }) => { - if (!data?.block) { + if (!data?.block || bot.game.gameMode === 'spectator') { playerState.reactive.lookingAtBlock = undefined return } @@ -27,6 +27,10 @@ function cursorBlockDisplay (bot: Bot) { } bot.on('highlightCursorBlock', updateCursorBlock) + bot.on('game', () => { + const block = bot.mouse.getCursorState().cursorBlock + updateCursorBlock(block ? { block } : undefined) + }) bot.on('blockBreakProgressStage', (block, stage) => { const mergedShape = bot.mouse.getMergedCursorShape(block) From ddf08107f2eaf834f85e22f0d19c77dd0b904a5b Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 4 May 2025 12:24:45 +0300 Subject: [PATCH 17/98] final step: move engine to another thread --- renderer/viewer/lib/lightEngine.ts | 48 +++++++++++++++------- renderer/viewer/lib/worldDataEmitter.ts | 22 ++++++---- renderer/viewer/lib/worldrendererCommon.ts | 6 +-- src/react/DebugOverlay.tsx | 24 +++++++---- src/shims/minecraftData.ts | 11 ++++- 5 files changed, 74 insertions(+), 37 deletions(-) diff --git a/renderer/viewer/lib/lightEngine.ts b/renderer/viewer/lib/lightEngine.ts index 23a0c1bb..6e4f0341 100644 --- a/renderer/viewer/lib/lightEngine.ts +++ b/renderer/viewer/lib/lightEngine.ts @@ -1,15 +1,18 @@ -import { LightWorld, createLightEngineForSyncWorld, convertPrismarineBlockToWorldBlock } from 'minecraft-lighting' +import { LightWorld, createLightEngineForSyncWorld, convertPrismarineBlockToWorldBlock, createPrismarineLightEngineWorker } from 'minecraft-lighting' import { world } from 'prismarine-world' -import { WorldRendererCommon } from './worldrendererCommon' +// import PrismarineWorker from 'minecraft-lighting/dist/prismarineWorker.worker.js' import { WorldDataEmitter } from './worldDataEmitter' let lightEngine: LightWorld | null = null +let lightEngineNew: ReturnType | null = null + export const getLightEngine = () => { if (!lightEngine) throw new Error('Light engine not initialized') return lightEngine } export const getLightEngineSafe = () => { - return lightEngine + // return lightEngine + return lightEngineNew } export const createLightEngineIfNeeded = (worldView: WorldDataEmitter) => { @@ -25,7 +28,21 @@ export const createLightEngineIfNeeded = (worldView: WorldDataEmitter) => { globalThis.lightEngine = lightEngine } -export const processLightChunk = async (x: number, z: number) => { +export const createLightEngineIfNeededNew = (worldView: WorldDataEmitter) => { + if (lightEngineNew) return + const worker = new Worker(new URL('minecraft-lighting/dist/prismarineWorker.worker.js', import.meta.url)) + lightEngineNew = createPrismarineLightEngineWorker(worker, worldView.world as unknown as world.WorldSync, loadedData) + lightEngineNew.initialize({ + minY: worldView.minY, + height: worldView.minY + worldView.worldHeight, + // writeLightToOriginalWorld: true, + // enableSkyLight: false, + }) + + globalThis.lightEngine = lightEngineNew +} + +export const processLightChunk = async (x: number, z: number, doLighting: boolean) => { const engine = getLightEngineSafe() if (!engine) return @@ -33,21 +50,21 @@ export const processLightChunk = async (x: number, z: number) => { const chunkZ = Math.floor(z / 16) // fillColumnWithZeroLight(engine.externalWorld, chunkX, chunkZ) - const updated = engine.receiveUpdateColumn(chunkX, chunkZ) + const updated = await engine.loadChunk(chunkX, chunkZ, doLighting) return updated } export const dumpLightData = (x: number, z: number) => { const engine = getLightEngineSafe() - return engine?.worldLightHolder.dumpChunk(Math.floor(x / 16), Math.floor(z / 16)) + // return engine?.worldLightHolder.dumpChunk(Math.floor(x / 16), Math.floor(z / 16)) } export const getDebugLightValues = (x: number, y: number, z: number) => { const engine = getLightEngineSafe() - return { - blockLight: engine?.worldLightHolder.getBlockLight(x, y, z) ?? -1, - skyLight: engine?.worldLightHolder.getSkyLight(x, y, z) ?? -1, - } + // return { + // blockLight: engine?.worldLightHolder.getBlockLight(x, y, z) ?? -1, + // skyLight: engine?.worldLightHolder.getSkyLight(x, y, z) ?? -1, + // } } export const updateBlockLight = async (x: number, y: number, z: number, stateId: number, distance: number) => { @@ -56,10 +73,11 @@ export const updateBlockLight = async (x: number, y: number, z: number, stateId: const chunkZ = Math.floor(z / 16) * 16 const engine = getLightEngineSafe() if (!engine) return - const result = await engine.setBlockUpdateChunkIfNeeded(x, y, z) - if (!result) return - console.log(`[light engine] updateBlockLight (${x}, ${y}, ${z}) took`, Math.round(result.time), 'ms', result.affectedChunks?.length ?? 0, 'chunks') - return result.affectedChunks + const start = performance.now() + const result = await engine.setBlock(x, y, z, stateId) + const end = performance.now() + console.log(`[light engine] updateBlockLight (${x}, ${y}, ${z}) took`, Math.round(end - start), 'ms', result.length, 'chunks') + return result // const engine = getLightEngineSafe() // if (!engine) return @@ -79,7 +97,7 @@ export const updateBlockLight = async (x: number, y: number, z: number, stateId: export const lightRemoveColumn = (x: number, z: number) => { const engine = getLightEngineSafe() if (!engine) return - engine.columnCleanup(Math.floor(x / 16), Math.floor(z / 16)) + engine.unloadChunk(Math.floor(x / 16), Math.floor(z / 16)) } export const destroyLightEngine = () => { diff --git a/renderer/viewer/lib/worldDataEmitter.ts b/renderer/viewer/lib/worldDataEmitter.ts index 4b5039dd..ee373da9 100644 --- a/renderer/viewer/lib/worldDataEmitter.ts +++ b/renderer/viewer/lib/worldDataEmitter.ts @@ -9,7 +9,7 @@ import { proxy } from 'valtio' import TypedEmitter from 'typed-emitter' import { delayedIterator } from '../../playground/shared' import { chunkPos } from './simpleUtils' -import { createLightEngineIfNeeded, destroyLightEngine, lightRemoveColumn, processLightChunk, updateBlockLight } from './lightEngine' +import { createLightEngineIfNeededNew, destroyLightEngine, lightRemoveColumn, processLightChunk, updateBlockLight } from './lightEngine' import { WorldRendererConfig } from './worldrendererCommon' export type ChunkPosKey = string // like '16,16' @@ -160,7 +160,7 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter { @@ -297,7 +297,7 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter TypedEmitter - if (!isLightUpdate && this.worldRendererConfig.clientSideLighting === 'full') { - result = await processLightChunk(pos.x, pos.z) ?? [] + let result = [] as Array<{ chunkX: number, chunkZ: number }> + if (!isLightUpdate) { + const computeLighting = this.worldRendererConfig.clientSideLighting === 'full' + const promise = processLightChunk(pos.x, pos.z, computeLighting) + if (computeLighting) { + result = (await promise) ?? [] + } } if (!result) return for (const affectedChunk of result) { - if (affectedChunk.x === chunkX && affectedChunk.z === chunkZ) continue - const loadedChunk = this.loadedChunks[`${affectedChunk.x},${affectedChunk.z}`] + if (affectedChunk.chunkX === chunkX && affectedChunk.chunkZ === chunkZ) continue + const loadedChunk = this.loadedChunks[`${affectedChunk.chunkX * 16},${affectedChunk.chunkZ * 16}`] if (!loadedChunk) continue - void this.loadChunk(new Vec3(affectedChunk.x * 16, 0, affectedChunk.z * 16), true) + void this.loadChunk(new Vec3(affectedChunk.chunkX * 16, 0, affectedChunk.chunkZ * 16), true) } // const latency = Math.floor(performance.now() - this.lastTime) // this.debugGotChunkLatency.push(latency) diff --git a/renderer/viewer/lib/worldrendererCommon.ts b/renderer/viewer/lib/worldrendererCommon.ts index 1e91a2d0..0bd5d093 100644 --- a/renderer/viewer/lib/worldrendererCommon.ts +++ b/renderer/viewer/lib/worldrendererCommon.ts @@ -1,10 +1,8 @@ /* eslint-disable guard-for-in */ import { EventEmitter } from 'events' import { Vec3 } from 'vec3' -import * as THREE from 'three' import mcDataRaw from 'minecraft-data/data.js' // note: using alias import TypedEmitter from 'typed-emitter' -import { ItemsRenderer } from 'mc-assets/dist/itemsRenderer' import { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider' import { generateSpiralMatrix } from 'flying-squid/dist/utils' import { subscribeKey } from 'valtio/utils' @@ -16,10 +14,10 @@ import { SoundSystem } from '../three/threeJsSound' import { buildCleanupDecorator } from './cleanupDecorator' import { HighestBlockInfo, MesherGeometryOutput, CustomBlockModels, BlockStateModelInfo, getBlockAssetsCacheKey, MesherConfig } from './mesher/shared' import { chunkPos } from './simpleUtils' -import { addNewStat, removeAllStats, removeStat, updatePanesVisibility, updateStatText } from './ui/newStats' +import { addNewStat, removeAllStats, updatePanesVisibility, updateStatText } from './ui/newStats' import { WorldDataEmitter } from './worldDataEmitter' import { IPlayerState } from './basePlayerState' -import { createLightEngineIfNeeded, dumpLightData, getLightEngine, getLightEngineSafe } from './lightEngine' +import { dumpLightData } from './lightEngine' import { MesherLogReader } from './mesherlogReader' import { setSkinsConfig } from './utils/skins' diff --git a/src/react/DebugOverlay.tsx b/src/react/DebugOverlay.tsx index fb4feb19..268f73a7 100644 --- a/src/react/DebugOverlay.tsx +++ b/src/react/DebugOverlay.tsx @@ -1,7 +1,6 @@ import { useEffect, useRef, useState } from 'react' import type { Block } from 'prismarine-block' import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods' -import { getDebugLightValues } from 'renderer/viewer/lib/lightEngine' import { getFixedFilesize } from '../downloadAndOpenFile' import { options } from '../optionsStorage' import { BlockStateModelInfo } from '../../renderer/viewer/lib/mesher/shared' @@ -130,13 +129,24 @@ export default () => { const freqUpdateInterval = setInterval(() => { const lightingEnabled = appViewer.inWorldRenderingConfig.enableLighting const { clientSideLighting } = appViewer.inWorldRenderingConfig - if (!lightingEnabled) { - setLightInfo({ - sky: bot.world.getSkyLight(bot.entity.position), - block: bot.world.getBlockLight(bot.entity.position), - info: lightingEnabled ? clientSideLighting === 'none' ? 'Server Lighting' : 'Server + Client Engine' : 'Lighting Disabled' - }) + let info = '' + if (lightingEnabled) { + if (clientSideLighting === 'none') { + info = 'Server Lighting' + } else if (clientSideLighting === 'full') { + info = 'Client Engine' + } else { + info = 'Server + Client Engine' + } + } else { + info = 'Lighting Disabled' } + setLightInfo({ + sky: bot.world.getSkyLight(bot.entity.position), + block: bot.world.getBlockLight(bot.entity.position), + info + }) + setPos({ ...bot.entity.position }) setBiomeId(bot.world.getBiome(bot.entity.position)) diff --git a/src/shims/minecraftData.ts b/src/shims/minecraftData.ts index 989aa698..88f871f0 100644 --- a/src/shims/minecraftData.ts +++ b/src/shims/minecraftData.ts @@ -1,9 +1,13 @@ import { versionToNumber } from 'renderer/viewer/common/utils' import { restoreMinecraftData } from '../optimizeJson' // import minecraftInitialDataJson from '../../generated/minecraft-initial-data.json' -import { toMajorVersion } from '../utils' import { importLargeData } from '../../generated/large-data-aliases' +const toMajorVersion = version => { + const [a, b] = (String(version)).split('.') + return `${a}.${b}` +} + const customResolver = () => { const resolver = Promise.withResolvers() let resolvedData @@ -19,6 +23,9 @@ const customResolver = () => { } } +//@ts-expect-error for workers using minecraft-data +globalThis.window ??= globalThis + const optimizedDataResolver = customResolver() window._MC_DATA_RESOLVER = optimizedDataResolver window._LOAD_MC_DATA = async () => { @@ -66,7 +73,7 @@ const possiblyGetFromCache = (version: string) => { cacheTime.set(version, Date.now()) return data } -window.allLoadedMcData = new Proxy({}, { +window.allLoadedMcData ??= new Proxy({}, { get (t, version: string) { // special properties like $typeof if (version.includes('$')) return From 5720cfaf3481d63b96bb090398b92f5a67430612 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 4 May 2025 12:25:47 +0300 Subject: [PATCH 18/98] up light --- package.json | 2 +- pnpm-lock.yaml | 30 ++++++++++++++++++++++++------ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index f106449f..e3942388 100644 --- a/package.json +++ b/package.json @@ -152,7 +152,7 @@ "https-browserify": "^1.0.0", "mc-assets": "^0.2.53", "minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next", - "minecraft-lighting": "^0.0.9", + "minecraft-lighting": "^0.0.10", "mineflayer": "github:zardoy/mineflayer#gen-the-master", "mineflayer-mouse": "^0.1.9", "mineflayer-pathfinder": "^2.4.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d77103dd..3b9ee359 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -340,8 +340,8 @@ importers: specifier: github:zardoy/minecraft-inventory-gui#next version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/98bc5bb8ee6da8b4b771c05b404cee796318ccd4(@types/react@18.3.18)(react@18.3.1) minecraft-lighting: - specifier: ^0.0.9 - version: 0.0.9 + specifier: ^0.0.10 + version: 0.0.10 mineflayer: specifier: github:zardoy/mineflayer#gen-the-master version: https://codeload.github.com/zardoy/mineflayer/tar.gz/aadfaaff1f55447a4d20e3ffb34db52791d9b1e0(encoding@0.1.13) @@ -6691,8 +6691,8 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/98bc5bb8ee6da8b4b771c05b404cee796318ccd4} version: 1.0.1 - minecraft-lighting@0.0.9: - resolution: {integrity: sha512-2ma3m3ia4YmcAFHlZ9BGo4CSVyXL2TltPoXZSeYHmgstGuZwECHzOMQOKjUEzCnQs11X9Rnqz9Dum+Y17bsbyA==} + minecraft-lighting@0.0.10: + resolution: {integrity: sha512-m3RNe5opaibquxyO0ly1FpKdehapvp9hRRY37RccKY4bio2LGnN3nCZ3PrOXy0C596YpxBsG1OCYg0dqtPzehg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284: @@ -7437,6 +7437,11 @@ packages: version: 1.38.1 engines: {node: '>=14'} + prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f: + resolution: {tarball: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f} + version: 1.38.1 + engines: {node: '>=14'} + prismarine-entity@2.5.0: resolution: {integrity: sha512-nRPCawUwf9r3iKqi4I7mZRlir1Ix+DffWYdWq6p/KNnmiXve+xHE5zv8XCdhZlUmOshugHv5ONl9o6ORAkCNIA==} @@ -14632,7 +14637,7 @@ snapshots: diamond-square@https://codeload.github.com/zardoy/diamond-square/tar.gz/cfaad2d1d5909fdfa63c8cc7bc05fb5e87782d71: dependencies: minecraft-data: 3.83.1 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5(minecraft-data@3.83.1) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.83.1) prismarine-registry: 1.11.0 random-seed: 0.3.0 vec3: 0.1.10 @@ -17349,7 +17354,7 @@ snapshots: - '@types/react' - react - minecraft-lighting@0.0.9: + minecraft-lighting@0.0.10: dependencies: vec3: 0.1.10 @@ -18299,6 +18304,19 @@ snapshots: transitivePeerDependencies: - minecraft-data + prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.83.1): + dependencies: + prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-nbt: 2.7.0 + prismarine-registry: 1.11.0 + smart-buffer: 4.2.0 + uint4: 0.1.2 + vec3: 0.1.10 + xxhash-wasm: 0.4.2 + transitivePeerDependencies: + - minecraft-data + prismarine-entity@2.5.0: dependencies: prismarine-chat: 1.11.0 From e95f84e92c74e4aa900c76e4462c4d1b1bc43e4e Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 4 May 2025 12:26:15 +0300 Subject: [PATCH 19/98] fix lock --- pnpm-lock.yaml | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a2fd25cc..7b1d012e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -136,7 +136,7 @@ importers: version: 4.17.21 mcraft-fun-mineflayer: specifier: ^0.1.23 - version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/5602eca4174c0aff079e60234d7c68327eeb6fe6(encoding@0.1.13)) + version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/aadfaaff1f55447a4d20e3ffb34db52791d9b1e0(encoding@0.1.13)) minecraft-data: specifier: 3.83.1 version: 3.83.1 @@ -151,7 +151,7 @@ importers: version: 2.0.4 net-browserify: specifier: github:zardoy/prismarinejs-net-browserify - version: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/64e351867c5711de8a183464284a8eb7d77d5f39 + version: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/356edb3b061be0753f53fc2cf7fa995d40d05469 node-gzip: specifier: ^1.1.2 version: 1.1.2 @@ -344,7 +344,7 @@ importers: version: 0.0.10 mineflayer: specifier: github:zardoy/mineflayer#gen-the-master - version: https://codeload.github.com/zardoy/mineflayer/tar.gz/5602eca4174c0aff079e60234d7c68327eeb6fe6(encoding@0.1.13) + version: https://codeload.github.com/zardoy/mineflayer/tar.gz/aadfaaff1f55447a4d20e3ffb34db52791d9b1e0(encoding@0.1.13) mineflayer-mouse: specifier: ^0.1.10 version: 0.1.10(@types/debug@4.1.12)(@types/node@22.13.9)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) @@ -438,7 +438,7 @@ importers: version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chunk: specifier: github:zardoy/prismarine-chunk#master - version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.83.1) + version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5(minecraft-data@3.83.1) prismarine-schematic: specifier: ^1.2.0 version: 1.2.3 @@ -6722,8 +6722,8 @@ packages: resolution: {integrity: sha512-3bxph4jfbkBh5HpeouorxzrfSLNV+i+1gugNJ2jf52HW+rt+tW7eiiFPxrJEsOVkPT/3O/dEIW7j93LRlojMkQ==} engines: {node: '>=22'} - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/5602eca4174c0aff079e60234d7c68327eeb6fe6: - resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/5602eca4174c0aff079e60234d7c68327eeb6fe6} + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/aadfaaff1f55447a4d20e3ffb34db52791d9b1e0: + resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/aadfaaff1f55447a4d20e3ffb34db52791d9b1e0} version: 4.27.0 engines: {node: '>=22'} @@ -6886,8 +6886,8 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/64e351867c5711de8a183464284a8eb7d77d5f39: - resolution: {tarball: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/64e351867c5711de8a183464284a8eb7d77d5f39} + net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/356edb3b061be0753f53fc2cf7fa995d40d05469: + resolution: {tarball: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/356edb3b061be0753f53fc2cf7fa995d40d05469} version: 0.2.4 nice-try@1.0.5: @@ -7432,8 +7432,8 @@ packages: prismarine-chat@1.11.0: resolution: {integrity: sha512-VJT/MWYB3qoiznUhrgvSQh76YFpzpCZpY85kJKxHLbd3UVoM0wsfs43Eg8dOltiZG92wc5/DTMLlT07TEeoa9w==} - prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f: - resolution: {tarball: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f} + prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5: + resolution: {tarball: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5} version: 1.38.1 engines: {node: '>=14'} @@ -13206,7 +13206,7 @@ snapshots: mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.83.1) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5(minecraft-data@3.83.1) prismarine-entity: 2.5.0 prismarine-item: 1.16.0 prismarine-nbt: 2.7.0 @@ -13242,7 +13242,7 @@ snapshots: mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.83.1) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5(minecraft-data@3.83.1) prismarine-entity: 2.5.0 prismarine-item: 1.16.0 prismarine-nbt: 2.7.0 @@ -17031,12 +17031,12 @@ snapshots: maxrects-packer: '@zardoy/maxrects-packer@2.7.4' zod: 3.24.2 - mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/5602eca4174c0aff079e60234d7c68327eeb6fe6(encoding@0.1.13)): + mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/aadfaaff1f55447a4d20e3ffb34db52791d9b1e0(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/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=3a55a278c417cc34ff3172cd1de8e22852935cba0586875cbd0635f1ffdaa5ab)(encoding@0.1.13) - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/5602eca4174c0aff079e60234d7c68327eeb6fe6(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/aadfaaff1f55447a4d20e3ffb34db52791d9b1e0(encoding@0.1.13) prismarine-item: 1.16.0 ws: 8.18.1 transitivePeerDependencies: @@ -17461,7 +17461,7 @@ snapshots: prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chat: 1.11.0 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.83.1) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5(minecraft-data@3.83.1) prismarine-entity: 2.5.0 prismarine-item: 1.16.0 prismarine-nbt: 2.7.0 @@ -17477,7 +17477,7 @@ snapshots: - encoding - supports-color - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/5602eca4174c0aff079e60234d7c68327eeb6fe6(encoding@0.1.13): + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/aadfaaff1f55447a4d20e3ffb34db52791d9b1e0(encoding@0.1.13): dependencies: '@nxg-org/mineflayer-physics-util': 1.8.7 minecraft-data: 3.83.1 @@ -17485,7 +17485,7 @@ snapshots: prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chat: 1.11.0 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.83.1) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5(minecraft-data@3.83.1) prismarine-entity: 2.5.0 prismarine-item: 1.16.0 prismarine-nbt: 2.7.0 @@ -17681,7 +17681,7 @@ snapshots: neo-async@2.6.2: {} - net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/64e351867c5711de8a183464284a8eb7d77d5f39: + net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/356edb3b061be0753f53fc2cf7fa995d40d05469: dependencies: body-parser: 1.20.3 express: 4.21.2 @@ -18291,7 +18291,7 @@ snapshots: prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 - prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.83.1): + prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5(minecraft-data@3.83.1): dependencies: prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 @@ -18342,7 +18342,7 @@ snapshots: prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.83.1): dependencies: prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.83.1) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5(minecraft-data@3.83.1) prismarine-nbt: 2.7.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c uint4: 0.1.2 From 90de0d0be1c1015c046b7cd14f32bcb25ac08ba0 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 4 May 2025 12:27:30 +0300 Subject: [PATCH 20/98] up chunk? --- pnpm-lock.yaml | 52 +++++++++++++++++--------------------------------- 1 file changed, 17 insertions(+), 35 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7b1d012e..704a6819 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -136,7 +136,7 @@ importers: version: 4.17.21 mcraft-fun-mineflayer: specifier: ^0.1.23 - version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/aadfaaff1f55447a4d20e3ffb34db52791d9b1e0(encoding@0.1.13)) + version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/5602eca4174c0aff079e60234d7c68327eeb6fe6(encoding@0.1.13)) minecraft-data: specifier: 3.83.1 version: 3.83.1 @@ -151,7 +151,7 @@ importers: version: 2.0.4 net-browserify: specifier: github:zardoy/prismarinejs-net-browserify - version: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/356edb3b061be0753f53fc2cf7fa995d40d05469 + version: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/64e351867c5711de8a183464284a8eb7d77d5f39 node-gzip: specifier: ^1.1.2 version: 1.1.2 @@ -344,7 +344,7 @@ importers: version: 0.0.10 mineflayer: specifier: github:zardoy/mineflayer#gen-the-master - version: https://codeload.github.com/zardoy/mineflayer/tar.gz/aadfaaff1f55447a4d20e3ffb34db52791d9b1e0(encoding@0.1.13) + version: https://codeload.github.com/zardoy/mineflayer/tar.gz/5602eca4174c0aff079e60234d7c68327eeb6fe6(encoding@0.1.13) mineflayer-mouse: specifier: ^0.1.10 version: 0.1.10(@types/debug@4.1.12)(@types/node@22.13.9)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0) @@ -438,7 +438,7 @@ importers: version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chunk: specifier: github:zardoy/prismarine-chunk#master - version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5(minecraft-data@3.83.1) + version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.83.1) prismarine-schematic: specifier: ^1.2.0 version: 1.2.3 @@ -6722,8 +6722,8 @@ packages: resolution: {integrity: sha512-3bxph4jfbkBh5HpeouorxzrfSLNV+i+1gugNJ2jf52HW+rt+tW7eiiFPxrJEsOVkPT/3O/dEIW7j93LRlojMkQ==} engines: {node: '>=22'} - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/aadfaaff1f55447a4d20e3ffb34db52791d9b1e0: - resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/aadfaaff1f55447a4d20e3ffb34db52791d9b1e0} + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/5602eca4174c0aff079e60234d7c68327eeb6fe6: + resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/5602eca4174c0aff079e60234d7c68327eeb6fe6} version: 4.27.0 engines: {node: '>=22'} @@ -6886,8 +6886,8 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/356edb3b061be0753f53fc2cf7fa995d40d05469: - resolution: {tarball: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/356edb3b061be0753f53fc2cf7fa995d40d05469} + net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/64e351867c5711de8a183464284a8eb7d77d5f39: + resolution: {tarball: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/64e351867c5711de8a183464284a8eb7d77d5f39} version: 0.2.4 nice-try@1.0.5: @@ -7432,11 +7432,6 @@ packages: prismarine-chat@1.11.0: resolution: {integrity: sha512-VJT/MWYB3qoiznUhrgvSQh76YFpzpCZpY85kJKxHLbd3UVoM0wsfs43Eg8dOltiZG92wc5/DTMLlT07TEeoa9w==} - prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5: - resolution: {tarball: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5} - version: 1.38.1 - engines: {node: '>=14'} - prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f: resolution: {tarball: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f} version: 1.38.1 @@ -13206,7 +13201,7 @@ snapshots: mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5(minecraft-data@3.83.1) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.83.1) prismarine-entity: 2.5.0 prismarine-item: 1.16.0 prismarine-nbt: 2.7.0 @@ -13242,7 +13237,7 @@ snapshots: mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5(minecraft-data@3.83.1) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.83.1) prismarine-entity: 2.5.0 prismarine-item: 1.16.0 prismarine-nbt: 2.7.0 @@ -17031,12 +17026,12 @@ snapshots: maxrects-packer: '@zardoy/maxrects-packer@2.7.4' zod: 3.24.2 - mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/aadfaaff1f55447a4d20e3ffb34db52791d9b1e0(encoding@0.1.13)): + mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/5602eca4174c0aff079e60234d7c68327eeb6fe6(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/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=3a55a278c417cc34ff3172cd1de8e22852935cba0586875cbd0635f1ffdaa5ab)(encoding@0.1.13) - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/aadfaaff1f55447a4d20e3ffb34db52791d9b1e0(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/5602eca4174c0aff079e60234d7c68327eeb6fe6(encoding@0.1.13) prismarine-item: 1.16.0 ws: 8.18.1 transitivePeerDependencies: @@ -17461,7 +17456,7 @@ snapshots: prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chat: 1.11.0 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5(minecraft-data@3.83.1) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.83.1) prismarine-entity: 2.5.0 prismarine-item: 1.16.0 prismarine-nbt: 2.7.0 @@ -17477,7 +17472,7 @@ snapshots: - encoding - supports-color - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/aadfaaff1f55447a4d20e3ffb34db52791d9b1e0(encoding@0.1.13): + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/5602eca4174c0aff079e60234d7c68327eeb6fe6(encoding@0.1.13): dependencies: '@nxg-org/mineflayer-physics-util': 1.8.7 minecraft-data: 3.83.1 @@ -17485,7 +17480,7 @@ snapshots: prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chat: 1.11.0 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5(minecraft-data@3.83.1) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.83.1) prismarine-entity: 2.5.0 prismarine-item: 1.16.0 prismarine-nbt: 2.7.0 @@ -17681,7 +17676,7 @@ snapshots: neo-async@2.6.2: {} - net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/356edb3b061be0753f53fc2cf7fa995d40d05469: + net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/64e351867c5711de8a183464284a8eb7d77d5f39: dependencies: body-parser: 1.20.3 express: 4.21.2 @@ -18291,19 +18286,6 @@ snapshots: prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 - prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5(minecraft-data@3.83.1): - dependencies: - prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 - prismarine-nbt: 2.7.0 - prismarine-registry: 1.11.0 - smart-buffer: 4.2.0 - uint4: 0.1.2 - vec3: 0.1.10 - xxhash-wasm: 0.4.2 - transitivePeerDependencies: - - minecraft-data - prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.83.1): dependencies: prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0) @@ -18342,7 +18324,7 @@ snapshots: prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.83.1): dependencies: prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/47e0e5b7ff7759b8d222de7746f71023df7ce8e5(minecraft-data@3.83.1) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.83.1) prismarine-nbt: 2.7.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c uint4: 0.1.2 From f185df993f9a4f327db7747bcade1859dd586449 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 8 May 2025 20:28:00 +0300 Subject: [PATCH 21/98] fix remaining issues with worker bundle with smart approach todo: fix chunks not receive skylight, recompute fix skylight values desync time --- renderer/viewer/lib/lightEngine.ts | 31 +++------ renderer/viewer/lib/mesher/mesher.ts | 10 +++ renderer/viewer/lib/worldDataEmitter.ts | 4 +- renderer/viewer/lib/worldrendererCommon.ts | 73 +++++++++++++--------- 4 files changed, 67 insertions(+), 51 deletions(-) diff --git a/renderer/viewer/lib/lightEngine.ts b/renderer/viewer/lib/lightEngine.ts index 6e4f0341..35bb1e47 100644 --- a/renderer/viewer/lib/lightEngine.ts +++ b/renderer/viewer/lib/lightEngine.ts @@ -1,36 +1,23 @@ -import { LightWorld, createLightEngineForSyncWorld, convertPrismarineBlockToWorldBlock, createPrismarineLightEngineWorker } from 'minecraft-lighting' +import { createPrismarineLightEngineWorker } from 'minecraft-lighting' import { world } from 'prismarine-world' // import PrismarineWorker from 'minecraft-lighting/dist/prismarineWorker.worker.js' import { WorldDataEmitter } from './worldDataEmitter' +import { initMesherWorker, meshersSendMcData } from './worldrendererCommon' -let lightEngine: LightWorld | null = null let lightEngineNew: ReturnType | null = null -export const getLightEngine = () => { - if (!lightEngine) throw new Error('Light engine not initialized') - return lightEngine -} export const getLightEngineSafe = () => { // return lightEngine return lightEngineNew } -export const createLightEngineIfNeeded = (worldView: WorldDataEmitter) => { - if (lightEngine) return - lightEngine = createLightEngineForSyncWorld(worldView.world as unknown as world.WorldSync, loadedData, { - minY: worldView.minY, - height: worldView.minY + worldView.worldHeight, - // writeLightToOriginalWorld: true, - // enableSkyLight: false, - }) - lightEngine.externalWorld.setBlock = () => {} - lightEngine.PARALLEL_CHUNK_PROCESSING = false - globalThis.lightEngine = lightEngine -} - -export const createLightEngineIfNeededNew = (worldView: WorldDataEmitter) => { +export const createLightEngineIfNeededNew = (worldView: WorldDataEmitter, version: string) => { if (lightEngineNew) return - const worker = new Worker(new URL('minecraft-lighting/dist/prismarineWorker.worker.js', import.meta.url)) + const worker = initMesherWorker((data) => { + // console.log('light engine worker message', data) + }) + meshersSendMcData([worker], version) + worker.postMessage({ type: 'sideControl', value: 'lightEngine' }) lightEngineNew = createPrismarineLightEngineWorker(worker, worldView.world as unknown as world.WorldSync, loadedData) lightEngineNew.initialize({ minY: worldView.minY, @@ -101,6 +88,6 @@ export const lightRemoveColumn = (x: number, z: number) => { } export const destroyLightEngine = () => { - lightEngine = null + lightEngineNew = null globalThis.lightEngine = null } diff --git a/renderer/viewer/lib/mesher/mesher.ts b/renderer/viewer/lib/mesher/mesher.ts index 3f06d60d..a656fc89 100644 --- a/renderer/viewer/lib/mesher/mesher.ts +++ b/renderer/viewer/lib/mesher/mesher.ts @@ -71,7 +71,10 @@ const softCleanup = () => { globalThis.world = world } +let sideControl = false const handleMessage = data => { + if (sideControl) return + const globalVar: any = globalThis if (data.type === 'mcData') { @@ -92,6 +95,13 @@ const handleMessage = data => { } switch (data.type) { + case 'sideControl': { + if (data.value === 'lightEngine') { + sideControl = true + import('minecraft-lighting/dist/prismarineWorker.worker.js') + } + break + } case 'mesherData': { setMesherData(data.blockstatesModels, data.blocksAtlas, data.config.outputFormat === 'webgpu') allDataReady = true diff --git a/renderer/viewer/lib/worldDataEmitter.ts b/renderer/viewer/lib/worldDataEmitter.ts index ee373da9..45227cdb 100644 --- a/renderer/viewer/lib/worldDataEmitter.ts +++ b/renderer/viewer/lib/worldDataEmitter.ts @@ -40,6 +40,7 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter @@ -99,6 +100,7 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter() bot._client.prependListener('spawn_entity', (data) => { if (data.objectData && data.entityId !== undefined) { @@ -297,7 +299,7 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter initWorkers (numWorkers = this.worldRendererConfig.mesherWorkers) { // init workers for (let i = 0; i < numWorkers + 1; i++) { - // Node environment needs an absolute path, but browser needs the url of the file - const workerName = 'mesher.js' - // eslint-disable-next-line node/no-path-concat - const src = typeof window === 'undefined' ? `${__dirname}/${workerName}` : workerName - - let worker: any - if (process.env.SINGLE_FILE_BUILD) { - const workerCode = document.getElementById('mesher-worker-code')!.textContent! - const blob = new Blob([workerCode], { type: 'text/javascript' }) - worker = new Worker(window.URL.createObjectURL(blob)) - } else { - worker = new Worker(src) - } - - worker.onmessage = ({ data }) => { + const worker = initMesherWorker((data) => { if (Array.isArray(data)) { this.messageQueue.push(...data) } else { this.messageQueue.push(data) } void this.processMessageQueue('worker') - } - if (worker.on) worker.on('message', (data) => { worker.onmessage({ data }) }) + }) this.workers.push(worker) } } @@ -567,17 +552,13 @@ export abstract class WorldRendererCommon } sendMesherMcData () { - const allMcData = mcDataRaw.pc[this.version] ?? mcDataRaw.pc[toMajorVersion(this.version)] - const mcData = { - version: JSON.parse(JSON.stringify(allMcData.version)) - } - for (const key of dynamicMcDataFiles) { - mcData[key] = allMcData[key] - } - - for (const worker of this.workers) { - worker.postMessage({ type: 'mcData', mcData, config: this.getMesherConfig() }) - } + meshersSendMcData( + this.workers, + this.version, + { + config: this.getMesherConfig() + } + ) this.logWorkerWork('# mcData sent') } @@ -1008,3 +989,39 @@ export abstract class WorldRendererCommon this.displayOptions.worldView.destroy() } } + +export const initMesherWorker = (onGotMessage: (data: any) => void) => { + // Node environment needs an absolute path, but browser needs the url of the file + const workerName = 'mesher.js' + // eslint-disable-next-line node/no-path-concat + const src = typeof window === 'undefined' ? `${__dirname}/${workerName}` : workerName + + let worker: any + if (process.env.SINGLE_FILE_BUILD) { + const workerCode = document.getElementById('mesher-worker-code')!.textContent! + const blob = new Blob([workerCode], { type: 'text/javascript' }) + worker = new Worker(window.URL.createObjectURL(blob)) + } else { + worker = new Worker(src) + } + + worker.onmessage = ({ data }) => { + onGotMessage(data) + } + if (worker.on) worker.on('message', (data) => { worker.onmessage({ data }) }) + return worker +} + +export const meshersSendMcData = (workers: Worker[], version: string, addData = {} as Record) => { + const allMcData = mcDataRaw.pc[version] ?? mcDataRaw.pc[toMajorVersion(version)] + const mcData = { + version: JSON.parse(JSON.stringify(allMcData.version)) + } + for (const key of dynamicMcDataFiles) { + mcData[key] = allMcData[key] + } + + for (const worker of workers) { + worker.postMessage({ type: 'mcData', mcData, ...addData }) + } +} From 6a8d15b638e776b0b92c0ce14628d18c1da671d1 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 14 Jul 2025 06:40:00 +0300 Subject: [PATCH 22/98] [deploy] properly destroy world view --- renderer/viewer/lib/worldDataEmitter.ts | 4 ++++ renderer/viewer/lib/worldrendererCommon.ts | 2 +- src/appViewer.ts | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/renderer/viewer/lib/worldDataEmitter.ts b/renderer/viewer/lib/worldDataEmitter.ts index b66554a3..5edb9dc4 100644 --- a/renderer/viewer/lib/worldDataEmitter.ts +++ b/renderer/viewer/lib/worldDataEmitter.ts @@ -34,6 +34,10 @@ export type WorldDataEmitterEvents = { export class WorldDataEmitterWorker extends (EventEmitter as new () => TypedEmitter) { static readonly restorerName = 'WorldDataEmitterWorker' + + destroy () { + this.removeAllListeners() + } } export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter) { diff --git a/renderer/viewer/lib/worldrendererCommon.ts b/renderer/viewer/lib/worldrendererCommon.ts index 4112b570..046e1a1a 100644 --- a/renderer/viewer/lib/worldrendererCommon.ts +++ b/renderer/viewer/lib/worldrendererCommon.ts @@ -569,7 +569,7 @@ export abstract class WorldRendererCommon getMesherConfig (): MesherConfig { return { version: this.version, - enableLighting: this.worldRendererConfig.enableLighting && !this.playerState.lightingDisabled, + enableLighting: this.worldRendererConfig.enableLighting && !this.playerStateReactive.lightingDisabled, skyLight: this.skyLight, smoothLighting: this.worldRendererConfig.smoothLighting, outputFormat: this.outputFormat, diff --git a/src/appViewer.ts b/src/appViewer.ts index 51879175..47201b69 100644 --- a/src/appViewer.ts +++ b/src/appViewer.ts @@ -261,6 +261,7 @@ export class AppViewer { if (cleanState) { this.currentState = undefined this.currentDisplay = null + this.worldView?.destroy() this.worldView = undefined } if (this.backend) { From 52c0c75ccfc78da777a65da7f8c1ae22bc6b9f41 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Wed, 16 Jul 2025 12:09:34 +0300 Subject: [PATCH 23/98] docs: update readme --- README.MD | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.MD b/README.MD index 61a5b733..e9127a73 100644 --- a/README.MD +++ b/README.MD @@ -14,7 +14,6 @@ For building the project yourself / contributing, see [Development, Debugging & > **Note**: You can deploy it on your own server in less than a minute using a one-liner script from [Minecraft Everywhere repo](https://github.com/zardoy/minecraft-everywhere) - ### Big Features - Official Mineflayer [plugin integration](https://github.com/zardoy/mcraft-fun-mineflayer-plugin)! View / Control your bot remotely. @@ -57,6 +56,7 @@ Howerver, it's known that these browsers have issues: Server versions 1.8 - 1.21.4 are supported. First class versions (most of the features are tested on these versions): + - 1.19.4 - 1.21.4 @@ -125,11 +125,11 @@ 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: -- `localStorage.debug = '*'` - Enables all debug messages! Warning: this will start all packets spam. +- If you type `debugToggle`, press enter in console - It will enables all debug messages! Warning: this will start all packets spam. Instead I recommend setting `options.debugLogNotFrequentPackets`. Also you can use `debugTopPackets` (with JSON.stringify) to see what packets were received/sent by name - `bot` - Mineflayer bot instance. See Mineflayer documentation for more. -- `viewer` - Three.js viewer instance, basically does all the rendering. +- `world` - Three.js world instance, basically does all the rendering (part of renderer backend). - `world.sectionObjects` - Object with all active chunk sections (geometries) in the world. Each chunk section is a Three.js mesh or group. - `debugSceneChunks` - The same as above, but relative to current bot position (e.g. 0,0 is the current chunk). - `debugChangedOptions` - See what options are changed. Don't change options here. @@ -139,7 +139,7 @@ Instead I recommend setting `options.debugLogNotFrequentPackets`. Also you can u - `nbt.simplify(someNbt)` - Simplifies nbt data, so it's easier to read. -The most useful thing in devtools is the watch expression. You can add any expression there and it will be re-evaluated in real time. For example, you can add `camera.position` to see the camera position and so on. +The most useful thing in devtools is the watch expression. You can add any expression there and it will be re-evaluated in real time. For example, you can add `world.getCameraPosition()` to see the camera position and so on. Watch expression From e9c7840dae6aad023dd3a9c0363f459503c82c30 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 16 Jul 2025 16:18:15 +0300 Subject: [PATCH 24/98] feat(mobile): fix annoying issues with box and foods usage on screen hold --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- src/react/GameInteractionOverlay.tsx | 19 +++++++++++++++---- src/react/TouchAreasControls.tsx | 11 +++++++---- 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index fe4adb16..257fff7c 100644 --- a/package.json +++ b/package.json @@ -156,7 +156,7 @@ "mc-assets": "^0.2.62", "minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next", "mineflayer": "github:zardoy/mineflayer#gen-the-master", - "mineflayer-mouse": "^0.1.11", + "mineflayer-mouse": "^0.1.14", "mineflayer-pathfinder": "^2.4.4", "npm-run-all": "^4.1.5", "os-browserify": "^0.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c37e0510..1e6fddf3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -343,8 +343,8 @@ importers: specifier: github:zardoy/mineflayer#gen-the-master version: https://codeload.github.com/zardoy/mineflayer/tar.gz/3daf1f4bdc6afad0dedd87b879875f3dbb7b0980(encoding@0.1.13) mineflayer-mouse: - specifier: ^0.1.11 - version: 0.1.11 + specifier: ^0.1.14 + version: 0.1.14 mineflayer-pathfinder: specifier: ^2.4.4 version: 2.4.5 @@ -6667,8 +6667,8 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824} version: 1.2.0 - mineflayer-mouse@0.1.11: - resolution: {integrity: sha512-BL47pXZ1+92BA/7ym6KaJctEHKnL0up+tpuagVwSKJvAgibeqWQJJwDlNUWkOLvpnruRKDxMR5OB1hUXFoDNSg==} + mineflayer-mouse@0.1.14: + resolution: {integrity: sha512-DjytRMlRLxR44GqZ6udMgbMO4At7Ura5TQC80exRhzkfptyCGLTWzXaf0oeXSNYkNMnaaEv4XP/9YRwuvL+rsQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} mineflayer-pathfinder@2.4.5: @@ -17337,7 +17337,7 @@ snapshots: - encoding - supports-color - mineflayer-mouse@0.1.11: + mineflayer-mouse@0.1.14: dependencies: change-case: 5.4.4 debug: 4.4.1 diff --git a/src/react/GameInteractionOverlay.tsx b/src/react/GameInteractionOverlay.tsx index 63cee586..268162ec 100644 --- a/src/react/GameInteractionOverlay.tsx +++ b/src/react/GameInteractionOverlay.tsx @@ -2,6 +2,7 @@ import { useRef, useEffect } from 'react' import { subscribe, useSnapshot } from 'valtio' import { useUtilsEffect } from '@zardoy/react-util' import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods' +import { isItemActivatableMobile } from 'mineflayer-mouse/dist/activatableItemsMobile' import { options } from '../optionsStorage' import { activeModalStack, isGameActive, miscUiState } from '../globalState' import { onCameraMove, CameraMoveEvent } from '../cameraRotationControls' @@ -77,7 +78,10 @@ function GameInteractionOverlayInner ({ if (options.touchInteractionType === 'classic') { virtualClickTimeout ??= setTimeout(() => { virtualClickActive = true - document.dispatchEvent(new MouseEvent('mousedown', { button: 0 })) + // If held item is activatable, use right click instead of left + const heldItemName = bot?.heldItem?.name + const isOnlyActivatable = heldItemName && isItemActivatableMobile(heldItemName, loadedData) + document.dispatchEvent(new MouseEvent('mousedown', { button: isOnlyActivatable ? 2 : 0 })) }, touchStartBreakingBlockMs) } } @@ -150,16 +154,23 @@ function GameInteractionOverlayInner ({ if (virtualClickActive) { // button 0 is left click - document.dispatchEvent(new MouseEvent('mouseup', { button: 0 })) + // If held item is activatable, use right click instead of left + const heldItemName = bot?.heldItem?.name + const isOnlyActivatable = heldItemName && isItemActivatableMobile(heldItemName, loadedData) + document.dispatchEvent(new MouseEvent('mouseup', { button: isOnlyActivatable ? 2 : 0 })) virtualClickActive = false } else if (!capturedPointer.active.activateCameraMove && (Date.now() - capturedPointer.active.time < touchStartBreakingBlockMs)) { // single click action const MOUSE_BUTTON_RIGHT = 2 const MOUSE_BUTTON_LEFT = 0 + const heldItemName = bot?.heldItem?.name + const isOnlyActivatable = heldItemName && isItemActivatableMobile(heldItemName, loadedData) const gonnaAttack = !!bot.mouse.getCursorState().entity || !!videoCursorInteraction() - document.dispatchEvent(new MouseEvent('mousedown', { button: gonnaAttack ? MOUSE_BUTTON_LEFT : MOUSE_BUTTON_RIGHT })) + // If not attacking entity and item is activatable, use right click for breaking + const useButton = !gonnaAttack && isOnlyActivatable ? MOUSE_BUTTON_RIGHT : (gonnaAttack ? MOUSE_BUTTON_LEFT : MOUSE_BUTTON_RIGHT) + document.dispatchEvent(new MouseEvent('mousedown', { button: useButton })) bot.mouse.update() - document.dispatchEvent(new MouseEvent('mouseup', { button: gonnaAttack ? MOUSE_BUTTON_LEFT : MOUSE_BUTTON_RIGHT })) + document.dispatchEvent(new MouseEvent('mouseup', { button: useButton })) } if (screenTouches > 0) { diff --git a/src/react/TouchAreasControls.tsx b/src/react/TouchAreasControls.tsx index ec9d201b..469097bd 100644 --- a/src/react/TouchAreasControls.tsx +++ b/src/react/TouchAreasControls.tsx @@ -1,5 +1,6 @@ import { CSSProperties, PointerEvent, useEffect, useRef, useState } from 'react' import { proxy, ref, useSnapshot } from 'valtio' +import activatableItemsMobile from 'mineflayer-mouse/dist/activatableItemsMobile' import { contro } from '../controls' import { options } from '../optionsStorage' import PixelartIcon from './PixelartIcon' @@ -72,10 +73,12 @@ export default ({ setupActive, closeButtonsSetup, foregroundGameActive }: Props) break: false, jump: bot?.getControlState('jump'), }[name] + const RIGHT_MOUSE_BUTTON = 2 + const LEFT_MOUSE_BUTTON = 0 const holdDown = { action () { if (!bot) return - document.dispatchEvent(new MouseEvent('mousedown', { button: 2 })) + document.dispatchEvent(new MouseEvent('mousedown', { button: RIGHT_MOUSE_BUTTON })) bot.mouse.update() }, sneak () { @@ -87,7 +90,7 @@ export default ({ setupActive, closeButtonsSetup, foregroundGameActive }: Props) }, break () { if (!bot) return - document.dispatchEvent(new MouseEvent('mousedown', { button: 0 })) + document.dispatchEvent(new MouseEvent('mousedown', { button: LEFT_MOUSE_BUTTON })) bot.mouse.update() active = true }, @@ -101,7 +104,7 @@ export default ({ setupActive, closeButtonsSetup, foregroundGameActive }: Props) } const holdUp = { action () { - document.dispatchEvent(new MouseEvent('mouseup', { button: 2 })) + document.dispatchEvent(new MouseEvent('mouseup', { button: RIGHT_MOUSE_BUTTON })) }, sneak () { void contro.emit('release', { @@ -112,7 +115,7 @@ export default ({ setupActive, closeButtonsSetup, foregroundGameActive }: Props) }, break () { if (!bot) return - document.dispatchEvent(new MouseEvent('mouseup', { button: 0 })) + document.dispatchEvent(new MouseEvent('mouseup', { button: LEFT_MOUSE_BUTTON })) bot.mouse.update() active = false }, From 5bd33a546a25b54015ac4b88c40b406ebc9246e6 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Fri, 18 Jul 2025 04:39:05 +0300 Subject: [PATCH 25/98] More build configs & optimise reconnect and immediate game enter (#398) feat(custom-builds): Add a way to bundle only specific minecraft version data, this does not affect assets though env: MIN_MC_VERSION MAX_MC_VERSION new SKIP_MC_DATA_RECIPES - if recipes are not used in game fix: refactor QS params handling to ensure panorama & main menu never loaded when immedieate game enter action is expected (eg ?autoConnect=1) --- rsbuild.config.ts | 11 +- scripts/genLargeDataAliases.ts | 5 +- scripts/makeOptimizedMcData.mjs | 80 +++++++--- src/downloadAndOpenFile.ts | 6 + src/env.d.ts | 20 ++- src/index.ts | 246 +++++++++++++++++------------- src/inventoryWindows.ts | 6 +- src/react/AddServerOrConnect.tsx | 14 +- src/react/ServersListProvider.tsx | 24 ++- 9 files changed, 253 insertions(+), 159 deletions(-) diff --git a/rsbuild.config.ts b/rsbuild.config.ts index e264f6b7..42d6867b 100644 --- a/rsbuild.config.ts +++ b/rsbuild.config.ts @@ -139,6 +139,13 @@ const appConfig = defineConfig({ // 50kb limit for data uri dataUriLimit: SINGLE_FILE_BUILD ? 1 * 1024 * 1024 * 1024 : 50 * 1024 }, + performance: { + // prefetch: { + // include(filename) { + // return filename.includes('mc-data') || filename.includes('mc-assets') + // }, + // }, + }, source: { entry: { index: './src/index.ts', @@ -154,7 +161,7 @@ const appConfig = defineConfig({ '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}` || githubRepositoryFallback}`), - 'process.env.DEPS_VERSIONS': JSON.stringify({}), + 'process.env.ALWAYS_MINIMAL_SERVER_UI': JSON.stringify(process.env.ALWAYS_MINIMAL_SERVER_UI), 'process.env.RELEASE_TAG': JSON.stringify(releaseTag), 'process.env.RELEASE_LINK': JSON.stringify(releaseLink), 'process.env.RELEASE_CHANGELOG': JSON.stringify(releaseChangelog), @@ -190,7 +197,7 @@ const appConfig = defineConfig({ childProcess.execSync('tsx ./scripts/optimizeBlockCollisions.ts', { stdio: 'inherit' }) } // childProcess.execSync(['tsx', './scripts/genLargeDataAliases.ts', ...(SINGLE_FILE_BUILD ? ['--compressed'] : [])].join(' '), { stdio: 'inherit' }) - genLargeDataAliases(SINGLE_FILE_BUILD) + genLargeDataAliases(SINGLE_FILE_BUILD || process.env.ALWAYS_COMPRESS_LARGE_DATA === 'true') 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') diff --git a/scripts/genLargeDataAliases.ts b/scripts/genLargeDataAliases.ts index 0cf206df..2372dbfd 100644 --- a/scripts/genLargeDataAliases.ts +++ b/scripts/genLargeDataAliases.ts @@ -16,7 +16,8 @@ export const genLargeDataAliases = async (isCompressed: boolean) => { let str = `${decoderCode}\nexport const importLargeData = async (mod: ${Object.keys(modules).map(x => `'${x}'`).join(' | ')}) => {\n` for (const [module, { compressed, raw }] of Object.entries(modules)) { - let importCode = `(await import('${isCompressed ? compressed : raw}')).default`; + const chunkName = module === 'mcData' ? 'mc-data' : 'mc-assets'; + let importCode = `(await import(/* webpackChunkName: "${chunkName}" */ '${isCompressed ? compressed : raw}')).default`; if (isCompressed) { importCode = `JSON.parse(decompressFromBase64(${importCode}))` } @@ -30,6 +31,8 @@ export const genLargeDataAliases = async (isCompressed: boolean) => { const decoderCode = /* ts */ ` import pako from 'pako'; +globalThis.pako = { inflate: pako.inflate.bind(pako) } + function decompressFromBase64(input) { console.time('decompressFromBase64') // Decode the Base64 string diff --git a/scripts/makeOptimizedMcData.mjs b/scripts/makeOptimizedMcData.mjs index 05948cf2..0b5752d8 100644 --- a/scripts/makeOptimizedMcData.mjs +++ b/scripts/makeOptimizedMcData.mjs @@ -6,8 +6,8 @@ import { dirname } from 'node:path' import supportedVersions from '../src/supportedVersions.mjs' import { gzipSizeFromFileSync } from 'gzip-size' import fs from 'fs' -import {default as _JsonOptimizer} from '../src/optimizeJson' -import { gzipSync } from 'zlib'; +import { default as _JsonOptimizer } from '../src/optimizeJson' +import { gzipSync } from 'zlib' import MinecraftData from 'minecraft-data' import MCProtocol from 'minecraft-protocol' @@ -21,12 +21,12 @@ const require = Module.createRequire(import.meta.url) const dataPaths = require('minecraft-data/minecraft-data/data/dataPaths.json') -function toMajor (version) { +function toMajor(version) { const [a, b] = (version + '').split('.') return `${a}.${b}` } -const versions = {} +let versions = {} const dataTypes = new Set() for (const [version, dataSet] of Object.entries(dataPaths.pc)) { @@ -42,6 +42,31 @@ const versionToNumber = (ver) => { return +`${x.padStart(2, '0')}${y.padStart(2, '0')}${z.padStart(2, '0')}` } +// Version clipping support +const minVersion = process.env.MIN_MC_VERSION +const maxVersion = process.env.MAX_MC_VERSION + +// Filter versions based on MIN_VERSION and MAX_VERSION if provided +if (minVersion || maxVersion) { + const filteredVersions = {} + const minVersionNum = minVersion ? versionToNumber(minVersion) : 0 + const maxVersionNum = maxVersion ? versionToNumber(maxVersion) : Infinity + + for (const [version, dataSet] of Object.entries(versions)) { + const versionNum = versionToNumber(version) + if (versionNum >= minVersionNum && versionNum <= maxVersionNum) { + filteredVersions[version] = dataSet + } + } + + versions = filteredVersions + + console.log(`Version clipping applied: ${minVersion || 'none'} to ${maxVersion || 'none'}`) + console.log(`Processing ${Object.keys(versions).length} versions:`, Object.keys(versions).sort((a, b) => versionToNumber(a) - versionToNumber(b))) +} + +console.log('Bundling version range:', Object.keys(versions)[0], 'to', Object.keys(versions).at(-1)) + // if not included here (even as {}) will not be bundled & accessible! // const compressedOutput = !!process.env.SINGLE_FILE_BUILD const compressedOutput = true @@ -57,18 +82,20 @@ const dataTypeBundling2 = { } } const dataTypeBundling = { - language: { + language: process.env.SKIP_MC_DATA_LANGUAGE === 'true' ? { + raw: {} + } : { ignoreRemoved: true, ignoreChanges: true }, blocks: { arrKey: 'name', - processData (current, prev) { + processData(current, prev) { for (const block of current) { if (block.transparent) { const forceOpaque = block.name.includes('shulker_box') || block.name.match(/^double_.+_slab\d?$/) || ['melon_block', 'lit_pumpkin', 'lit_redstone_ore', 'lit_furnace'].includes(block.name) - const prevBlock = prev?.find(x => x.name === block.name); + const prevBlock = prev?.find(x => x.name === block.name) if (forceOpaque || (prevBlock && !prevBlock.transparent)) { block.transparent = false } @@ -136,7 +163,9 @@ const dataTypeBundling = { blockLoot: { arrKey: 'block' }, - recipes: { + recipes: process.env.SKIP_MC_DATA_RECIPES === 'true' ? { + raw: {} + } : { raw: true // processData: processRecipes }, @@ -150,7 +179,7 @@ const dataTypeBundling = { // } } -function processRecipes (current, prev, getData, version) { +function processRecipes(current, prev, getData, version) { // can require the same multiple times per different versions if (current._proccessed) return const items = getData('items') @@ -242,30 +271,39 @@ for (const [i, [version, dataSet]] of versionsArr.reverse().entries()) { for (const [dataType, dataPath] of Object.entries(dataSet)) { const config = dataTypeBundling[dataType] if (!config) continue - if (dataType === 'blockCollisionShapes' && versionToNumber(version) >= versionToNumber('1.13')) { - // contents += ` get ${dataType} () { return window.globalGetCollisionShapes?.("${version}") },\n` - continue - } + const ignoreCollisionShapes = dataType === 'blockCollisionShapes' && versionToNumber(version) >= versionToNumber('1.13') + let injectCode = '' - const getData = (type) => { + const getRealData = (type) => { const loc = `minecraft-data/data/${dataSet[type]}/` const dataPathAbsolute = require.resolve(`minecraft-data/${loc}${type}`) // const data = fs.readFileSync(dataPathAbsolute, 'utf8') const dataRaw = require(dataPathAbsolute) return dataRaw } - const dataRaw = getData(dataType) + const dataRaw = getRealData(dataType) let rawData = dataRaw if (config.raw) { rawDataVersions[dataType] ??= {} rawDataVersions[dataType][version] = rawData - rawData = dataRaw + if (config.raw === true) { + rawData = dataRaw + } else { + rawData = config.raw + } + + if (ignoreCollisionShapes && dataType === 'blockCollisionShapes') { + rawData = { + blocks: {}, + shapes: {} + } + } } else { if (!diffSources[dataType]) { diffSources[dataType] = new JsonOptimizer(config.arrKey, config.ignoreChanges, config.ignoreRemoved) } try { - config.processData?.(dataRaw, previousData[dataType], getData, version) + config.processData?.(dataRaw, previousData[dataType], getRealData, version) diffSources[dataType].recordDiff(version, dataRaw) injectCode = `restoreDiff(sources, ${JSON.stringify(dataType)}, ${JSON.stringify(version)})` } catch (err) { @@ -297,16 +335,16 @@ console.log('total size (mb)', totalSize / 1024 / 1024) console.log( 'size per data type (mb, %)', Object.fromEntries(Object.entries(sizePerDataType).map(([dataType, size]) => { - return [dataType, [size / 1024 / 1024, Math.round(size / totalSize * 100)]]; + return [dataType, [size / 1024 / 1024, Math.round(size / totalSize * 100)]] }).sort((a, b) => { //@ts-ignore - return b[1][1] - a[1][1]; + return b[1][1] - a[1][1] })) ) function compressToBase64(input) { - const buffer = gzipSync(input); - return buffer.toString('base64'); + const buffer = gzipSync(input) + return buffer.toString('base64') } const filePath = './generated/minecraft-data-optimized.json' diff --git a/src/downloadAndOpenFile.ts b/src/downloadAndOpenFile.ts index 1e703369..1ff318ff 100644 --- a/src/downloadAndOpenFile.ts +++ b/src/downloadAndOpenFile.ts @@ -11,6 +11,12 @@ export const getFixedFilesize = (bytes: number) => { return prettyBytes(bytes, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) } +export const isInterestedInDownload = () => { + const { map, texturepack, replayFileUrl } = appQueryParams + const { mapDir } = appQueryParamsArray + return !!map || !!texturepack || !!replayFileUrl || !!mapDir +} + const inner = async () => { const { map, texturepack, replayFileUrl } = appQueryParams const { mapDir } = appQueryParamsArray diff --git a/src/env.d.ts b/src/env.d.ts index 9b3e9774..e565fcec 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -2,30 +2,36 @@ declare namespace NodeJS { interface ProcessEnv { // Build configuration NODE_ENV: 'development' | 'production' - SINGLE_FILE_BUILD?: string + MIN_MC_VERSION?: string + MAX_MC_VERSION?: string + ALWAYS_COMPRESS_LARGE_DATA?: 'true' | 'false' + SINGLE_FILE_BUILD?: 'true' | 'false' WS_PORT?: string - DISABLE_SERVICE_WORKER?: string + DISABLE_SERVICE_WORKER?: 'true' | 'false' CONFIG_JSON_SOURCE?: 'BUNDLED' | 'REMOTE' LOCAL_CONFIG_FILE?: string BUILD_VERSION?: string - // GitHub and Vercel related + // Build internals GITHUB_REPOSITORY?: string VERCEL_GIT_REPO_OWNER?: string VERCEL_GIT_REPO_SLUG?: string - // UI and Features + // UI MAIN_MENU_LINKS?: string + ALWAYS_MINIMAL_SERVER_UI?: 'true' | 'false' + + // App features ENABLE_COOKIE_STORAGE?: string COOKIE_STORAGE_PREFIX?: string - // Release information + // Build info. Release information RELEASE_TAG?: string RELEASE_LINK?: string RELEASE_CHANGELOG?: string - // Other configurations - DEPS_VERSIONS?: string + // Build info INLINED_APP_CONFIG?: string + GITHUB_URL?: string } } diff --git a/src/index.ts b/src/index.ts index 185caab6..7a553ca7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -29,7 +29,7 @@ import './reactUi' import { lockUrl, onBotCreate } from './controls' import './dragndrop' import { possiblyCleanHandle } from './browserfs' -import downloadAndOpenFile from './downloadAndOpenFile' +import downloadAndOpenFile, { isInterestedInDownload } from './downloadAndOpenFile' import fs from 'fs' import net, { Socket } from 'net' @@ -97,6 +97,7 @@ import { registerOpenBenchmarkListener } from './benchmark' import { tryHandleBuiltinCommand } from './builtinCommands' import { loadingTimerState } from './react/LoadingTimer' import { loadPluginsIntoWorld } from './react/CreateWorldProvider' +import { getCurrentProxy, getCurrentUsername } from './react/ServersList' window.debug = debug window.beforeRenderFrame = [] @@ -166,6 +167,7 @@ export async function connect (connectOptions: ConnectOptions) { }) } + appStatusState.showReconnect = false loadingTimerState.loading = true loadingTimerState.start = Date.now() miscUiState.hasErrors = false @@ -880,37 +882,7 @@ export async function connect (connectOptions: ConnectOptions) { } } -const reconnectOptions = sessionStorage.getItem('reconnectOptions') ? JSON.parse(sessionStorage.getItem('reconnectOptions')!) : undefined - listenGlobalEvents() -const unsubscribe = subscribe(miscUiState, async () => { - if (miscUiState.fsReady && miscUiState.appConfig) { - unsubscribe() - if (reconnectOptions) { - sessionStorage.removeItem('reconnectOptions') - if (Date.now() - reconnectOptions.timestamp < 1000 * 60 * 2) { - void connect(reconnectOptions.value) - } - } else { - if (appQueryParams.singleplayer === '1' || appQueryParams.sp === '1') { - loadSingleplayer({}, { - worldFolder: undefined, - ...appQueryParams.version ? { version: appQueryParams.version } : {} - }) - } - if (appQueryParams.loadSave) { - const savePath = `/data/worlds/${appQueryParams.loadSave}` - try { - await fs.promises.stat(savePath) - } catch (err) { - alert(`Save ${savePath} not found`) - return - } - await loadInMemorySave(savePath) - } - } - } -}) // #region fire click event on touch as we disable default behaviors let activeTouch: { touch: Touch, elem: HTMLElement, start: number } | undefined @@ -946,90 +918,153 @@ document.body.addEventListener('touchstart', (e) => { }, { passive: false }) // #endregion -// qs open actions -if (!reconnectOptions) { - downloadAndOpenFile().then((downloadAction) => { - if (downloadAction) return - if (appQueryParams.reconnect && process.env.NODE_ENV === 'development') { - const lastConnect = JSON.parse(localStorage.lastConnectOptions ?? {}) +// immediate game enter actions: reconnect or URL QS +const maybeEnterGame = () => { + const waitForConfigFsLoad = (fn: () => void) => { + let unsubscribe: () => void | undefined + const checkDone = () => { + if (miscUiState.fsReady && miscUiState.appConfig) { + fn() + unsubscribe?.() + return true + } + return false + } + + if (!checkDone()) { + const text = miscUiState.appConfig ? 'Loading' : 'Loading config' + setLoadingScreenStatus(text) + unsubscribe = subscribe(miscUiState, checkDone) + } + } + + const reconnectOptions = sessionStorage.getItem('reconnectOptions') ? JSON.parse(sessionStorage.getItem('reconnectOptions')!) : undefined + + if (reconnectOptions) { + sessionStorage.removeItem('reconnectOptions') + if (Date.now() - reconnectOptions.timestamp < 1000 * 60 * 2) { + return waitForConfigFsLoad(async () => { + void connect(reconnectOptions.value) + }) + } + } + + if (appQueryParams.reconnect && process.env.NODE_ENV === 'development') { + const lastConnect = JSON.parse(localStorage.lastConnectOptions ?? {}) + return waitForConfigFsLoad(async () => { void connect({ botVersion: appQueryParams.version ?? undefined, ...lastConnect, ip: appQueryParams.ip || undefined }) - return - } - if (appQueryParams.ip || appQueryParams.proxy) { - const waitAppConfigLoad = !appQueryParams.proxy - const openServerEditor = () => { - hideModal() - if (appQueryParams.onlyConnect) { - showModal({ reactType: 'only-connect-server' }) - } else { - showModal({ reactType: 'editServer' }) - } - } - showModal({ reactType: 'empty' }) - if (waitAppConfigLoad) { - const unsubscribe = subscribe(miscUiState, checkCanDisplay) - checkCanDisplay() - // eslint-disable-next-line no-inner-declarations - function checkCanDisplay () { - if (miscUiState.appConfig) { - unsubscribe() - openServerEditor() - return true - } - } - } else { - openServerEditor() - } - } - - void Promise.resolve().then(() => { - // try to connect to peer - const peerId = appQueryParams.connectPeer - const peerOptions = {} as ConnectPeerOptions - if (appQueryParams.server) { - peerOptions.server = appQueryParams.server - } - const version = appQueryParams.peerVersion - if (peerId) { - let username: string | null = options.guestUsername - if (options.askGuestName) username = prompt('Enter your username', username) - if (!username) return - options.guestUsername = username - void connect({ - username, - botVersion: version || undefined, - peerId, - peerOptions - }) - } }) + } - if (appQueryParams.serversList && !appQueryParams.ip) { - showModal({ reactType: 'serversList' }) - } - - const viewerWsConnect = appQueryParams.viewerConnect - if (viewerWsConnect) { - void connect({ - username: `viewer-${Math.random().toString(36).slice(2, 10)}`, - viewerWsConnect, + if (appQueryParams.singleplayer === '1' || appQueryParams.sp === '1') { + return waitForConfigFsLoad(async () => { + loadSingleplayer({}, { + worldFolder: undefined, + ...appQueryParams.version ? { version: appQueryParams.version } : {} }) - } - - if (appQueryParams.modal) { - const modals = appQueryParams.modal.split(',') - for (const modal of modals) { - showModal({ reactType: modal }) + }) + } + if (appQueryParams.loadSave) { + const enterSave = async () => { + const savePath = `/data/worlds/${appQueryParams.loadSave}` + try { + await fs.promises.stat(savePath) + await loadInMemorySave(savePath) + } catch (err) { + alert(`Save ${savePath} not found`) } } - }, (err) => { - console.error(err) - alert(`Something went wrong: ${err}`) - }) + return waitForConfigFsLoad(enterSave) + } + + if (appQueryParams.ip || appQueryParams.proxy) { + const waitAppConfigLoad = !appQueryParams.proxy + const openServerAction = () => { + if (appQueryParams.autoConnect && miscUiState.appConfig?.allowAutoConnect) { + void connect({ + server: appQueryParams.ip, + proxy: getCurrentProxy(), + botVersion: appQueryParams.version ?? undefined, + username: getCurrentUsername()!, + }) + return + } + + setLoadingScreenStatus(undefined) + if (appQueryParams.onlyConnect || process.env.ALWAYS_MINIMAL_SERVER_UI === 'true') { + showModal({ reactType: 'only-connect-server' }) + } else { + showModal({ reactType: 'editServer' }) + } + } + + // showModal({ reactType: 'empty' }) + if (waitAppConfigLoad) { + return waitForConfigFsLoad(openServerAction) + } + openServerAction() + return + } + + if (appQueryParams.connectPeer) { + // try to connect to peer + const peerId = appQueryParams.connectPeer + const peerOptions = {} as ConnectPeerOptions + if (appQueryParams.server) { + peerOptions.server = appQueryParams.server + } + const version = appQueryParams.peerVersion + let username: string | null = options.guestUsername + if (options.askGuestName) username = prompt('Enter your username to connect to peer', username) + if (!username) return + options.guestUsername = username + void connect({ + username, + botVersion: version || undefined, + peerId, + peerOptions + }) + return + + } + + if (appQueryParams.viewerConnect) { + void connect({ + username: `viewer-${Math.random().toString(36).slice(2, 10)}`, + viewerWsConnect: appQueryParams.viewerConnect, + }) + return + } + + if (appQueryParams.modal) { + const modals = appQueryParams.modal.split(',') + for (const modal of modals) { + showModal({ reactType: modal }) + } + return + } + + if (appQueryParams.serversList && !miscUiState.appConfig?.appParams?.serversList) { + // open UI only if it's in URL + showModal({ reactType: 'serversList' }) + } + + if (isInterestedInDownload()) { + void downloadAndOpenFile() + } + + void possiblyHandleStateVariable() +} + +try { + maybeEnterGame() +} catch (err) { + console.error(err) + alert(`Something went wrong: ${err}`) } // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion @@ -1040,6 +1075,5 @@ if (initialLoader) { } window.pageLoaded = true -void possiblyHandleStateVariable() appViewer.waitBackendLoadPromises.push(appStartup()) registerOpenBenchmarkListener() diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index c9f60d59..1e17db14 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -522,7 +522,7 @@ const getResultingRecipe = (slots: Array, gridRows: number) => { type Result = RecipeItem | undefined let shapelessResult: Result let shapeResult: Result - outer: for (const [id, recipeVariants] of Object.entries(loadedData.recipes)) { + outer: for (const [id, recipeVariants] of Object.entries(loadedData.recipes ?? {})) { for (const recipeVariant of recipeVariants) { if ('inShape' in recipeVariant && equals(currentShape, recipeVariant.inShape as number[][])) { shapeResult = recipeVariant.result! @@ -550,7 +550,7 @@ const getAllItemRecipes = (itemName: string) => { const item = loadedData.itemsByName[itemName] if (!item) return const itemId = item.id - const recipes = loadedData.recipes[itemId] + const recipes = loadedData.recipes?.[itemId] if (!recipes) return const results = [] as Array<{ result: Item, @@ -595,7 +595,7 @@ const getAllItemUsages = (itemName: string) => { if (!item) return const foundRecipeIds = [] as string[] - for (const [id, recipes] of Object.entries(loadedData.recipes)) { + for (const [id, recipes] of Object.entries(loadedData.recipes ?? {})) { for (const recipe of recipes) { if ('inShape' in recipe) { if (recipe.inShape.some(row => row.includes(item.id))) { diff --git a/src/react/AddServerOrConnect.tsx b/src/react/AddServerOrConnect.tsx index 08e4d69e..d478b3e7 100644 --- a/src/react/AddServerOrConnect.tsx +++ b/src/react/AddServerOrConnect.tsx @@ -29,10 +29,9 @@ interface Props { accounts?: string[] authenticatedAccounts?: number versions?: string[] - allowAutoConnect?: boolean } -export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQs, onQsConnect, placeholders, accounts, versions, allowAutoConnect }: Props) => { +export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQs, onQsConnect, placeholders, accounts, versions }: Props) => { const isSmallHeight = !usePassesScaledDimensions(null, 350) const qsParamName = parseQs ? appQueryParams.name : undefined const qsParamIp = parseQs ? appQueryParams.ip : undefined @@ -40,7 +39,6 @@ export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQ const qsParamProxy = parseQs ? appQueryParams.proxy : undefined const qsParamUsername = parseQs ? appQueryParams.username : undefined const qsParamLockConnect = parseQs ? appQueryParams.lockConnect : undefined - const qsParamAutoConnect = parseQs ? appQueryParams.autoConnect : undefined const parsedQsIp = parseServerAddress(qsParamIp) const parsedInitialIp = parseServerAddress(initialData?.ip) @@ -118,12 +116,6 @@ export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQ } } - useEffect(() => { - if (qsParamAutoConnect && qsParamIp && qsParamVersion && allowAutoConnect) { - onQsConnect?.(commonUseOptions) - } - }, []) - const displayConnectButton = qsParamIp const serverExamples = ['example.com:25565', 'play.hypixel.net', 'ws://play.pcm.gg'] // pick random example @@ -231,7 +223,7 @@ export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQ Cancel - {displayConnectButton ? 'Save' : Save} + {displayConnectButton ? translate('Save') : {translate('Save')}} } {displayConnectButton && ( @@ -246,7 +238,7 @@ export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQ onQsConnect?.(commonUseOptions) }} > - Connect + {translate('Connect')} )} diff --git a/src/react/ServersListProvider.tsx b/src/react/ServersListProvider.tsx index d95d4e9e..2509db74 100644 --- a/src/react/ServersListProvider.tsx +++ b/src/react/ServersListProvider.tsx @@ -156,13 +156,22 @@ const Inner = ({ hidden, customServersList }: { hidden?: boolean, customServersL const isWebSocket = server.ip.startsWith('ws://') || server.ip.startsWith('wss://') let data if (isWebSocket) { - const pingResult = await getServerInfo(server.ip, undefined, undefined, true) - console.log('pingResult.fullInfo.description', pingResult.fullInfo.description) - data = { - formattedText: pingResult.fullInfo.description, - textNameRight: `ws ${pingResult.latency}ms`, - textNameRightGrayed: `${pingResult.fullInfo.players?.online ?? '??'}/${pingResult.fullInfo.players?.max ?? '??'}`, - offline: false + try { + const pingResult = await getServerInfo(server.ip, undefined, undefined, true) + console.log('pingResult.fullInfo.description', pingResult.fullInfo.description) + data = { + formattedText: pingResult.fullInfo.description, + textNameRight: `ws ${pingResult.latency}ms`, + textNameRightGrayed: `${pingResult.fullInfo.players?.online ?? '??'}/${pingResult.fullInfo.players?.max ?? '??'}`, + offline: false + } + } catch (err) { + data = { + formattedText: 'Failed to connect', + textNameRight: '', + textNameRightGrayed: '', + offline: true + } } } else { data = await fetchServerStatus(server.ip, /* signal */undefined, server.versionOverride) // DONT ADD SIGNAL IT WILL CRUSH JS RUNTIME @@ -217,7 +226,6 @@ const Inner = ({ hidden, customServersList }: { hidden?: boolean, customServersL }) const editModalJsx = isEditScreenModal ? Date: Fri, 18 Jul 2025 02:55:29 +0000 Subject: [PATCH 26/98] fix: Effects and Game Indicators overlay toggles didn't work (#397) --- src/react/IndicatorEffectsProvider.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/react/IndicatorEffectsProvider.tsx b/src/react/IndicatorEffectsProvider.tsx index c479d7e1..df52296a 100644 --- a/src/react/IndicatorEffectsProvider.tsx +++ b/src/react/IndicatorEffectsProvider.tsx @@ -5,7 +5,6 @@ import { Effect } from 'mineflayer' import { inGameError } from '../utils' import { fsState } from '../loadSave' import { gameAdditionalState, miscUiState } from '../globalState' -import { options } from '../optionsStorage' import IndicatorEffects, { EffectType, defaultIndicatorsState } from './IndicatorEffects' import { images } from './effectsImages' @@ -67,7 +66,6 @@ export default ({ displayEffects = true, displayIndicators = true }: { displayEf const { mesherWork } = useSnapshot(appViewer.rendererState).world const { hasErrors } = useSnapshot(miscUiState) - const { disabledUiParts } = useSnapshot(options) const { isReadonly, openReadOperations, openWriteOperations } = useSnapshot(fsState) const { noConnection, poorConnection } = useSnapshot(gameAdditionalState) const allIndicators: typeof defaultIndicatorsState = { @@ -122,7 +120,7 @@ export default ({ displayEffects = true, displayIndicators = true }: { displayEf return } From c360115f6017e5e0a37a5ad787fb54b10d122d2b Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 18 Jul 2025 07:46:25 +0300 Subject: [PATCH 27/98] fix: fix rare ios safari bug where hotbar would not be visible due to unclear fixed&bottom:0 css using --- src/react/HotbarRenderApp.tsx | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/react/HotbarRenderApp.tsx b/src/react/HotbarRenderApp.tsx index ed1f42e6..6b6e3207 100644 --- a/src/react/HotbarRenderApp.tsx +++ b/src/react/HotbarRenderApp.tsx @@ -200,17 +200,28 @@ const HotbarInner = () => {
+ }}> +
+
} From 45408476a539302c098a0969012662b16430225c Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 18 Jul 2025 07:53:47 +0300 Subject: [PATCH 28/98] fix(appStorage): Fix that settings were not possible to save on vercel domains, use robust self-checking mechanism to ensure user data never lost when cookies storage enabled! --- src/react/appStorageProvider.ts | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/react/appStorageProvider.ts b/src/react/appStorageProvider.ts index bce6feca..fd469186 100644 --- a/src/react/appStorageProvider.ts +++ b/src/react/appStorageProvider.ts @@ -91,6 +91,14 @@ const setCookieValue = (key: string, value: string): boolean => { } document.cookie = cookie + + // Verify the cookie was actually saved by reading it back + const savedValue = getCookieValue(key) + if (savedValue !== value) { + console.warn(`Cookie verification failed for key '${key}'. Expected: ${value}, Got: ${savedValue}`) + return false + } + return true } catch (error) { console.error(`Failed to set cookie for key '${key}':`, error) @@ -229,12 +237,19 @@ export const getRandomUsername = (appConfig: AppConfig) => { export const appStorage = proxy({ ...defaultStorageData }) +// Track if cookies failed in this session +let cookiesFailedThisSession = false + // Check if cookie storage should be used (will be set by options) const shouldUseCookieStorage = () => { - const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent) + // If cookies failed this session, don't try again + if (cookiesFailedThisSession) { + return false + } + const isSecureCookiesAvailable = () => { // either https or localhost - return window.location.protocol === 'https:' || (window.location.hostname === 'localhost' && !isSafari) + return window.location.protocol === 'https:' || (window.location.hostname === 'localhost') } if (!isSecureCookiesAvailable()) { return false @@ -345,8 +360,10 @@ const saveKey = (key: keyof StorageData) => { // Remove from localStorage if cookie save was successful markLocalStorageAsMigrated(key) } else { - // Disabling for now so no confusing conflicts modal after page reload - // useLocalStorage = true + // Cookie save failed, disable cookies for this session and fallback to localStorage + console.warn(`Cookie save failed for key '${key}', disabling cookies for this session`) + cookiesFailedThisSession = true + useLocalStorage = true } } } From de9bfba3a8924578a76c049c5e7e355735501f19 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 18 Jul 2025 08:02:13 +0300 Subject: [PATCH 29/98] allow auto connect on mcraft for last integrations --- config.mcraft-only.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config.mcraft-only.json b/config.mcraft-only.json index 7d9a7b59..52a3aa2c 100644 --- a/config.mcraft-only.json +++ b/config.mcraft-only.json @@ -1,4 +1,5 @@ { "alwaysReconnectButton": true, - "reportBugButtonWithReconnect": true + "reportBugButtonWithReconnect": true, + "allowAutoConnect": true } From 0dca8bbbe5848812697e959c796b0bc6367db6d5 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 18 Jul 2025 08:32:32 +0300 Subject: [PATCH 30/98] fix(important): F3 actions didn't work on mobile at all like chunks reload --- src/controls.ts | 18 ++++++--- src/react/OptionsItems.tsx | 81 +++++++++++++++++++++++++++----------- 2 files changed, 70 insertions(+), 29 deletions(-) diff --git a/src/controls.ts b/src/controls.ts index 9430f9c1..db6a6fc6 100644 --- a/src/controls.ts +++ b/src/controls.ts @@ -818,6 +818,11 @@ export const f3Keybinds: Array<{ } ] +export const reloadChunksAction = () => { + const action = f3Keybinds.find(f3Keybind => f3Keybind.key === 'KeyA') + void action!.action() +} + document.addEventListener('keydown', (e) => { if (!isGameActive(false)) return if (contro.pressedKeys.has('F3')) { @@ -987,14 +992,17 @@ export function updateBinds (commands: any) { } export const onF3LongPress = async () => { - const select = await showOptionsModal('', f3Keybinds.filter(f3Keybind => { + const actions = f3Keybinds.filter(f3Keybind => { return f3Keybind.mobileTitle && (f3Keybind.enabled?.() ?? true) - }).map(f3Keybind => { + }) + const actionNames = actions.map(f3Keybind => { return `${f3Keybind.mobileTitle}${f3Keybind.key ? ` (F3+${f3Keybind.key})` : ''}` - })) + }) + const select = await showOptionsModal('', actionNames) if (!select) return - const f3Keybind = f3Keybinds.find(f3Keybind => f3Keybind.mobileTitle === select) - if (f3Keybind) void f3Keybind.action() + const actionIndex = actionNames.indexOf(select) + const f3Keybind = actions[actionIndex]! + void f3Keybind.action() } export const handleMobileButtonCustomAction = (action: CustomAction) => { diff --git a/src/react/OptionsItems.tsx b/src/react/OptionsItems.tsx index 32f99b9e..ccc04bcb 100644 --- a/src/react/OptionsItems.tsx +++ b/src/react/OptionsItems.tsx @@ -4,11 +4,13 @@ import { titleCase } from 'title-case' import { useMemo } from 'react' import { disabledSettings, options, qsOptions } from '../optionsStorage' import { hideAllModals, miscUiState } from '../globalState' +import { reloadChunksAction } from '../controls' import Button from './Button' import Slider from './Slider' import Screen from './Screen' import { showOptionsModal } from './SelectOption' import PixelartIcon, { pixelartIcons } from './PixelartIcon' +import { reconnectReload } from './AppStatusProvider' type GeneralItem = { id?: string @@ -18,7 +20,8 @@ type GeneralItem = { tooltip?: string // description?: string enableWarning?: string - willHaveNoEffect?: boolean + requiresRestart?: boolean + requiresChunksReload?: boolean values?: Array disableIf?: [option: keyof typeof options, value: any] } @@ -56,7 +59,14 @@ const useCommonComponentsProps = (item: OptionMeta) => { } } -export const OptionButton = ({ item }: { item: Extract }) => { +const ignoreReloadWarningsCache = new Set() + +export const OptionButton = ({ item, onClick, valueText, cacheKey }: { + item: Extract, + onClick?: () => void, + valueText?: string, + cacheKey?: string, +}) => { const { disabledBecauseOfSetting } = useCommonComponentsProps(item) const optionValue = useSnapshot(options)[item.id!] @@ -84,40 +94,63 @@ export const OptionButton = ({ item }: { item: Extract { if (disabledReason) { - await showOptionsModal(`The option is unavailable. ${disabledReason}`, []) + await showOptionsModal(`${translate('The option is not available')}: ${disabledReason}`, []) return } if (item.enableWarning && !options[item.id!]) { const result = await showOptionsModal(item.enableWarning, ['Enable']) if (!result) return } - const { values } = item - if (values) { - const getOptionValue = (arrItem) => { - if (typeof arrItem === 'string') { - return arrItem + onClick?.() + if (item.id) { + const { values } = item + if (values) { + const getOptionValue = (arrItem) => { + if (typeof arrItem === 'string') { + return arrItem + } else { + return arrItem[0] + } + } + const currentIndex = values.findIndex((value) => { + return getOptionValue(value) === optionValue + }) + if (currentIndex === -1) { + options[item.id] = getOptionValue(values[0]) } else { - return arrItem[0] + const nextIndex = event.shiftKey + ? (currentIndex - 1 + values.length) % values.length + : (currentIndex + 1) % values.length + options[item.id] = getOptionValue(values[nextIndex]) + } + } else { + options[item.id] = !options[item.id] + } + } + + const toCacheKey = cacheKey ?? item.id ?? '' + if (toCacheKey && !ignoreReloadWarningsCache.has(toCacheKey)) { + ignoreReloadWarningsCache.add(toCacheKey) + + if (item.requiresRestart) { + const result = await showOptionsModal(translate('The option requires a restart to take effect'), ['Restart', 'I will do it later'], { + cancel: false, + }) + if (result) { + reconnectReload() } } - const currentIndex = values.findIndex((value) => { - return getOptionValue(value) === optionValue - }) - if (currentIndex === -1) { - options[item.id!] = getOptionValue(values[0]) - } else { - const nextIndex = event.shiftKey - ? (currentIndex - 1 + values.length) % values.length - : (currentIndex + 1) % values.length - options[item.id!] = getOptionValue(values[nextIndex]) + if (item.requiresChunksReload) { + const result = await showOptionsModal(translate('The option requires a chunks reload to take effect'), ['Reload', 'I will do it later'], { + cancel: false, + }) + if (result) { + reloadChunksAction() + } } - } else { - options[item.id!] = !options[item.id!] } }} title={disabledReason ? `${disabledReason} | ${item.tooltip}` : item.tooltip} From b6d4728c447592d1a01303448d41adc83822b973 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 18 Jul 2025 09:44:50 +0300 Subject: [PATCH 31/98] display disconnect always last --- renderer/viewer/three/documentRenderer.ts | 3 ++- src/devtools.ts | 1 - src/react/PauseScreen.tsx | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/renderer/viewer/three/documentRenderer.ts b/renderer/viewer/three/documentRenderer.ts index 8ff31e69..a5dc060d 100644 --- a/renderer/viewer/three/documentRenderer.ts +++ b/renderer/viewer/three/documentRenderer.ts @@ -61,8 +61,9 @@ export class DocumentRenderer { this.previousCanvasWidth = this.canvas.width this.previousCanvasHeight = this.canvas.height + const supportsWebGL2 = 'WebGL2RenderingContext' in window // Only initialize stats and DOM-related features in main thread - if (!externalCanvas) { + if (!externalCanvas && supportsWebGL2) { this.stats = new TopRightStats(this.canvas as HTMLCanvasElement, this.config.statsVisible) this.setupFpsTracking() } diff --git a/src/devtools.ts b/src/devtools.ts index 8890fdea..6c47f73d 100644 --- a/src/devtools.ts +++ b/src/devtools.ts @@ -255,7 +255,6 @@ function connectWebSocket () { const wsUrl = getWebSocketUrl() if (!wsUrl) { - console.log('WebSocket server not configured') return } diff --git a/src/react/PauseScreen.tsx b/src/react/PauseScreen.tsx index 3036aa4b..856ac932 100644 --- a/src/react/PauseScreen.tsx +++ b/src/react/PauseScreen.tsx @@ -289,11 +289,6 @@ export default () => { />
) : null} - {!lockConnect && <> - - } {(noConnection || appConfig?.alwaysReconnectButton) && (
)} + {!lockConnect && <> + + } From a49877870325b010160c3e9e3dfca5719b8552f8 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 18 Jul 2025 09:56:50 +0300 Subject: [PATCH 32/98] always wait for config load so autoConnect works on remote config --- src/index.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/index.ts b/src/index.ts index 7a553ca7..8de98894 100644 --- a/src/index.ts +++ b/src/index.ts @@ -982,7 +982,6 @@ const maybeEnterGame = () => { } if (appQueryParams.ip || appQueryParams.proxy) { - const waitAppConfigLoad = !appQueryParams.proxy const openServerAction = () => { if (appQueryParams.autoConnect && miscUiState.appConfig?.allowAutoConnect) { void connect({ @@ -1003,11 +1002,7 @@ const maybeEnterGame = () => { } // showModal({ reactType: 'empty' }) - if (waitAppConfigLoad) { - return waitForConfigFsLoad(openServerAction) - } - openServerAction() - return + return waitForConfigFsLoad(openServerAction) } if (appQueryParams.connectPeer) { From 4d7e3df8597fffea6a66f7a6df4810dae1d8cd96 Mon Sep 17 00:00:00 2001 From: Max Lee Date: Fri, 18 Jul 2025 11:18:05 +0000 Subject: [PATCH 33/98] feat: Item projectiles support (#395) --- pnpm-lock.yaml | 14 +++--- renderer/viewer/three/entities.ts | 72 ++++++++++++++++----------- renderer/viewer/three/holdingBlock.ts | 2 +- 3 files changed, 50 insertions(+), 38 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1e6fddf3..52c2ff30 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -136,7 +136,7 @@ importers: version: 4.17.21 mcraft-fun-mineflayer: specifier: ^0.1.23 - version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/3daf1f4bdc6afad0dedd87b879875f3dbb7b0980(encoding@0.1.13)) + version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/26c04c26f8eed2499a7c56eb4664649f3f54b767(encoding@0.1.13)) minecraft-data: specifier: 3.92.0 version: 3.92.0 @@ -341,7 +341,7 @@ importers: version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/89c33d396f3fde4804c71f4be3c203ade1833b41(@types/react@18.3.18)(react@18.3.1) mineflayer: specifier: github:zardoy/mineflayer#gen-the-master - version: https://codeload.github.com/zardoy/mineflayer/tar.gz/3daf1f4bdc6afad0dedd87b879875f3dbb7b0980(encoding@0.1.13) + version: https://codeload.github.com/zardoy/mineflayer/tar.gz/26c04c26f8eed2499a7c56eb4664649f3f54b767(encoding@0.1.13) mineflayer-mouse: specifier: ^0.1.14 version: 0.1.14 @@ -6678,8 +6678,8 @@ packages: resolution: {integrity: sha512-GtW4hkijyZbSu5LKYYD89xZu+XY7OoP7IkrCnNEn6EdPm0+vr2THoJgFGKrlze9/81+T+P3E4qvJXNFiU/zeJg==} engines: {node: '>=22'} - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/3daf1f4bdc6afad0dedd87b879875f3dbb7b0980: - resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/3daf1f4bdc6afad0dedd87b879875f3dbb7b0980} + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/26c04c26f8eed2499a7c56eb4664649f3f54b767: + resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/26c04c26f8eed2499a7c56eb4664649f3f54b767} version: 4.30.0 engines: {node: '>=22'} @@ -16956,12 +16956,12 @@ snapshots: maxrects-packer: '@zardoy/maxrects-packer@2.7.4' zod: 3.24.2 - mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/3daf1f4bdc6afad0dedd87b879875f3dbb7b0980(encoding@0.1.13)): + mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/26c04c26f8eed2499a7c56eb4664649f3f54b767(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/6c2204a813690ead420e2b8c7f0ef32ca357d176(patch_hash=a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0)(encoding@0.1.13) - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/3daf1f4bdc6afad0dedd87b879875f3dbb7b0980(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/26c04c26f8eed2499a7c56eb4664649f3f54b767(encoding@0.1.13) prismarine-item: 1.16.0 ws: 8.18.1 transitivePeerDependencies: @@ -17379,7 +17379,7 @@ snapshots: - encoding - supports-color - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/3daf1f4bdc6afad0dedd87b879875f3dbb7b0980(encoding@0.1.13): + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/26c04c26f8eed2499a7c56eb4664649f3f54b767(encoding@0.1.13): dependencies: '@nxg-org/mineflayer-physics-util': 1.8.10 minecraft-data: 3.92.0 diff --git a/renderer/viewer/three/entities.ts b/renderer/viewer/three/entities.ts index 51292d2f..6c6f8900 100644 --- a/renderer/viewer/three/entities.ts +++ b/renderer/viewer/three/entities.ts @@ -717,7 +717,7 @@ export class Entities { return typeof component === 'string' ? component : component.text ?? '' } - getItemMesh (item, specificProps: ItemSpecificContextProperties, previousModel?: string) { + getItemMesh (item, specificProps: ItemSpecificContextProperties, faceCamera = false, previousModel?: string) { if (!item.nbt && item.nbtData) item.nbt = item.nbtData const textureUv = this.worldRenderer.getItemRenderData(item, specificProps) if (previousModel && previousModel === textureUv?.modelName) return undefined @@ -757,26 +757,37 @@ export class Entities { 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 + (sizeX), 1 - v - sizeY) - 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, - ]) + let mesh: THREE.Object3D + let itemsTextureFlipped: THREE.Texture | undefined + if (faceCamera) { + const spriteMat = new THREE.SpriteMaterial({ + map: itemsTexture, + transparent: true, + alphaTest: 0.1, + }) + mesh = new THREE.Sprite(spriteMat) + } else { + itemsTextureFlipped = itemsTexture.clone() + itemsTextureFlipped.repeat.x *= -1 + itemsTextureFlipped.needsUpdate = true + itemsTextureFlipped.offset.set(u + (sizeX), 1 - v - sizeY) + 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, + ]) + } let SCALE = 1 if (specificProps['minecraft:display_context'] === 'ground') { SCALE = 0.5 @@ -805,8 +816,6 @@ export class Entities { } update (entity: SceneEntity['originalEntity'], overrides) { - const justAdded = !this.entities[entity.id] - const isPlayerModel = entity.name === 'player' if (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`}` @@ -817,6 +826,7 @@ export class Entities { } // this can be undefined in case where packet entity_destroy was sent twice (so it was already deleted) let e = this.entities[entity.id] + const justAdded = !e if (entity.delete) { if (!e) return @@ -836,21 +846,23 @@ export class Entities { if (e === undefined) { const group = new THREE.Group() as unknown as SceneEntity group.originalEntity = entity - if (entity.name === 'item' || entity.name === 'tnt' || entity.name === 'falling_block') { - const item = entity.name === 'tnt' - ? { name: 'tnt' } + if (entity.name === 'item' || entity.name === 'tnt' || entity.name === 'falling_block' || entity.name === 'snowball' + || entity.name === 'egg' || entity.name === 'ender_pearl' || entity.name === 'experience_bottle' + || entity.name === 'splash_potion' || entity.name === 'lingering_potion') { + const item = entity.name === 'tnt' || entity.type === 'projectile' + ? { name: entity.name } : entity.name === 'falling_block' ? { blockState: entity['objectData'] } : entity.metadata?.find((m: any) => typeof m === 'object' && m?.itemCount) if (item) { const object = this.getItemMesh(item, { 'minecraft:display_context': 'ground', - }) + }, entity.type === 'projectile') if (object) { mesh = object.mesh - if (entity.name === 'item') { + if (entity.name === 'item' || entity.type === 'projectile') { mesh.scale.set(0.5, 0.5, 0.5) - mesh.position.set(0, 0.2, 0) + mesh.position.set(0, entity.name === 'item' ? 0.2 : 0.1, 0) } else { mesh.scale.set(2, 2, 2) mesh.position.set(0, 0.5, 0) @@ -858,8 +870,8 @@ export class Entities { // 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() if (entity.name === 'item') { + const clock = new THREE.Clock() mesh.onBeforeRender = () => { const delta = clock.getDelta() mesh!.rotation.y += delta diff --git a/renderer/viewer/three/holdingBlock.ts b/renderer/viewer/three/holdingBlock.ts index c8a56386..f9d00f0e 100644 --- a/renderer/viewer/three/holdingBlock.ts +++ b/renderer/viewer/three/holdingBlock.ts @@ -357,7 +357,7 @@ export default class HoldingBlock { 'minecraft:display_context': 'firstperson', 'minecraft:use_duration': this.worldRenderer.playerStateReactive.itemUsageTicks, 'minecraft:using_item': !!this.worldRenderer.playerStateReactive.itemUsageTicks, - }, this.lastItemModelName) + }, false, this.lastItemModelName) if (result) { const { mesh: itemMesh, isBlock, modelName } = result if (isBlock) { From b9c8ade9bf05b3eea2842bcfe2e03bb7428cfafb Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 20 Jul 2025 10:06:57 +0300 Subject: [PATCH 34/98] fix: fix chat was crashing sometimes --- src/index.ts | 4 ++++ src/react/Chat.tsx | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 8de98894..d28261f4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -278,6 +278,10 @@ export async function connect (connectOptions: ConnectOptions) { return } } + if (e.reason?.stack?.includes('chrome-extension://')) { + // ignore issues caused by chrome extension + return + } handleError(e.reason) }, { signal: errorAbortController.signal diff --git a/src/react/Chat.tsx b/src/react/Chat.tsx index a45d7a69..20d77a10 100644 --- a/src/react/Chat.tsx +++ b/src/react/Chat.tsx @@ -45,7 +45,7 @@ const MessageLine = ({ message, currentPlayerName, chatOpened }: { message: Mess return
  • val).map(([name]) => name).join(' ')} data-time={message.timestamp ? new Date(message.timestamp).toLocaleString('en-US', { hour12: false }) : undefined}> {message.parts.map((msg, i) => { // Check if this is a text part that might contain a mention - if (msg.text && currentPlayerName) { + if (typeof msg.text === 'string' && currentPlayerName) { const parts = msg.text.split(new RegExp(`(@${currentPlayerName})`, 'i')) if (parts.length > 1) { return parts.map((txtPart, j) => { From 67855ae25a97d00a5206299963794bf223e2eebe Mon Sep 17 00:00:00 2001 From: Kesu <40177436+Kesuaheli@users.noreply.github.com> Date: Sun, 27 Jul 2025 15:24:26 +0200 Subject: [PATCH 35/98] fix: fix some window titles (#401) --- src/inventoryWindows.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index 1e17db14..bc7dcbaf 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -9,6 +9,7 @@ import PItem, { Item } from 'prismarine-item' import { versionToNumber } from 'renderer/viewer/common/utils' import { getRenamedData } from 'flying-squid/dist/blockRenames' import PrismarineChatLoader from 'prismarine-chat' +import * as nbt from 'prismarine-nbt' import { BlockModel } from 'mc-assets' import { renderSlot } from 'renderer/viewer/three/renderSlot' import Generic95 from '../assets/generic_95.png' @@ -59,9 +60,9 @@ export const onGameLoad = () => { bot.on('windowOpen', (win) => { const implementedWindow = implementedContainersGuiMap[mapWindowType(win.type as string, win.inventoryStart)] if (implementedWindow) { - openWindow(implementedWindow) + openWindow(implementedWindow, nbt.simplify(win.title as any)) } else if (options.unimplementedContainers) { - openWindow('ChestWin') + openWindow('ChestWin', nbt.simplify(win.title as any)) } else { // todo format displayClientChat(`[client error] cannot open unimplemented window ${win.id} (${win.type}). Slots: ${win.slots.map(item => getItemName(item)).filter(Boolean).join(', ')}`) @@ -354,7 +355,7 @@ const upWindowItemsLocal = () => { } let skipClosePacketSending = false -const openWindow = (type: string | undefined) => { +const openWindow = (type: string | undefined, title: string | any = undefined) => { // if (activeModalStack.some(x => x.reactType?.includes?.('player_win:'))) { if (activeModalStack.length) { // game is not in foreground, don't close current modal if (type) { @@ -384,7 +385,6 @@ const openWindow = (type: string | undefined) => { const inv = openItemsCanvas(type) inv.canvasManager.children[0].mobileHelpers = miscUiState.currentTouch window.inventory = inv - const title = bot.currentWindow?.title const PrismarineChat = PrismarineChatLoader(bot.version) try { inv.canvasManager.children[0].customTitleText = title ? From c4b284b9b7b644fc0c7eee2a7231752df499b452 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sat, 2 Aug 2025 21:34:33 +0300 Subject: [PATCH 36/98] fix: fix supported versions display in server menu --- src/react/ServersListProvider.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/react/ServersListProvider.tsx b/src/react/ServersListProvider.tsx index 2509db74..75f95d3f 100644 --- a/src/react/ServersListProvider.tsx +++ b/src/react/ServersListProvider.tsx @@ -1,9 +1,11 @@ import { useEffect, useMemo, useState } from 'react' import { useUtilsEffect } from '@zardoy/react-util' import { useSnapshot } from 'valtio' +import { supportedVersions } from 'minecraft-protocol' +import { versionToNumber } from 'mc-assets/dist/utils' import { ConnectOptions } from '../connect' import { activeModalStack, hideCurrentModal, miscUiState, notHideableModalsWithoutForce, showModal } from '../globalState' -import supportedVersions from '../supportedVersions.mjs' +import appSupportedVersions from '../supportedVersions.mjs' import { appQueryParams } from '../appParams' import { fetchServerStatus, isServerValid } from '../api/mcStatusApi' import { getServerInfo } from '../mineflayer/mc-protocol' @@ -20,6 +22,10 @@ import Button from './Button' import { pixelartIcons } from './PixelartIcon' import { showNotification } from './NotificationProvider' +const firstProtocolVersion = versionToNumber(supportedVersions[0]) +const lastProtocolVersion = versionToNumber(supportedVersions.at(-1)!) +const protocolSupportedVersions = appSupportedVersions.filter(v => versionToNumber(v) >= firstProtocolVersion && versionToNumber(v) <= lastProtocolVersion) + const EXPLICIT_SHARE_SERVER_MODE = false if (appQueryParams.lockConnect) { @@ -262,7 +268,7 @@ const Inner = ({ hidden, customServersList }: { hidden?: boolean, customServersL } dispatchEvent(new CustomEvent('connect', { detail: connectOptions })) }} - versions={supportedVersions} + versions={protocolSupportedVersions} /> : null const serversListJsx = Date: Sun, 3 Aug 2025 03:20:38 +0300 Subject: [PATCH 37/98] fix: up protocol to support 1.21.5 --- patches/minecraft-protocol.patch | 110 +++++++++++++++++-------------- pnpm-lock.yaml | 77 +++++++++++----------- 2 files changed, 101 insertions(+), 86 deletions(-) diff --git a/patches/minecraft-protocol.patch b/patches/minecraft-protocol.patch index 6a82d5cf..108085a1 100644 --- a/patches/minecraft-protocol.patch +++ b/patches/minecraft-protocol.patch @@ -1,8 +1,8 @@ diff --git a/src/client/chat.js b/src/client/chat.js -index 8d0869b150681574ad19292a026cce9f67a137ee..2efa2e6600f017b566155974cb9fb1856fa582f9 100644 +index a50f4b988ad9fb29d5eb9e1633b498615aa9cd28..b8b819eef0762a77d9db5c0fb06be648303628c7 100644 --- a/src/client/chat.js +++ b/src/client/chat.js -@@ -109,7 +109,7 @@ module.exports = function (client, options) { +@@ -110,7 +110,7 @@ module.exports = function (client, options) { for (const player of packet.data) { if (player.chatSession) { client._players[player.uuid] = { @@ -11,8 +11,8 @@ index 8d0869b150681574ad19292a026cce9f67a137ee..2efa2e6600f017b566155974cb9fb185 publicKeyDER: player.chatSession.publicKey.keyBytes, sessionUuid: player.chatSession.uuid } -@@ -119,7 +119,7 @@ module.exports = function (client, options) { - +@@ -120,7 +120,7 @@ module.exports = function (client, options) { + if (player.crypto) { client._players[player.uuid] = { - publicKey: crypto.createPublicKey({ key: player.crypto.publicKey, format: 'der', type: 'spki' }), @@ -20,7 +20,7 @@ index 8d0869b150681574ad19292a026cce9f67a137ee..2efa2e6600f017b566155974cb9fb185 publicKeyDER: player.crypto.publicKey, signature: player.crypto.signature, displayName: player.displayName || player.name -@@ -189,7 +189,7 @@ module.exports = function (client, options) { +@@ -190,7 +190,7 @@ module.exports = function (client, options) { if (mcData.supportFeature('useChatSessions')) { const tsDelta = BigInt(Date.now()) - packet.timestamp const expired = !packet.timestamp || tsDelta > messageExpireTime || tsDelta < 0 @@ -28,26 +28,26 @@ index 8d0869b150681574ad19292a026cce9f67a137ee..2efa2e6600f017b566155974cb9fb185 + const verified = false && !packet.unsignedChatContent && updateAndValidateSession(packet.senderUuid, packet.plainMessage, packet.signature, packet.index, packet.previousMessages, packet.salt, packet.timestamp) && !expired if (verified) client._signatureCache.push(packet.signature) client.emit('playerChat', { - plainMessage: packet.plainMessage, -@@ -354,7 +354,7 @@ module.exports = function (client, options) { + globalIndex: packet.globalIndex, +@@ -356,7 +356,7 @@ module.exports = function (client, options) { } } - + - client._signedChat = (message, options = {}) => { + client._signedChat = async (message, options = {}) => { options.timestamp = options.timestamp || BigInt(Date.now()) options.salt = options.salt || 1n - -@@ -396,7 +396,7 @@ module.exports = function (client, options) { + +@@ -401,7 +401,7 @@ module.exports = function (client, options) { message, timestamp: options.timestamp, salt: options.salt, - signature: (client.profileKeys && client._session) ? client.signMessage(message, options.timestamp, options.salt, undefined, acknowledgements) : undefined, + signature: (client.profileKeys && client._session) ? await client.signMessage(message, options.timestamp, options.salt, undefined, acknowledgements) : undefined, offset: client._lastSeenMessages.pending, + checksum: computeChatChecksum(client._lastSeenMessages), // 1.21.5+ acknowledged - }) -@@ -410,7 +410,7 @@ module.exports = function (client, options) { +@@ -416,7 +416,7 @@ module.exports = function (client, options) { message, timestamp: options.timestamp, salt: options.salt, @@ -57,7 +57,7 @@ index 8d0869b150681574ad19292a026cce9f67a137ee..2efa2e6600f017b566155974cb9fb185 previousMessages: client._lastSeenMessages.map((e) => ({ messageSender: e.sender, diff --git a/src/client/encrypt.js b/src/client/encrypt.js -index b9d21bab9faccd5dbf1975fc423fc55c73e906c5..99ffd76527b410e3a393181beb260108f4c63536 100644 +index 63cc2bd9615100bd2fd63dfe14c094aa6b8cd1c9..36df57d1196af9761d920fa285ac48f85410eaef 100644 --- a/src/client/encrypt.js +++ b/src/client/encrypt.js @@ -25,7 +25,11 @@ module.exports = function (client, options) { @@ -71,7 +71,7 @@ index b9d21bab9faccd5dbf1975fc423fc55c73e906c5..99ffd76527b410e3a393181beb260108 + // clearTimeout(loginTimeout) + // }) } - + function onJoinServerResponse (err) { diff --git a/src/client/play.js b/src/client/play.js index 559607f34e9a5b2b7809423f8ca4cd6746b60225..4dc1c3139438cc2729b05c57e57bd00252728f8a 100644 @@ -87,27 +87,10 @@ index 559607f34e9a5b2b7809423f8ca4cd6746b60225..4dc1c3139438cc2729b05c57e57bd002 }) // Server should send finish_configuration on its own right after sending the client a dimension codec diff --git a/src/client.js b/src/client.js -index 5c7a62b013daa69be91ec9e763b1f48ffe96ffa6..174d42a77740a937afcb106e1f39a9ee824a24b9 100644 +index e369e77d055ba919e8f9da7b8e8b5dc879c74cf4..11c6bff299f1186ab1ecb6744f53ff0c648ab192 100644 --- a/src/client.js +++ b/src/client.js -@@ -89,10 +89,12 @@ class Client extends EventEmitter { - parsed.metadata.name = parsed.data.name - parsed.data = parsed.data.params - parsed.metadata.state = state -- debug('read packet ' + state + '.' + parsed.metadata.name) -- if (debug.enabled) { -- const s = JSON.stringify(parsed.data, null, 2) -- debug(s && s.length > 10000 ? parsed.data : s) -+ if (!globalThis.excludeCommunicationDebugEvents?.includes(parsed.metadata.name)) { -+ debug('read packet ' + state + '.' + parsed.metadata.name) -+ if (debug.enabled) { -+ const s = JSON.stringify(parsed.data, null, 2) -+ debug(s && s.length > 10000 ? parsed.data : s) -+ } - } - if (this._hasBundlePacket && parsed.metadata.name === 'bundle_delimiter') { - if (this._mcBundle.length) { // End bundle -@@ -110,7 +112,13 @@ class Client extends EventEmitter { +@@ -111,7 +111,13 @@ class Client extends EventEmitter { this._hasBundlePacket = false } } else { @@ -122,9 +105,9 @@ index 5c7a62b013daa69be91ec9e763b1f48ffe96ffa6..174d42a77740a937afcb106e1f39a9ee } }) } -@@ -168,7 +176,10 @@ class Client extends EventEmitter { +@@ -169,7 +175,10 @@ class Client extends EventEmitter { } - + const onFatalError = (err) => { - this.emit('error', err) + // todo find out what is trying to write after client disconnect @@ -133,8 +116,8 @@ index 5c7a62b013daa69be91ec9e763b1f48ffe96ffa6..174d42a77740a937afcb106e1f39a9ee + } endSocket() } - -@@ -197,6 +208,8 @@ class Client extends EventEmitter { + +@@ -198,6 +207,8 @@ class Client extends EventEmitter { serializer -> framer -> socket -> splitter -> deserializer */ if (this.serializer) { this.serializer.end() @@ -143,17 +126,48 @@ index 5c7a62b013daa69be91ec9e763b1f48ffe96ffa6..174d42a77740a937afcb106e1f39a9ee } else { if (this.socket) this.socket.end() } -@@ -238,8 +251,11 @@ class Client extends EventEmitter { - - write (name, params) { - if (!this.serializer.writable) { return } -- debug('writing packet ' + this.state + '.' + name) -- debug(params) -+ if (!globalThis.excludeCommunicationDebugEvents?.includes(name)) { -+ debug(`[${this.state}] from ${this.isServer ? 'server' : 'client'}: ` + name) -+ debug(params) -+ } +@@ -243,6 +254,7 @@ class Client extends EventEmitter { + debug('writing packet ' + this.state + '.' + name) + debug(params) + } + this.emit('writePacket', name, params) this.serializer.write({ name, params }) } - + +diff --git a/src/client.js.rej b/src/client.js.rej +new file mode 100644 +index 0000000000000000000000000000000000000000..1101e2477adfdc004381b78e7d70953dacb7b484 +--- /dev/null ++++ b/src/client.js.rej +@@ -0,0 +1,31 @@ ++@@ -89,10 +89,12 @@ ++ parsed.metadata.name = parsed.data.name ++ parsed.data = parsed.data.params ++ parsed.metadata.state = state ++- debug('read packet ' + state + '.' + parsed.metadata.name) ++- if (debug.enabled) { ++- const s = JSON.stringify(parsed.data, null, 2) ++- debug(s && s.length > 10000 ? parsed.data : s) +++ if (!globalThis.excludeCommunicationDebugEvents?.includes(parsed.metadata.name)) { +++ debug('read packet ' + state + '.' + parsed.metadata.name) +++ if (debug.enabled) { +++ const s = JSON.stringify(parsed.data, null, 2) +++ debug(s && s.length > 10000 ? parsed.data : s) +++ } ++ } ++ if (this._hasBundlePacket && parsed.metadata.name === 'bundle_delimiter') { ++ if (this._mcBundle.length) { // End bundle ++@@ -239,8 +252,11 @@ ++ ++ write (name, params) { ++ if (!this.serializer.writable) { return } ++- debug('writing packet ' + this.state + '.' + name) ++- debug(params) +++ if (!globalThis.excludeCommunicationDebugEvents?.includes(name)) { +++ debug(`[${this.state}] from ${this.isServer ? 'server' : 'client'}: ` + name) +++ debug(params) +++ } +++ this.emit('writePacket', name, params) ++ this.serializer.write({ name, params }) ++ } ++ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 52c2ff30..aa67e479 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,7 +22,7 @@ overrides: patchedDependencies: minecraft-protocol: - hash: a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0 + hash: 40871f9192322be5e5fc0018a0a506a43f33973816b1b544743817609b606150 path: patches/minecraft-protocol.patch mineflayer-item-map-downloader@1.2.0: hash: a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad @@ -136,13 +136,13 @@ importers: version: 4.17.21 mcraft-fun-mineflayer: specifier: ^0.1.23 - version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/26c04c26f8eed2499a7c56eb4664649f3f54b767(encoding@0.1.13)) + version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/c9c77d6511e37c452ebe48790724da165d6ad448(encoding@0.1.13)) minecraft-data: specifier: 3.92.0 version: 3.92.0 minecraft-protocol: specifier: github:PrismarineJS/node-minecraft-protocol#master - version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176(patch_hash=a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0)(encoding@0.1.13) + version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/ca5af6307a817b655a76c82f56a4c6c78e272453(patch_hash=40871f9192322be5e5fc0018a0a506a43f33973816b1b544743817609b606150)(encoding@0.1.13) mineflayer-item-map-downloader: specifier: github:zardoy/mineflayer-item-map-downloader version: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824(patch_hash=a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad)(encoding@0.1.13) @@ -341,7 +341,7 @@ importers: version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/89c33d396f3fde4804c71f4be3c203ade1833b41(@types/react@18.3.18)(react@18.3.1) mineflayer: specifier: github:zardoy/mineflayer#gen-the-master - version: https://codeload.github.com/zardoy/mineflayer/tar.gz/26c04c26f8eed2499a7c56eb4664649f3f54b767(encoding@0.1.13) + version: https://codeload.github.com/zardoy/mineflayer/tar.gz/c9c77d6511e37c452ebe48790724da165d6ad448(encoding@0.1.13) mineflayer-mouse: specifier: ^0.1.14 version: 0.1.14 @@ -6651,9 +6651,9 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/89c33d396f3fde4804c71f4be3c203ade1833b41} version: 1.0.1 - minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176: - resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176} - version: 1.58.0 + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/ca5af6307a817b655a76c82f56a4c6c78e272453: + resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/ca5af6307a817b655a76c82f56a4c6c78e272453} + version: 1.60.0 engines: {node: '>=22'} minecraft-wrap@1.6.0: @@ -6678,9 +6678,9 @@ packages: resolution: {integrity: sha512-GtW4hkijyZbSu5LKYYD89xZu+XY7OoP7IkrCnNEn6EdPm0+vr2THoJgFGKrlze9/81+T+P3E4qvJXNFiU/zeJg==} engines: {node: '>=22'} - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/26c04c26f8eed2499a7c56eb4664649f3f54b767: - resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/26c04c26f8eed2499a7c56eb4664649f3f54b767} - version: 4.30.0 + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/c9c77d6511e37c452ebe48790724da165d6ad448: + resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/c9c77d6511e37c452ebe48790724da165d6ad448} + version: 4.31.0 engines: {node: '>=22'} minimalistic-assert@1.0.1: @@ -7392,8 +7392,8 @@ packages: prismarine-entity@2.5.0: resolution: {integrity: sha512-nRPCawUwf9r3iKqi4I7mZRlir1Ix+DffWYdWq6p/KNnmiXve+xHE5zv8XCdhZlUmOshugHv5ONl9o6ORAkCNIA==} - prismarine-item@1.16.0: - resolution: {integrity: sha512-88Tz+/6HquYIsDuseae5G3IbqLeMews2L+ba2gX+p6K6soU9nuFhCfbwN56QuB7d/jZFcWrCYAPE5+UhwWh67w==} + prismarine-item@1.17.0: + resolution: {integrity: sha512-wN1OjP+f+Uvtjo3KzeCkVSy96CqZ8yG7cvuvlGwcYupQ6ct7LtNkubHp0AHuLMJ0vbbfAC0oZ2bWOgI1DYp8WA==} prismarine-nbt@2.7.0: resolution: {integrity: sha512-Du9OLQAcCj3y29YtewOJbbV4ARaSUEJiTguw0PPQbPBy83f+eCyDRkyBpnXTi/KPyEpgYCzsjGzElevLpFoYGQ==} @@ -8324,6 +8324,7 @@ packages: source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} + deprecated: The work that was done in this beta branch won't be included in future versions sourcemap-codec@1.4.8: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} @@ -9668,7 +9669,7 @@ snapshots: '@babel/core': 7.26.9 '@babel/helper-compilation-targets': 7.26.5 '@babel/helper-plugin-utils': 7.26.5 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.1 lodash.debounce: 4.0.8 resolve: 1.22.10 transitivePeerDependencies: @@ -11318,7 +11319,7 @@ snapshots: minecraft-data: 3.92.0 mineflayer: 4.30.0(encoding@0.1.13) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 - prismarine-item: 1.16.0 + prismarine-item: 1.17.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b vec3: 0.1.10 transitivePeerDependencies: @@ -12881,7 +12882,7 @@ snapshots: dependencies: '@typescript-eslint/types': 6.1.0 '@typescript-eslint/visitor-keys': 6.1.0 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.1 globby: 11.1.0 is-glob: 4.0.3 semver: 7.7.1 @@ -13077,13 +13078,13 @@ snapshots: flatmap: 0.0.3 long: 5.3.1 minecraft-data: 3.92.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176(patch_hash=a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/ca5af6307a817b655a76c82f56a4c6c78e272453(patch_hash=40871f9192322be5e5fc0018a0a506a43f33973816b1b544743817609b606150)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) prismarine-entity: 2.5.0 - prismarine-item: 1.16.0 + prismarine-item: 1.17.0 prismarine-nbt: 2.7.0 prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0) prismarine-windows: 2.9.0 @@ -13113,13 +13114,13 @@ snapshots: flatmap: 0.0.3 long: 5.3.1 minecraft-data: 3.92.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176(patch_hash=a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/ca5af6307a817b655a76c82f56a4c6c78e272453(patch_hash=40871f9192322be5e5fc0018a0a506a43f33973816b1b544743817609b606150)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) prismarine-entity: 2.5.0 - prismarine-item: 1.16.0 + prismarine-item: 1.17.0 prismarine-nbt: 2.7.0 prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0) prismarine-windows: 2.9.0 @@ -14502,7 +14503,7 @@ snapshots: detect-port@1.6.1: dependencies: address: 1.2.2 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.1 transitivePeerDependencies: - supports-color @@ -16109,7 +16110,7 @@ snapshots: https-proxy-agent@4.0.0: dependencies: agent-base: 5.1.1 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.1 transitivePeerDependencies: - supports-color @@ -16956,13 +16957,13 @@ snapshots: maxrects-packer: '@zardoy/maxrects-packer@2.7.4' zod: 3.24.2 - mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/26c04c26f8eed2499a7c56eb4664649f3f54b767(encoding@0.1.13)): + mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/c9c77d6511e37c452ebe48790724da165d6ad448(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/6c2204a813690ead420e2b8c7f0ef32ca357d176(patch_hash=a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0)(encoding@0.1.13) - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/26c04c26f8eed2499a7c56eb4664649f3f54b767(encoding@0.1.13) - prismarine-item: 1.16.0 + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/ca5af6307a817b655a76c82f56a4c6c78e272453(patch_hash=40871f9192322be5e5fc0018a0a506a43f33973816b1b544743817609b606150)(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/c9c77d6511e37c452ebe48790724da165d6ad448(encoding@0.1.13) + prismarine-item: 1.17.0 ws: 8.18.1 transitivePeerDependencies: - bufferutil @@ -17191,7 +17192,7 @@ snapshots: micromark@4.0.2: dependencies: '@types/debug': 4.1.12 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.1 decode-named-character-reference: 1.1.0 devlop: 1.1.0 micromark-core-commonmark: 2.0.3 @@ -17279,7 +17280,7 @@ snapshots: - '@types/react' - react - minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176(patch_hash=a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0)(encoding@0.1.13): + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/ca5af6307a817b655a76c82f56a4c6c78e272453(patch_hash=40871f9192322be5e5fc0018a0a506a43f33973816b1b544743817609b606150)(encoding@0.1.13): dependencies: '@types/node-rsa': 1.1.4 '@types/readable-stream': 4.0.18 @@ -17341,7 +17342,7 @@ snapshots: dependencies: change-case: 5.4.4 debug: 4.4.1 - prismarine-item: 1.16.0 + prismarine-item: 1.17.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c transitivePeerDependencies: - supports-color @@ -17351,7 +17352,7 @@ snapshots: minecraft-data: 3.92.0 prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-entity: 2.5.0 - prismarine-item: 1.16.0 + prismarine-item: 1.17.0 prismarine-nbt: 2.7.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b vec3: 0.1.10 @@ -17359,13 +17360,13 @@ snapshots: mineflayer@4.30.0(encoding@0.1.13): dependencies: minecraft-data: 3.92.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176(patch_hash=a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/ca5af6307a817b655a76c82f56a4c6c78e272453(patch_hash=40871f9192322be5e5fc0018a0a506a43f33973816b1b544743817609b606150)(encoding@0.1.13) prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chat: 1.11.0 prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) prismarine-entity: 2.5.0 - prismarine-item: 1.16.0 + prismarine-item: 1.17.0 prismarine-nbt: 2.7.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b prismarine-recipe: 1.3.1(prismarine-registry@1.11.0) @@ -17379,17 +17380,17 @@ snapshots: - encoding - supports-color - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/26c04c26f8eed2499a7c56eb4664649f3f54b767(encoding@0.1.13): + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/c9c77d6511e37c452ebe48790724da165d6ad448(encoding@0.1.13): dependencies: '@nxg-org/mineflayer-physics-util': 1.8.10 minecraft-data: 3.92.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176(patch_hash=a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/ca5af6307a817b655a76c82f56a4c6c78e272453(patch_hash=40871f9192322be5e5fc0018a0a506a43f33973816b1b544743817609b606150)(encoding@0.1.13) prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chat: 1.11.0 prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) prismarine-entity: 2.5.0 - prismarine-item: 1.16.0 + prismarine-item: 1.17.0 prismarine-nbt: 2.7.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b prismarine-recipe: 1.3.1(prismarine-registry@1.11.0) @@ -18181,7 +18182,7 @@ snapshots: minecraft-data: 3.92.0 prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) prismarine-chat: 1.11.0 - prismarine-item: 1.16.0 + prismarine-item: 1.17.0 prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 @@ -18207,11 +18208,11 @@ snapshots: prismarine-entity@2.5.0: dependencies: prismarine-chat: 1.11.0 - prismarine-item: 1.16.0 + prismarine-item: 1.17.0 prismarine-registry: 1.11.0 vec3: 0.1.10 - prismarine-item@1.16.0: + prismarine-item@1.17.0: dependencies: prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 @@ -18265,7 +18266,7 @@ snapshots: prismarine-windows@2.9.0: dependencies: - prismarine-item: 1.16.0 + prismarine-item: 1.17.0 prismarine-registry: 1.11.0 typed-emitter: 2.1.0 From d41527edc8db54c26ce1edcdee3664e44e324726 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 3 Aug 2025 03:33:37 +0300 Subject: [PATCH 38/98] manually fix lockfile because of silly pnpm dep resolution --- pnpm-lock.yaml | 31 ++----------------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aa67e479..b79d619e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6674,10 +6674,6 @@ packages: mineflayer-pathfinder@2.4.5: resolution: {integrity: sha512-Jh3JnUgRLwhMh2Dugo4SPza68C41y+NPP5sdsgxRu35ydndo70i1JJGxauVWbXrpNwIxYNztUw78aFyb7icw8g==} - mineflayer@4.30.0: - resolution: {integrity: sha512-GtW4hkijyZbSu5LKYYD89xZu+XY7OoP7IkrCnNEn6EdPm0+vr2THoJgFGKrlze9/81+T+P3E4qvJXNFiU/zeJg==} - engines: {node: '>=22'} - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/c9c77d6511e37c452ebe48790724da165d6ad448: resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/c9c77d6511e37c452ebe48790724da165d6ad448} version: 4.31.0 @@ -11317,7 +11313,7 @@ snapshots: dependencies: '@nxg-org/mineflayer-util-plugin': 1.8.4 minecraft-data: 3.92.0 - mineflayer: 4.30.0(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/c9c77d6511e37c452ebe48790724da165d6ad448(encoding@0.1.13) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-item: 1.17.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b @@ -17332,7 +17328,7 @@ snapshots: mineflayer-item-map-downloader@https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824(patch_hash=a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad)(encoding@0.1.13): dependencies: - mineflayer: 4.30.0(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/c9c77d6511e37c452ebe48790724da165d6ad448(encoding@0.1.13) sharp: 0.30.7 transitivePeerDependencies: - encoding @@ -17357,29 +17353,6 @@ snapshots: prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b vec3: 0.1.10 - mineflayer@4.30.0(encoding@0.1.13): - dependencies: - minecraft-data: 3.92.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/ca5af6307a817b655a76c82f56a4c6c78e272453(patch_hash=40871f9192322be5e5fc0018a0a506a43f33973816b1b544743817609b606150)(encoding@0.1.13) - prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 - prismarine-chat: 1.11.0 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) - prismarine-entity: 2.5.0 - prismarine-item: 1.17.0 - prismarine-nbt: 2.7.0 - prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b - prismarine-recipe: 1.3.1(prismarine-registry@1.11.0) - prismarine-registry: 1.11.0 - prismarine-windows: 2.9.0 - prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c - protodef: 1.18.0 - typed-emitter: 1.4.0 - vec3: 0.1.10 - transitivePeerDependencies: - - encoding - - supports-color - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/c9c77d6511e37c452ebe48790724da165d6ad448(encoding@0.1.13): dependencies: '@nxg-org/mineflayer-physics-util': 1.8.10 From d7bd26b6b5f94e838d04c4cf19a8b9acc5966c5e Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 6 Aug 2025 01:47:09 +0300 Subject: [PATCH 39/98] up protocol patch --- patches/minecraft-protocol.patch | 13 -------- pnpm-lock.yaml | 51 ++++++++++++++++++++++++-------- 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/patches/minecraft-protocol.patch b/patches/minecraft-protocol.patch index 108085a1..e74f7e1d 100644 --- a/patches/minecraft-protocol.patch +++ b/patches/minecraft-protocol.patch @@ -73,19 +73,6 @@ index 63cc2bd9615100bd2fd63dfe14c094aa6b8cd1c9..36df57d1196af9761d920fa285ac48f8 } function onJoinServerResponse (err) { -diff --git a/src/client/play.js b/src/client/play.js -index 559607f34e9a5b2b7809423f8ca4cd6746b60225..4dc1c3139438cc2729b05c57e57bd00252728f8a 100644 ---- a/src/client/play.js -+++ b/src/client/play.js -@@ -53,7 +53,7 @@ module.exports = function (client, options) { - client.write('configuration_acknowledged', {}) - } - client.state = states.CONFIGURATION -- client.on('select_known_packs', () => { -+ client.once('select_known_packs', () => { - client.write('select_known_packs', { packs: [] }) - }) - // Server should send finish_configuration on its own right after sending the client a dimension codec diff --git a/src/client.js b/src/client.js index e369e77d055ba919e8f9da7b8e8b5dc879c74cf4..11c6bff299f1186ab1ecb6744f53ff0c648ab192 100644 --- a/src/client.js diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b79d619e..db095f5b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,7 +22,7 @@ overrides: patchedDependencies: minecraft-protocol: - hash: 40871f9192322be5e5fc0018a0a506a43f33973816b1b544743817609b606150 + hash: b417b3b7c5fd96e59abab5c1075b86b88bada2c980e4b54df13ca69b8f0091d9 path: patches/minecraft-protocol.patch mineflayer-item-map-downloader@1.2.0: hash: a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad @@ -142,7 +142,7 @@ importers: version: 3.92.0 minecraft-protocol: specifier: github:PrismarineJS/node-minecraft-protocol#master - version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/ca5af6307a817b655a76c82f56a4c6c78e272453(patch_hash=40871f9192322be5e5fc0018a0a506a43f33973816b1b544743817609b606150)(encoding@0.1.13) + version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb(patch_hash=b417b3b7c5fd96e59abab5c1075b86b88bada2c980e4b54df13ca69b8f0091d9)(encoding@0.1.13) mineflayer-item-map-downloader: specifier: github:zardoy/mineflayer-item-map-downloader version: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824(patch_hash=a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad)(encoding@0.1.13) @@ -6651,9 +6651,9 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/89c33d396f3fde4804c71f4be3c203ade1833b41} version: 1.0.1 - minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/ca5af6307a817b655a76c82f56a4c6c78e272453: - resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/ca5af6307a817b655a76c82f56a4c6c78e272453} - version: 1.60.0 + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb: + resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb} + version: 1.60.1 engines: {node: '>=22'} minecraft-wrap@1.6.0: @@ -6674,6 +6674,10 @@ packages: mineflayer-pathfinder@2.4.5: resolution: {integrity: sha512-Jh3JnUgRLwhMh2Dugo4SPza68C41y+NPP5sdsgxRu35ydndo70i1JJGxauVWbXrpNwIxYNztUw78aFyb7icw8g==} + mineflayer@4.31.0: + resolution: {integrity: sha512-oqiNa5uP4kXiPlj4+Jn+9QozPMsMy0U8/YP5d6+KSAeWthtuJHeQqcYgWG5lkC3LHMqHqtEu4MNdXt6GZjFNTQ==} + engines: {node: '>=22'} + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/c9c77d6511e37c452ebe48790724da165d6ad448: resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/c9c77d6511e37c452ebe48790724da165d6ad448} version: 4.31.0 @@ -11313,7 +11317,7 @@ snapshots: dependencies: '@nxg-org/mineflayer-util-plugin': 1.8.4 minecraft-data: 3.92.0 - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/c9c77d6511e37c452ebe48790724da165d6ad448(encoding@0.1.13) + mineflayer: 4.31.0(encoding@0.1.13) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-item: 1.17.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b @@ -13074,7 +13078,7 @@ snapshots: flatmap: 0.0.3 long: 5.3.1 minecraft-data: 3.92.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/ca5af6307a817b655a76c82f56a4c6c78e272453(patch_hash=40871f9192322be5e5fc0018a0a506a43f33973816b1b544743817609b606150)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb(patch_hash=b417b3b7c5fd96e59abab5c1075b86b88bada2c980e4b54df13ca69b8f0091d9)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 @@ -13110,7 +13114,7 @@ snapshots: flatmap: 0.0.3 long: 5.3.1 minecraft-data: 3.92.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/ca5af6307a817b655a76c82f56a4c6c78e272453(patch_hash=40871f9192322be5e5fc0018a0a506a43f33973816b1b544743817609b606150)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb(patch_hash=b417b3b7c5fd96e59abab5c1075b86b88bada2c980e4b54df13ca69b8f0091d9)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 @@ -16957,7 +16961,7 @@ snapshots: 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/ca5af6307a817b655a76c82f56a4c6c78e272453(patch_hash=40871f9192322be5e5fc0018a0a506a43f33973816b1b544743817609b606150)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb(patch_hash=b417b3b7c5fd96e59abab5c1075b86b88bada2c980e4b54df13ca69b8f0091d9)(encoding@0.1.13) mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/c9c77d6511e37c452ebe48790724da165d6ad448(encoding@0.1.13) prismarine-item: 1.17.0 ws: 8.18.1 @@ -17276,7 +17280,7 @@ snapshots: - '@types/react' - react - minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/ca5af6307a817b655a76c82f56a4c6c78e272453(patch_hash=40871f9192322be5e5fc0018a0a506a43f33973816b1b544743817609b606150)(encoding@0.1.13): + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb(patch_hash=b417b3b7c5fd96e59abab5c1075b86b88bada2c980e4b54df13ca69b8f0091d9)(encoding@0.1.13): dependencies: '@types/node-rsa': 1.1.4 '@types/readable-stream': 4.0.18 @@ -17328,7 +17332,7 @@ snapshots: mineflayer-item-map-downloader@https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824(patch_hash=a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad)(encoding@0.1.13): dependencies: - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/c9c77d6511e37c452ebe48790724da165d6ad448(encoding@0.1.13) + mineflayer: 4.31.0(encoding@0.1.13) sharp: 0.30.7 transitivePeerDependencies: - encoding @@ -17353,11 +17357,34 @@ snapshots: prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b vec3: 0.1.10 + mineflayer@4.31.0(encoding@0.1.13): + dependencies: + minecraft-data: 3.92.0 + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb(patch_hash=b417b3b7c5fd96e59abab5c1075b86b88bada2c980e4b54df13ca69b8f0091d9)(encoding@0.1.13) + prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-chat: 1.11.0 + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) + prismarine-entity: 2.5.0 + prismarine-item: 1.17.0 + prismarine-nbt: 2.7.0 + prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b + prismarine-recipe: 1.3.1(prismarine-registry@1.11.0) + prismarine-registry: 1.11.0 + prismarine-windows: 2.9.0 + prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c + protodef: 1.18.0 + typed-emitter: 1.4.0 + vec3: 0.1.10 + transitivePeerDependencies: + - encoding + - supports-color + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/c9c77d6511e37c452ebe48790724da165d6ad448(encoding@0.1.13): dependencies: '@nxg-org/mineflayer-physics-util': 1.8.10 minecraft-data: 3.92.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/ca5af6307a817b655a76c82f56a4c6c78e272453(patch_hash=40871f9192322be5e5fc0018a0a506a43f33973816b1b544743817609b606150)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb(patch_hash=b417b3b7c5fd96e59abab5c1075b86b88bada2c980e4b54df13ca69b8f0091d9)(encoding@0.1.13) prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chat: 1.11.0 From 167b49da08bf66f2617188f6d905e5efee9da42f Mon Sep 17 00:00:00 2001 From: Vitaly Date: Fri, 8 Aug 2025 01:07:52 +0200 Subject: [PATCH 40/98] fix: fix cannot write after stream was destroyed message (#413) --- README.MD | 2 +- patches/minecraft-protocol.patch | 77 +++++++++----------------------- pnpm-lock.yaml | 16 +++---- src/index.ts | 8 +++- 4 files changed, 36 insertions(+), 67 deletions(-) diff --git a/README.MD b/README.MD index e9127a73..7978cee5 100644 --- a/README.MD +++ b/README.MD @@ -54,7 +54,7 @@ Howerver, it's known that these browsers have issues: ### Versions Support -Server versions 1.8 - 1.21.4 are supported. +Server versions 1.8 - 1.21.5 are supported. First class versions (most of the features are tested on these versions): - 1.19.4 diff --git a/patches/minecraft-protocol.patch b/patches/minecraft-protocol.patch index e74f7e1d..5dec44d7 100644 --- a/patches/minecraft-protocol.patch +++ b/patches/minecraft-protocol.patch @@ -1,8 +1,8 @@ diff --git a/src/client/chat.js b/src/client/chat.js -index a50f4b988ad9fb29d5eb9e1633b498615aa9cd28..b8b819eef0762a77d9db5c0fb06be648303628c7 100644 +index 0021870994fc59a82f0ac8aba0a65a8be43ef2f4..a53fceb843105ea2a1d88722b3fc7c3b43cb102a 100644 --- a/src/client/chat.js +++ b/src/client/chat.js -@@ -110,7 +110,7 @@ module.exports = function (client, options) { +@@ -116,7 +116,7 @@ module.exports = function (client, options) { for (const player of packet.data) { if (player.chatSession) { client._players[player.uuid] = { @@ -11,8 +11,8 @@ index a50f4b988ad9fb29d5eb9e1633b498615aa9cd28..b8b819eef0762a77d9db5c0fb06be648 publicKeyDER: player.chatSession.publicKey.keyBytes, sessionUuid: player.chatSession.uuid } -@@ -120,7 +120,7 @@ module.exports = function (client, options) { - +@@ -126,7 +126,7 @@ module.exports = function (client, options) { + if (player.crypto) { client._players[player.uuid] = { - publicKey: crypto.createPublicKey({ key: player.crypto.publicKey, format: 'der', type: 'spki' }), @@ -20,7 +20,7 @@ index a50f4b988ad9fb29d5eb9e1633b498615aa9cd28..b8b819eef0762a77d9db5c0fb06be648 publicKeyDER: player.crypto.publicKey, signature: player.crypto.signature, displayName: player.displayName || player.name -@@ -190,7 +190,7 @@ module.exports = function (client, options) { +@@ -196,7 +196,7 @@ module.exports = function (client, options) { if (mcData.supportFeature('useChatSessions')) { const tsDelta = BigInt(Date.now()) - packet.timestamp const expired = !packet.timestamp || tsDelta > messageExpireTime || tsDelta < 0 @@ -29,16 +29,16 @@ index a50f4b988ad9fb29d5eb9e1633b498615aa9cd28..b8b819eef0762a77d9db5c0fb06be648 if (verified) client._signatureCache.push(packet.signature) client.emit('playerChat', { globalIndex: packet.globalIndex, -@@ -356,7 +356,7 @@ module.exports = function (client, options) { +@@ -362,7 +362,7 @@ module.exports = function (client, options) { } } - + - client._signedChat = (message, options = {}) => { + client._signedChat = async (message, options = {}) => { options.timestamp = options.timestamp || BigInt(Date.now()) options.salt = options.salt || 1n - -@@ -401,7 +401,7 @@ module.exports = function (client, options) { + +@@ -407,7 +407,7 @@ module.exports = function (client, options) { message, timestamp: options.timestamp, salt: options.salt, @@ -47,7 +47,7 @@ index a50f4b988ad9fb29d5eb9e1633b498615aa9cd28..b8b819eef0762a77d9db5c0fb06be648 offset: client._lastSeenMessages.pending, checksum: computeChatChecksum(client._lastSeenMessages), // 1.21.5+ acknowledged -@@ -416,7 +416,7 @@ module.exports = function (client, options) { +@@ -422,7 +422,7 @@ module.exports = function (client, options) { message, timestamp: options.timestamp, salt: options.salt, @@ -71,10 +71,10 @@ index 63cc2bd9615100bd2fd63dfe14c094aa6b8cd1c9..36df57d1196af9761d920fa285ac48f8 + // clearTimeout(loginTimeout) + // }) } - + function onJoinServerResponse (err) { diff --git a/src/client.js b/src/client.js -index e369e77d055ba919e8f9da7b8e8b5dc879c74cf4..11c6bff299f1186ab1ecb6744f53ff0c648ab192 100644 +index e369e77d055ba919e8f9da7b8e8b5dc879c74cf4..54bb9e6644388e9b6bd42b3012951875989cdf0c 100644 --- a/src/client.js +++ b/src/client.js @@ -111,7 +111,13 @@ class Client extends EventEmitter { @@ -94,7 +94,7 @@ index e369e77d055ba919e8f9da7b8e8b5dc879c74cf4..11c6bff299f1186ab1ecb6744f53ff0c } @@ -169,7 +175,10 @@ class Client extends EventEmitter { } - + const onFatalError = (err) => { - this.emit('error', err) + // todo find out what is trying to write after client disconnect @@ -103,58 +103,23 @@ index e369e77d055ba919e8f9da7b8e8b5dc879c74cf4..11c6bff299f1186ab1ecb6744f53ff0c + } endSocket() } - -@@ -198,6 +207,8 @@ class Client extends EventEmitter { + +@@ -198,6 +207,10 @@ class Client extends EventEmitter { serializer -> framer -> socket -> splitter -> deserializer */ if (this.serializer) { this.serializer.end() -+ this.socket?.end() -+ this.socket?.emit('end') ++ setTimeout(() => { ++ this.socket?.end() ++ this.socket?.emit('end') ++ }, 2000) // allow the serializer to finish writing } else { if (this.socket) this.socket.end() } -@@ -243,6 +254,7 @@ class Client extends EventEmitter { +@@ -243,6 +256,7 @@ class Client extends EventEmitter { debug('writing packet ' + this.state + '.' + name) debug(params) } + this.emit('writePacket', name, params) this.serializer.write({ name, params }) } - -diff --git a/src/client.js.rej b/src/client.js.rej -new file mode 100644 -index 0000000000000000000000000000000000000000..1101e2477adfdc004381b78e7d70953dacb7b484 ---- /dev/null -+++ b/src/client.js.rej -@@ -0,0 +1,31 @@ -+@@ -89,10 +89,12 @@ -+ parsed.metadata.name = parsed.data.name -+ parsed.data = parsed.data.params -+ parsed.metadata.state = state -+- debug('read packet ' + state + '.' + parsed.metadata.name) -+- if (debug.enabled) { -+- const s = JSON.stringify(parsed.data, null, 2) -+- debug(s && s.length > 10000 ? parsed.data : s) -++ if (!globalThis.excludeCommunicationDebugEvents?.includes(parsed.metadata.name)) { -++ debug('read packet ' + state + '.' + parsed.metadata.name) -++ if (debug.enabled) { -++ const s = JSON.stringify(parsed.data, null, 2) -++ debug(s && s.length > 10000 ? parsed.data : s) -++ } -+ } -+ if (this._hasBundlePacket && parsed.metadata.name === 'bundle_delimiter') { -+ if (this._mcBundle.length) { // End bundle -+@@ -239,8 +252,11 @@ -+ -+ write (name, params) { -+ if (!this.serializer.writable) { return } -+- debug('writing packet ' + this.state + '.' + name) -+- debug(params) -++ if (!globalThis.excludeCommunicationDebugEvents?.includes(name)) { -++ debug(`[${this.state}] from ${this.isServer ? 'server' : 'client'}: ` + name) -++ debug(params) -++ } -++ this.emit('writePacket', name, params) -+ this.serializer.write({ name, params }) -+ } -+ + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index db095f5b..132ef32c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,7 +22,7 @@ overrides: patchedDependencies: minecraft-protocol: - hash: b417b3b7c5fd96e59abab5c1075b86b88bada2c980e4b54df13ca69b8f0091d9 + hash: 2a88e61fea1825d9fa5f1584fde810421d553d23e45e6dc829c3697ee3358bea path: patches/minecraft-protocol.patch mineflayer-item-map-downloader@1.2.0: hash: a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad @@ -142,7 +142,7 @@ importers: version: 3.92.0 minecraft-protocol: specifier: github:PrismarineJS/node-minecraft-protocol#master - version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb(patch_hash=b417b3b7c5fd96e59abab5c1075b86b88bada2c980e4b54df13ca69b8f0091d9)(encoding@0.1.13) + version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb(patch_hash=2a88e61fea1825d9fa5f1584fde810421d553d23e45e6dc829c3697ee3358bea)(encoding@0.1.13) mineflayer-item-map-downloader: specifier: github:zardoy/mineflayer-item-map-downloader version: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824(patch_hash=a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad)(encoding@0.1.13) @@ -13078,7 +13078,7 @@ snapshots: flatmap: 0.0.3 long: 5.3.1 minecraft-data: 3.92.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb(patch_hash=b417b3b7c5fd96e59abab5c1075b86b88bada2c980e4b54df13ca69b8f0091d9)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb(patch_hash=2a88e61fea1825d9fa5f1584fde810421d553d23e45e6dc829c3697ee3358bea)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 @@ -13114,7 +13114,7 @@ snapshots: flatmap: 0.0.3 long: 5.3.1 minecraft-data: 3.92.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb(patch_hash=b417b3b7c5fd96e59abab5c1075b86b88bada2c980e4b54df13ca69b8f0091d9)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb(patch_hash=2a88e61fea1825d9fa5f1584fde810421d553d23e45e6dc829c3697ee3358bea)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 @@ -16961,7 +16961,7 @@ snapshots: 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/b404bcaed4c039c5558e889c8617aa866cd7bddb(patch_hash=b417b3b7c5fd96e59abab5c1075b86b88bada2c980e4b54df13ca69b8f0091d9)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb(patch_hash=2a88e61fea1825d9fa5f1584fde810421d553d23e45e6dc829c3697ee3358bea)(encoding@0.1.13) mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/c9c77d6511e37c452ebe48790724da165d6ad448(encoding@0.1.13) prismarine-item: 1.17.0 ws: 8.18.1 @@ -17280,7 +17280,7 @@ snapshots: - '@types/react' - react - minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb(patch_hash=b417b3b7c5fd96e59abab5c1075b86b88bada2c980e4b54df13ca69b8f0091d9)(encoding@0.1.13): + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb(patch_hash=2a88e61fea1825d9fa5f1584fde810421d553d23e45e6dc829c3697ee3358bea)(encoding@0.1.13): dependencies: '@types/node-rsa': 1.1.4 '@types/readable-stream': 4.0.18 @@ -17360,7 +17360,7 @@ snapshots: mineflayer@4.31.0(encoding@0.1.13): dependencies: minecraft-data: 3.92.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb(patch_hash=b417b3b7c5fd96e59abab5c1075b86b88bada2c980e4b54df13ca69b8f0091d9)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb(patch_hash=2a88e61fea1825d9fa5f1584fde810421d553d23e45e6dc829c3697ee3358bea)(encoding@0.1.13) prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chat: 1.11.0 @@ -17384,7 +17384,7 @@ snapshots: dependencies: '@nxg-org/mineflayer-physics-util': 1.8.10 minecraft-data: 3.92.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb(patch_hash=b417b3b7c5fd96e59abab5c1075b86b88bada2c980e4b54df13ca69b8f0091d9)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb(patch_hash=2a88e61fea1825d9fa5f1584fde810421d553d23e45e6dc829c3697ee3358bea)(encoding@0.1.13) prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chat: 1.11.0 diff --git a/src/index.ts b/src/index.ts index d28261f4..c0213f56 100644 --- a/src/index.ts +++ b/src/index.ts @@ -231,8 +231,12 @@ export async function connect (connectOptions: ConnectOptions) { bot.emit('end', '') bot.removeAllListeners() bot._client.removeAllListeners() - //@ts-expect-error TODO? - bot._client = undefined + bot._client = { + //@ts-expect-error + write (packetName) { + console.warn('Tried to write packet', packetName, 'after bot was destroyed') + } + } //@ts-expect-error window.bot = bot = undefined } From caf4695637f1e1b86a98db90f12021df00822ca7 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 8 Aug 2025 18:33:20 +0300 Subject: [PATCH 41/98] feat: silly player on fire renderer effect --- renderer/viewer/lib/basePlayerState.ts | 1 + src/entities.ts | 63 ++++++++++ src/react/FireRenderer.tsx | 152 +++++++++++++++++++++++++ src/reactUi.tsx | 2 + 4 files changed, 218 insertions(+) create mode 100644 src/react/FireRenderer.tsx diff --git a/renderer/viewer/lib/basePlayerState.ts b/renderer/viewer/lib/basePlayerState.ts index af5d9d06..9cf1350a 100644 --- a/renderer/viewer/lib/basePlayerState.ts +++ b/renderer/viewer/lib/basePlayerState.ts @@ -48,6 +48,7 @@ export const getInitialPlayerState = () => proxy({ heldItemMain: undefined as HandItemBlock | undefined, heldItemOff: undefined as HandItemBlock | undefined, perspective: 'first_person' as CameraPerspective, + onFire: false, cameraSpectatingEntity: undefined as number | undefined, diff --git a/src/entities.ts b/src/entities.ts index 79602aa5..cf91ff2c 100644 --- a/src/entities.ts +++ b/src/entities.ts @@ -126,6 +126,28 @@ customEvents.on('gameLoaded', () => { if (entityStatus === EntityStatus.HURT) { getThreeJsRendererMethods()?.damageEntity(entityId, entityStatus) } + + if (entityStatus === EntityStatus.BURNED) { + updateEntityStates(entityId, true, true) + } + }) + + // on fire events + bot._client.on('entity_metadata', (data) => { + if (data.entityId !== bot.entity.id) return + handleEntityMetadata(data) + }) + + bot.on('end', () => { + if (onFireTimeout) { + clearTimeout(onFireTimeout) + } + }) + + bot.on('respawn', () => { + if (onFireTimeout) { + clearTimeout(onFireTimeout) + } }) const updateCamera = (entity: Entity) => { @@ -296,3 +318,44 @@ customEvents.on('gameLoaded', () => { }) }) + +// Constants +const SHARED_FLAGS_KEY = 0 +const ENTITY_FLAGS = { + ON_FIRE: 0x01, // Bit 0 + SNEAKING: 0x02, // Bit 1 + SPRINTING: 0x08, // Bit 3 + SWIMMING: 0x10, // Bit 4 + INVISIBLE: 0x20, // Bit 5 + GLOWING: 0x40, // Bit 6 + FALL_FLYING: 0x80 // Bit 7 (elytra flying) +} + +let onFireTimeout: NodeJS.Timeout | undefined +const updateEntityStates = (entityId: number, onFire: boolean, timeout?: boolean) => { + if (entityId !== bot.entity.id) return + appViewer.playerState.reactive.onFire = onFire + if (onFireTimeout) { + clearTimeout(onFireTimeout) + } + if (timeout) { + onFireTimeout = setTimeout(() => { + updateEntityStates(entityId, false, false) + }, 5000) + } +} + +// Process entity metadata packet +function handleEntityMetadata (packet: { entityId: number, metadata: Array<{ key: number, type: string, value: number }> }) { + const { entityId, metadata } = packet + + // Find shared flags in metadata + const flagsData = metadata.find(meta => meta.key === SHARED_FLAGS_KEY && + meta.type === 'byte') + + // Update fire state if flags were found + if (flagsData) { + const wasOnFire = appViewer.playerState.reactive.onFire + appViewer.playerState.reactive.onFire = (flagsData.value & ENTITY_FLAGS.ON_FIRE) !== 0 + } +} diff --git a/src/react/FireRenderer.tsx b/src/react/FireRenderer.tsx new file mode 100644 index 00000000..3a188558 --- /dev/null +++ b/src/react/FireRenderer.tsx @@ -0,0 +1,152 @@ +/* eslint-disable no-await-in-loop */ +import { useSnapshot } from 'valtio' +import { useEffect, useState } from 'react' +import { getLoadedImage } from 'mc-assets/dist/utils' +import { createCanvas } from 'renderer/viewer/lib/utils' + +const TEXTURE_UPDATE_INTERVAL = 100 // 5 times per second + +export default () => { + const { onFire, perspective } = useSnapshot(appViewer.playerState.reactive) + const [fireTextures, setFireTextures] = useState([]) + const [currentTextureIndex, setCurrentTextureIndex] = useState(0) + + useEffect(() => { + let animationFrameId: number + let lastTextureUpdate = 0 + + const updateTexture = (timestamp: number) => { + if (onFire && fireTextures.length > 0) { + if (timestamp - lastTextureUpdate >= TEXTURE_UPDATE_INTERVAL) { + setCurrentTextureIndex(prev => (prev + 1) % fireTextures.length) + lastTextureUpdate = timestamp + } + } + animationFrameId = requestAnimationFrame(updateTexture) + } + + animationFrameId = requestAnimationFrame(updateTexture) + return () => cancelAnimationFrame(animationFrameId) + }, [onFire, fireTextures]) + + useEffect(() => { + const loadTextures = async () => { + const fireImageUrls: string[] = [] + + const { resourcesManager } = appViewer + const { blocksAtlasParser } = resourcesManager + if (!blocksAtlasParser?.atlas?.latest) { + console.warn('FireRenderer: Blocks atlas parser not available') + return + } + + const keys = Object.keys(blocksAtlasParser.atlas.latest.textures).filter(key => /^fire_\d+$/.exec(key)) + for (const key of keys) { + const textureInfo = blocksAtlasParser.getTextureInfo(key) as { u: number, v: number, width?: number, height?: number } + if (textureInfo) { + const defaultSize = blocksAtlasParser.atlas.latest.tileSize + const imageWidth = blocksAtlasParser.atlas.latest.width + const imageHeight = blocksAtlasParser.atlas.latest.height + const textureWidth = textureInfo.width ?? defaultSize + const textureHeight = textureInfo.height ?? defaultSize + + // Create a temporary canvas for the full texture + const tempCanvas = createCanvas(textureWidth, textureHeight) + const tempCtx = tempCanvas.getContext('2d') + if (tempCtx && blocksAtlasParser.latestImage) { + const image = await getLoadedImage(blocksAtlasParser.latestImage) + tempCtx.drawImage( + image, + textureInfo.u * imageWidth, + textureInfo.v * imageHeight, + textureWidth, + textureHeight, + 0, + 0, + textureWidth, + textureHeight + ) + + // Create final canvas with only top 20% of the texture + const finalHeight = Math.ceil(textureHeight * 0.4) + const canvas = createCanvas(textureWidth, finalHeight) + const ctx = canvas.getContext('2d') + if (ctx) { + // Draw only the top portion + ctx.drawImage( + tempCanvas, + 0, + 0, // Start from top + textureWidth, + finalHeight, + 0, + 0, + textureWidth, + finalHeight + ) + + const blob = await canvas.convertToBlob() + const url = URL.createObjectURL(blob) + fireImageUrls.push(url) + } + } + } + } + + setFireTextures(fireImageUrls) + } + + // Load textures initially + if (appViewer.resourcesManager.currentResources) { + void loadTextures() + } + + // Set up listener for texture updates + const onAssetsUpdated = () => { + void loadTextures() + } + appViewer.resourcesManager.on('assetsTexturesUpdated', onAssetsUpdated) + + // Cleanup + return () => { + appViewer.resourcesManager.off('assetsTexturesUpdated', onAssetsUpdated) + // Cleanup texture URLs + for (const url of fireTextures) URL.revokeObjectURL(url) + } + }, []) + + if (!onFire || fireTextures.length === 0 || perspective !== 'first_person') return null + + return ( +
    +
    +
    + ) +} diff --git a/src/reactUi.tsx b/src/reactUi.tsx index b15cb79d..4f8c4541 100644 --- a/src/reactUi.tsx +++ b/src/reactUi.tsx @@ -66,6 +66,7 @@ import CreditsAboutModal from './react/CreditsAboutModal' import GlobalOverlayHints from './react/GlobalOverlayHints' import FullscreenTime from './react/FullscreenTime' import StorageConflictModal from './react/StorageConflictModal' +import FireRenderer from './react/FireRenderer' const isFirefox = ua.getBrowser().name === 'Firefox' if (isFirefox) { @@ -171,6 +172,7 @@ const InGameUi = () => { + {!disabledUiParts.includes('fire') && }
    From 53cbff7699d5255974a09e5f1e50b02759116a63 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 8 Aug 2025 18:37:10 +0300 Subject: [PATCH 42/98] dont conflict fire with chat --- src/react/FireRenderer.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/react/FireRenderer.tsx b/src/react/FireRenderer.tsx index 3a188558..20ad4606 100644 --- a/src/react/FireRenderer.tsx +++ b/src/react/FireRenderer.tsx @@ -130,7 +130,8 @@ export default () => { display: 'flex', justifyContent: 'center', alignItems: 'flex-end', - overflow: 'hidden' + overflow: 'hidden', + zIndex: -1 }} >
    Date: Fri, 8 Aug 2025 21:52:55 +0300 Subject: [PATCH 43/98] fix: some blocks textures were not update in hotbar after texturepack change --- renderer/viewer/three/renderSlot.ts | 20 +++++++++++++------- src/inventoryWindows.ts | 1 + src/react/HotbarRenderApp.tsx | 3 ++- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/renderer/viewer/three/renderSlot.ts b/renderer/viewer/three/renderSlot.ts index d82e58e3..321633eb 100644 --- a/renderer/viewer/three/renderSlot.ts +++ b/renderer/viewer/three/renderSlot.ts @@ -10,11 +10,11 @@ export type ResolvedItemModelRender = { export const renderSlot = (model: ResolvedItemModelRender, resourcesManager: ResourcesManagerCommon, debugIsQuickbar = false, fullBlockModelSupport = false): { texture: string, - blockData?: Record & { resolvedModel: BlockModel }, - scale?: number, - slice?: number[], - modelName?: string, -} | undefined => { + blockData: Record & { resolvedModel: BlockModel } | null, + scale: number | null, + slice: number[] | null, + modelName: string | null, +} => { let itemModelName = model.modelName const isItem = loadedData.itemsByName[itemModelName] @@ -37,6 +37,8 @@ export const renderSlot = (model: ResolvedItemModelRender, resourcesManager: Res texture: 'gui', slice: [x, y, atlas.tileSize, atlas.tileSize], scale: 0.25, + blockData: null, + modelName: null } } } @@ -63,14 +65,18 @@ export const renderSlot = (model: ResolvedItemModelRender, resourcesManager: Res return { texture: itemTexture.type, slice: itemTexture.slice, - modelName: itemModelName + modelName: itemModelName, + blockData: null, + scale: null } } else { // is block return { texture: 'blocks', blockData: itemTexture, - modelName: itemModelName + modelName: itemModelName, + slice: null, + scale: null } } } diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index bc7dcbaf..a9f89d1b 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -259,6 +259,7 @@ export const upInventoryItems = (isInventory: boolean, invWindow = lastWindow) = // inv.pwindow.inv.slots[2].blockData = getBlockData('dirt') const customSlots = mapSlots((isInventory ? bot.inventory : bot.currentWindow)!.slots) invWindow.pwindow.setSlots(customSlots) + return customSlots } export const onModalClose = (callback: () => any) => { diff --git a/src/react/HotbarRenderApp.tsx b/src/react/HotbarRenderApp.tsx index 6b6e3207..c782e6ef 100644 --- a/src/react/HotbarRenderApp.tsx +++ b/src/react/HotbarRenderApp.tsx @@ -115,7 +115,7 @@ const HotbarInner = () => { container.current.appendChild(inv.canvas) const upHotbarItems = () => { if (!appViewer.resourcesManager?.itemsAtlasParser) return - upInventoryItems(true, inv) + globalThis.debugHotbarItems = upInventoryItems(true, inv) } canvasManager.canvas.onclick = (e) => { @@ -127,6 +127,7 @@ const HotbarInner = () => { } } + globalThis.debugUpHotbarItems = upHotbarItems upHotbarItems() bot.inventory.on('updateSlot', upHotbarItems) appViewer.resourcesManager.on('assetsTexturesUpdated', upHotbarItems) From fb395041b9e40449819e5a8d0361f06264bfa80b Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 11 Aug 2025 01:39:08 +0300 Subject: [PATCH 44/98] fix: fix on 1.18.2 many blocks like mushrom blocks, fence gates, deepslate, basalt, copper stuff like ore, infested stone, cakes and tinted glass was resulting in instant breaking on the client dev: add debugTestPing --- scripts/makeOptimizedMcData.mjs | 7 +++-- src/mineflayer/mc-protocol.ts | 49 ++++++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/scripts/makeOptimizedMcData.mjs b/scripts/makeOptimizedMcData.mjs index 0b5752d8..76e0f1c2 100644 --- a/scripts/makeOptimizedMcData.mjs +++ b/scripts/makeOptimizedMcData.mjs @@ -90,16 +90,19 @@ const dataTypeBundling = { }, blocks: { arrKey: 'name', - processData(current, prev) { + processData(current, prev, _, version) { for (const block of current) { + const prevBlock = prev?.find(x => x.name === block.name) if (block.transparent) { const forceOpaque = block.name.includes('shulker_box') || block.name.match(/^double_.+_slab\d?$/) || ['melon_block', 'lit_pumpkin', 'lit_redstone_ore', 'lit_furnace'].includes(block.name) - const prevBlock = prev?.find(x => x.name === block.name) if (forceOpaque || (prevBlock && !prevBlock.transparent)) { block.transparent = false } } + if (block.hardness === 0 && prevBlock && prevBlock.hardness > 0) { + block.hardness = prevBlock.hardness + } } } // ignoreRemoved: true, diff --git a/src/mineflayer/mc-protocol.ts b/src/mineflayer/mc-protocol.ts index 2376cd03..a0348c5d 100644 --- a/src/mineflayer/mc-protocol.ts +++ b/src/mineflayer/mc-protocol.ts @@ -1,8 +1,11 @@ +import net from 'net' import { Client } from 'minecraft-protocol' import { appQueryParams } from '../appParams' import { downloadAllMinecraftData, getVersionAutoSelect } from '../connect' import { gameAdditionalState } from '../globalState' import { ProgressReporter } from '../core/progressReporter' +import { parseServerAddress } from '../parseServerAddress' +import { getCurrentProxy } from '../react/ServersList' import { pingServerVersion, validatePacket } from './minecraft-protocol-extra' import { getWebsocketStream } from './websocket-core' @@ -35,7 +38,7 @@ setInterval(() => { }, 1000) -export const getServerInfo = async (ip: string, port?: number, preferredVersion = getVersionAutoSelect(), ping = false, progressReporter?: ProgressReporter) => { +export const getServerInfo = async (ip: string, port?: number, preferredVersion = getVersionAutoSelect(), ping = false, progressReporter?: ProgressReporter, setProxyParams?: ProxyParams) => { await downloadAllMinecraftData() const isWebSocket = ip.startsWith('ws://') || ip.startsWith('wss://') let stream @@ -43,6 +46,8 @@ export const getServerInfo = async (ip: string, port?: number, preferredVersion progressReporter?.setMessage('Connecting to WebSocket server') stream = (await getWebsocketStream(ip)).mineflayerStream progressReporter?.setMessage('WebSocket connected. Ping packet sent, waiting for response') + } else if (setProxyParams) { + setProxy(setProxyParams) } window.setLoadingMessage = (message?: string) => { if (message === undefined) { @@ -59,3 +64,45 @@ export const getServerInfo = async (ip: string, port?: number, preferredVersion window.setLoadingMessage = undefined }) } + +globalThis.debugTestPing = async (ip: string) => { + const parsed = parseServerAddress(ip, false) + const result = await getServerInfo(parsed.host, parsed.port ? Number(parsed.port) : undefined, undefined, true, undefined, { address: getCurrentProxy(), }) + console.log('result', result) + return result +} + +export const getDefaultProxyParams = () => { + return { + headers: { + Authorization: `Bearer ${new URLSearchParams(location.search).get('token') ?? ''}` + } + } +} + +export type ProxyParams = { + address?: string + headers?: Record +} + +export const setProxy = (proxyParams: ProxyParams) => { + if (proxyParams.address?.startsWith(':')) { + proxyParams.address = `${location.protocol}//${location.hostname}${proxyParams.address}` + } + if (proxyParams.address && location.port !== '80' && location.port !== '443' && !/:\d+$/.test(proxyParams.address)) { + const https = proxyParams.address.startsWith('https://') || location.protocol === 'https:' + proxyParams.address = `${proxyParams.address}:${https ? 443 : 80}` + } + + const parsedProxy = parseServerAddress(proxyParams.address, false) + const proxy = { host: parsedProxy.host, port: parsedProxy.port } + proxyParams.headers ??= getDefaultProxyParams().headers + net['setProxy']({ + hostname: proxy.host, + port: proxy.port, + headers: proxyParams.headers + }) + return { + proxy + } +} From e7c358d3fc0497b66fa25c70a401d2aed4be3294 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 11 Aug 2025 03:12:05 +0300 Subject: [PATCH 45/98] feat: add `minecraft-web-client:block-interactions-customization` --- package.json | 2 +- pnpm-lock.yaml | 20 ++++++++++---------- src/customChannels.ts | 31 +++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 257fff7c..b719b877 100644 --- a/package.json +++ b/package.json @@ -156,7 +156,7 @@ "mc-assets": "^0.2.62", "minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next", "mineflayer": "github:zardoy/mineflayer#gen-the-master", - "mineflayer-mouse": "^0.1.14", + "mineflayer-mouse": "^0.1.17", "mineflayer-pathfinder": "^2.4.4", "npm-run-all": "^4.1.5", "os-browserify": "^0.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 132ef32c..da2b3912 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -343,8 +343,8 @@ importers: specifier: github:zardoy/mineflayer#gen-the-master version: https://codeload.github.com/zardoy/mineflayer/tar.gz/c9c77d6511e37c452ebe48790724da165d6ad448(encoding@0.1.13) mineflayer-mouse: - specifier: ^0.1.14 - version: 0.1.14 + specifier: ^0.1.17 + version: 0.1.17 mineflayer-pathfinder: specifier: ^2.4.4 version: 2.4.5 @@ -6667,8 +6667,8 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824} version: 1.2.0 - mineflayer-mouse@0.1.14: - resolution: {integrity: sha512-DjytRMlRLxR44GqZ6udMgbMO4At7Ura5TQC80exRhzkfptyCGLTWzXaf0oeXSNYkNMnaaEv4XP/9YRwuvL+rsQ==} + mineflayer-mouse@0.1.17: + resolution: {integrity: sha512-0eCR8pnGb42Qd9QmAxOjl0PhA5Fa+9+6H1G/YsbsO5rg5mDf94Tusqp/8NAGLPQCPVDzbarLskXdjR3h0E0bEQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} mineflayer-pathfinder@2.4.5: @@ -10294,7 +10294,7 @@ snapshots: '@babel/parser': 7.26.9 '@babel/template': 7.26.9 '@babel/types': 7.26.9 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.1 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -12864,7 +12864,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 6.1.0(typescript@5.5.4) '@typescript-eslint/utils': 6.1.0(eslint@8.57.1)(typescript@5.5.4) - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.1 eslint: 8.57.1 ts-api-utils: 1.4.3(typescript@5.5.4) optionalDependencies: @@ -12896,7 +12896,7 @@ snapshots: dependencies: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.1 globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 @@ -12911,7 +12911,7 @@ snapshots: dependencies: '@typescript-eslint/types': 8.26.0 '@typescript-eslint/visitor-keys': 8.26.0 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.1 fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 @@ -17338,7 +17338,7 @@ snapshots: - encoding - supports-color - mineflayer-mouse@0.1.14: + mineflayer-mouse@0.1.17: dependencies: change-case: 5.4.4 debug: 4.4.1 @@ -18457,7 +18457,7 @@ snapshots: puppeteer-core@2.1.1: dependencies: '@types/mime-types': 2.1.4 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.1 extract-zip: 1.7.0 https-proxy-agent: 4.0.0 mime: 2.6.0 diff --git a/src/customChannels.ts b/src/customChannels.ts index 57c057d5..3f6a8217 100644 --- a/src/customChannels.ts +++ b/src/customChannels.ts @@ -15,6 +15,7 @@ export default () => { registerMediaChannels() registerSectionAnimationChannels() registeredJeiChannel() + registerBlockInteractionsCustomizationChannel() }) } @@ -32,6 +33,36 @@ const registerChannel = (channelName: string, packetStructure: any[], handler: ( console.debug(`registered custom channel ${channelName} channel`) } +const registerBlockInteractionsCustomizationChannel = () => { + const CHANNEL_NAME = 'minecraft-web-client:block-interactions-customization' + const packetStructure = [ + 'container', + [ + { + name: 'newConfiguration', + type: ['pstring', { countType: 'i16' }] + }, + ] + ] + + registerChannel(CHANNEL_NAME, packetStructure, (data) => { + const config = JSON.parse(data.newConfiguration) + if (config.customBreakTime !== undefined && Object.values(config.customBreakTime).every(x => typeof x === 'number')) { + bot.mouse.customBreakTime = config.customBreakTime + } + if (config.customBreakTimeToolAllowance !== undefined) { + bot.mouse.customBreakTimeToolAllowance = new Set(config.customBreakTimeToolAllowance) + } + + if (config.blockPlacePrediction !== undefined) { + bot.mouse.settings.blockPlacePrediction = config.blockPlacePrediction + } + if (config.blockPlacePredictionDelay !== undefined) { + bot.mouse.settings.blockPlacePredictionDelay = config.blockPlacePredictionDelay + } + }, true) +} + const registerBlockModelsChannel = () => { const CHANNEL_NAME = 'minecraft-web-client:blockmodels' From cdd8c31a0e9261ee57fb66ff8ca5af0e074bff78 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 11 Aug 2025 21:21:44 +0300 Subject: [PATCH 46/98] fix: fix player colored username rendering, fix sometimes skin was overriden --- renderer/viewer/three/entities.ts | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/renderer/viewer/three/entities.ts b/renderer/viewer/three/entities.ts index 6c6f8900..24f64803 100644 --- a/renderer/viewer/three/entities.ts +++ b/renderer/viewer/three/entities.ts @@ -141,7 +141,7 @@ const addNametag = (entity, options: { fontFamily: string }, mesh, version: stri const canvas = getUsernameTexture(entity, options, version) const tex = new THREE.Texture(canvas) tex.needsUpdate = true - let nameTag + let nameTag: THREE.Object3D if (entity.nameTagFixed) { const geometry = new THREE.PlaneGeometry() const material = new THREE.MeshBasicMaterial({ map: tex }) @@ -171,6 +171,7 @@ const addNametag = (entity, options: { fontFamily: string }, mesh, version: stri nameTag.name = 'nametag' mesh.add(nameTag) + return nameTag } } @@ -494,6 +495,10 @@ export class Entities { // todo true/undefined doesnt reset the skin to the default one // eslint-disable-next-line max-params async updatePlayerSkin (entityId: string | number, username: string | undefined, uuidCache: string | undefined, skinUrl: string | true, capeUrl: string | true | undefined = undefined) { + const isCustomSkin = skinUrl !== stevePngUrl + if (isCustomSkin) { + this.loadedSkinEntityIds.add(String(entityId)) + } if (uuidCache) { if (typeof skinUrl === 'string' || typeof capeUrl === 'string') this.uuidPerSkinUrlsCache[uuidCache] = {} if (typeof skinUrl === 'string') this.uuidPerSkinUrlsCache[uuidCache].skinUrl = skinUrl @@ -912,20 +917,14 @@ export class Entities { mesh = wrapper if (entity.username) { - // todo proper colors - const nameTag = new NameTagObject(fromFormattedString(entity.username).text, { - font: `48px ${this.entitiesOptions.fontFamily}`, - }) - nameTag.position.y = playerObject.position.y + playerObject.scale.y * 16 + 3 - nameTag.renderOrder = 1000 - - nameTag.name = 'nametag' - - //@ts-expect-error - wrapper.add(nameTag) + const nametag = addNametag(entity, { fontFamily: 'mojangles' }, wrapper, this.worldRenderer.version) + if (nametag) { + nametag.position.y = playerObject.position.y + playerObject.scale.y * 16 + 3 + nametag.scale.multiplyScalar(12) + } } } else { - mesh = getEntityMesh(entity, this.worldRenderer, this.entitiesOptions, overrides) + mesh = getEntityMesh(entity, this.worldRenderer, this.entitiesOptions, { ...overrides, customModel: entity['customModel'] }) } if (!mesh) return mesh.name = 'mesh' @@ -1181,8 +1180,7 @@ export class Entities { const cameraPos = this.worldRenderer.cameraObject.position const distance = mesh.position.distanceTo(cameraPos) if (distance < MAX_DISTANCE_SKIN_LOAD && distance < (this.worldRenderer.viewDistance * 16)) { - if (this.loadedSkinEntityIds.has(entityId)) return - this.loadedSkinEntityIds.add(entityId) + if (this.loadedSkinEntityIds.has(String(entityId))) return void this.updatePlayerSkin(entityId, mesh.playerObject.realUsername, mesh.playerObject.realPlayerUuid, true, true) } } From 0a474e67803827cdd657b135ba6a7380091cdab4 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Tue, 12 Aug 2025 06:27:06 +0300 Subject: [PATCH 47/98] feat: add custom experimental waypints impl --- experiments/three-labels.html | 5 + experiments/three-labels.ts | 67 ++++ renderer/viewer/three/graphicsBackend.ts | 3 + renderer/viewer/three/waypointSprite.ts | 394 ++++++++++++++++++++ renderer/viewer/three/waypoints.ts | 142 +++++++ renderer/viewer/three/worldrendererThree.ts | 25 +- src/customChannels.ts | 57 +++ 7 files changed, 685 insertions(+), 8 deletions(-) create mode 100644 experiments/three-labels.html create mode 100644 experiments/three-labels.ts create mode 100644 renderer/viewer/three/waypointSprite.ts create mode 100644 renderer/viewer/three/waypoints.ts diff --git a/experiments/three-labels.html b/experiments/three-labels.html new file mode 100644 index 00000000..2b25bc23 --- /dev/null +++ b/experiments/three-labels.html @@ -0,0 +1,5 @@ + + diff --git a/experiments/three-labels.ts b/experiments/three-labels.ts new file mode 100644 index 00000000..b69dc95b --- /dev/null +++ b/experiments/three-labels.ts @@ -0,0 +1,67 @@ +import * as THREE from 'three' +import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js' +import { createWaypointSprite, WAYPOINT_CONFIG } from '../renderer/viewer/three/waypointSprite' + +// Create scene, camera and renderer +const scene = new THREE.Scene() +const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000) +const renderer = new THREE.WebGLRenderer({ antialias: true }) +renderer.setSize(window.innerWidth, window.innerHeight) +document.body.appendChild(renderer.domElement) + +// Add FirstPersonControls +const controls = new FirstPersonControls(camera, renderer.domElement) +controls.lookSpeed = 0.1 +controls.movementSpeed = 10 +controls.lookVertical = true +controls.constrainVertical = true +controls.verticalMin = 0.1 +controls.verticalMax = Math.PI - 0.1 + +// Position camera +camera.position.y = 1.6 // Typical eye height +camera.lookAt(0, 1.6, -1) + +// Create a helper grid and axes +const grid = new THREE.GridHelper(20, 20) +scene.add(grid) +const axes = new THREE.AxesHelper(5) +scene.add(axes) + +// Create waypoint sprite via utility +const waypoint = createWaypointSprite({ + position: new THREE.Vector3(0, 0, -5), + color: 0xff0000, + label: 'Target', +}) +scene.add(waypoint.group) + +// Use built-in offscreen arrow from utils +waypoint.enableOffscreenArrow(true) +waypoint.setArrowParent(scene) + +// Animation loop +function animate() { + requestAnimationFrame(animate) + + const delta = Math.min(clock.getDelta(), 0.1) + controls.update(delta) + + // Unified camera update (size, distance text, arrow, visibility) + const sizeVec = renderer.getSize(new THREE.Vector2()) + waypoint.updateForCamera(camera.position, camera, sizeVec.width, sizeVec.height) + + renderer.render(scene, camera) +} + +// Handle window resize +window.addEventListener('resize', () => { + camera.aspect = window.innerWidth / window.innerHeight + camera.updateProjectionMatrix() + renderer.setSize(window.innerWidth, window.innerHeight) +}) + +// Add clock for controls +const clock = new THREE.Clock() + +animate() diff --git a/renderer/viewer/three/graphicsBackend.ts b/renderer/viewer/three/graphicsBackend.ts index 5ea89b34..92b6ec66 100644 --- a/renderer/viewer/three/graphicsBackend.ts +++ b/renderer/viewer/three/graphicsBackend.ts @@ -44,6 +44,9 @@ const getBackendMethods = (worldRenderer: WorldRendererThree) => { shakeFromDamage: worldRenderer.cameraShake.shakeFromDamage.bind(worldRenderer.cameraShake), onPageInteraction: worldRenderer.media.onPageInteraction.bind(worldRenderer.media), downloadMesherLog: worldRenderer.downloadMesherLog.bind(worldRenderer), + + addWaypoint: worldRenderer.waypoints.addWaypoint.bind(worldRenderer.waypoints), + removeWaypoint: worldRenderer.waypoints.removeWaypoint.bind(worldRenderer.waypoints), } } diff --git a/renderer/viewer/three/waypointSprite.ts b/renderer/viewer/three/waypointSprite.ts new file mode 100644 index 00000000..7c8cf1f6 --- /dev/null +++ b/renderer/viewer/three/waypointSprite.ts @@ -0,0 +1,394 @@ +import * as THREE from 'three' + +// Centralized visual configuration (in screen pixels) +export const WAYPOINT_CONFIG = { + // Target size in screen pixels (this controls the final sprite size) + TARGET_SCREEN_PX: 150, + // Canvas size for internal rendering (keep power of 2 for textures) + CANVAS_SIZE: 256, + // Relative positions in canvas (0-1) + LAYOUT: { + DOT_Y: 0.3, + NAME_Y: 0.45, + DISTANCE_Y: 0.55, + }, + // Multiplier for canvas internal resolution to keep text crisp + CANVAS_SCALE: 2, + ARROW: { + enabledDefault: false, + pixelSize: 30, + paddingPx: 50, + }, +} + +export type WaypointSprite = { + group: THREE.Group + sprite: THREE.Sprite + // Offscreen arrow controls + enableOffscreenArrow: (enabled: boolean) => void + setArrowParent: (parent: THREE.Object3D | null) => void + // Convenience combined updater + updateForCamera: ( + cameraPosition: THREE.Vector3, + camera: THREE.PerspectiveCamera, + viewportWidthPx: number, + viewportHeightPx: number + ) => boolean + // Utilities + setColor: (color: number) => void + setLabel: (label?: string) => void + updateDistanceText: (label: string, distanceText: string) => void + setVisible: (visible: boolean) => void + setPosition: (x: number, y: number, z: number) => void + dispose: () => void +} + +export function createWaypointSprite (options: { + position: THREE.Vector3 | { x: number, y: number, z: number }, + color?: number, + label?: string, + depthTest?: boolean, + // Y offset in world units used by updateScaleWorld only (screen-pixel API ignores this) + labelYOffset?: number, +}): WaypointSprite { + const color = options.color ?? 0xFF_00_00 + const depthTest = options.depthTest ?? false + const labelYOffset = options.labelYOffset ?? 1.5 + + // Build combined sprite + const sprite = createCombinedSprite(color, options.label ?? '', '0m', depthTest) + sprite.renderOrder = 10 + let currentLabel = options.label ?? '' + + // Offscreen arrow (detached by default) + let arrowSprite: THREE.Sprite | undefined + let arrowParent: THREE.Object3D | null = null + let arrowEnabled = WAYPOINT_CONFIG.ARROW.enabledDefault + + // Group for easy add/remove + const group = new THREE.Group() + group.add(sprite) + + // Initial position + const { x, y, z } = options.position + group.position.set(x, y, z) + + function setColor (newColor: number) { + const canvas = drawCombinedCanvas(newColor, currentLabel, '0m') + const texture = new THREE.CanvasTexture(canvas) + const mat = sprite.material + mat.map?.dispose() + mat.map = texture + mat.needsUpdate = true + } + + function setLabel (newLabel?: string) { + currentLabel = newLabel ?? '' + const canvas = drawCombinedCanvas(color, currentLabel, '0m') + const texture = new THREE.CanvasTexture(canvas) + const mat = sprite.material + mat.map?.dispose() + mat.map = texture + mat.needsUpdate = true + } + + function updateDistanceText (label: string, distanceText: string) { + const canvas = drawCombinedCanvas(color, label, distanceText) + const texture = new THREE.CanvasTexture(canvas) + const mat = sprite.material + mat.map?.dispose() + mat.map = texture + mat.needsUpdate = true + } + + function setVisible (visible: boolean) { + sprite.visible = visible + } + + function setPosition (nx: number, ny: number, nz: number) { + group.position.set(nx, ny, nz) + } + + // Keep constant pixel size on screen using global config + function updateScaleScreenPixels ( + cameraPosition: THREE.Vector3, + cameraFov: number, + distance: number, + viewportHeightPx: number + ) { + const vFovRad = cameraFov * Math.PI / 180 + const worldUnitsPerScreenHeightAtDist = Math.tan(vFovRad / 2) * 2 * distance + // Use configured target screen size + const scale = worldUnitsPerScreenHeightAtDist * (WAYPOINT_CONFIG.TARGET_SCREEN_PX / viewportHeightPx) + sprite.scale.set(scale, scale, 1) + } + + function ensureArrow () { + if (arrowSprite) return + const size = 128 + const canvas = document.createElement('canvas') + canvas.width = size + canvas.height = size + const ctx = canvas.getContext('2d')! + ctx.clearRect(0, 0, size, size) + ctx.beginPath() + ctx.moveTo(size * 0.2, size * 0.5) + ctx.lineTo(size * 0.8, size * 0.5) + ctx.lineTo(size * 0.5, size * 0.2) + ctx.closePath() + ctx.lineWidth = 4 + ctx.strokeStyle = 'black' + ctx.stroke() + ctx.fillStyle = 'white' + ctx.fill() + const texture = new THREE.CanvasTexture(canvas) + const material = new THREE.SpriteMaterial({ map: texture, transparent: true, depthTest: false, depthWrite: false }) + arrowSprite = new THREE.Sprite(material) + arrowSprite.renderOrder = 12 + arrowSprite.visible = false + if (arrowParent) arrowParent.add(arrowSprite) + } + + function enableOffscreenArrow (enabled: boolean) { + arrowEnabled = enabled + if (!enabled && arrowSprite) arrowSprite.visible = false + } + + function setArrowParent (parent: THREE.Object3D | null) { + if (arrowSprite?.parent) arrowSprite.parent.remove(arrowSprite) + arrowParent = parent + if (arrowSprite && parent) parent.add(arrowSprite) + } + + function updateOffscreenArrow ( + camera: THREE.PerspectiveCamera, + viewportWidthPx: number, + viewportHeightPx: number + ): boolean { + if (!arrowEnabled) return true + ensureArrow() + if (!arrowSprite) return true + + // Build camera basis using camera.up to respect custom orientations + const forward = new THREE.Vector3() + camera.getWorldDirection(forward) // camera look direction + const upWorld = camera.up.clone().normalize() + const right = new THREE.Vector3().copy(forward).cross(upWorld).normalize() + const upCam = new THREE.Vector3().copy(right).cross(forward).normalize() + + // Vector from camera to waypoint + const camPos = new THREE.Vector3().setFromMatrixPosition(camera.matrixWorld) + const toWp = new THREE.Vector3(group.position.x, group.position.y, group.position.z).sub(camPos) + + // Components in camera basis + const z = toWp.dot(forward) + const x = toWp.dot(right) + const y = toWp.dot(upCam) + + const aspect = viewportWidthPx / viewportHeightPx + const vFovRad = camera.fov * Math.PI / 180 + const hFovRad = 2 * Math.atan(Math.tan(vFovRad / 2) * aspect) + + // Determine if waypoint is inside view frustum using angular checks + const thetaX = Math.atan2(x, z) + const thetaY = Math.atan2(y, z) + const visible = z > 0 && Math.abs(thetaX) <= hFovRad / 2 && Math.abs(thetaY) <= vFovRad / 2 + if (visible) { + arrowSprite.visible = false + return true + } + + // Direction on screen in normalized frustum units + let rx = thetaX / (hFovRad / 2) + let ry = thetaY / (vFovRad / 2) + + // If behind the camera, snap to dominant axis to avoid confusing directions + if (z <= 0) { + if (Math.abs(rx) > Math.abs(ry)) { + rx = Math.sign(rx) + ry = 0 + } else { + rx = 0 + ry = Math.sign(ry) + } + } + + // Place on the rectangle border [-1,1]x[-1,1] + const s = Math.max(Math.abs(rx), Math.abs(ry)) || 1 + let ndcX = rx / s + let ndcY = ry / s + + // Apply padding in pixel space by clamping + const padding = WAYPOINT_CONFIG.ARROW.paddingPx + const pxX = ((ndcX + 1) * 0.5) * viewportWidthPx + const pxY = ((1 - ndcY) * 0.5) * viewportHeightPx + const clampedPxX = Math.min(Math.max(pxX, padding), viewportWidthPx - padding) + const clampedPxY = Math.min(Math.max(pxY, padding), viewportHeightPx - padding) + ndcX = (clampedPxX / viewportWidthPx) * 2 - 1 + ndcY = -(clampedPxY / viewportHeightPx) * 2 + 1 + + // Compute world position at a fixed distance in front of the camera using camera basis + const placeDist = Math.max(2, camera.near * 4) + const halfPlaneHeight = Math.tan(vFovRad / 2) * placeDist + const halfPlaneWidth = halfPlaneHeight * aspect + const pos = camPos.clone() + .add(forward.clone().multiplyScalar(placeDist)) + .add(right.clone().multiplyScalar(ndcX * halfPlaneWidth)) + .add(upCam.clone().multiplyScalar(ndcY * halfPlaneHeight)) + + // Update arrow sprite + arrowSprite.visible = true + arrowSprite.position.copy(pos) + + // Angle for rotation relative to screen right/up (derived from camera up vector) + const angle = Math.atan2(ry, rx) + arrowSprite.material.rotation = angle - Math.PI / 2 + + // Constant pixel size for arrow (use fixed placement distance) + const worldUnitsPerScreenHeightAtDist = Math.tan(vFovRad / 2) * 2 * placeDist + const sPx = worldUnitsPerScreenHeightAtDist * (WAYPOINT_CONFIG.ARROW.pixelSize / viewportHeightPx) + arrowSprite.scale.set(sPx, sPx, 1) + return false + } + + function computeDistance (cameraPosition: THREE.Vector3): number { + return cameraPosition.distanceTo(group.position) + } + + function updateForCamera ( + cameraPosition: THREE.Vector3, + camera: THREE.PerspectiveCamera, + viewportWidthPx: number, + viewportHeightPx: number + ): boolean { + const distance = computeDistance(cameraPosition) + // Keep constant pixel size + updateScaleScreenPixels(cameraPosition, camera.fov, distance, viewportHeightPx) + // Update text + updateDistanceText(currentLabel, `${Math.round(distance)}m`) + // Update arrow and visibility + const onScreen = updateOffscreenArrow(camera, viewportWidthPx, viewportHeightPx) + setVisible(onScreen) + return onScreen + } + + function dispose () { + const mat = sprite.material + mat.map?.dispose() + mat.dispose() + if (arrowSprite) { + const am = arrowSprite.material + am.map?.dispose() + am.dispose() + } + } + + return { + group, + sprite, + enableOffscreenArrow, + setArrowParent, + updateForCamera, + setColor, + setLabel, + updateDistanceText, + setVisible, + setPosition, + dispose, + } +} + +// Internal helpers +function drawCombinedCanvas (color: number, id: string, distance: string): HTMLCanvasElement { + const scale = WAYPOINT_CONFIG.CANVAS_SCALE * (globalThis.devicePixelRatio || 1) + const size = WAYPOINT_CONFIG.CANVAS_SIZE * scale + const canvas = document.createElement('canvas') + canvas.width = size + canvas.height = size + const ctx = canvas.getContext('2d')! + + // Clear canvas + ctx.clearRect(0, 0, size, size) + + // Draw dot + const centerX = size / 2 + const dotY = Math.round(size * WAYPOINT_CONFIG.LAYOUT.DOT_Y) + const radius = Math.round(size * 0.05) // Dot takes up ~12% of canvas height + const borderWidth = Math.max(2, Math.round(4 * scale)) + + // Outer border (black) + ctx.beginPath() + ctx.arc(centerX, dotY, radius + borderWidth, 0, Math.PI * 2) + ctx.fillStyle = 'black' + ctx.fill() + + // Inner circle (colored) + ctx.beginPath() + ctx.arc(centerX, dotY, radius, 0, Math.PI * 2) + ctx.fillStyle = `#${color.toString(16).padStart(6, '0')}` + ctx.fill() + + // Text properties + ctx.textAlign = 'center' + ctx.textBaseline = 'middle' + + // Title + const nameFontPx = Math.round(size * 0.08) // ~8% of canvas height + const distanceFontPx = Math.round(size * 0.06) // ~6% of canvas height + ctx.font = `bold ${nameFontPx}px mojangles` + ctx.lineWidth = Math.max(2, Math.round(3 * scale)) + const nameY = Math.round(size * WAYPOINT_CONFIG.LAYOUT.NAME_Y) + + ctx.strokeStyle = 'black' + ctx.strokeText(id, centerX, nameY) + ctx.fillStyle = 'white' + ctx.fillText(id, centerX, nameY) + + // Distance + ctx.font = `bold ${distanceFontPx}px mojangles` + ctx.lineWidth = Math.max(2, Math.round(2 * scale)) + const distanceY = Math.round(size * WAYPOINT_CONFIG.LAYOUT.DISTANCE_Y) + + ctx.strokeStyle = 'black' + ctx.strokeText(distance, centerX, distanceY) + ctx.fillStyle = '#CCCCCC' + ctx.fillText(distance, centerX, distanceY) + + return canvas +} + +function createCombinedSprite (color: number, id: string, distance: string, depthTest: boolean): THREE.Sprite { + const canvas = drawCombinedCanvas(color, id, distance) + const texture = new THREE.CanvasTexture(canvas) + texture.anisotropy = 1 + texture.magFilter = THREE.LinearFilter + texture.minFilter = THREE.LinearFilter + const material = new THREE.SpriteMaterial({ + map: texture, + transparent: true, + opacity: 1, + depthTest, + depthWrite: false, + }) + const sprite = new THREE.Sprite(material) + sprite.position.set(0, 0, 0) + return sprite +} + +export const WaypointHelpers = { + // World-scale constant size helper + computeWorldScale (distance: number, fixedReference = 10) { + return Math.max(0.0001, distance / fixedReference) + }, + // Screen-pixel constant size helper + computeScreenPixelScale ( + camera: THREE.PerspectiveCamera, + distance: number, + pixelSize: number, + viewportHeightPx: number + ) { + const vFovRad = camera.fov * Math.PI / 180 + const worldUnitsPerScreenHeightAtDist = Math.tan(vFovRad / 2) * 2 * distance + return worldUnitsPerScreenHeightAtDist * (pixelSize / viewportHeightPx) + } +} diff --git a/renderer/viewer/three/waypoints.ts b/renderer/viewer/three/waypoints.ts new file mode 100644 index 00000000..c7ec9a93 --- /dev/null +++ b/renderer/viewer/three/waypoints.ts @@ -0,0 +1,142 @@ +import * as THREE from 'three' +import { WorldRendererThree } from './worldrendererThree' +import { createWaypointSprite, type WaypointSprite } from './waypointSprite' + +interface Waypoint { + id: string + x: number + y: number + z: number + minDistance: number + color: number + label?: string + sprite: WaypointSprite +} + +interface WaypointOptions { + color?: number + label?: string + minDistance?: number +} + +export class WaypointsRenderer { + private readonly waypoints = new Map() + private readonly waypointScene = new THREE.Scene() + private readonly FIXED_REFERENCE = 10 // Fixed reference distance for scaling + + constructor ( + private readonly worldRenderer: WorldRendererThree + ) { + if (process.env.NODE_ENV !== 'production') { + this.addWaypoint('spawn', 0, 0, 0, { }) + } + } + + private updateWaypoints () { + const playerPos = this.worldRenderer.cameraObject.position + const sizeVec = this.worldRenderer.renderer.getSize(new THREE.Vector2()) + + for (const waypoint of this.waypoints.values()) { + const waypointPos = new THREE.Vector3(waypoint.x, waypoint.y, waypoint.z) + const distance = playerPos.distanceTo(waypointPos) + const visible = !waypoint.minDistance || distance >= waypoint.minDistance + + waypoint.sprite.setVisible(visible) + + if (visible) { + // Update position + waypoint.sprite.setPosition(waypoint.x, waypoint.y, waypoint.z) + // Ensure camera-based update each frame + waypoint.sprite.updateForCamera(this.worldRenderer.getCameraPosition(), this.worldRenderer.camera, sizeVec.width, sizeVec.height) + } + } + } + + render () { + if (this.waypoints.size === 0) return + + // Update waypoint scaling + this.updateWaypoints() + + // Render waypoints scene with the world camera + this.worldRenderer.renderer.render(this.waypointScene, this.worldRenderer.camera) + } + + // Removed sprite/label texture creation. Use utils/waypointSprite.ts + + addWaypoint ( + id: string, + x: number, + y: number, + z: number, + options: WaypointOptions = {} + ) { + // Remove existing waypoint if it exists + this.removeWaypoint(id) + + const color = options.color ?? 0xFF_00_00 + const { label } = options + const minDistance = options.minDistance ?? 0 + + const sprite = createWaypointSprite({ + position: new THREE.Vector3(x, y, z), + color, + label: (label || id), + }) + sprite.enableOffscreenArrow(true) + sprite.setArrowParent(this.waypointScene) + + this.waypointScene.add(sprite.group) + + this.waypoints.set(id, { + id, x, y, z, minDistance, + color, label, + sprite, + }) + } + + removeWaypoint (id: string) { + const waypoint = this.waypoints.get(id) + if (waypoint) { + this.waypointScene.remove(waypoint.sprite.group) + waypoint.sprite.dispose() + this.waypoints.delete(id) + } + } + + clear () { + for (const id of this.waypoints.keys()) { + this.removeWaypoint(id) + } + } + + testWaypoint () { + this.addWaypoint('Test Point', 0, 70, 0, { color: 0x00_FF_00, label: 'Test Point' }) + this.addWaypoint('Spawn', 0, 64, 0, { color: 0xFF_FF_00, label: 'Spawn' }) + this.addWaypoint('Far Point', 100, 70, 100, { color: 0x00_00_FF, label: 'Far Point' }) + } + + getWaypoint (id: string): Waypoint | undefined { + return this.waypoints.get(id) + } + + getAllWaypoints (): Waypoint[] { + return [...this.waypoints.values()] + } + + setWaypointColor (id: string, color: number) { + const waypoint = this.waypoints.get(id) + if (waypoint) { + waypoint.sprite.setColor(color) + waypoint.color = color + } + } + + setWaypointLabel (id: string, label?: string) { + const waypoint = this.waypoints.get(id) + if (waypoint) { + waypoint.label = label + waypoint.sprite.setLabel(label) + } + } +} diff --git a/renderer/viewer/three/worldrendererThree.ts b/renderer/viewer/three/worldrendererThree.ts index bc95f06b..82856cb9 100644 --- a/renderer/viewer/three/worldrendererThree.ts +++ b/renderer/viewer/three/worldrendererThree.ts @@ -23,6 +23,7 @@ import { ThreeJsSound } from './threeJsSound' import { CameraShake } from './cameraShake' import { ThreeJsMedia } from './threeJsMedia' import { Fountain } from './threeJsParticles' +import { WaypointsRenderer } from './waypoints' type SectionKey = string @@ -48,6 +49,7 @@ export class WorldRendererThree extends WorldRendererCommon { cameraContainer: THREE.Object3D media: ThreeJsMedia waitingChunksToDisplay = {} as { [chunkKey: string]: SectionKey[] } + waypoints: WaypointsRenderer camera: THREE.PerspectiveCamera renderTimeAvg = 0 sectionsOffsetsAnimations = {} as { @@ -99,6 +101,8 @@ export class WorldRendererThree extends WorldRendererCommon { this.soundSystem = new ThreeJsSound(this) this.cameraShake = new CameraShake(this, this.onRender) this.media = new ThreeJsMedia(this) + this.waypoints = new WaypointsRenderer(this) + // this.fountain = new Fountain(this.scene, this.scene, { // position: new THREE.Vector3(0, 10, 0), // }) @@ -119,6 +123,8 @@ export class WorldRendererThree extends WorldRendererCommon { this.protocolCustomBlocks.clear() // Reset section animations this.sectionsOffsetsAnimations = {} + // Clear waypoints + this.waypoints.clear() }) } @@ -453,7 +459,7 @@ export class WorldRendererThree extends WorldRendererCommon { return worldPos } - getWorldCameraPosition () { + getSectionCameraPosition () { const pos = this.getCameraPosition() return new Vec3( Math.floor(pos.x / 16), @@ -463,7 +469,7 @@ export class WorldRendererThree extends WorldRendererCommon { } updateCameraSectionPos () { - const newSectionPos = this.getWorldCameraPosition() + const newSectionPos = this.getSectionCameraPosition() if (!this.cameraSectionPos.equals(newSectionPos)) { this.cameraSectionPos = newSectionPos this.cameraSectionPositionUpdate() @@ -737,6 +743,8 @@ export class WorldRendererThree extends WorldRendererCommon { fountain.render() } + this.waypoints.render() + for (const onRender of this.onRender) { onRender() } @@ -1022,6 +1030,13 @@ class StarField { constructor ( private readonly worldRenderer: WorldRendererThree ) { + const clock = new THREE.Clock() + const speed = 0.2 + this.worldRenderer.onRender.push(() => { + if (!this.points) return + this.points.position.copy(this.worldRenderer.getCameraPosition()); + (this.points.material as StarfieldMaterial).uniforms.time.value = clock.getElapsedTime() * speed + }) } addToScene () { @@ -1032,7 +1047,6 @@ class StarField { const count = 7000 const factor = 7 const saturation = 10 - const speed = 0.2 const geometry = new THREE.BufferGeometry() @@ -1065,11 +1079,6 @@ class StarField { this.points = new THREE.Points(geometry, material) this.worldRenderer.scene.add(this.points) - const clock = new THREE.Clock() - this.points.onBeforeRender = (renderer, scene, camera) => { - this.points?.position.copy?.(this.worldRenderer.getCameraPosition()) - material.uniforms.time.value = clock.getElapsedTime() * speed - } this.points.renderOrder = -1 } diff --git a/src/customChannels.ts b/src/customChannels.ts index 3f6a8217..6d3aa7e9 100644 --- a/src/customChannels.ts +++ b/src/customChannels.ts @@ -16,6 +16,7 @@ export default () => { registerSectionAnimationChannels() registeredJeiChannel() registerBlockInteractionsCustomizationChannel() + registerWaypointChannels() }) } @@ -63,6 +64,62 @@ const registerBlockInteractionsCustomizationChannel = () => { }, true) } +const registerWaypointChannels = () => { + const packetStructure = [ + 'container', + [ + { + name: 'id', + type: ['pstring', { countType: 'i16' }] + }, + { + name: 'x', + type: 'f32' + }, + { + name: 'y', + type: 'f32' + }, + { + name: 'z', + type: 'f32' + }, + { + name: 'minDistance', + type: 'i32' + }, + { + name: 'label', + type: ['pstring', { countType: 'i16' }] + }, + { + name: 'color', + type: 'i32' + } + ] + ] + + registerChannel('minecraft-web-client:waypoint-add', packetStructure, (data) => { + getThreeJsRendererMethods()?.addWaypoint(data.id, data.x, data.y, data.z, { + minDistance: data.minDistance, + label: data.label || undefined, + color: data.color || undefined + }) + }) + + registerChannel('minecraft-web-client:waypoint-delete', [ + 'container', + [ + { + name: 'id', + type: ['pstring', { countType: 'i16' }] + } + ] + ], (data) => { + getThreeJsRendererMethods()?.removeWaypoint(data.id) + }) +} + const registerBlockModelsChannel = () => { const CHANNEL_NAME = 'minecraft-web-client:blockmodels' From 8827aab981d27d95b4863156a35fd66ba7b810c2 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Tue, 12 Aug 2025 06:27:42 +0300 Subject: [PATCH 48/98] dont add test waypoints on dev --- renderer/viewer/three/waypoints.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/renderer/viewer/three/waypoints.ts b/renderer/viewer/three/waypoints.ts index c7ec9a93..45937ee8 100644 --- a/renderer/viewer/three/waypoints.ts +++ b/renderer/viewer/three/waypoints.ts @@ -22,14 +22,10 @@ interface WaypointOptions { export class WaypointsRenderer { private readonly waypoints = new Map() private readonly waypointScene = new THREE.Scene() - private readonly FIXED_REFERENCE = 10 // Fixed reference distance for scaling constructor ( private readonly worldRenderer: WorldRendererThree ) { - if (process.env.NODE_ENV !== 'production') { - this.addWaypoint('spawn', 0, 0, 0, { }) - } } private updateWaypoints () { From 60fc5ef315ea8b73f58070e271dd8f3dc90fc8e4 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 13 Aug 2025 19:19:46 +0300 Subject: [PATCH 49/98] feat: add skybox renderer: test it by dragging an image window into window, fix waypoint block pos --- renderer/viewer/three/graphicsBackend.ts | 3 + renderer/viewer/three/skyboxRenderer.ts | 77 +++++++++++++++++++++ renderer/viewer/three/waypoints.ts | 2 +- renderer/viewer/three/worldrendererThree.ts | 11 +++ src/dragndrop.ts | 32 +++++++++ 5 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 renderer/viewer/three/skyboxRenderer.ts diff --git a/renderer/viewer/three/graphicsBackend.ts b/renderer/viewer/three/graphicsBackend.ts index 92b6ec66..04cb00ca 100644 --- a/renderer/viewer/three/graphicsBackend.ts +++ b/renderer/viewer/three/graphicsBackend.ts @@ -47,6 +47,9 @@ const getBackendMethods = (worldRenderer: WorldRendererThree) => { addWaypoint: worldRenderer.waypoints.addWaypoint.bind(worldRenderer.waypoints), removeWaypoint: worldRenderer.waypoints.removeWaypoint.bind(worldRenderer.waypoints), + + // New method for updating skybox + setSkyboxImage: worldRenderer.skyboxRenderer.setSkyboxImage.bind(worldRenderer.skyboxRenderer) } } diff --git a/renderer/viewer/three/skyboxRenderer.ts b/renderer/viewer/three/skyboxRenderer.ts new file mode 100644 index 00000000..294c72aa --- /dev/null +++ b/renderer/viewer/three/skyboxRenderer.ts @@ -0,0 +1,77 @@ +import * as THREE from 'three' + +export class SkyboxRenderer { + private texture: THREE.Texture | null = null + private mesh: THREE.Mesh | null = null + + constructor (private readonly scene: THREE.Scene, public initialImage: string | null) {} + + async init () { + if (this.initialImage) { + await this.setSkyboxImage(this.initialImage) + } + } + + async setSkyboxImage (imageUrl: string) { + // Dispose old textures if they exist + if (this.texture) { + this.texture.dispose() + } + + // Load the equirectangular texture + const textureLoader = new THREE.TextureLoader() + this.texture = await new Promise((resolve) => { + textureLoader.load( + imageUrl, + (texture) => { + texture.mapping = THREE.EquirectangularReflectionMapping + texture.encoding = THREE.sRGBEncoding + // Keep pixelated look + texture.minFilter = THREE.NearestFilter + texture.magFilter = THREE.NearestFilter + texture.needsUpdate = true + resolve(texture) + } + ) + }) + + // Create or update the skybox + if (this.mesh) { + // Just update the texture on the existing material + this.mesh.material.map = this.texture + this.mesh.material.needsUpdate = true + } else { + // Create a large sphere geometry for the skybox + const geometry = new THREE.SphereGeometry(500, 60, 40) + // Flip the geometry inside out + geometry.scale(-1, 1, 1) + + // Create material using the loaded texture + const material = new THREE.MeshBasicMaterial({ + map: this.texture, + side: THREE.FrontSide // Changed to FrontSide since we're flipping the geometry + }) + + // Create and add the skybox mesh + this.mesh = new THREE.Mesh(geometry, material) + this.scene.add(this.mesh) + } + } + + update (cameraPosition: THREE.Vector3) { + if (this.mesh) { + this.mesh.position.copy(cameraPosition) + } + } + + dispose () { + if (this.texture) { + this.texture.dispose() + } + if (this.mesh) { + this.mesh.geometry.dispose() + ;(this.mesh.material as THREE.Material).dispose() + this.scene.remove(this.mesh) + } + } +} diff --git a/renderer/viewer/three/waypoints.ts b/renderer/viewer/three/waypoints.ts index 45937ee8..cebd779a 100644 --- a/renderer/viewer/three/waypoints.ts +++ b/renderer/viewer/three/waypoints.ts @@ -85,7 +85,7 @@ export class WaypointsRenderer { this.waypointScene.add(sprite.group) this.waypoints.set(id, { - id, x, y, z, minDistance, + id, x: x + 0.5, y: y + 0.5, z: z + 0.5, minDistance, color, label, sprite, }) diff --git a/renderer/viewer/three/worldrendererThree.ts b/renderer/viewer/three/worldrendererThree.ts index 82856cb9..fb6c8e11 100644 --- a/renderer/viewer/three/worldrendererThree.ts +++ b/renderer/viewer/three/worldrendererThree.ts @@ -24,6 +24,7 @@ import { CameraShake } from './cameraShake' import { ThreeJsMedia } from './threeJsMedia' import { Fountain } from './threeJsParticles' import { WaypointsRenderer } from './waypoints' +import { SkyboxRenderer } from './skyboxRenderer' type SectionKey = string @@ -71,6 +72,7 @@ export class WorldRendererThree extends WorldRendererCommon { } fountains: Fountain[] = [] DEBUG_RAYCAST = false + skyboxRenderer: SkyboxRenderer private currentPosTween?: tweenJs.Tween private currentRotTween?: tweenJs.Tween<{ pitch: number, yaw: number }> @@ -94,6 +96,10 @@ export class WorldRendererThree extends WorldRendererCommon { this.holdingBlock = new HoldingBlock(this) this.holdingBlockLeft = new HoldingBlock(this, true) + // Initialize skybox renderer + this.skyboxRenderer = new SkyboxRenderer(this.scene, null) + void this.skyboxRenderer.init() + this.addDebugOverlay() this.resetScene() void this.init() @@ -708,6 +714,10 @@ export class WorldRendererThree extends WorldRendererCommon { this.cursorBlock.render() this.updateSectionOffsets() + // Update skybox position to follow camera + const cameraPos = this.getCameraPosition() + this.skyboxRenderer.update(cameraPos) + const sizeOrFovChanged = sizeChanged || this.displayOptions.inWorldRenderingConfig.fov !== this.camera.fov if (sizeOrFovChanged) { const size = this.renderer.getSize(new THREE.Vector2()) @@ -947,6 +957,7 @@ export class WorldRendererThree extends WorldRendererCommon { destroy (): void { super.destroy() + this.skyboxRenderer.dispose() } shouldObjectVisible (object: THREE.Object3D) { diff --git a/src/dragndrop.ts b/src/dragndrop.ts index 6be90551..5a16bc05 100644 --- a/src/dragndrop.ts +++ b/src/dragndrop.ts @@ -3,6 +3,7 @@ import fs from 'fs' import * as nbt from 'prismarine-nbt' import RegionFile from 'prismarine-provider-anvil/src/region' import { versions } from 'minecraft-data' +import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods' import { openWorldDirectory, openWorldZip } from './browserfs' import { isGameActive } from './globalState' import { showNotification } from './react/NotificationProvider' @@ -12,6 +13,9 @@ const parseNbt = promisify(nbt.parse) const simplifyNbt = nbt.simplify window.nbt = nbt +// Supported image types for skybox +const VALID_IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.webp'] + // todo display drop zone for (const event of ['drag', 'dragstart', 'dragend', 'dragover', 'dragenter', 'dragleave', 'drop']) { window.addEventListener(event, (e: any) => { @@ -45,6 +49,34 @@ window.addEventListener('drop', async e => { }) async function handleDroppedFile (file: File) { + // Check for image files first when game is active + if (isGameActive(false) && VALID_IMAGE_EXTENSIONS.some(ext => file.name.toLowerCase().endsWith(ext))) { + try { + // Convert image to base64 + const reader = new FileReader() + const base64Promise = new Promise((resolve, reject) => { + reader.onload = () => resolve(reader.result as string) + reader.onerror = reject + }) + reader.readAsDataURL(file) + const base64Image = await base64Promise + + // Get ThreeJS backend methods and update skybox + const setSkyboxImage = getThreeJsRendererMethods()?.setSkyboxImage + if (setSkyboxImage) { + await setSkyboxImage(base64Image) + showNotification('Skybox updated successfully') + } else { + showNotification('Cannot update skybox - renderer does not support it') + } + return + } catch (err) { + console.error('Failed to update skybox:', err) + showNotification('Failed to update skybox', 'error') + return + } + } + if (file.name.endsWith('.zip')) { void openWorldZip(file) return From 15e3325971ed85ee54d5a3022b789a8881a7f4e4 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Thu, 14 Aug 2025 01:25:24 +0300 Subject: [PATCH 50/98] add param for testing for immediate reconnect after kick or error (warning: will cause infinite reload loop) --- src/appParams.ts | 1 + src/index.ts | 13 +++++++++---- src/react/AppStatusProvider.tsx | 24 ++++++++++++++---------- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/appParams.ts b/src/appParams.ts index 59a24788..8d487f8d 100644 --- a/src/appParams.ts +++ b/src/appParams.ts @@ -12,6 +12,7 @@ export type AppQsParams = { username?: string lockConnect?: string autoConnect?: string + alwaysReconnect?: string // googledrive.ts params state?: string // ServersListProvider.tsx params diff --git a/src/index.ts b/src/index.ts index c0213f56..4a118cee 100644 --- a/src/index.ts +++ b/src/index.ts @@ -62,7 +62,7 @@ import { onAppLoad, resourcepackReload, resourcePackState } from './resourcePack import { ConnectPeerOptions, connectToPeer } from './localServerMultiplayer' import CustomChannelClient from './customClient' import { registerServiceWorker } from './serviceWorker' -import { appStatusState, lastConnectOptions } from './react/AppStatusProvider' +import { appStatusState, lastConnectOptions, quickDevReconnect } from './react/AppStatusProvider' import { fsState } from './loadSave' import { watchFov } from './rendererUtils' @@ -215,8 +215,13 @@ export async function connect (connectOptions: ConnectOptions) { const destroyAll = (wasKicked = false) => { if (ended) return loadingTimerState.loading = false - if (!wasKicked && miscUiState.appConfig?.allowAutoConnect && appQueryParams.autoConnect && hadConnected) { - location.reload() + const { alwaysReconnect } = appQueryParams + if ((!wasKicked && miscUiState.appConfig?.allowAutoConnect && appQueryParams.autoConnect && hadConnected) || (alwaysReconnect)) { + if (alwaysReconnect === 'quick' || alwaysReconnect === 'fast') { + quickDevReconnect() + } else { + location.reload() + } } errorAbortController.abort() ended = true @@ -957,7 +962,7 @@ const maybeEnterGame = () => { } } - if (appQueryParams.reconnect && process.env.NODE_ENV === 'development') { + if (appQueryParams.reconnect && localStorage.lastConnectOptions && process.env.NODE_ENV === 'development') { const lastConnect = JSON.parse(localStorage.lastConnectOptions ?? {}) return waitForConfigFsLoad(async () => { void connect({ diff --git a/src/react/AppStatusProvider.tsx b/src/react/AppStatusProvider.tsx index e7e36cb7..9c7b34ac 100644 --- a/src/react/AppStatusProvider.tsx +++ b/src/react/AppStatusProvider.tsx @@ -54,6 +54,17 @@ export const reconnectReload = () => { } } +export const quickDevReconnect = () => { + if (!lastConnectOptions.value) { + return + } + + resetAppStatusState() + window.dispatchEvent(new window.CustomEvent('connect', { + detail: lastConnectOptions.value + })) +} + export default () => { const lastState = useRef(JSON.parse(JSON.stringify(appStatusState))) const currentState = useSnapshot(appStatusState) @@ -105,13 +116,6 @@ export default () => { } }, [isOpen]) - const reconnect = () => { - resetAppStatusState() - window.dispatchEvent(new window.CustomEvent('connect', { - detail: lastConnectOptions.value - })) - } - useEffect(() => { const controller = new AbortController() window.addEventListener('keyup', (e) => { @@ -119,7 +123,7 @@ export default () => { if (activeModalStack.at(-1)?.reactType !== 'app-status') return // todo do only if reconnect is possible if (e.code !== 'KeyR' || !lastConnectOptions.value) return - reconnect() + quickDevReconnect() }, { signal: controller.signal }) @@ -140,7 +144,7 @@ export default () => { const account = await showOptionsModal('Choose account to connect with', [...accounts.map(account => account.username), 'Use other account']) if (!account) return lastConnectOptions.value!.authenticatedAccount = accounts.find(acc => acc.username === account) || true - reconnect() + quickDevReconnect() } const lastAutoCapturedPackets = getLastAutoCapturedPackets() @@ -184,7 +188,7 @@ export default () => { actionsSlot={ <> {displayAuthButton &&
    ) })} diff --git a/src/react/ChunksDebugScreen.tsx b/src/react/ChunksDebugScreen.tsx index de33e454..28b0bbc4 100644 --- a/src/react/ChunksDebugScreen.tsx +++ b/src/react/ChunksDebugScreen.tsx @@ -2,6 +2,8 @@ import { useEffect, useState } from 'react' import { useUtilsEffect } from '@zardoy/react-util' import { WorldRendererCommon } from 'renderer/viewer/lib/worldrendererCommon' import { WorldRendererThree } from 'renderer/viewer/three/worldrendererThree' +import { Vec3 } from 'vec3' +import { generateSpiralMatrix } from 'flying-squid/dist/utils' import Screen from './Screen' import ChunksDebug, { ChunkDebug } from './ChunksDebug' import { useIsModalActive } from './utilsApp' @@ -12,6 +14,10 @@ const Inner = () => { const [update, setUpdate] = useState(0) useUtilsEffect(({ interval }) => { + const up = () => { + // setUpdate(u => u + 1) + } + bot.on('chunkColumnLoad', up) interval( 500, () => { @@ -20,15 +26,46 @@ const Inner = () => { setUpdate(u => u + 1) } ) + return () => { + bot.removeListener('chunkColumnLoad', up) + } }, []) + // Track first load time for all chunks + const allLoadTimes = Object.values(worldView!.debugChunksInfo) + .map(chunk => chunk?.loads[0]?.time ?? Infinity) + .filter(time => time !== Infinity) + .sort((a, b) => a - b) + + const allSpiralChunks = Object.fromEntries(generateSpiralMatrix(worldView!.viewDistance).map(pos => [`${pos[0]},${pos[1]}`, pos])) + const mapChunk = (key: string, state: ChunkDebug['state']): ChunkDebug => { + const x = Number(key.split(',')[0]) + const z = Number(key.split(',')[1]) + const chunkX = Math.floor(x / 16) + const chunkZ = Math.floor(z / 16) + + delete allSpiralChunks[`${chunkX},${chunkZ}`] const chunk = worldView!.debugChunksInfo[key] + const firstLoadTime = chunk?.loads[0]?.time + const loadIndex = firstLoadTime ? allLoadTimes.indexOf(firstLoadTime) + 1 : 0 + // const timeSinceFirstLoad = firstLoadTime ? firstLoadTime - allLoadTimes[0] : 0 + const timeSinceFirstLoad = firstLoadTime ? firstLoadTime - allLoadTimes[0] : 0 + let line = '' + let line2 = '' + if (loadIndex) { + line = `${loadIndex}` + line2 = `${timeSinceFirstLoad}ms` + } + if (chunk?.loads.length > 1) { + line += ` - ${chunk.loads.length}` + } + return { - x: Number(key.split(',')[0]), - z: Number(key.split(',')[1]), + x, + z, state, - lines: [String(chunk?.loads.length ?? 0)], + lines: [line, line2], sidebarLines: [ `loads: ${chunk?.loads?.map(l => `${l.reason} ${l.dataLength} ${l.time}`).join('\n')}`, // `blockUpdates: ${chunk.blockUpdates}`, @@ -55,14 +92,22 @@ const Inner = () => { const chunksDone = Object.keys(world.finishedChunks).map(key => mapChunk(key, 'done')) + + const chunksWaitingOrder = Object.values(allSpiralChunks).map(([x, z]) => { + const pos = new Vec3(x * 16, 0, z * 16) + if (bot.world.getColumnAt(pos) === null) return null + return mapChunk(`${pos.x},${pos.z}`, 'order-queued') + }).filter(a => !!a) + const allChunks = [ ...chunksWaitingServer, ...chunksWaitingClient, ...clientProcessingChunks, ...chunksDone, ...chunksDoneEmpty, + ...chunksWaitingOrder, ] - return + return Date: Sat, 16 Aug 2025 09:15:37 +0300 Subject: [PATCH 56/98] - Introduced a patchAssets script to apply custom textures to the blocks and items atlases. - Enhanced the ThreeJsSound class to support sound playback timeout and volume adjustments. - Added a custom sound system to handle named sound effects with metadata. --- assets/customTextures/readme.md | 2 + renderer/viewer/three/threeJsSound.ts | 40 ++++++-- rsbuild.config.ts | 4 + scripts/patchAssets.ts | 137 ++++++++++++++++++++++++++ src/basicSounds.ts | 30 +++++- src/defaultOptions.ts | 1 + src/react/Slider.tsx | 58 ++++++++++- src/sounds/botSoundSystem.ts | 11 ++- src/sounds/customSoundSystem.ts | 44 +++++++++ src/sounds/soundsMap.ts | 10 +- src/watchOptions.ts | 4 + 11 files changed, 323 insertions(+), 18 deletions(-) create mode 100644 assets/customTextures/readme.md create mode 100644 scripts/patchAssets.ts create mode 100644 src/sounds/customSoundSystem.ts diff --git a/assets/customTextures/readme.md b/assets/customTextures/readme.md new file mode 100644 index 00000000..e2a78c20 --- /dev/null +++ b/assets/customTextures/readme.md @@ -0,0 +1,2 @@ +here you can place custom textures for bundled files (blocks/items) e.g. blocks/stone.png +get file names from here (blocks/items) https://zardoy.github.io/mc-assets/ diff --git a/renderer/viewer/three/threeJsSound.ts b/renderer/viewer/three/threeJsSound.ts index 46aefda9..699bb2cc 100644 --- a/renderer/viewer/three/threeJsSound.ts +++ b/renderer/viewer/three/threeJsSound.ts @@ -2,7 +2,7 @@ import * as THREE from 'three' import { WorldRendererThree } from './worldrendererThree' export interface SoundSystem { - playSound: (position: { x: number, y: number, z: number }, path: string, volume?: number, pitch?: number) => void + playSound: (position: { x: number, y: number, z: number }, path: string, volume?: number, pitch?: number, timeout?: number) => void destroy: () => void } @@ -10,7 +10,17 @@ export class ThreeJsSound implements SoundSystem { audioListener: THREE.AudioListener | undefined private readonly activeSounds = new Set() private readonly audioContext: AudioContext | undefined + private readonly soundVolumes = new Map() + baseVolume = 1 + constructor (public worldRenderer: WorldRendererThree) { + worldRenderer.onWorldSwitched.push(() => { + this.stopAll() + }) + + worldRenderer.onReactiveConfigUpdated('volume', (volume) => { + this.changeVolume(volume) + }) } initAudioListener () { @@ -19,20 +29,24 @@ export class ThreeJsSound implements SoundSystem { this.worldRenderer.camera.add(this.audioListener) } - playSound (position: { x: number, y: number, z: number }, path: string, volume = 1, pitch = 1) { + playSound (position: { x: number, y: number, z: number }, path: string, volume = 1, pitch = 1, timeout = 500) { this.initAudioListener() const sound = new THREE.PositionalAudio(this.audioListener!) this.activeSounds.add(sound) + this.soundVolumes.set(sound, volume) const audioLoader = new THREE.AudioLoader() const start = Date.now() void audioLoader.loadAsync(path).then((buffer) => { - if (Date.now() - start > 500) return + if (Date.now() - start > timeout) { + console.warn('Ignored playing sound', path, 'due to timeout:', timeout, 'ms <', Date.now() - start, 'ms') + return + } // play sound.setBuffer(buffer) sound.setRefDistance(20) - sound.setVolume(volume) + sound.setVolume(volume * this.baseVolume) sound.setPlaybackRate(pitch) // set the pitch this.worldRenderer.scene.add(sound) // set sound position @@ -43,21 +57,35 @@ export class ThreeJsSound implements SoundSystem { sound.disconnect() } this.activeSounds.delete(sound) + this.soundVolumes.delete(sound) audioLoader.manager.itemEnd(path) } sound.play() }) } - destroy () { - // Stop and clean up all active sounds + stopAll () { for (const sound of this.activeSounds) { + if (!sound) continue sound.stop() if (sound.source) { sound.disconnect() } + this.worldRenderer.scene.remove(sound) } + this.activeSounds.clear() + this.soundVolumes.clear() + } + changeVolume (volume: number) { + this.baseVolume = volume + for (const [sound, individualVolume] of this.soundVolumes) { + sound.setVolume(individualVolume * this.baseVolume) + } + } + + destroy () { + this.stopAll() // Remove and cleanup audio listener if (this.audioListener) { this.audioListener.removeFromParent() diff --git a/rsbuild.config.ts b/rsbuild.config.ts index 42d6867b..6cd6b2ed 100644 --- a/rsbuild.config.ts +++ b/rsbuild.config.ts @@ -240,6 +240,10 @@ const appConfig = defineConfig({ prep() }) build.onAfterBuild(async () => { + if (fs.readdirSync('./assets/customTextures').length > 0) { + childProcess.execSync('tsx ./scripts/patchAssets.ts', { stdio: 'inherit' }) + } + if (SINGLE_FILE_BUILD) { // check that only index.html is in the dist/single folder const singleBuildFiles = fs.readdirSync('./dist/single') diff --git a/scripts/patchAssets.ts b/scripts/patchAssets.ts new file mode 100644 index 00000000..99994f5f --- /dev/null +++ b/scripts/patchAssets.ts @@ -0,0 +1,137 @@ +import blocksAtlas from 'mc-assets/dist/blocksAtlases.json' +import itemsAtlas from 'mc-assets/dist/itemsAtlases.json' +import * as fs from 'fs' +import * as path from 'path' +import sharp from 'sharp' + +interface AtlasFile { + latest: { + suSv: number + tileSize: number + width: number + height: number + textures: { + [key: string]: { + u: number + v: number + su: number + sv: number + tileIndex: number + } + } + } +} + +async function patchTextureAtlas( + atlasType: 'blocks' | 'items', + atlasData: AtlasFile, + customTexturesDir: string, + distDir: string +) { + // Check if custom textures directory exists and has files + if (!fs.existsSync(customTexturesDir) || fs.readdirSync(customTexturesDir).length === 0) { + return + } + + // Find the latest atlas file + const atlasFiles = fs.readdirSync(distDir) + .filter(file => file.startsWith(`${atlasType}AtlasLatest`) && file.endsWith('.png')) + .sort() + + if (atlasFiles.length === 0) { + console.log(`No ${atlasType}AtlasLatest.png found in ${distDir}`) + return + } + + const latestAtlasFile = atlasFiles[atlasFiles.length - 1] + const atlasPath = path.join(distDir, latestAtlasFile) + console.log(`Patching ${atlasPath}`) + + // Get atlas dimensions + const atlasMetadata = await sharp(atlasPath).metadata() + if (!atlasMetadata.width || !atlasMetadata.height) { + throw new Error(`Failed to get atlas dimensions for ${atlasPath}`) + } + + // Process each custom texture + const customTextureFiles = fs.readdirSync(customTexturesDir) + .filter(file => file.endsWith('.png')) + + if (customTextureFiles.length === 0) return + + // Prepare composite operations + const composites: sharp.OverlayOptions[] = [] + + for (const textureFile of customTextureFiles) { + const textureName = path.basename(textureFile, '.png') + + if (atlasData.latest.textures[textureName]) { + const textureData = atlasData.latest.textures[textureName] + const customTexturePath = path.join(customTexturesDir, textureFile) + + try { + // Convert UV coordinates to pixel coordinates + const x = Math.round(textureData.u * atlasMetadata.width) + const y = Math.round(textureData.v * atlasMetadata.height) + const width = Math.round((textureData.su ?? atlasData.latest.suSv) * atlasMetadata.width) + const height = Math.round((textureData.sv ?? atlasData.latest.suSv) * atlasMetadata.height) + + // Resize custom texture to match atlas dimensions and add to composite operations + const resizedTextureBuffer = await sharp(customTexturePath) + .resize(width, height, { + fit: 'fill', + kernel: 'nearest' // Preserve pixel art quality + }) + .png() + .toBuffer() + + composites.push({ + input: resizedTextureBuffer, + left: x, + top: y, + blend: 'over' + }) + + console.log(`Prepared ${textureName} at (${x}, ${y}) with size (${width}, ${height})`) + } catch (error) { + console.error(`Failed to prepare ${textureName}:`, error) + } + } else { + console.warn(`Texture ${textureName} not found in ${atlasType} atlas`) + } + } + + if (composites.length > 0) { + // Apply all patches at once using Sharp's composite + await sharp(atlasPath) + .composite(composites) + .png() + .toFile(atlasPath + '.tmp') + + // Replace original with patched version + fs.renameSync(atlasPath + '.tmp', atlasPath) + console.log(`Saved patched ${atlasType} atlas to ${atlasPath}`) + } +} + +async function main() { + const customBlocksDir = './assets/customTextures/blocks' + const customItemsDir = './assets/customTextures/items' + const distDir = './dist/static/image' + + try { + // Patch blocks atlas + await patchTextureAtlas('blocks', blocksAtlas as unknown as AtlasFile, customBlocksDir, distDir) + + // Patch items atlas + await patchTextureAtlas('items', itemsAtlas as unknown as AtlasFile, customItemsDir, distDir) + + console.log('Texture atlas patching completed!') + } catch (error) { + console.error('Failed to patch texture atlases:', error) + process.exit(1) + } +} + +// Run the script +main() diff --git a/src/basicSounds.ts b/src/basicSounds.ts index 40428c6b..37f8dccd 100644 --- a/src/basicSounds.ts +++ b/src/basicSounds.ts @@ -43,7 +43,7 @@ export async function loadSound (path: string, contents = path) { } } -export const loadOrPlaySound = async (url, soundVolume = 1, loadTimeout = 500) => { +export const loadOrPlaySound = async (url, soundVolume = 1, loadTimeout = options.remoteSoundsLoadTimeout, loop = false) => { const soundBuffer = sounds[url] if (!soundBuffer) { const start = Date.now() @@ -51,10 +51,10 @@ export const loadOrPlaySound = async (url, soundVolume = 1, loadTimeout = 500) = if (cancelled || Date.now() - start > loadTimeout) return } - return playSound(url, soundVolume) + return playSound(url, soundVolume, loop) } -export async function playSound (url, soundVolume = 1) { +export async function playSound (url, soundVolume = 1, loop = false) { const volume = soundVolume * (options.volume / 100) if (!volume) return @@ -75,6 +75,7 @@ export async function playSound (url, soundVolume = 1) { const gainNode = audioContext.createGain() const source = audioContext.createBufferSource() source.buffer = soundBuffer + source.loop = loop source.connect(gainNode) gainNode.connect(audioContext.destination) gainNode.gain.value = volume @@ -99,6 +100,16 @@ export async function playSound (url, soundVolume = 1) { onEnded (callback: () => void) { callbacks.push(callback) }, + stop () { + try { + source.stop() + // Remove from active sounds + const index = activeSounds.findIndex(s => s.source === source) + if (index !== -1) activeSounds.splice(index, 1) + } catch (err) { + console.warn('Failed to stop sound:', err) + } + }, } } @@ -113,6 +124,19 @@ export function stopAllSounds () { activeSounds.length = 0 } +export function stopSound (url: string) { + const soundIndex = activeSounds.findIndex(s => s.source.buffer === sounds[url]) + if (soundIndex !== -1) { + const { source } = activeSounds[soundIndex] + try { + source.stop() + } catch (err) { + console.warn('Failed to stop sound:', err) + } + activeSounds.splice(soundIndex, 1) + } +} + export function changeVolumeOfCurrentlyPlayingSounds (newVolume: number) { const normalizedVolume = newVolume / 100 for (const { gainNode, volumeMultiplier } of activeSounds) { diff --git a/src/defaultOptions.ts b/src/defaultOptions.ts index 95dbfd63..378cd14a 100644 --- a/src/defaultOptions.ts +++ b/src/defaultOptions.ts @@ -85,6 +85,7 @@ export const defaultOptions = { } as any, preferLoadReadonly: false, experimentalClientSelfReload: true, + remoteSoundsLoadTimeout: 500, disableLoadPrompts: false, guestUsername: 'guest', askGuestName: true, diff --git a/src/react/Slider.tsx b/src/react/Slider.tsx index e177578c..2a068264 100644 --- a/src/react/Slider.tsx +++ b/src/react/Slider.tsx @@ -1,5 +1,5 @@ // Slider.tsx -import React, { useState, useEffect } from 'react' +import React, { useState, useEffect, useRef, useCallback } from 'react' import styles from './slider.module.css' import SharedHudVars from './SharedHudVars' @@ -12,6 +12,7 @@ interface Props extends React.ComponentProps<'div'> { min?: number; max?: number; disabledReason?: string; + throttle?: number | false; // milliseconds, default 100, false to disable updateValue?: (value: number) => void; updateOnDragEnd?: boolean; @@ -26,15 +27,24 @@ const Slider: React.FC = ({ min = 0, max = 100, disabledReason, + throttle = 0, updateOnDragEnd = false, updateValue, ...divProps }) => { + label = translate(label) + disabledReason = translate(disabledReason) + valueDisplay = typeof valueDisplay === 'string' ? translate(valueDisplay) : valueDisplay + const [value, setValue] = useState(valueProp) const getRatio = (v = value) => Math.max(Math.min((v - min) / (max - min), 1), 0) const [ratio, setRatio] = useState(getRatio()) + // Throttling refs + const timeoutRef = useRef(null) + const lastValueRef = useRef(valueProp) + useEffect(() => { setValue(valueProp) }, [valueProp]) @@ -42,14 +52,52 @@ const Slider: React.FC = ({ setRatio(getRatio()) }, [value, min, max]) - const fireValueUpdate = (dragEnd: boolean, v = value) => { + const throttledUpdateValue = useCallback((newValue: number, dragEnd: boolean) => { if (updateOnDragEnd !== dragEnd) return - updateValue?.(v) + if (!updateValue) return + + lastValueRef.current = newValue + + if (!throttle) { + // No throttling + updateValue(newValue) + return + } + + // Clear existing timeout + if (timeoutRef.current) { + clearTimeout(timeoutRef.current) + } + + // Set new timeout + timeoutRef.current = setTimeout(() => { + updateValue(lastValueRef.current) + timeoutRef.current = null + }, throttle) + }, [updateValue, updateOnDragEnd, throttle]) + + // Cleanup timeout on unmount + useEffect(() => { + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current) + // Fire the last value immediately on cleanup + if (updateValue && lastValueRef.current !== undefined) { + updateValue(lastValueRef.current) + } + } + } + }, [updateValue]) + + const fireValueUpdate = (dragEnd: boolean, v = value) => { + throttledUpdateValue(v, dragEnd) } + const labelText = `${label}: ${valueDisplay ?? value} ${unit}` + return ( -
    +
    17 ? 'settings-text-container-long' : ''}`} style={{ width }} {...divProps}> = ({
    diff --git a/src/sounds/botSoundSystem.ts b/src/sounds/botSoundSystem.ts index 72aa3da8..cb237072 100644 --- a/src/sounds/botSoundSystem.ts +++ b/src/sounds/botSoundSystem.ts @@ -11,6 +11,7 @@ import { showNotification } from '../react/NotificationProvider' import { pixelartIcons } from '../react/PixelartIcon' import { createSoundMap, SoundMap } from './soundsMap' import { musicSystem } from './musicSystem' +import './customSoundSystem' let soundMap: SoundMap | undefined @@ -50,8 +51,9 @@ subscribeKey(miscUiState, 'gameLoaded', async () => { appViewer.backend?.soundSystem?.playSound( position, soundData.url, - soundData.volume * (options.volume / 100), - Math.max(Math.min(pitch ?? 1, 2), 0.5) + soundData.volume, + Math.max(Math.min(pitch ?? 1, 2), 0.5), + soundData.timeout ?? options.remoteSoundsLoadTimeout ) } if (getDistance(bot.entity.position, position) < 4 * 16) { @@ -81,7 +83,7 @@ subscribeKey(miscUiState, 'gameLoaded', async () => { } const randomMusicKey = musicKeys[Math.floor(Math.random() * musicKeys.length)] const soundData = await soundMap.getSoundUrl(randomMusicKey) - if (!soundData) return + if (!soundData || !soundMap) return await musicSystem.playMusic(soundData.url, soundData.volume) } @@ -109,6 +111,9 @@ subscribeKey(miscUiState, 'gameLoaded', async () => { } bot.on('soundEffectHeard', async (soundId, position, volume, pitch) => { + if (/^https?:/.test(soundId.replace('minecraft:', ''))) { + return + } await playHardcodedSound(soundId, position, volume, pitch) }) diff --git a/src/sounds/customSoundSystem.ts b/src/sounds/customSoundSystem.ts new file mode 100644 index 00000000..5dfa89f7 --- /dev/null +++ b/src/sounds/customSoundSystem.ts @@ -0,0 +1,44 @@ +import { loadOrPlaySound, stopAllSounds, stopSound } from '../basicSounds' + +const customSoundSystem = () => { + bot._client.on('named_sound_effect', packet => { + let { soundName } = packet + let metadata = {} as { loadTimeout?: number, loop?: boolean } + + // Extract JSON metadata from parentheses at the end + const jsonMatch = /\(({.*})\)$/.exec(soundName) + if (jsonMatch) { + try { + metadata = JSON.parse(jsonMatch[1]) + soundName = soundName.slice(0, -jsonMatch[0].length) + } catch (e) { + console.warn('Failed to parse sound metadata:', jsonMatch[1]) + } + } + + if (/^https?:/.test(soundName.replace('minecraft:', ''))) { + const { loadTimeout, loop } = metadata + void loadOrPlaySound(soundName, packet.volume, loadTimeout, loop) + } + }) + + bot._client.on('stop_sound', packet => { + const { flags, source, sound } = packet + + if (flags === 0) { + // Stop all sounds + stopAllSounds() + } else if (sound) { + // Stop specific sound by name + stopSound(sound) + } + }) + + bot.on('end', () => { + stopAllSounds() + }) +} + +customEvents.on('mineflayerBotCreated', () => { + customSoundSystem() +}) diff --git a/src/sounds/soundsMap.ts b/src/sounds/soundsMap.ts index 1b0a0178..47028971 100644 --- a/src/sounds/soundsMap.ts +++ b/src/sounds/soundsMap.ts @@ -35,6 +35,7 @@ interface ResourcePackSoundEntry { name: string stream?: boolean volume?: number + timeout?: number } interface ResourcePackSound { @@ -140,7 +141,7 @@ export class SoundMap { await scan(soundsBasePath) } - async getSoundUrl (soundKey: string, volume = 1): Promise<{ url: string; volume: number } | undefined> { + async getSoundUrl (soundKey: string, volume = 1): Promise<{ url: string; volume: number, timeout?: number } | undefined> { // First check resource pack sounds.json if (this.activeResourcePackSoundsJson && soundKey in this.activeResourcePackSoundsJson) { const rpSound = this.activeResourcePackSoundsJson[soundKey] @@ -151,6 +152,13 @@ export class SoundMap { if (this.activeResourcePackBasePath) { const tryFormat = async (format: string) => { try { + if (sound.name.startsWith('http://') || sound.name.startsWith('https://')) { + return { + url: sound.name, + volume: soundVolume * Math.max(Math.min(volume, 1), 0), + timeout: sound.timeout + } + } const resourcePackPath = path.join(this.activeResourcePackBasePath!, `/assets/minecraft/sounds/${sound.name}.${format}`) const fileData = await fs.promises.readFile(resourcePackPath) return { diff --git a/src/watchOptions.ts b/src/watchOptions.ts index 478da4fb..2f5199c0 100644 --- a/src/watchOptions.ts +++ b/src/watchOptions.ts @@ -80,6 +80,10 @@ export const watchOptionsAfterViewerInit = () => { updateFpsLimit(o) }) + watchValue(options, o => { + appViewer.inWorldRenderingConfig.volume = Math.max(o.volume / 100, 0) + }) + watchValue(options, o => { appViewer.inWorldRenderingConfig.vrSupport = o.vrSupport appViewer.inWorldRenderingConfig.vrPageGameRendering = o.vrPageGameRendering From d6eb1601e918b179b11ed2898df2befbd7d93d2d Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 16 Aug 2025 09:16:29 +0300 Subject: [PATCH 57/98] disable remote sounds by default --- src/defaultOptions.ts | 1 + src/sounds/customSoundSystem.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/defaultOptions.ts b/src/defaultOptions.ts index 378cd14a..28b64059 100644 --- a/src/defaultOptions.ts +++ b/src/defaultOptions.ts @@ -85,6 +85,7 @@ export const defaultOptions = { } as any, preferLoadReadonly: false, experimentalClientSelfReload: true, + remoteSoundsSupport: false, remoteSoundsLoadTimeout: 500, disableLoadPrompts: false, guestUsername: 'guest', diff --git a/src/sounds/customSoundSystem.ts b/src/sounds/customSoundSystem.ts index 5dfa89f7..1880aa70 100644 --- a/src/sounds/customSoundSystem.ts +++ b/src/sounds/customSoundSystem.ts @@ -1,7 +1,9 @@ import { loadOrPlaySound, stopAllSounds, stopSound } from '../basicSounds' +import { options } from '../optionsStorage' const customSoundSystem = () => { bot._client.on('named_sound_effect', packet => { + if (!options.remoteSoundsSupport) return let { soundName } = packet let metadata = {} as { loadTimeout?: number, loop?: boolean } From 9a84a7acfba4a9a7c56fdb4612fa2e35818ae085 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 18 Aug 2025 11:37:20 +0300 Subject: [PATCH 58/98] do less annoying logging --- renderer/viewer/lib/worldDataEmitter.ts | 1 - src/appViewer.ts | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/renderer/viewer/lib/worldDataEmitter.ts b/renderer/viewer/lib/worldDataEmitter.ts index 3bc2acd3..86a85f77 100644 --- a/renderer/viewer/lib/worldDataEmitter.ts +++ b/renderer/viewer/lib/worldDataEmitter.ts @@ -379,7 +379,6 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter { if (!options.experimentalClientSelfReload) return - displayClientChat(`[client] client panicked due to too long loading time. Soft reloading chunks...`) + if (process.env.NODE_ENV === 'development') { + displayClientChat(`[client] client panicked due to too long loading time. Soft reloading chunks...`) + } void reloadChunks() } window.worldView = this.worldView From a8fa3d47d1e43d95313cd19ea1838678e423ba21 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Tue, 19 Aug 2025 12:49:33 +0300 Subject: [PATCH 59/98] up protocol & mineflayer for 1.21.6 --- pnpm-lock.yaml | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f495c831..e7cb1872 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -137,13 +137,13 @@ importers: version: 4.17.21 mcraft-fun-mineflayer: specifier: ^0.1.23 - version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/8aaec9f31657ebb5195cc13db09e5fc6760d4a09(encoding@0.1.13)) + version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13)) minecraft-data: specifier: 3.92.0 version: 3.92.0 minecraft-protocol: specifier: github:PrismarineJS/node-minecraft-protocol#master - version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb(patch_hash=2a88e61fea1825d9fa5f1584fde810421d553d23e45e6dc829c3697ee3358bea)(encoding@0.1.13) + version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=2a88e61fea1825d9fa5f1584fde810421d553d23e45e6dc829c3697ee3358bea)(encoding@0.1.13) mineflayer-item-map-downloader: specifier: github:zardoy/mineflayer-item-map-downloader version: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824(patch_hash=a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad)(encoding@0.1.13) @@ -342,7 +342,7 @@ importers: version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/89c33d396f3fde4804c71f4be3c203ade1833b41(@types/react@18.3.18)(react@18.3.1) mineflayer: specifier: github:zardoy/mineflayer#gen-the-master - version: https://codeload.github.com/zardoy/mineflayer/tar.gz/8aaec9f31657ebb5195cc13db09e5fc6760d4a09(encoding@0.1.13) + version: https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13) mineflayer-mouse: specifier: ^0.1.17 version: 0.1.17 @@ -6649,8 +6649,8 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/89c33d396f3fde4804c71f4be3c203ade1833b41} version: 1.0.1 - minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb: - resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb} + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074: + resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074} version: 1.60.1 engines: {node: '>=22'} @@ -6669,8 +6669,8 @@ packages: resolution: {integrity: sha512-0eCR8pnGb42Qd9QmAxOjl0PhA5Fa+9+6H1G/YsbsO5rg5mDf94Tusqp/8NAGLPQCPVDzbarLskXdjR3h0E0bEQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/8aaec9f31657ebb5195cc13db09e5fc6760d4a09: - resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/8aaec9f31657ebb5195cc13db09e5fc6760d4a09} + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8: + resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8} version: 8.0.0 engines: {node: '>=22'} @@ -11308,7 +11308,7 @@ snapshots: dependencies: '@nxg-org/mineflayer-util-plugin': 1.8.4 minecraft-data: 3.92.0 - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/8aaec9f31657ebb5195cc13db09e5fc6760d4a09(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-item: 1.17.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b @@ -13069,7 +13069,7 @@ snapshots: flatmap: 0.0.3 long: 5.3.1 minecraft-data: 3.92.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb(patch_hash=2a88e61fea1825d9fa5f1584fde810421d553d23e45e6dc829c3697ee3358bea)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=2a88e61fea1825d9fa5f1584fde810421d553d23e45e6dc829c3697ee3358bea)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 @@ -13105,7 +13105,7 @@ snapshots: flatmap: 0.0.3 long: 5.3.1 minecraft-data: 3.92.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb(patch_hash=2a88e61fea1825d9fa5f1584fde810421d553d23e45e6dc829c3697ee3358bea)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=2a88e61fea1825d9fa5f1584fde810421d553d23e45e6dc829c3697ee3358bea)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 @@ -16948,12 +16948,12 @@ snapshots: maxrects-packer: '@zardoy/maxrects-packer@2.7.4' zod: 3.24.2 - mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/8aaec9f31657ebb5195cc13db09e5fc6760d4a09(encoding@0.1.13)): + mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(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/b404bcaed4c039c5558e889c8617aa866cd7bddb(patch_hash=2a88e61fea1825d9fa5f1584fde810421d553d23e45e6dc829c3697ee3358bea)(encoding@0.1.13) - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/8aaec9f31657ebb5195cc13db09e5fc6760d4a09(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=2a88e61fea1825d9fa5f1584fde810421d553d23e45e6dc829c3697ee3358bea)(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13) prismarine-item: 1.17.0 ws: 8.18.1 transitivePeerDependencies: @@ -17271,7 +17271,7 @@ snapshots: - '@types/react' - react - minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb(patch_hash=2a88e61fea1825d9fa5f1584fde810421d553d23e45e6dc829c3697ee3358bea)(encoding@0.1.13): + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=2a88e61fea1825d9fa5f1584fde810421d553d23e45e6dc829c3697ee3358bea)(encoding@0.1.13): dependencies: '@types/node-rsa': 1.1.4 '@types/readable-stream': 4.0.18 @@ -17323,7 +17323,7 @@ snapshots: mineflayer-item-map-downloader@https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824(patch_hash=a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad)(encoding@0.1.13): dependencies: - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/8aaec9f31657ebb5195cc13db09e5fc6760d4a09(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13) sharp: 0.30.7 transitivePeerDependencies: - encoding @@ -17338,11 +17338,11 @@ snapshots: transitivePeerDependencies: - supports-color - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/8aaec9f31657ebb5195cc13db09e5fc6760d4a09(encoding@0.1.13): + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13): dependencies: '@nxg-org/mineflayer-physics-util': 1.8.10 minecraft-data: 3.92.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/b404bcaed4c039c5558e889c8617aa866cd7bddb(patch_hash=2a88e61fea1825d9fa5f1584fde810421d553d23e45e6dc829c3697ee3358bea)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=2a88e61fea1825d9fa5f1584fde810421d553d23e45e6dc829c3697ee3358bea)(encoding@0.1.13) prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chat: 1.11.0 From 4a5f2e799ca1778cd45dce9fa3132d66881bec37 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 20 Aug 2025 20:02:57 +0300 Subject: [PATCH 60/98] disable experimentalClientSelfReload by default until it's reworked with more fine-tuned checks against server connection --- src/defaultOptions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/defaultOptions.ts b/src/defaultOptions.ts index 28b64059..6045e70b 100644 --- a/src/defaultOptions.ts +++ b/src/defaultOptions.ts @@ -84,7 +84,7 @@ export const defaultOptions = { gameMode: 1 } as any, preferLoadReadonly: false, - experimentalClientSelfReload: true, + experimentalClientSelfReload: false, remoteSoundsSupport: false, remoteSoundsLoadTimeout: 500, disableLoadPrompts: false, From 72e9e656cca70d66372ddfa722abdf01fb4981e3 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 20 Aug 2025 20:42:40 +0300 Subject: [PATCH 61/98] new! helpful errors on custom channels payloads! --- patches/minecraft-protocol.patch | 13 +++++++++++++ src/mineflayer/mc-protocol.ts | 30 ++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/patches/minecraft-protocol.patch b/patches/minecraft-protocol.patch index 5dec44d7..e29f87d9 100644 --- a/patches/minecraft-protocol.patch +++ b/patches/minecraft-protocol.patch @@ -73,6 +73,19 @@ index 63cc2bd9615100bd2fd63dfe14c094aa6b8cd1c9..36df57d1196af9761d920fa285ac48f8 } function onJoinServerResponse (err) { +diff --git a/src/client/pluginChannels.js b/src/client/pluginChannels.js +index 671eb452f31e6b5fcd57d715f1009d010160c65f..7f69f511c8fb97d431ec5125c851b49be8e2ab76 100644 +--- a/src/client/pluginChannels.js ++++ b/src/client/pluginChannels.js +@@ -57,7 +57,7 @@ module.exports = function (client, options) { + try { + packet.data = proto.parsePacketBuffer(channel, packet.data).data + } catch (error) { +- client.emit('error', error) ++ client.emit('error', error, { customPayload: packet }) + return + } + } diff --git a/src/client.js b/src/client.js index e369e77d055ba919e8f9da7b8e8b5dc879c74cf4..54bb9e6644388e9b6bd42b3012951875989cdf0c 100644 --- a/src/client.js diff --git a/src/mineflayer/mc-protocol.ts b/src/mineflayer/mc-protocol.ts index a0348c5d..0171387a 100644 --- a/src/mineflayer/mc-protocol.ts +++ b/src/mineflayer/mc-protocol.ts @@ -11,6 +11,36 @@ import { getWebsocketStream } from './websocket-core' let lastPacketTime = 0 customEvents.on('mineflayerBotCreated', () => { + // const oldParsePacketBuffer = bot._client.deserializer.parsePacketBuffer + // try { + // const parsed = oldParsePacketBuffer(buffer) + // } catch (err) { + // debugger + // reportError(new Error(`Error parsing packet ${buffer.subarray(0, 30).toString('hex')}`, { cause: err })) + // throw err + // } + // } + class MinecraftProtocolError extends Error { + constructor (message: string, cause?: Error, public data?: any) { + if (data?.customPayload) { + message += ` (Custom payload: ${data.customPayload.channel})` + } + super(message, { cause }) + this.name = 'MinecraftProtocolError' + } + } + + const onClientError = (err, data) => { + const error = new MinecraftProtocolError(`Minecraft protocol client error: ${err.message}`, err, data) + reportError(error) + } + if (typeof bot._client['_events'].error === 'function') { + // dont report to bot for more explicit error + bot._client['_events'].error = onClientError + } else { + bot._client.on('error' as any, onClientError) + } + // todo move more code here if (!appQueryParams.noPacketsValidation) { (bot._client as unknown as Client).on('packet', (data, packetMeta, buffer, fullBuffer) => { From 6e0d54ea17178a091d8c103a48f82a4723f5b23d Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 20 Aug 2025 20:45:02 +0300 Subject: [PATCH 62/98] up mc-protocol patch --- pnpm-lock.yaml | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e7cb1872..c7e1d1b9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,7 +23,7 @@ overrides: patchedDependencies: minecraft-protocol: - hash: 2a88e61fea1825d9fa5f1584fde810421d553d23e45e6dc829c3697ee3358bea + hash: 4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b path: patches/minecraft-protocol.patch mineflayer-item-map-downloader@1.2.0: hash: a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad @@ -143,7 +143,7 @@ importers: version: 3.92.0 minecraft-protocol: specifier: github:PrismarineJS/node-minecraft-protocol#master - version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=2a88e61fea1825d9fa5f1584fde810421d553d23e45e6dc829c3697ee3358bea)(encoding@0.1.13) + version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) mineflayer-item-map-downloader: specifier: github:zardoy/mineflayer-item-map-downloader version: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824(patch_hash=a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad)(encoding@0.1.13) @@ -430,13 +430,13 @@ importers: version: 1.3.9 prismarine-block: specifier: github:zardoy/prismarine-block#next-era - version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) prismarine-chunk: specifier: github:zardoy/prismarine-chunk#master version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) prismarine-schematic: specifier: ^1.2.0 - version: 1.2.3 + version: 1.2.3(prismarine-registry@1.11.0) process: specifier: ^0.11.10 version: 0.11.10 @@ -6651,7 +6651,7 @@ packages: minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074: resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074} - version: 1.60.1 + version: 1.61.0 engines: {node: '>=22'} minecraft-wrap@1.6.0: @@ -11309,7 +11309,7 @@ snapshots: '@nxg-org/mineflayer-util-plugin': 1.8.4 minecraft-data: 3.92.0 mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) prismarine-item: 1.17.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b vec3: 0.1.10 @@ -13069,7 +13069,7 @@ snapshots: flatmap: 0.0.3 long: 5.3.1 minecraft-data: 3.92.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=2a88e61fea1825d9fa5f1584fde810421d553d23e45e6dc829c3697ee3358bea)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 @@ -13105,7 +13105,7 @@ snapshots: flatmap: 0.0.3 long: 5.3.1 minecraft-data: 3.92.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=2a88e61fea1825d9fa5f1584fde810421d553d23e45e6dc829c3697ee3358bea)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 @@ -16952,7 +16952,7 @@ snapshots: 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/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=2a88e61fea1825d9fa5f1584fde810421d553d23e45e6dc829c3697ee3358bea)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13) prismarine-item: 1.17.0 ws: 8.18.1 @@ -17271,7 +17271,7 @@ snapshots: - '@types/react' - react - minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=2a88e61fea1825d9fa5f1584fde810421d553d23e45e6dc829c3697ee3358bea)(encoding@0.1.13): + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13): dependencies: '@types/node-rsa': 1.1.4 '@types/readable-stream': 4.0.18 @@ -17342,9 +17342,9 @@ snapshots: dependencies: '@nxg-org/mineflayer-physics-util': 1.8.10 minecraft-data: 3.92.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=2a88e61fea1825d9fa5f1584fde810421d553d23e45e6dc829c3697ee3358bea)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) prismarine-chat: 1.11.0 prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) prismarine-entity: 2.5.0 @@ -18135,7 +18135,7 @@ snapshots: minecraft-data: 3.92.0 prismarine-registry: 1.11.0 - prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9: + prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0): dependencies: minecraft-data: 3.92.0 prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) @@ -18143,6 +18143,8 @@ snapshots: prismarine-item: 1.17.0 prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 + transitivePeerDependencies: + - prismarine-registry prismarine-chat@1.11.0: dependencies: @@ -18153,7 +18155,7 @@ snapshots: prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0): dependencies: prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 smart-buffer: 4.2.0 @@ -18187,7 +18189,7 @@ snapshots: prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0): dependencies: - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) prismarine-nbt: 2.7.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c @@ -18211,16 +18213,18 @@ snapshots: prismarine-registry@1.11.0: dependencies: minecraft-data: 3.92.0 - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) prismarine-nbt: 2.7.0 - prismarine-schematic@1.2.3: + prismarine-schematic@1.2.3(prismarine-registry@1.11.0): dependencies: minecraft-data: 3.92.0 - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) prismarine-nbt: 2.7.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c vec3: 0.1.10 + transitivePeerDependencies: + - prismarine-registry prismarine-windows@2.9.0: dependencies: From a12c61bc6c8f3ede1910dba4567d71d7f907feeb Mon Sep 17 00:00:00 2001 From: Vitaly Date: Thu, 21 Aug 2025 13:21:02 +0300 Subject: [PATCH 63/98] add simple monaco (#418) --- package.json | 1 + pnpm-lock.yaml | 58 +++++++++++++++----- src/core/ideChannels.ts | 106 +++++++++++++++++++++++++++++++++++++ src/customChannels.ts | 2 + src/react/MonacoEditor.css | 58 ++++++++++++++++++++ src/react/MonacoEditor.tsx | 73 +++++++++++++++++++++++++ src/reactUi.tsx | 3 +- 7 files changed, 286 insertions(+), 15 deletions(-) create mode 100644 src/core/ideChannels.ts create mode 100644 src/react/MonacoEditor.css create mode 100644 src/react/MonacoEditor.tsx diff --git a/package.json b/package.json index 1d9dfc0c..a8c2c4e7 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "dependencies": { "@dimaka/interface": "0.0.3-alpha.0", "@floating-ui/react": "^0.26.1", + "@monaco-editor/react": "^4.7.0", "@nxg-org/mineflayer-auto-jump": "^0.7.18", "@nxg-org/mineflayer-tracker": "1.3.0", "@react-oauth/google": "^0.12.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c7e1d1b9..8acb9681 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -42,6 +42,9 @@ importers: '@floating-ui/react': specifier: ^0.26.1 version: 0.26.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@monaco-editor/react': + specifier: ^4.7.0 + version: 4.7.0(monaco-editor@0.52.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@nxg-org/mineflayer-auto-jump': specifier: ^0.7.18 version: 0.7.18 @@ -430,13 +433,13 @@ importers: version: 1.3.9 prismarine-block: specifier: github:zardoy/prismarine-block#next-era - version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chunk: specifier: github:zardoy/prismarine-chunk#master version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) prismarine-schematic: specifier: ^1.2.0 - version: 1.2.3(prismarine-registry@1.11.0) + version: 1.2.3 process: specifier: ^0.11.10 version: 0.11.10 @@ -1989,6 +1992,16 @@ packages: '@module-federation/webpack-bundler-runtime@0.11.2': resolution: {integrity: sha512-WdwIE6QF+MKs/PdVu0cKPETF743JB9PZ62/qf7Uo3gU4fjsUMc37RnbJZ/qB60EaHHfjwp1v6NnhZw1r4eVsnw==} + '@monaco-editor/loader@1.5.0': + resolution: {integrity: sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==} + + '@monaco-editor/react@4.7.0': + resolution: {integrity: sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==} + peerDependencies: + monaco-editor: '>= 0.25.0 < 1' + react: ^18.2.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@msgpack/msgpack@2.8.0': resolution: {integrity: sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==} engines: {node: '>= 10'} @@ -6769,6 +6782,9 @@ packages: mojangson@2.0.4: resolution: {integrity: sha512-HYmhgDjr1gzF7trGgvcC/huIg2L8FsVbi/KacRe6r1AswbboGVZDS47SOZlomPuMWvZLas8m9vuHHucdZMwTmQ==} + monaco-editor@0.52.2: + resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==} + moo@0.5.2: resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==} @@ -8373,6 +8389,9 @@ packages: stacktrace-js@2.0.2: resolution: {integrity: sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==} + state-local@1.0.7: + resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==} + static-extend@0.1.2: resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==} engines: {node: '>=0.10.0'} @@ -11254,6 +11273,17 @@ snapshots: '@module-federation/runtime': 0.11.2 '@module-federation/sdk': 0.11.2 + '@monaco-editor/loader@1.5.0': + dependencies: + state-local: 1.0.7 + + '@monaco-editor/react@4.7.0(monaco-editor@0.52.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@monaco-editor/loader': 1.5.0 + monaco-editor: 0.52.2 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + '@msgpack/msgpack@2.8.0': {} '@ndelangen/get-tarball@3.0.9': @@ -11309,7 +11339,7 @@ snapshots: '@nxg-org/mineflayer-util-plugin': 1.8.4 minecraft-data: 3.92.0 mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-item: 1.17.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b vec3: 0.1.10 @@ -17344,7 +17374,7 @@ snapshots: minecraft-data: 3.92.0 minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chat: 1.11.0 prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) prismarine-entity: 2.5.0 @@ -17461,6 +17491,8 @@ snapshots: dependencies: nearley: 2.20.1 + monaco-editor@0.52.2: {} + moo@0.5.2: {} morgan@1.10.0: @@ -18135,7 +18167,7 @@ snapshots: minecraft-data: 3.92.0 prismarine-registry: 1.11.0 - prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0): + prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9: dependencies: minecraft-data: 3.92.0 prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) @@ -18143,8 +18175,6 @@ snapshots: prismarine-item: 1.17.0 prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 - transitivePeerDependencies: - - prismarine-registry prismarine-chat@1.11.0: dependencies: @@ -18155,7 +18185,7 @@ snapshots: prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0): dependencies: prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 smart-buffer: 4.2.0 @@ -18189,7 +18219,7 @@ snapshots: prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0): dependencies: - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) prismarine-nbt: 2.7.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c @@ -18213,18 +18243,16 @@ snapshots: prismarine-registry@1.11.0: dependencies: minecraft-data: 3.92.0 - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-nbt: 2.7.0 - prismarine-schematic@1.2.3(prismarine-registry@1.11.0): + prismarine-schematic@1.2.3: dependencies: minecraft-data: 3.92.0 - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-nbt: 2.7.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c vec3: 0.1.10 - transitivePeerDependencies: - - prismarine-registry prismarine-windows@2.9.0: dependencies: @@ -19521,6 +19549,8 @@ snapshots: stack-generator: 2.0.10 stacktrace-gps: 3.1.2 + state-local@1.0.7: {} + static-extend@0.1.2: dependencies: define-property: 0.2.5 diff --git a/src/core/ideChannels.ts b/src/core/ideChannels.ts new file mode 100644 index 00000000..a9c517f7 --- /dev/null +++ b/src/core/ideChannels.ts @@ -0,0 +1,106 @@ +import { proxy } from 'valtio' + +export const ideState = proxy({ + id: '', + contents: '', + line: 0, + column: 0, + language: 'typescript', + title: '', +}) +globalThis.ideState = ideState + +export const registerIdeChannels = () => { + registerIdeOpenChannel() + registerIdeSaveChannel() +} + +const registerIdeOpenChannel = () => { + const CHANNEL_NAME = 'minecraft-web-client:ide-open' + + const packetStructure = [ + 'container', + [ + { + name: 'id', + type: ['pstring', { countType: 'i16' }] + }, + { + name: 'language', + type: ['pstring', { countType: 'i16' }] + }, + { + name: 'contents', + type: ['pstring', { countType: 'i16' }] + }, + { + name: 'line', + type: 'i32' + }, + { + name: 'column', + type: 'i32' + }, + { + name: 'title', + type: ['pstring', { countType: 'i16' }] + } + ] + ] + + bot._client.registerChannel(CHANNEL_NAME, packetStructure, true) + + bot._client.on(CHANNEL_NAME as any, (data) => { + const { id, language, contents, line, column, title } = data + + ideState.contents = contents + ideState.line = line + ideState.column = column + ideState.id = id + ideState.language = language || 'typescript' + ideState.title = title + }) + + console.debug(`registered custom channel ${CHANNEL_NAME} channel`) +} +const IDE_SAVE_CHANNEL_NAME = 'minecraft-web-client:ide-save' +const registerIdeSaveChannel = () => { + + const packetStructure = [ + 'container', + [ + { + name: 'id', + type: ['pstring', { countType: 'i16' }] + }, + { + name: 'contents', + type: ['pstring', { countType: 'i16' }] + }, + { + name: 'language', + type: ['pstring', { countType: 'i16' }] + }, + { + name: 'line', + type: 'i32' + }, + { + name: 'column', + type: 'i32' + }, + ] + ] + bot._client.registerChannel(IDE_SAVE_CHANNEL_NAME, packetStructure, true) +} + +export const saveIde = () => { + bot._client.writeChannel(IDE_SAVE_CHANNEL_NAME, { + id: ideState.id, + contents: ideState.contents, + language: ideState.language, + // todo: reflect updated + line: ideState.line, + column: ideState.column, + }) +} diff --git a/src/customChannels.ts b/src/customChannels.ts index 6d3aa7e9..717c7c93 100644 --- a/src/customChannels.ts +++ b/src/customChannels.ts @@ -2,6 +2,7 @@ import PItem from 'prismarine-item' import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods' import { options } from './optionsStorage' import { jeiCustomCategories } from './inventoryWindows' +import { registerIdeChannels } from './core/ideChannels' export default () => { customEvents.on('mineflayerBotCreated', async () => { @@ -17,6 +18,7 @@ export default () => { registeredJeiChannel() registerBlockInteractionsCustomizationChannel() registerWaypointChannels() + registerIdeChannels() }) } diff --git a/src/react/MonacoEditor.css b/src/react/MonacoEditor.css new file mode 100644 index 00000000..86d2ad0a --- /dev/null +++ b/src/react/MonacoEditor.css @@ -0,0 +1,58 @@ +.monaco-editor-container { + position: fixed; + inset: 0; + z-index: 1000; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 16px; + background-color: rgba(0, 0, 0, 0.5); +} + +.monaco-editor-title { + font-size: 20px; + font-weight: bold; + color: #fff; + margin-bottom: 8px; +} + +.monaco-editor-wrapper { + position: relative; + width: 100%; + height: 100%; + max-width: 80vw; + max-height: 80vh; + border: 3px solid #000; + background-color: #000; + padding: 3px; + box-shadow: inset 0 0 0 1px #fff, inset 0 0 0 2px #000; +} + +.monaco-editor-close { + position: fixed; + top: 16px; + left: 16px; + z-index: 1001; + cursor: pointer; + padding: 8px; +} + +@media (max-width: 768px) { + .monaco-editor-container { + padding: 0; + } + .monaco-editor-wrapper { + max-width: 100%; + max-height: 100%; + border-radius: 0; + } + .monaco-editor-close { + top: 8px; + left: 8px; + } + .monaco-editor-title { + /* todo: make it work on mobile */ + display: none; + } +} diff --git a/src/react/MonacoEditor.tsx b/src/react/MonacoEditor.tsx new file mode 100644 index 00000000..32162b21 --- /dev/null +++ b/src/react/MonacoEditor.tsx @@ -0,0 +1,73 @@ +import { proxy, useSnapshot } from 'valtio' +import { useEffect } from 'react' +import { Editor } from '@monaco-editor/react' +import PixelartIcon, { pixelartIcons } from '../react/PixelartIcon' +import { useIsModalActive } from '../react/utilsApp' +import { showNotification } from '../react/NotificationProvider' +import { hideModal, showModal } from '../globalState' +import { ideState, saveIde } from '../core/ideChannels' +import './MonacoEditor.css' + +export default () => { + const { contents, line, column, id, language, title } = useSnapshot(ideState) + const isModalActive = useIsModalActive('monaco-editor') + const bodyFont = getComputedStyle(document.body).fontFamily + + useEffect(() => { + if (id && !isModalActive) { + showModal({ reactType: 'monaco-editor' }) + } + if (!id && isModalActive) { + hideModal() + } + }, [id]) + + useEffect(() => { + if (!isModalActive && id) { + try { + saveIde() + } catch (err) { + reportError(err) + showNotification('Failed to save the editor', 'Please try again', true) + } + ideState.id = '' + ideState.contents = '' + } + }, [isModalActive]) + + if (!isModalActive) return null + + return
    +
    + { + hideModal() + }} + /> +
    +
    + {title} +
    +
    + { + ideState.contents = value ?? '' + }} + value={contents} + options={{ + fontFamily: bodyFont, + minimap: { + enabled: true, + }, + }} + /> +
    +
    +} diff --git a/src/reactUi.tsx b/src/reactUi.tsx index 4f8c4541..1e6f13eb 100644 --- a/src/reactUi.tsx +++ b/src/reactUi.tsx @@ -67,6 +67,7 @@ import GlobalOverlayHints from './react/GlobalOverlayHints' import FullscreenTime from './react/FullscreenTime' import StorageConflictModal from './react/StorageConflictModal' import FireRenderer from './react/FireRenderer' +import MonacoEditor from './react/MonacoEditor' const isFirefox = ua.getBrowser().name === 'Firefox' if (isFirefox) { @@ -248,7 +249,6 @@ const App = () => { - @@ -259,6 +259,7 @@ const App = () => {
    + From bc2972fe99692510643cb482128ab978970a9c2f Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 24 Aug 2025 19:53:44 +0300 Subject: [PATCH 64/98] fix registering custom channels too late (a few ms diff) --- src/customChannels.ts | 19 ++++++++----------- src/resourcePack.ts | 11 ----------- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/src/customChannels.ts b/src/customChannels.ts index 717c7c93..8e70078f 100644 --- a/src/customChannels.ts +++ b/src/customChannels.ts @@ -7,18 +7,15 @@ import { registerIdeChannels } from './core/ideChannels' export default () => { customEvents.on('mineflayerBotCreated', async () => { if (!options.customChannels) return - await new Promise(resolve => { - bot.once('login', () => { - resolve(true) - }) + bot.once('login', () => { + registerBlockModelsChannel() + registerMediaChannels() + registerSectionAnimationChannels() + registeredJeiChannel() + registerBlockInteractionsCustomizationChannel() + registerWaypointChannels() + registerIdeChannels() }) - registerBlockModelsChannel() - registerMediaChannels() - registerSectionAnimationChannels() - registeredJeiChannel() - registerBlockInteractionsCustomizationChannel() - registerWaypointChannels() - registerIdeChannels() }) } diff --git a/src/resourcePack.ts b/src/resourcePack.ts index e0f0fca4..ea6c73fd 100644 --- a/src/resourcePack.ts +++ b/src/resourcePack.ts @@ -486,17 +486,6 @@ const downloadAndUseResourcePack = async (url: string, progressReporter: Progres } } -const waitForGameEvent = async () => { - if (miscUiState.gameLoaded) return - await new Promise(resolve => { - const listener = () => resolve() - customEvents.once('gameLoaded', listener) - watchUnloadForCleanup(() => { - customEvents.removeListener('gameLoaded', listener) - }) - }) -} - export const onAppLoad = () => { customEvents.on('mineflayerBotCreated', () => { // todo also handle resourcePack From 8f62fbd4daddbdccf61191949889925bed2b4632 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 26 Aug 2025 13:50:36 +0300 Subject: [PATCH 65/98] fix: window title sometimes was not showing up on old versions --- src/chatUtils.ts | 8 ++++++++ src/inventoryWindows.ts | 15 +++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/chatUtils.ts b/src/chatUtils.ts index 88437bc3..849d5847 100644 --- a/src/chatUtils.ts +++ b/src/chatUtils.ts @@ -118,6 +118,14 @@ export const formatMessage = (message: MessageInput, mcData: IndexedData = globa return msglist } +export const messageToString = (message: MessageInput | string) => { + if (typeof message === 'string') { + return message + } + const msglist = formatMessage(message) + return msglist.map(msg => msg.text).join('') +} + const blockToItemRemaps = { water: 'water_bucket', lava: 'lava_bucket', diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index a9f89d1b..166e42a7 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -57,12 +57,23 @@ export const onGameLoad = () => { return type } + const maybeParseNbtJson = (data: any) => { + if (typeof data === 'string') { + try { + data = JSON.parse(data) + } catch (err) { + // ignore + } + } + return nbt.simplify(data) ?? data + } + bot.on('windowOpen', (win) => { const implementedWindow = implementedContainersGuiMap[mapWindowType(win.type as string, win.inventoryStart)] if (implementedWindow) { - openWindow(implementedWindow, nbt.simplify(win.title as any)) + openWindow(implementedWindow, maybeParseNbtJson(win.title)) } else if (options.unimplementedContainers) { - openWindow('ChestWin', nbt.simplify(win.title as any)) + openWindow('ChestWin', maybeParseNbtJson(win.title)) } else { // todo format displayClientChat(`[client error] cannot open unimplemented window ${win.id} (${win.type}). Slots: ${win.slots.map(item => getItemName(item)).filter(Boolean).join(', ')}`) From 9718610131a44b1d0aa46fdf97d696d0af4dc663 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 27 Aug 2025 12:08:20 +0300 Subject: [PATCH 66/98] ci: add deployment step for mcw-mcraft-page repository in GitHub Actions --- .github/workflows/release.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cbf52251..68ead92f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,6 +49,18 @@ jobs: publish_dir: .vercel/output/static force_orphan: true + # Deploy to mcw-mcraft-page repository (github.mcraft.fun) + - name: Deploy to mcw-mcraft-page repository + uses: peaceiris/actions-gh-pages@v3 + with: + personal_token: ${{ secrets.MCW_MCRAFT_PAGE_DEPLOY_TOKEN }} + external_repository: ${{ github.repository_owner }}/mcw-mcraft-pages + publish_dir: .vercel/output/static + publish_branch: main + destination_dir: docs + cname: github.mcraft.fun + force_orphan: true + - name: Change index.html title run: | # change Minecraft Web Client to Minecraft Web Client — Free Online Browser Version From 2a1746eb7a6bd96f40de9cffbb13d78e422388f2 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 27 Aug 2025 13:33:39 +0300 Subject: [PATCH 67/98] [skip ci] fix repository name --- .github/workflows/release.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 68ead92f..3710eebd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,12 +49,11 @@ jobs: publish_dir: .vercel/output/static force_orphan: true - # Deploy to mcw-mcraft-page repository (github.mcraft.fun) - - name: Deploy to mcw-mcraft-page repository + - name: Deploy to mwc-mcraft-pages repository uses: peaceiris/actions-gh-pages@v3 with: personal_token: ${{ secrets.MCW_MCRAFT_PAGE_DEPLOY_TOKEN }} - external_repository: ${{ github.repository_owner }}/mcw-mcraft-pages + external_repository: ${{ github.repository_owner }}/mwc-mcraft-pages publish_dir: .vercel/output/static publish_branch: main destination_dir: docs From 1f240d8c2047de469965076dc23ae72fe534a83a Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 27 Aug 2025 19:50:53 +0300 Subject: [PATCH 68/98] up mouse allowing to disable positive break block --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- src/customChannels.ts | 14 +------------- 3 files changed, 7 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index a8c2c4e7..5dcd9547 100644 --- a/package.json +++ b/package.json @@ -157,7 +157,7 @@ "mc-assets": "^0.2.62", "minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next", "mineflayer": "github:zardoy/mineflayer#gen-the-master", - "mineflayer-mouse": "^0.1.17", + "mineflayer-mouse": "^0.1.21", "npm-run-all": "^4.1.5", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8acb9681..6c393ac7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -347,8 +347,8 @@ importers: specifier: github:zardoy/mineflayer#gen-the-master version: https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13) mineflayer-mouse: - specifier: ^0.1.17 - version: 0.1.17 + specifier: ^0.1.21 + version: 0.1.21 npm-run-all: specifier: ^4.1.5 version: 4.1.5 @@ -6678,8 +6678,8 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824} version: 1.2.0 - mineflayer-mouse@0.1.17: - resolution: {integrity: sha512-0eCR8pnGb42Qd9QmAxOjl0PhA5Fa+9+6H1G/YsbsO5rg5mDf94Tusqp/8NAGLPQCPVDzbarLskXdjR3h0E0bEQ==} + mineflayer-mouse@0.1.21: + resolution: {integrity: sha512-1XTVuw3twIrEcqQ1QRSB8NcStIUEZ+tbxiAG6rOrN/9M4thhtlS5PTJzFdmdrcYgWEBLvuOdJszaKE5zFfiXhg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8: @@ -17359,7 +17359,7 @@ snapshots: - encoding - supports-color - mineflayer-mouse@0.1.17: + mineflayer-mouse@0.1.21: dependencies: change-case: 5.4.4 debug: 4.4.1 diff --git a/src/customChannels.ts b/src/customChannels.ts index 8e70078f..b566f9dd 100644 --- a/src/customChannels.ts +++ b/src/customChannels.ts @@ -47,19 +47,7 @@ const registerBlockInteractionsCustomizationChannel = () => { registerChannel(CHANNEL_NAME, packetStructure, (data) => { const config = JSON.parse(data.newConfiguration) - if (config.customBreakTime !== undefined && Object.values(config.customBreakTime).every(x => typeof x === 'number')) { - bot.mouse.customBreakTime = config.customBreakTime - } - if (config.customBreakTimeToolAllowance !== undefined) { - bot.mouse.customBreakTimeToolAllowance = new Set(config.customBreakTimeToolAllowance) - } - - if (config.blockPlacePrediction !== undefined) { - bot.mouse.settings.blockPlacePrediction = config.blockPlacePrediction - } - if (config.blockPlacePredictionDelay !== undefined) { - bot.mouse.settings.blockPlacePredictionDelay = config.blockPlacePredictionDelay - } + bot.mouse.setConfigFromPacket(config) }, true) } From e81d608554d8d0e98982df7f3570b70793a0927c Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 27 Aug 2025 19:52:09 +0300 Subject: [PATCH 69/98] fix cname --- .github/workflows/release.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3710eebd..3e8c4136 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,6 +49,10 @@ jobs: publish_dir: .vercel/output/static force_orphan: true + # Create CNAME file for custom domain + - name: Create CNAME file + run: echo "github.mcraft.fun" > .vercel/output/static/CNAME + - name: Deploy to mwc-mcraft-pages repository uses: peaceiris/actions-gh-pages@v3 with: @@ -57,7 +61,6 @@ jobs: publish_dir: .vercel/output/static publish_branch: main destination_dir: docs - cname: github.mcraft.fun force_orphan: true - name: Change index.html title From d0d5234ba43c33d11325696a763163196c3206ad Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 31 Aug 2025 18:31:49 +0300 Subject: [PATCH 70/98] fix: stop right click emulation once window got opened eg chest --- src/mineflayer/plugins/mouse.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mineflayer/plugins/mouse.ts b/src/mineflayer/plugins/mouse.ts index fc1ce0fd..14e19345 100644 --- a/src/mineflayer/plugins/mouse.ts +++ b/src/mineflayer/plugins/mouse.ts @@ -110,7 +110,7 @@ const domListeners = (bot: Bot) => { }, { signal: abortController.signal }) bot.mouse.beforeUpdateChecks = () => { - if (!document.hasFocus()) { + if (!document.hasFocus() || !isGameActive(true)) { // deactive all buttons bot.mouse.buttons.fill(false) } From cb82188272ef5bec257efdf0150460136cf267bd Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 31 Aug 2025 19:31:26 +0300 Subject: [PATCH 71/98] add addPing query param for testing --- README.MD | 1 + src/appParams.ts | 1 + src/index.ts | 2 +- src/mineflayer/mc-protocol.ts | 3 ++- 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.MD b/README.MD index 7978cee5..82aac011 100644 --- a/README.MD +++ b/README.MD @@ -176,6 +176,7 @@ Server specific: - `?lockConnect=true` - Only works then `ip` parameter is set. Disables cancel/save buttons and all inputs in the connect screen already set as parameters. Useful for integrates iframes. - `?autoConnect=true` - Only works then `ip` and `version` parameters are set and `allowAutoConnect` is `true` in config.json! Directly connects to the specified server. Useful for integrates iframes. - `?serversList=` - `` can be a list of servers in the format `ip:version,ip` or a url to a json file with the same format (array) or a txt file with line-delimited list of server IPs. +- `?addPing=` - Add a latency to both sides of the connection. Useful for testing ping issues. For example `?addPing=100` will add 200ms to your ping. Single player specific: diff --git a/src/appParams.ts b/src/appParams.ts index 8d487f8d..4c8ca186 100644 --- a/src/appParams.ts +++ b/src/appParams.ts @@ -47,6 +47,7 @@ export type AppQsParams = { connectText?: string freezeSettings?: string testIosCrash?: string + addPing?: string // Replay params replayFilter?: string diff --git a/src/index.ts b/src/index.ts index 4a118cee..54731a16 100644 --- a/src/index.ts +++ b/src/index.ts @@ -305,7 +305,7 @@ export async function connect (connectOptions: ConnectOptions) { if (connectOptions.server && !connectOptions.viewerWsConnect && !parsedServer.isWebSocket) { console.log(`using proxy ${proxy.host}:${proxy.port || location.port}`) - net['setProxy']({ hostname: proxy.host, port: proxy.port, headers: { Authorization: `Bearer ${new URLSearchParams(location.search).get('token') ?? ''}` } }) + net['setProxy']({ hostname: proxy.host, port: proxy.port, headers: { Authorization: `Bearer ${new URLSearchParams(location.search).get('token') ?? ''}` }, artificialDelay: appQueryParams.addPing ? Number(appQueryParams.addPing) : undefined }) } const renderDistance = singleplayer ? renderDistanceSingleplayer : multiplayerRenderDistance diff --git a/src/mineflayer/mc-protocol.ts b/src/mineflayer/mc-protocol.ts index 0171387a..cd21d01f 100644 --- a/src/mineflayer/mc-protocol.ts +++ b/src/mineflayer/mc-protocol.ts @@ -130,7 +130,8 @@ export const setProxy = (proxyParams: ProxyParams) => { net['setProxy']({ hostname: proxy.host, port: proxy.port, - headers: proxyParams.headers + headers: proxyParams.headers, + artificialDelay: appQueryParams.addPing ? Number(appQueryParams.addPing) : undefined }) return { proxy From 513201be87401c53231103da9264693a3ccadb92 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Mon, 1 Sep 2025 08:56:08 +0300 Subject: [PATCH 72/98] up browserify --- pnpm-lock.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6c393ac7..6a4773d2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -155,7 +155,7 @@ importers: version: 2.0.4 net-browserify: specifier: github:zardoy/prismarinejs-net-browserify - version: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/17fb901e8ea480a52c8fd46373695be172be8aa5 + version: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/e754999ffdea67853bc9b10553b5e9908b40f618 node-gzip: specifier: ^1.1.2 version: 1.1.2 @@ -6849,8 +6849,8 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/17fb901e8ea480a52c8fd46373695be172be8aa5: - resolution: {tarball: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/17fb901e8ea480a52c8fd46373695be172be8aa5} + net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/e754999ffdea67853bc9b10553b5e9908b40f618: + resolution: {tarball: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/e754999ffdea67853bc9b10553b5e9908b40f618} version: 0.2.4 nice-try@1.0.5: @@ -17574,7 +17574,7 @@ snapshots: neo-async@2.6.2: {} - net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/17fb901e8ea480a52c8fd46373695be172be8aa5: + net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/e754999ffdea67853bc9b10553b5e9908b40f618: dependencies: body-parser: 1.20.3 express: 4.21.2 From 7e3ba8bece2e4c85bcc8ef7f2caa22627f88d50d Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 2 Sep 2025 19:02:30 +0300 Subject: [PATCH 73/98] up integrated server for the latest fixes and better stability --- package.json | 2 +- pnpm-lock.yaml | 28 ++++++++++++++++++++-------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 5dcd9547..b5f66bfb 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "esbuild-plugin-polyfill-node": "^0.3.0", "express": "^4.18.2", "filesize": "^10.0.12", - "flying-squid": "npm:@zardoy/flying-squid@^0.0.62", + "flying-squid": "npm:@zardoy/flying-squid@^0.0.104", "framer-motion": "^12.9.2", "fs-extra": "^11.1.1", "google-drive-browserfs": "github:zardoy/browserfs#google-drive", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6a4773d2..f6188b6d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -121,8 +121,8 @@ importers: specifier: ^10.0.12 version: 10.1.6 flying-squid: - specifier: npm:@zardoy/flying-squid@^0.0.62 - version: '@zardoy/flying-squid@0.0.62(encoding@0.1.13)' + specifier: npm:@zardoy/flying-squid@^0.0.104 + version: '@zardoy/flying-squid@0.0.104(encoding@0.1.13)' framer-motion: specifier: ^12.9.2 version: 12.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -3387,13 +3387,13 @@ 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.49': - resolution: {integrity: sha512-Kt4wr5/R+44tcLU9gjuNG2an9weWeKEpIoKXfsgJN2GGQqdnbd5nBpxfGDdgZ9aMdFugsVW8BsyPZNhj9vbMXA==} + '@zardoy/flying-squid@0.0.104': + resolution: {integrity: sha512-jGhQ7fn7o8UN+mUwZbt9674D37YLuBi+Au4TwKcopCA6huIQdHTFNl2e+0ZSTI5mnhN+NpyVoR3vmtH6L58vHQ==} engines: {node: '>=8'} hasBin: true - '@zardoy/flying-squid@0.0.62': - resolution: {integrity: sha512-M6icydO/yrmwevBhmgKcqEPC63AhWfU/Es9N/uadVrmKaxGm2FQMMLcybbutRYm1xZ6qsdxDUOUZnN56PsVwfQ==} + '@zardoy/flying-squid@0.0.49': + resolution: {integrity: sha512-Kt4wr5/R+44tcLU9gjuNG2an9weWeKEpIoKXfsgJN2GGQqdnbd5nBpxfGDdgZ9aMdFugsVW8BsyPZNhj9vbMXA==} engines: {node: '>=8'} hasBin: true @@ -6444,6 +6444,12 @@ packages: resolution: {integrity: sha512-RYZeD1+joNlPuUpi+tIWkbP0ieVJr+R6IFkI6/8juhSxx9zE4osoSmteybrfspGm8A6u+YbbY1epqRKEMwVR6Q==} engines: {node: '>=18.0.0'} + mc-bridge@0.1.3: + resolution: {integrity: sha512-H9jPt2xEU77itC27dSz3qazHYqN9qVsv4HgMPozg7RqQ1uwgXmEa+ojKIlDtXf/TLJsG6Kv4EbzGa8a1Wh72uA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + minecraft-data: 3.92.0 + mcraft-fun-mineflayer@0.1.23: resolution: {integrity: sha512-qmI1rQQ0Ro5zJdi99rClWLF+mS9JZffgNX2vyWWesS3Hsk3Xblp/8swYTJKHSaFpNgzkVfXV92fEIrBqeH6iKA==} version: 0.1.23 @@ -13088,7 +13094,7 @@ snapshots: '@types/emscripten': 1.40.0 tslib: 1.14.1 - '@zardoy/flying-squid@0.0.49(encoding@0.1.13)': + '@zardoy/flying-squid@0.0.104(encoding@0.1.13)': dependencies: '@tootallnate/once': 2.0.0 chalk: 5.4.1 @@ -13098,11 +13104,13 @@ snapshots: exit-hook: 2.2.1 flatmap: 0.0.3 long: 5.3.1 + mc-bridge: 0.1.3(minecraft-data@3.92.0) minecraft-data: 3.92.0 minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) prismarine-entity: 2.5.0 prismarine-item: 1.17.0 @@ -13124,7 +13132,7 @@ snapshots: - encoding - supports-color - '@zardoy/flying-squid@0.0.62(encoding@0.1.13)': + '@zardoy/flying-squid@0.0.49(encoding@0.1.13)': dependencies: '@tootallnate/once': 2.0.0 chalk: 5.4.1 @@ -16978,6 +16986,10 @@ snapshots: maxrects-packer: '@zardoy/maxrects-packer@2.7.4' zod: 3.24.2 + mc-bridge@0.1.3(minecraft-data@3.92.0): + dependencies: + minecraft-data: 3.92.0 + mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13)): dependencies: '@zardoy/flying-squid': 0.0.49(encoding@0.1.13) From 9d54c70fb724884cd98782cdca093aef622095ba Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 2 Sep 2025 19:05:18 +0300 Subject: [PATCH 74/98] use node 22 --- .github/workflows/benchmark.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index f913b9b6..e80b7100 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -26,7 +26,7 @@ jobs: uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 22 cache: "pnpm" - name: Move Cypress to dependencies run: | From 70534d8b5a64ed9df64229b8933c6adc2ac61ea4 Mon Sep 17 00:00:00 2001 From: Kesuaheli Date: Thu, 4 Sep 2025 12:51:56 +0200 Subject: [PATCH 75/98] fix: adding support for newer skin profile data structure in player heads --- renderer/viewer/three/worldrendererThree.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/renderer/viewer/three/worldrendererThree.ts b/renderer/viewer/three/worldrendererThree.ts index fb6c8e11..39b8e1de 100644 --- a/renderer/viewer/three/worldrendererThree.ts +++ b/renderer/viewer/three/worldrendererThree.ts @@ -767,12 +767,17 @@ export class WorldRendererThree extends WorldRendererCommon { } renderHead (position: Vec3, rotation: number, isWall: boolean, blockEntity) { - const textures = blockEntity.SkullOwner?.Properties?.textures[0] - if (!textures) return + let textureData: string + if (blockEntity.SkullOwner) { + textureData = blockEntity.SkullOwner.Properties?.textures[0]?.Value + } else { + textureData = blockEntity.profile?.properties?.find(p => p.name === 'textures')?.value + } + if (!textureData) return try { - const textureData = JSON.parse(Buffer.from(textures.Value, 'base64').toString()) - let skinUrl = textureData.textures?.SKIN?.url + const decodedData = JSON.parse(Buffer.from(textureData, 'base64').toString()) + let skinUrl = decodedData.textures?.SKIN?.url const { skinTexturesProxy } = this.worldRendererConfig if (skinTexturesProxy) { skinUrl = skinUrl?.replace('http://textures.minecraft.net/', skinTexturesProxy) From 528d8f516b1d8f86552fbf64d8130b138e1aba23 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Thu, 4 Sep 2025 21:55:02 +0300 Subject: [PATCH 76/98] Update worldrendererThree.ts --- renderer/viewer/three/worldrendererThree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/renderer/viewer/three/worldrendererThree.ts b/renderer/viewer/three/worldrendererThree.ts index 39b8e1de..440061ad 100644 --- a/renderer/viewer/three/worldrendererThree.ts +++ b/renderer/viewer/three/worldrendererThree.ts @@ -769,7 +769,7 @@ export class WorldRendererThree extends WorldRendererCommon { renderHead (position: Vec3, rotation: number, isWall: boolean, blockEntity) { let textureData: string if (blockEntity.SkullOwner) { - textureData = blockEntity.SkullOwner.Properties?.textures[0]?.Value + textureData = blockEntity.SkullOwner.Properties?.textures?.[0]?.Value } else { textureData = blockEntity.profile?.properties?.find(p => p.name === 'textures')?.value } From b2e36840b9d3531ed352e08e09fc54734026cc51 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Fri, 5 Sep 2025 05:02:54 +0300 Subject: [PATCH 77/98] feat: brand new default skybox with fog, better daycycle and colors (#425) Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- README.MD | 1 + renderer/viewer/lib/worldDataEmitter.ts | 32 +++ renderer/viewer/lib/worldrendererCommon.ts | 66 +++-- renderer/viewer/three/skyboxRenderer.ts | 281 +++++++++++++++++++- renderer/viewer/three/worldrendererThree.ts | 23 +- src/dayCycle.ts | 46 ---- src/index.ts | 2 - src/watchOptions.ts | 1 + 8 files changed, 375 insertions(+), 77 deletions(-) delete mode 100644 src/dayCycle.ts diff --git a/README.MD b/README.MD index 82aac011..74d4eb41 100644 --- a/README.MD +++ b/README.MD @@ -233,3 +233,4 @@ Only during development: - [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) +- [js-minecraft](https://github.com/LabyStudio/js-minecraft) - An insanely well done clone from the graphical side that inspired many features here diff --git a/renderer/viewer/lib/worldDataEmitter.ts b/renderer/viewer/lib/worldDataEmitter.ts index 86a85f77..dfbdb35c 100644 --- a/renderer/viewer/lib/worldDataEmitter.ts +++ b/renderer/viewer/lib/worldDataEmitter.ts @@ -7,6 +7,7 @@ import { Vec3 } from 'vec3' import { BotEvents } from 'mineflayer' import { proxy } from 'valtio' import TypedEmitter from 'typed-emitter' +import { Biome } from 'minecraft-data' import { delayedIterator } from '../../playground/shared' import { chunkPos } from './simpleUtils' @@ -28,6 +29,8 @@ export type WorldDataEmitterEvents = { updateLight: (data: { pos: Vec3 }) => void onWorldSwitch: () => void end: () => void + biomeUpdate: (data: { biome: Biome }) => void + biomeReset: () => void } export class WorldDataEmitterWorker extends (EventEmitter as new () => TypedEmitter) { @@ -360,8 +363,37 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter { export const worldCleanup = buildCleanupDecorator('resetWorld') export const defaultWorldRendererConfig = { + // Debug settings showChunkBorders: false, + enableDebugOverlay: false, + + // Performance settings mesherWorkers: 4, - isPlayground: false, - renderEars: true, - skinTexturesProxy: undefined as string | undefined, - // game renderer setting actually - showHand: false, - viewBobbing: false, - extraBlockRenderers: true, - clipWorldBelowY: undefined as number | undefined, + addChunksBatchWaitTime: 200, + _experimentalSmoothChunkLoading: true, + _renderByChunks: false, + + // Rendering engine settings + dayCycle: true, smoothLighting: true, enableLighting: true, starfield: true, - addChunksBatchWaitTime: 200, + renderEntities: true, + extraBlockRenderers: true, + foreground: true, + fov: 75, + volume: 1, + + // Camera visual related settings + showHand: false, + viewBobbing: false, + renderEars: true, + highlightBlockColor: 'blue', + + // Player models + fetchPlayerSkins: true, + skinTexturesProxy: undefined as string | undefined, + + // VR settings vrSupport: true, vrPageGameRendering: true, - renderEntities: true, - fov: 75, - fetchPlayerSkins: true, - highlightBlockColor: 'blue', - foreground: true, - enableDebugOverlay: false, - _experimentalSmoothChunkLoading: true, - _renderByChunks: false, - volume: 1 + + // World settings + clipWorldBelowY: undefined as number | undefined, + isPlayground: false } export type WorldRendererConfig = typeof defaultWorldRendererConfig @@ -496,6 +509,10 @@ export abstract class WorldRendererCommon timeUpdated? (newTime: number): void + biomeUpdated? (biome: any): void + + biomeReset? (): void + updateViewerPosition (pos: Vec3) { this.viewerChunkPosition = pos for (const [key, value] of Object.entries(this.loadedChunks)) { @@ -817,12 +834,9 @@ export abstract class WorldRendererCommon }) worldEmitter.on('time', (timeOfDay) => { + if (!this.worldRendererConfig.dayCycle) return this.timeUpdated?.(timeOfDay) - if (timeOfDay < 0 || timeOfDay > 24_000) { - throw new Error('Invalid time of day. It should be between 0 and 24000.') - } - this.timeOfTheDay = timeOfDay // if (this.worldRendererConfig.skyLight === skyLight) return @@ -831,6 +845,14 @@ export abstract class WorldRendererCommon // (this).rerenderAllChunks?.() // } }) + + worldEmitter.on('biomeUpdate', ({ biome }) => { + this.biomeUpdated?.(biome) + }) + + worldEmitter.on('biomeReset', () => { + this.biomeReset?.() + }) } setBlockStateIdInner (pos: Vec3, stateId: number | undefined, needAoRecalculation = true) { diff --git a/renderer/viewer/three/skyboxRenderer.ts b/renderer/viewer/three/skyboxRenderer.ts index 294c72aa..aa8c3bb6 100644 --- a/renderer/viewer/three/skyboxRenderer.ts +++ b/renderer/viewer/three/skyboxRenderer.ts @@ -1,10 +1,28 @@ import * as THREE from 'three' +export const DEFAULT_TEMPERATURE = 0.75 + export class SkyboxRenderer { private texture: THREE.Texture | null = null private mesh: THREE.Mesh | null = null + private skyMesh: THREE.Mesh | null = null + private voidMesh: THREE.Mesh | null = null - constructor (private readonly scene: THREE.Scene, public initialImage: string | null) {} + // World state + private worldTime = 0 + private partialTicks = 0 + private viewDistance = 4 + private temperature = DEFAULT_TEMPERATURE + private inWater = false + private waterBreathing = false + private fogBrightness = 0 + private prevFogBrightness = 0 + + constructor (private readonly scene: THREE.Scene, public initialImage: string | null) { + if (!initialImage) { + this.createGradientSky() + } + } async init () { if (this.initialImage) { @@ -58,10 +76,255 @@ export class SkyboxRenderer { } } - update (cameraPosition: THREE.Vector3) { - if (this.mesh) { - this.mesh.position.copy(cameraPosition) + update (cameraPosition: THREE.Vector3, newViewDistance: number) { + if (newViewDistance !== this.viewDistance) { + this.viewDistance = newViewDistance + this.updateSkyColors() } + + if (this.mesh) { + // Update skybox position + this.mesh.position.copy(cameraPosition) + } else if (this.skyMesh) { + // Update gradient sky position + this.skyMesh.position.copy(cameraPosition) + this.voidMesh?.position.copy(cameraPosition) + this.updateSkyColors() // Update colors based on time of day + } + } + + // Update world time + updateTime (timeOfDay: number, partialTicks = 0) { + this.worldTime = timeOfDay + this.partialTicks = partialTicks + this.updateSkyColors() + } + + // Update view distance + updateViewDistance (viewDistance: number) { + this.viewDistance = viewDistance + this.updateSkyColors() + } + + // Update temperature (for biome support) + updateTemperature (temperature: number) { + this.temperature = temperature + this.updateSkyColors() + } + + // Update water state + updateWaterState (inWater: boolean, waterBreathing: boolean) { + this.inWater = inWater + this.waterBreathing = waterBreathing + this.updateSkyColors() + } + + private createGradientSky () { + const size = 64 + const scale = 256 / size + 2 + + { + const geometry = new THREE.PlaneGeometry(size * scale * 2, size * scale * 2) + geometry.rotateX(-Math.PI / 2) + geometry.translate(0, 16, 0) + + const material = new THREE.MeshBasicMaterial({ + color: 0xff_ff_ff, + side: THREE.DoubleSide, + depthTest: false + }) + + this.skyMesh = new THREE.Mesh(geometry, material) + this.scene.add(this.skyMesh) + } + + { + const geometry = new THREE.PlaneGeometry(size * scale * 2, size * scale * 2) + geometry.rotateX(-Math.PI / 2) + geometry.translate(0, -16, 0) + + const material = new THREE.MeshBasicMaterial({ + color: 0xff_ff_ff, + side: THREE.DoubleSide, + depthTest: false + }) + + this.voidMesh = new THREE.Mesh(geometry, material) + this.scene.add(this.voidMesh) + } + + this.updateSkyColors() + } + + private getFogColor (partialTicks = 0): THREE.Vector3 { + const angle = this.getCelestialAngle(partialTicks) + let rotation = Math.cos(angle * Math.PI * 2) * 2 + 0.5 + rotation = Math.max(0, Math.min(1, rotation)) + + let x = 0.752_941_2 + let y = 0.847_058_83 + let z = 1 + + x *= (rotation * 0.94 + 0.06) + y *= (rotation * 0.94 + 0.06) + z *= (rotation * 0.91 + 0.09) + + return new THREE.Vector3(x, y, z) + } + + private getSkyColor (x = 0, z = 0, partialTicks = 0): THREE.Vector3 { + const angle = this.getCelestialAngle(partialTicks) + let brightness = Math.cos(angle * 3.141_593 * 2) * 2 + 0.5 + + if (brightness < 0) brightness = 0 + if (brightness > 1) brightness = 1 + + const temperature = this.getTemperature(x, z) + const rgb = this.getSkyColorByTemp(temperature) + + const red = ((rgb >> 16) & 0xff) / 255 + const green = ((rgb >> 8) & 0xff) / 255 + const blue = (rgb & 0xff) / 255 + + return new THREE.Vector3( + red * brightness, + green * brightness, + blue * brightness + ) + } + + private calculateCelestialAngle (time: number, partialTicks: number): number { + const modTime = (time % 24_000) + let angle = (modTime + partialTicks) / 24_000 - 0.25 + + if (angle < 0) { + angle++ + } + if (angle > 1) { + angle-- + } + + angle = 1 - ((Math.cos(angle * Math.PI) + 1) / 2) + angle += (angle - angle) / 3 + + return angle + } + + private getCelestialAngle (partialTicks: number): number { + return this.calculateCelestialAngle(this.worldTime, partialTicks) + } + + private getTemperature (x: number, z: number): number { + return this.temperature + } + + private getSkyColorByTemp (temperature: number): number { + temperature /= 3 + if (temperature < -1) temperature = -1 + if (temperature > 1) temperature = 1 + + const hue = 0.622_222_2 - temperature * 0.05 + const saturation = 0.5 + temperature * 0.1 + const brightness = 1 + + return this.hsbToRgb(hue, saturation, brightness) + } + + private hsbToRgb (hue: number, saturation: number, brightness: number): number { + let r = 0; let g = 0; let b = 0 + if (saturation === 0) { + r = g = b = Math.floor(brightness * 255 + 0.5) + } else { + const h = (hue - Math.floor(hue)) * 6 + const f = h - Math.floor(h) + const p = brightness * (1 - saturation) + const q = brightness * (1 - saturation * f) + const t = brightness * (1 - (saturation * (1 - f))) + switch (Math.floor(h)) { + case 0: + r = Math.floor(brightness * 255 + 0.5) + g = Math.floor(t * 255 + 0.5) + b = Math.floor(p * 255 + 0.5) + break + case 1: + r = Math.floor(q * 255 + 0.5) + g = Math.floor(brightness * 255 + 0.5) + b = Math.floor(p * 255 + 0.5) + break + case 2: + r = Math.floor(p * 255 + 0.5) + g = Math.floor(brightness * 255 + 0.5) + b = Math.floor(t * 255 + 0.5) + break + case 3: + r = Math.floor(p * 255 + 0.5) + g = Math.floor(q * 255 + 0.5) + b = Math.floor(brightness * 255 + 0.5) + break + case 4: + r = Math.floor(t * 255 + 0.5) + g = Math.floor(p * 255 + 0.5) + b = Math.floor(brightness * 255 + 0.5) + break + case 5: + r = Math.floor(brightness * 255 + 0.5) + g = Math.floor(p * 255 + 0.5) + b = Math.floor(q * 255 + 0.5) + break + } + } + return 0xff_00_00_00 | (r << 16) | (g << 8) | (Math.trunc(b)) + } + + private updateSkyColors () { + if (!this.skyMesh || !this.voidMesh) return + + // Update fog brightness with smooth transition + this.prevFogBrightness = this.fogBrightness + const renderDistance = this.viewDistance / 32 + const brightnessAtPosition = 1 // Could be affected by light level in future + const targetBrightness = brightnessAtPosition * (1 - renderDistance) + renderDistance + this.fogBrightness += (targetBrightness - this.fogBrightness) * 0.1 + + // Handle water fog + if (this.inWater) { + const waterViewDistance = this.waterBreathing ? 100 : 5 + this.scene.fog = new THREE.Fog(new THREE.Color(0, 0, 1), 0.0025, waterViewDistance) + this.scene.background = new THREE.Color(0, 0, 1) + + // Update sky and void colors for underwater effect + ;(this.skyMesh.material as THREE.MeshBasicMaterial).color.set(new THREE.Color(0, 0, 1)) + ;(this.voidMesh.material as THREE.MeshBasicMaterial).color.set(new THREE.Color(0, 0, 0.6)) + return + } + + // Normal sky colors + const viewDistance = this.viewDistance * 16 + const viewFactor = 1 - (0.25 + 0.75 * this.viewDistance / 32) ** 0.25 + + const angle = this.getCelestialAngle(this.partialTicks) + const skyColor = this.getSkyColor(0, 0, this.partialTicks) + const fogColor = this.getFogColor(this.partialTicks) + + const brightness = Math.cos(angle * Math.PI * 2) * 2 + 0.5 + const clampedBrightness = Math.max(0, Math.min(1, brightness)) + + // Interpolate fog brightness + const interpolatedBrightness = this.prevFogBrightness + (this.fogBrightness - this.prevFogBrightness) * this.partialTicks + + const red = (fogColor.x + (skyColor.x - fogColor.x) * viewFactor) * clampedBrightness * interpolatedBrightness + const green = (fogColor.y + (skyColor.y - fogColor.y) * viewFactor) * clampedBrightness * interpolatedBrightness + const blue = (fogColor.z + (skyColor.z - fogColor.z) * viewFactor) * clampedBrightness * interpolatedBrightness + + this.scene.background = new THREE.Color(red, green, blue) + this.scene.fog = new THREE.Fog(new THREE.Color(red, green, blue), 0.0025, viewDistance * 2) + + ;(this.skyMesh.material as THREE.MeshBasicMaterial).color.set(new THREE.Color(skyColor.x, skyColor.y, skyColor.z)) + ;(this.voidMesh.material as THREE.MeshBasicMaterial).color.set(new THREE.Color( + skyColor.x * 0.2 + 0.04, + skyColor.y * 0.2 + 0.04, + skyColor.z * 0.6 + 0.1 + )) } dispose () { @@ -73,5 +336,15 @@ export class SkyboxRenderer { ;(this.mesh.material as THREE.Material).dispose() this.scene.remove(this.mesh) } + if (this.skyMesh) { + this.skyMesh.geometry.dispose() + ;(this.skyMesh.material as THREE.Material).dispose() + this.scene.remove(this.skyMesh) + } + if (this.voidMesh) { + this.voidMesh.geometry.dispose() + ;(this.voidMesh.material as THREE.Material).dispose() + this.scene.remove(this.voidMesh) + } } } diff --git a/renderer/viewer/three/worldrendererThree.ts b/renderer/viewer/three/worldrendererThree.ts index 440061ad..29e9223c 100644 --- a/renderer/viewer/three/worldrendererThree.ts +++ b/renderer/viewer/three/worldrendererThree.ts @@ -3,6 +3,7 @@ import { Vec3 } from 'vec3' import nbt from 'prismarine-nbt' import PrismarineChatLoader from 'prismarine-chat' import * as tweenJs from '@tweenjs/tween.js' +import { Biome } from 'minecraft-data' import { renderSign } from '../sign-renderer' import { DisplayWorldOptions, GraphicsInitOptions } from '../../../src/appViewer' import { chunkPos, sectionPos } from '../lib/simpleUtils' @@ -24,7 +25,7 @@ import { CameraShake } from './cameraShake' import { ThreeJsMedia } from './threeJsMedia' import { Fountain } from './threeJsParticles' import { WaypointsRenderer } from './waypoints' -import { SkyboxRenderer } from './skyboxRenderer' +import { DEFAULT_TEMPERATURE, SkyboxRenderer } from './skyboxRenderer' type SectionKey = string @@ -173,7 +174,10 @@ export class WorldRendererThree extends WorldRendererCommon { override watchReactivePlayerState () { super.watchReactivePlayerState() this.onReactivePlayerStateUpdated('inWater', (value) => { - this.scene.fog = value ? new THREE.Fog(0x00_00_ff, 0.1, this.playerStateReactive.waterBreathing ? 100 : 20) : null + this.skyboxRenderer.updateWaterState(value, this.playerStateReactive.waterBreathing) + }) + this.onReactivePlayerStateUpdated('waterBreathing', (value) => { + this.skyboxRenderer.updateWaterState(this.playerStateReactive.inWater, value) }) this.onReactivePlayerStateUpdated('ambientLight', (value) => { if (!value) return @@ -264,6 +268,19 @@ export class WorldRendererThree extends WorldRendererCommon { } else { this.starField.remove() } + + this.skyboxRenderer.updateTime(newTime) + } + + biomeUpdated (biome: Biome): void { + if (biome?.temperature !== undefined) { + this.skyboxRenderer.updateTemperature(biome.temperature) + } + } + + biomeReset (): void { + // Reset to default temperature when biome is unknown + this.skyboxRenderer.updateTemperature(DEFAULT_TEMPERATURE) } getItemRenderData (item: Record, specificProps: ItemSpecificContextProperties) { @@ -716,7 +733,7 @@ export class WorldRendererThree extends WorldRendererCommon { // Update skybox position to follow camera const cameraPos = this.getCameraPosition() - this.skyboxRenderer.update(cameraPos) + this.skyboxRenderer.update(cameraPos, this.viewDistance) const sizeOrFovChanged = sizeChanged || this.displayOptions.inWorldRenderingConfig.fov !== this.camera.fov if (sizeOrFovChanged) { diff --git a/src/dayCycle.ts b/src/dayCycle.ts deleted file mode 100644 index 50e63a21..00000000 --- a/src/dayCycle.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { options } from './optionsStorage' -import { assertDefined } from './utils' -import { updateBackground } from './water' - -export default () => { - const timeUpdated = () => { - // 0 morning - const dayTotal = 24_000 - const evening = 11_500 - const night = 13_500 - const morningStart = 23_000 - const morningEnd = 23_961 - const timeProgress = options.dayCycleAndLighting ? bot.time.timeOfDay : 0 - - // todo check actual colors - const dayColorRainy = { r: 111 / 255, g: 156 / 255, b: 236 / 255 } - // todo yes, we should make animations (and rain) - // eslint-disable-next-line unicorn/numeric-separators-style - const dayColor = bot.isRaining ? dayColorRainy : { r: 0.6784313725490196, g: 0.8470588235294118, b: 0.9019607843137255 } // lightblue - // let newColor = dayColor - let int = 1 - if (timeProgress < evening) { - // stay dayily - } else if (timeProgress < night) { - const progressNorm = timeProgress - evening - const progressMax = night - evening - int = 1 - progressNorm / progressMax - } else if (timeProgress < morningStart) { - int = 0 - } else if (timeProgress < morningEnd) { - const progressNorm = timeProgress - morningStart - const progressMax = night - morningEnd - int = progressNorm / progressMax - } - // todo need to think wisely how to set these values & also move directional light around! - const colorInt = Math.max(int, 0.1) - updateBackground({ r: dayColor.r * colorInt, g: dayColor.g * colorInt, b: dayColor.b * colorInt }) - if (!options.newVersionsLighting && bot.supportFeature('blockStateId')) { - appViewer.playerState.reactive.ambientLight = Math.max(int, 0.25) - appViewer.playerState.reactive.directionalLight = Math.min(int, 0.5) - } - } - - bot.on('time', timeUpdated) - timeUpdated() -} diff --git a/src/index.ts b/src/index.ts index 54731a16..7764188f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -56,7 +56,6 @@ import { isCypress } from './standaloneUtils' import { startLocalServer, unsupportedLocalServerFeatures } from './createLocalServer' import defaultServerOptions from './defaultLocalServerOptions' -import dayCycle from './dayCycle' import { onAppLoad, resourcepackReload, resourcePackState } from './resourcePack' import { ConnectPeerOptions, connectToPeer } from './localServerMultiplayer' @@ -794,7 +793,6 @@ export async function connect (connectOptions: ConnectOptions) { } initMotionTracking() - dayCycle() // Bot position callback const botPosition = () => { diff --git a/src/watchOptions.ts b/src/watchOptions.ts index 2f5199c0..da75cc74 100644 --- a/src/watchOptions.ts +++ b/src/watchOptions.ts @@ -128,5 +128,6 @@ export const watchOptionsAfterWorldViewInit = (worldView: WorldDataEmitter) => { appViewer.inWorldRenderingConfig.renderEars = o.renderEars appViewer.inWorldRenderingConfig.showHand = o.showHand appViewer.inWorldRenderingConfig.viewBobbing = o.viewBobbing + appViewer.inWorldRenderingConfig.dayCycle = o.dayCycleAndLighting }) } From 265d02d18d46fbde79437857eb58254fb68b1cf0 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sun, 7 Sep 2025 18:23:13 +0000 Subject: [PATCH 78/98] up protocol for 1.21.8 --- pnpm-lock.yaml | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6188b6d..fd8be62b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -140,13 +140,13 @@ importers: version: 4.17.21 mcraft-fun-mineflayer: specifier: ^0.1.23 - version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13)) + version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e(encoding@0.1.13)) minecraft-data: specifier: 3.92.0 version: 3.92.0 minecraft-protocol: specifier: github:PrismarineJS/node-minecraft-protocol#master - version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) + version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) mineflayer-item-map-downloader: specifier: github:zardoy/mineflayer-item-map-downloader version: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824(patch_hash=a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad)(encoding@0.1.13) @@ -345,7 +345,7 @@ importers: version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/89c33d396f3fde4804c71f4be3c203ade1833b41(@types/react@18.3.18)(react@18.3.1) mineflayer: specifier: github:zardoy/mineflayer#gen-the-master - version: https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13) + version: https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e(encoding@0.1.13) mineflayer-mouse: specifier: ^0.1.21 version: 0.1.21 @@ -6668,8 +6668,8 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/89c33d396f3fde4804c71f4be3c203ade1833b41} version: 1.0.1 - minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074: - resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074} + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9: + resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9} version: 1.61.0 engines: {node: '>=22'} @@ -6688,8 +6688,8 @@ packages: resolution: {integrity: sha512-1XTVuw3twIrEcqQ1QRSB8NcStIUEZ+tbxiAG6rOrN/9M4thhtlS5PTJzFdmdrcYgWEBLvuOdJszaKE5zFfiXhg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8: - resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8} + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e: + resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e} version: 8.0.0 engines: {node: '>=22'} @@ -11344,7 +11344,7 @@ snapshots: dependencies: '@nxg-org/mineflayer-util-plugin': 1.8.4 minecraft-data: 3.92.0 - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e(encoding@0.1.13) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-item: 1.17.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b @@ -13106,7 +13106,7 @@ snapshots: long: 5.3.1 mc-bridge: 0.1.3(minecraft-data@3.92.0) minecraft-data: 3.92.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 @@ -13143,7 +13143,7 @@ snapshots: flatmap: 0.0.3 long: 5.3.1 minecraft-data: 3.92.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 @@ -16990,12 +16990,12 @@ snapshots: dependencies: minecraft-data: 3.92.0 - mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13)): + mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e(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/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e(encoding@0.1.13) prismarine-item: 1.17.0 ws: 8.18.1 transitivePeerDependencies: @@ -17313,7 +17313,7 @@ snapshots: - '@types/react' - react - minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13): + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13): dependencies: '@types/node-rsa': 1.1.4 '@types/readable-stream': 4.0.18 @@ -17365,7 +17365,7 @@ snapshots: mineflayer-item-map-downloader@https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824(patch_hash=a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad)(encoding@0.1.13): dependencies: - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e(encoding@0.1.13) sharp: 0.30.7 transitivePeerDependencies: - encoding @@ -17380,11 +17380,11 @@ snapshots: transitivePeerDependencies: - supports-color - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13): + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e(encoding@0.1.13): dependencies: '@nxg-org/mineflayer-physics-util': 1.8.10 minecraft-data: 3.92.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chat: 1.11.0 From 7f7a14ac65105649621ed825245ca23c2f2c5f85 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 8 Sep 2025 04:19:38 +0300 Subject: [PATCH 79/98] feat: Add overlay model viewer. Already integrated into inventory to display player! --- renderer/viewer/lib/createPlayerObject.ts | 55 +++ renderer/viewer/three/entities.ts | 7 +- src/inventoryWindows.ts | 35 ++ src/react/OverlayModelViewer.tsx | 505 ++++++++++++++++++++++ src/reactUi.tsx | 2 + 5 files changed, 598 insertions(+), 6 deletions(-) create mode 100644 renderer/viewer/lib/createPlayerObject.ts create mode 100644 src/react/OverlayModelViewer.tsx diff --git a/renderer/viewer/lib/createPlayerObject.ts b/renderer/viewer/lib/createPlayerObject.ts new file mode 100644 index 00000000..836c8062 --- /dev/null +++ b/renderer/viewer/lib/createPlayerObject.ts @@ -0,0 +1,55 @@ +import { PlayerObject, PlayerAnimation } from 'skinview3d' +import * as THREE from 'three' +import { WalkingGeneralSwing } from '../three/entity/animations' +import { loadSkinImage, stevePngUrl } from './utils/skins' + +export type PlayerObjectType = PlayerObject & { + animation?: PlayerAnimation + realPlayerUuid: string + realUsername: string +} + +export function createPlayerObject (options: { + username?: string + uuid?: string + scale?: number +}): { + playerObject: PlayerObjectType + wrapper: THREE.Group + } { + const wrapper = new THREE.Group() + const playerObject = new PlayerObject() as PlayerObjectType + + playerObject.realPlayerUuid = options.uuid ?? '' + playerObject.realUsername = options.username ?? '' + playerObject.position.set(0, 16, 0) + + // fix issues with starfield + playerObject.traverse((obj) => { + if (obj instanceof THREE.Mesh && obj.material instanceof THREE.MeshStandardMaterial) { + obj.material.transparent = true + } + }) + + wrapper.add(playerObject as any) + const scale = options.scale ?? (1 / 16) + wrapper.scale.set(scale, scale, scale) + wrapper.rotation.set(0, Math.PI, 0) + + // Set up animation + playerObject.animation = new WalkingGeneralSwing() + ;(playerObject.animation as WalkingGeneralSwing).isMoving = false + playerObject.animation.update(playerObject, 0) + + return { playerObject, wrapper } +} + +export const applySkinToPlayerObject = async (playerObject: PlayerObjectType, skinUrl: string) => { + return loadSkinImage(skinUrl || stevePngUrl).then(({ canvas }) => { + const skinTexture = new THREE.CanvasTexture(canvas) + skinTexture.magFilter = THREE.NearestFilter + skinTexture.minFilter = THREE.NearestFilter + skinTexture.needsUpdate = true + playerObject.skin.map = skinTexture as any + }).catch(console.error) +} diff --git a/renderer/viewer/three/entities.ts b/renderer/viewer/three/entities.ts index 7849686b..fad30182 100644 --- a/renderer/viewer/three/entities.ts +++ b/renderer/viewer/three/entities.ts @@ -20,6 +20,7 @@ import { ItemSpecificContextProperties } from '../lib/basePlayerState' import { loadSkinFromUsername, loadSkinImage, stevePngUrl } from '../lib/utils/skins' import { renderComponent } from '../sign-renderer' import { createCanvas } from '../lib/utils' +import { PlayerObjectType } from '../lib/createPlayerObject' import { getBlockMeshFromModel } from './holdingBlock' import { createItemMesh } from './itemMesh' import * as Entity from './entity/EntityMesh' @@ -33,12 +34,6 @@ export const steveTexture = loadThreeJsTextureFromUrl(stevePngUrl) export const TWEEN_DURATION = 120 -type PlayerObjectType = PlayerObject & { - animation?: PlayerAnimation - realPlayerUuid: string - realUsername: string -} - function convert2sComplementToHex (complement: number) { if (complement < 0) { complement = (0xFF_FF_FF_FF + complement + 1) >>> 0 diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index 166e42a7..d16fee20 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -12,6 +12,7 @@ import PrismarineChatLoader from 'prismarine-chat' import * as nbt from 'prismarine-nbt' import { BlockModel } from 'mc-assets' import { renderSlot } from 'renderer/viewer/three/renderSlot' +import { loadSkinFromUsername } from 'renderer/viewer/lib/utils/skins' import Generic95 from '../assets/generic_95.png' import { appReplacableResources } from './generated/resources' import { activeModalStack, hideCurrentModal, hideModal, miscUiState, showModal } from './globalState' @@ -23,6 +24,7 @@ import { getItemDescription } from './itemsDescriptions' import { MessageFormatPart } from './chatUtils' import { GeneralInputItem, getItemMetadata, getItemModelName, getItemNameRaw, RenderItem } from './mineflayer/items' import { playerState } from './mineflayer/playerState' +import { modelViewerState } from './react/OverlayModelViewer' const loadedImagesCache = new Map() const cleanLoadedImagesCache = () => { @@ -40,6 +42,34 @@ export const jeiCustomCategories = proxy({ value: [] as Array<{ id: string, categoryTitle: string, items: any[] }> }) +let remotePlayerSkin: string | undefined | Promise + +export const showInventoryPlayer = () => { + modelViewerState.model = { + positioning: { + windowWidth: 176, + windowHeight: 166, + x: 25, + y: 8, + width: 50, + height: 70, + scaled: true, + onlyInitialScale: true, + followCursor: true, + }, + // models: ['https://bucket.mcraft.fun/sitarbuckss.glb'], + // debug: true, + steveModelSkin: appViewer.playerState.reactive.playerSkin ?? (typeof remotePlayerSkin === 'string' ? remotePlayerSkin : ''), + } + if (remotePlayerSkin === undefined && !appViewer.playerState.reactive.playerSkin) { + remotePlayerSkin = loadSkinFromUsername(bot.username, 'skin').then(a => { + setTimeout(() => { showInventoryPlayer() }, 0) // todo patch instead and make reactive + remotePlayerSkin = a ?? '' + return remotePlayerSkin + }) + } +} + export const onGameLoad = () => { version = bot.version @@ -392,7 +422,12 @@ const openWindow = (type: string | undefined, title: string | any = undefined) = miscUiState.displaySearchInput = false destroyFn() skipClosePacketSending = false + + modelViewerState.model = undefined }) + if (type === undefined) { + showInventoryPlayer() + } cleanLoadedImagesCache() const inv = openItemsCanvas(type) inv.canvasManager.children[0].mobileHelpers = miscUiState.currentTouch diff --git a/src/react/OverlayModelViewer.tsx b/src/react/OverlayModelViewer.tsx new file mode 100644 index 00000000..24dc836d --- /dev/null +++ b/src/react/OverlayModelViewer.tsx @@ -0,0 +1,505 @@ +import { proxy, useSnapshot, subscribe } from 'valtio' +import { useEffect, useMemo, useRef } from 'react' +import * as THREE from 'three' +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' +import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader' +import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader' +import { applySkinToPlayerObject, createPlayerObject, PlayerObjectType } from '../../renderer/viewer/lib/createPlayerObject' +import { currentScaling } from '../scaleInterface' + +THREE.ColorManagement.enabled = false + +export const modelViewerState = proxy({ + model: undefined as undefined | { + models?: string[] // Array of model URLs (URL itself is the cache key) + steveModelSkin?: string + debug?: boolean + // absolute positioning + positioning: { + windowWidth: number + windowHeight: number + x: number + y: number + width: number + height: number + scaled?: boolean + onlyInitialScale?: boolean + followCursor?: boolean + } + modelCustomization?: { [modelUrl: string]: { color?: string, opacity?: number, metalness?: number, roughness?: number } } + resetRotationOnReleae?: boolean + continiousRender?: boolean + } +}) +globalThis.modelViewerState = modelViewerState + +// Global debug function to get camera and model values +globalThis.getModelViewerValues = () => { + const scene = globalThis.sceneRef?.current + if (!scene) return null + + const { camera, playerObject } = scene + if (!playerObject) return null + + const wrapper = playerObject.parent + if (!wrapper) return null + + const box = new THREE.Box3().setFromObject(wrapper) + const size = box.getSize(new THREE.Vector3()) + const center = box.getCenter(new THREE.Vector3()) + + return { + camera: { + position: camera.position.clone(), + fov: camera.fov, + aspect: camera.aspect + }, + model: { + position: wrapper.position.clone(), + rotation: wrapper.rotation.clone(), + scale: wrapper.scale.clone(), + size, + center + }, + cursor: { + position: globalThis.cursorPosition || { x: 0, y: 0 }, + normalized: globalThis.cursorPosition ? { + x: globalThis.cursorPosition.x * 2 - 1, + y: globalThis.cursorPosition.y * 2 - 1 + } : { x: 0, y: 0 } + }, + visibleArea: { + height: 2 * Math.tan(camera.fov * Math.PI / 180 / 2) * camera.position.z, + width: 2 * Math.tan(camera.fov * Math.PI / 180 / 2) * camera.position.z * camera.aspect + } + } +} + +export default () => { + const { model } = useSnapshot(modelViewerState) + const containerRef = useRef(null) + const sceneRef = useRef<{ + scene: THREE.Scene + camera: THREE.PerspectiveCamera + renderer: THREE.WebGLRenderer + controls: OrbitControls + playerObject?: PlayerObjectType + dispose: () => void + }>() + const initialScale = useMemo(() => { + return currentScaling.scale + }, []) + globalThis.sceneRef = sceneRef + + // Cursor following state + const cursorPosition = useRef({ x: 0, y: 0 }) + const isFollowingCursor = useRef(false) + + // Model management state + const loadedModels = useRef>(new Map()) + const modelLoaders = useRef>(new Map()) + + // Model management functions + const loadModel = (modelUrl: string) => { + if (loadedModels.current.has(modelUrl)) return // Already loaded + + const isGLTF = modelUrl.toLowerCase().endsWith('.gltf') || modelUrl.toLowerCase().endsWith('.glb') + const loader = isGLTF ? new GLTFLoader() : new OBJLoader() + modelLoaders.current.set(modelUrl, loader) + + const onLoad = (object: THREE.Object3D) => { + // Apply customization if available + const customization = model?.modelCustomization?.[modelUrl] + if (customization) { + object.traverse((child) => { + if (child instanceof THREE.Mesh && child.material) { + const material = child.material as THREE.MeshStandardMaterial + if (customization.color) { + material.color.setHex(parseInt(customization.color.replace('#', ''), 16)) + } + if (customization.opacity !== undefined) { + material.opacity = customization.opacity + material.transparent = customization.opacity < 1 + } + if (customization.metalness !== undefined) { + material.metalness = customization.metalness + } + if (customization.roughness !== undefined) { + material.roughness = customization.roughness + } + } + }) + } + + // Center and scale model + const box = new THREE.Box3().setFromObject(object) + const center = box.getCenter(new THREE.Vector3()) + const size = box.getSize(new THREE.Vector3()) + const maxDim = Math.max(size.x, size.y, size.z) + const scale = 2 / maxDim + object.scale.setScalar(scale) + object.position.sub(center.multiplyScalar(scale)) + + // Store the model using URL as key + loadedModels.current.set(modelUrl, object) + sceneRef.current?.scene.add(object) + + // Trigger render + if (sceneRef.current) { + setTimeout(() => { + const render = () => sceneRef.current?.renderer.render(sceneRef.current.scene, sceneRef.current.camera) + render() + }, 0) + } + } + + if (isGLTF) { + (loader as GLTFLoader).load(modelUrl, (gltf) => { + onLoad(gltf.scene) + }) + } else { + (loader as OBJLoader).load(modelUrl, onLoad) + } + } + + const removeModel = (modelUrl: string) => { + const model = loadedModels.current.get(modelUrl) + if (model) { + sceneRef.current?.scene.remove(model) + model.traverse((child) => { + if (child instanceof THREE.Mesh) { + if (child.material) { + if (Array.isArray(child.material)) { + for (const mat of child.material) { + mat.dispose() + } + } else { + child.material.dispose() + } + } + if (child.geometry) { + child.geometry.dispose() + } + } + }) + loadedModels.current.delete(modelUrl) + } + modelLoaders.current.delete(modelUrl) + } + + // Subscribe to model changes + useEffect(() => { + if (!modelViewerState.model?.models) return + + const modelsChanged = () => { + const currentModels = modelViewerState.model?.models || [] + const currentModelUrls = new Set(currentModels) + const loadedModelUrls = new Set(loadedModels.current.keys()) + + // Remove models that are no longer in the state + for (const modelUrl of loadedModelUrls) { + if (!currentModelUrls.has(modelUrl)) { + removeModel(modelUrl) + } + } + + // Add new models + for (const modelUrl of currentModels) { + if (!loadedModelUrls.has(modelUrl)) { + loadModel(modelUrl) + } + } + } + const unsubscribe = subscribe(modelViewerState.model.models, modelsChanged) + + let unmounted = false + setTimeout(() => { + if (unmounted) return + modelsChanged() + }) + + return () => { + unmounted = true + unsubscribe?.() + } + }, [model?.models]) + + useEffect(() => { + if (!model || !containerRef.current) return + + // Setup scene + const scene = new THREE.Scene() + scene.background = null // Transparent background + + // Setup camera with optimal settings for player model viewing + const camera = new THREE.PerspectiveCamera( + 50, // Reduced FOV for better model viewing + model.positioning.width / model.positioning.height, + 0.1, + 1000 + ) + camera.position.set(0, 0, 3) // Position camera to view player model optimally + + // Setup renderer with pixel density awareness + const renderer = new THREE.WebGLRenderer({ alpha: true }) + let scale = window.devicePixelRatio || 1 + if (modelViewerState.model?.positioning.scaled) { + scale *= currentScaling.scale + } + renderer.setPixelRatio(scale) + renderer.setSize(model.positioning.width, model.positioning.height) + containerRef.current.appendChild(renderer.domElement) + + // Setup controls + const controls = new OrbitControls(camera, renderer.domElement) + // controls.enableZoom = false + // controls.enablePan = false + controls.minPolarAngle = Math.PI / 2 // Lock vertical rotation + controls.maxPolarAngle = Math.PI / 2 + controls.enableDamping = true + controls.dampingFactor = 0.05 + + // Add ambient light + const ambientLight = new THREE.AmbientLight(0xff_ff_ff, 1) + scene.add(ambientLight) + + // Cursor following function + const updatePlayerLookAt = () => { + if (!isFollowingCursor.current || !sceneRef.current?.playerObject) return + + const { playerObject } = sceneRef.current + const { x, y } = cursorPosition.current + + // Convert 0-1 cursor position to normalized coordinates (-1 to 1) + const normalizedX = x * 2 - 1 + const normalizedY = y * 2 - 1 // Inverted: top of screen = negative pitch, bottom = positive pitch + + // Calculate head rotation based on cursor position + // Limit head movement to realistic angles + const maxHeadYaw = Math.PI / 3 // 60 degrees + const maxHeadPitch = Math.PI / 4 // 45 degrees + + const headYaw = normalizedX * maxHeadYaw + const headPitch = normalizedY * maxHeadPitch + + // Apply head rotation with smooth interpolation + const lerpFactor = 0.1 // Smooth interpolation factor + playerObject.skin.head.rotation.y = THREE.MathUtils.lerp( + playerObject.skin.head.rotation.y, + headYaw, + lerpFactor + ) + playerObject.skin.head.rotation.x = THREE.MathUtils.lerp( + playerObject.skin.head.rotation.x, + headPitch, + lerpFactor + ) + + // Apply slight body rotation for more natural movement + const bodyYaw = headYaw * 0.3 // Body follows head but with less rotation + playerObject.rotation.y = THREE.MathUtils.lerp( + playerObject.rotation.y, + bodyYaw, + lerpFactor * 0.5 // Slower body movement + ) + + render() + } + + // Render function + const render = () => { + renderer.render(scene, camera) + } + + // Setup animation/render strategy + if (model.continiousRender) { + // Continuous animation loop + const animate = () => { + requestAnimationFrame(animate) + render() + } + animate() + } else { + // Render only on camera movement + controls.addEventListener('change', render) + // Initial render + render() + // Render after model loads + if (model.steveModelSkin !== undefined) { + // Create player model + const { playerObject, wrapper } = createPlayerObject({ + scale: 1 // Start with base scale, will adjust below + }) + + // Calculate proper scale and positioning for camera view + const box = new THREE.Box3().setFromObject(wrapper) + const size = box.getSize(new THREE.Vector3()) + const center = box.getCenter(new THREE.Vector3()) + + // Calculate scale to fit within camera view (considering FOV and distance) + const cameraDistance = camera.position.z + const fov = camera.fov * Math.PI / 180 // Convert to radians + const visibleHeight = 2 * Math.tan(fov / 2) * cameraDistance + const visibleWidth = visibleHeight * (model.positioning.width / model.positioning.height) + + const scaleFactor = Math.min( + (visibleHeight) / size.y, + (visibleWidth) / size.x + ) + + wrapper.scale.multiplyScalar(scaleFactor) + + // Center the player object + wrapper.position.sub(center.multiplyScalar(scaleFactor)) + + // Rotate to face camera (remove the default 180° rotation) + wrapper.rotation.set(0, 0, 0) + + scene.add(wrapper) + sceneRef.current = { + ...sceneRef.current!, + playerObject + } + + void applySkinToPlayerObject(playerObject, model.steveModelSkin).then(() => { + setTimeout(render, 0) + }) + + // Set up cursor following if enabled + if (model.positioning.followCursor) { + isFollowingCursor.current = true + } + } + } + + // Window cursor tracking for followCursor + let lastCursorUpdate = 0 + let waitingRender = false + const handleWindowPointerMove = (event: PointerEvent) => { + if (!model.positioning.followCursor) return + + // Track cursor position as 0-1 across the entire window + const newPosition = { + x: event.clientX / window.innerWidth, + y: event.clientY / window.innerHeight + } + cursorPosition.current = newPosition + globalThis.cursorPosition = newPosition // Expose for debug + lastCursorUpdate = Date.now() + updatePlayerLookAt() + if (!waitingRender) { + requestAnimationFrame(() => { + render() + waitingRender = false + }) + waitingRender = true + } + } + + // Add window event listeners + if (model.positioning.followCursor) { + window.addEventListener('pointermove', handleWindowPointerMove) + isFollowingCursor.current = true + } + + // Store refs for cleanup + sceneRef.current = { + ...sceneRef.current!, + scene, + camera, + renderer, + controls, + dispose () { + if (!model.continiousRender) { + controls.removeEventListener('change', render) + } + if (model.positioning.followCursor) { + window.removeEventListener('pointermove', handleWindowPointerMove) + } + + // Clean up loaded models + for (const [modelUrl, model] of loadedModels.current) { + scene.remove(model) + model.traverse((child) => { + if (child instanceof THREE.Mesh) { + if (child.material) { + if (Array.isArray(child.material)) { + for (const mat of child.material) { + mat.dispose() + } + } else { + child.material.dispose() + } + } + if (child.geometry) { + child.geometry.dispose() + } + } + }) + } + loadedModels.current.clear() + modelLoaders.current.clear() + + const playerObject = sceneRef.current?.playerObject + if (playerObject?.skin.map) { + (playerObject.skin.map as unknown as THREE.Texture).dispose() + } + renderer.dispose() + renderer.domElement?.remove() + } + } + + return () => { + sceneRef.current?.dispose() + } + }, [model]) + + if (!model) return null + + const { x, y, width, height, scaled, onlyInitialScale } = model.positioning + const { windowWidth } = model.positioning + const { windowHeight } = model.positioning + const scaleValue = onlyInitialScale ? initialScale : 'var(--guiScale)' + + return ( +
    +
    +
    +
    +
    + ) +} diff --git a/src/reactUi.tsx b/src/reactUi.tsx index 1e6f13eb..6339686e 100644 --- a/src/reactUi.tsx +++ b/src/reactUi.tsx @@ -68,6 +68,7 @@ import FullscreenTime from './react/FullscreenTime' import StorageConflictModal from './react/StorageConflictModal' import FireRenderer from './react/FireRenderer' import MonacoEditor from './react/MonacoEditor' +import OverlayModelViewer from './react/OverlayModelViewer' const isFirefox = ua.getBrowser().name === 'Firefox' if (isFirefox) { @@ -259,6 +260,7 @@ const App = () => {
    + From 739a6fad24478c5b13b03f96babd853c569dcbbc Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 8 Sep 2025 04:34:17 +0300 Subject: [PATCH 80/98] fix lockfile --- pnpm-lock.yaml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fd8be62b..516370ce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -140,7 +140,7 @@ importers: version: 4.17.21 mcraft-fun-mineflayer: specifier: ^0.1.23 - version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e(encoding@0.1.13)) + version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13)) minecraft-data: specifier: 3.92.0 version: 3.92.0 @@ -345,7 +345,7 @@ importers: version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/89c33d396f3fde4804c71f4be3c203ade1833b41(@types/react@18.3.18)(react@18.3.1) mineflayer: specifier: github:zardoy/mineflayer#gen-the-master - version: https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e(encoding@0.1.13) + version: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13) mineflayer-mouse: specifier: ^0.1.21 version: 0.1.21 @@ -6670,7 +6670,7 @@ packages: minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9: resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9} - version: 1.61.0 + version: 1.62.0 engines: {node: '>=22'} minecraft-wrap@1.6.0: @@ -6688,8 +6688,8 @@ packages: resolution: {integrity: sha512-1XTVuw3twIrEcqQ1QRSB8NcStIUEZ+tbxiAG6rOrN/9M4thhtlS5PTJzFdmdrcYgWEBLvuOdJszaKE5zFfiXhg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e: - resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e} + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659: + resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659} version: 8.0.0 engines: {node: '>=22'} @@ -11344,7 +11344,7 @@ snapshots: dependencies: '@nxg-org/mineflayer-util-plugin': 1.8.4 minecraft-data: 3.92.0 - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-item: 1.17.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b @@ -16990,12 +16990,12 @@ snapshots: dependencies: minecraft-data: 3.92.0 - mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e(encoding@0.1.13)): + mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(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/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13) prismarine-item: 1.17.0 ws: 8.18.1 transitivePeerDependencies: @@ -17365,7 +17365,7 @@ snapshots: mineflayer-item-map-downloader@https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824(patch_hash=a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad)(encoding@0.1.13): dependencies: - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13) sharp: 0.30.7 transitivePeerDependencies: - encoding @@ -17380,7 +17380,7 @@ snapshots: transitivePeerDependencies: - supports-color - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/4493c8ccbd6f7e775d76149d838f4386ae959f0e(encoding@0.1.13): + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13): dependencies: '@nxg-org/mineflayer-physics-util': 1.8.10 minecraft-data: 3.92.0 From 0b1183f541bb76a47ed15cfd63ef59340f91be9f Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 8 Sep 2025 04:36:09 +0300 Subject: [PATCH 81/98] up minecraft-data --- package.json | 4 ++-- scripts/makeOptimizedMcData.mjs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b5f66bfb..ff673726 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "jszip": "^3.10.1", "lodash-es": "^4.17.21", "mcraft-fun-mineflayer": "^0.1.23", - "minecraft-data": "3.92.0", + "minecraft-data": "3.98.0", "minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master", "mineflayer-item-map-downloader": "github:zardoy/mineflayer-item-map-downloader", "mojangson": "^2.0.4", @@ -205,7 +205,7 @@ "diamond-square": "github:zardoy/diamond-square", "prismarine-block": "github:zardoy/prismarine-block#next-era", "prismarine-world": "github:zardoy/prismarine-world#next-era", - "minecraft-data": "3.92.0", + "minecraft-data": "3.98.0", "prismarine-provider-anvil": "github:zardoy/prismarine-provider-anvil#everything", "prismarine-physics": "github:zardoy/prismarine-physics", "minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master", diff --git a/scripts/makeOptimizedMcData.mjs b/scripts/makeOptimizedMcData.mjs index 76e0f1c2..a572d067 100644 --- a/scripts/makeOptimizedMcData.mjs +++ b/scripts/makeOptimizedMcData.mjs @@ -371,6 +371,7 @@ console.log('size', fs.lstatSync(filePath).size / 1000 / 1000, gzipSizeFromFileS const { defaultVersion } = MCProtocol const data = MinecraftData(defaultVersion) +console.log('defaultVersion', defaultVersion, !!data) const initialMcData = { [defaultVersion]: { version: data.version, From f24cb49a8728a63cab19b8cc76fdbfb1888566eb Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 8 Sep 2025 04:55:43 +0300 Subject: [PATCH 82/98] up lockfile --- pnpm-lock.yaml | 74 +++++++++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 516370ce..5bcd74a0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,7 +13,7 @@ overrides: diamond-square: github:zardoy/diamond-square prismarine-block: github:zardoy/prismarine-block#next-era prismarine-world: github:zardoy/prismarine-world#next-era - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 prismarine-provider-anvil: github:zardoy/prismarine-provider-anvil#everything prismarine-physics: github:zardoy/prismarine-physics minecraft-protocol: github:PrismarineJS/node-minecraft-protocol#master @@ -142,8 +142,8 @@ importers: specifier: ^0.1.23 version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13)) minecraft-data: - specifier: 3.92.0 - version: 3.92.0 + specifier: 3.98.0 + version: 3.98.0 minecraft-protocol: specifier: github:PrismarineJS/node-minecraft-protocol#master version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) @@ -170,7 +170,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/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0) + version: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.98.0) prosemirror-example-setup: specifier: ^1.2.2 version: 1.2.3 @@ -436,7 +436,7 @@ importers: version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chunk: specifier: github:zardoy/prismarine-chunk#master - version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) + version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0) prismarine-schematic: specifier: ^1.2.0 version: 1.2.3 @@ -6448,7 +6448,7 @@ packages: resolution: {integrity: sha512-H9jPt2xEU77itC27dSz3qazHYqN9qVsv4HgMPozg7RqQ1uwgXmEa+ojKIlDtXf/TLJsG6Kv4EbzGa8a1Wh72uA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 mcraft-fun-mineflayer@0.1.23: resolution: {integrity: sha512-qmI1rQQ0Ro5zJdi99rClWLF+mS9JZffgNX2vyWWesS3Hsk3Xblp/8swYTJKHSaFpNgzkVfXV92fEIrBqeH6iKA==} @@ -6658,8 +6658,8 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - minecraft-data@3.92.0: - resolution: {integrity: sha512-CGfO50svzm+pSRa4Mbq4owsmRKbPCNkSZ3MCOyH+epC7yNjh+PUhPQFHWq72O51qsY7pAB5qM/bJn1ncwG1J5g==} + minecraft-data@3.98.0: + resolution: {integrity: sha512-JAPqJ/TZoxMUlAPPdWUh1v5wdqvYGFSZ4rW9bUtmaKBkGpomDSjw4V02ocBqbxKJvcTtmc5nM/LfN9/0DDqHrQ==} minecraft-folder-path@1.2.0: resolution: {integrity: sha512-qaUSbKWoOsH9brn0JQuBhxNAzTDMwrOXorwuRxdJKKKDYvZhtml+6GVCUrY5HRiEsieBEjCUnhVpDuQiKsiFaw==} @@ -7387,7 +7387,7 @@ packages: prismarine-biome@1.3.0: resolution: {integrity: sha512-GY6nZxq93mTErT7jD7jt8YS1aPrOakbJHh39seYsJFXvueIOdHAmW16kYQVrTVMW5MlWLQVxV/EquRwOgr4MnQ==} peerDependencies: - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 prismarine-registry: ^1.1.0 prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9: @@ -11343,7 +11343,7 @@ snapshots: '@nxg-org/mineflayer-trajectories@1.2.0(encoding@0.1.13)': dependencies: '@nxg-org/mineflayer-util-plugin': 1.8.4 - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-item: 1.17.0 @@ -13104,18 +13104,18 @@ snapshots: exit-hook: 2.2.1 flatmap: 0.0.3 long: 5.3.1 - mc-bridge: 0.1.3(minecraft-data@3.92.0) - minecraft-data: 3.92.0 + mc-bridge: 0.1.3(minecraft-data@3.98.0) + minecraft-data: 3.98.0 minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0) prismarine-entity: 2.5.0 prismarine-item: 1.17.0 prismarine-nbt: 2.7.0 - prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0) + prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.98.0) prismarine-windows: 2.9.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c rambda: 9.4.2 @@ -13142,16 +13142,16 @@ snapshots: exit-hook: 2.2.1 flatmap: 0.0.3 long: 5.3.1 - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0) prismarine-entity: 2.5.0 prismarine-item: 1.17.0 prismarine-nbt: 2.7.0 - prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0) + prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.98.0) prismarine-windows: 2.9.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c rambda: 9.4.2 @@ -14542,8 +14542,8 @@ snapshots: diamond-square@https://codeload.github.com/zardoy/diamond-square/tar.gz/cfaad2d1d5909fdfa63c8cc7bc05fb5e87782d71: dependencies: - minecraft-data: 3.92.0 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) + minecraft-data: 3.98.0 + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0) prismarine-registry: 1.11.0 random-seed: 0.3.0 vec3: 0.1.10 @@ -16986,9 +16986,9 @@ snapshots: maxrects-packer: '@zardoy/maxrects-packer@2.7.4' zod: 3.24.2 - mc-bridge@0.1.3(minecraft-data@3.92.0): + mc-bridge@0.1.3(minecraft-data@3.98.0): dependencies: - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13)): dependencies: @@ -17302,7 +17302,7 @@ snapshots: min-indent@1.0.1: {} - minecraft-data@3.92.0: {} + minecraft-data@3.98.0: {} minecraft-folder-path@1.2.0: {} @@ -17322,7 +17322,7 @@ snapshots: debug: 4.4.0(supports-color@8.1.1) endian-toggle: 0.0.0 lodash.merge: 4.6.2 - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 minecraft-folder-path: 1.2.0 node-fetch: 2.7.0(encoding@0.1.13) node-rsa: 0.4.2 @@ -17383,12 +17383,12 @@ snapshots: mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13): dependencies: '@nxg-org/mineflayer-physics-util': 1.8.10 - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) - prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) + prismarine-biome: 1.3.0(minecraft-data@3.98.0)(prismarine-registry@1.11.0) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chat: 1.11.0 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0) prismarine-entity: 2.5.0 prismarine-item: 1.17.0 prismarine-nbt: 2.7.0 @@ -18174,15 +18174,15 @@ snapshots: transitivePeerDependencies: - supports-color - prismarine-biome@1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0): + prismarine-biome@1.3.0(minecraft-data@3.98.0)(prismarine-registry@1.11.0): dependencies: - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 prismarine-registry: 1.11.0 prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9: dependencies: - minecraft-data: 3.92.0 - prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) + minecraft-data: 3.98.0 + prismarine-biome: 1.3.0(minecraft-data@3.98.0)(prismarine-registry@1.11.0) prismarine-chat: 1.11.0 prismarine-item: 1.17.0 prismarine-nbt: 2.7.0 @@ -18194,9 +18194,9 @@ snapshots: prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 - prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0): + prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0): dependencies: - prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) + prismarine-biome: 1.3.0(minecraft-data@3.98.0)(prismarine-registry@1.11.0) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 @@ -18225,14 +18225,14 @@ snapshots: prismarine-physics@https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b: dependencies: - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 prismarine-nbt: 2.7.0 vec3: 0.1.10 - prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0): + prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.98.0): dependencies: prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0) prismarine-nbt: 2.7.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c uint4: 0.1.2 @@ -18254,13 +18254,13 @@ snapshots: prismarine-registry@1.11.0: dependencies: - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-nbt: 2.7.0 prismarine-schematic@1.2.3: dependencies: - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-nbt: 2.7.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c From 1525fac2a192f1fe7e2f858480c444948bbe9651 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 8 Sep 2025 05:22:24 +0300 Subject: [PATCH 83/98] fix: some visual camera world view issues (visible lines between blocks) --- renderer/viewer/three/cameraShake.ts | 25 ++++++++++++++++++++-- renderer/viewer/three/world/cursorBlock.ts | 18 ++++++++++++---- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/renderer/viewer/three/cameraShake.ts b/renderer/viewer/three/cameraShake.ts index 593b4628..7b159509 100644 --- a/renderer/viewer/three/cameraShake.ts +++ b/renderer/viewer/three/cameraShake.ts @@ -80,8 +80,12 @@ export class CameraShake { camera.setRotationFromQuaternion(yawQuat) } else { // For regular camera, apply all rotations - const pitchQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), this.basePitch) - const yawQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.baseYaw) + // Add tiny offsets to prevent z-fighting at ideal angles (90, 180, 270 degrees) + const pitchOffset = this.addAntiZfightingOffset(this.basePitch) + const yawOffset = this.addAntiZfightingOffset(this.baseYaw) + + const pitchQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), pitchOffset) + const yawQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), yawOffset) const rollQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 0, 1), THREE.MathUtils.degToRad(this.rollAngle)) // Combine rotations in the correct order: pitch -> yaw -> roll const finalQuat = yawQuat.multiply(pitchQuat).multiply(rollQuat) @@ -96,4 +100,21 @@ export class CameraShake { private easeInOut (t: number): number { return t < 0.5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2 } + + private addAntiZfightingOffset (angle: number): number { + const offset = 0.001 // Very small offset in radians (about 0.057 degrees) + + // Check if the angle is close to ideal angles (0, π/2, π, 3π/2) + const normalizedAngle = ((angle % (Math.PI * 2)) + Math.PI * 2) % (Math.PI * 2) + const tolerance = 0.01 // Tolerance for considering an angle "ideal" + + if (Math.abs(normalizedAngle) < tolerance || + Math.abs(normalizedAngle - Math.PI / 2) < tolerance || + Math.abs(normalizedAngle - Math.PI) < tolerance || + Math.abs(normalizedAngle - 3 * Math.PI / 2) < tolerance) { + return angle + offset + } + + return angle + } } diff --git a/renderer/viewer/three/world/cursorBlock.ts b/renderer/viewer/three/world/cursorBlock.ts index b71c1b8d..a03a6999 100644 --- a/renderer/viewer/three/world/cursorBlock.ts +++ b/renderer/viewer/three/world/cursorBlock.ts @@ -28,7 +28,7 @@ export class CursorBlock { } cursorLineMaterial: LineMaterial - interactionLines: null | { blockPos: Vec3, mesh: THREE.Group } = null + interactionLines: null | { blockPos: Vec3, mesh: THREE.Group, shapePositions: BlocksShapes | undefined } = null prevColor: string | undefined blockBreakMesh: THREE.Mesh breakTextures: THREE.Texture[] = [] @@ -62,6 +62,13 @@ export class CursorBlock { this.worldRenderer.onReactivePlayerStateUpdated('gameMode', () => { this.updateLineMaterial() }) + // todo figure out why otherwise fog from skybox breaks it + setTimeout(() => { + this.updateLineMaterial() + if (this.interactionLines) { + this.setHighlightCursorBlock(this.interactionLines.blockPos, this.interactionLines.shapePositions, true) + } + }) } // Update functions @@ -69,6 +76,9 @@ export class CursorBlock { const inCreative = this.worldRenderer.playerStateReactive.gameMode === 'creative' const pixelRatio = this.worldRenderer.renderer.getPixelRatio() + if (this.cursorLineMaterial) { + this.cursorLineMaterial.dispose() + } this.cursorLineMaterial = new LineMaterial({ color: (() => { switch (this.worldRenderer.worldRendererConfig.highlightBlockColor) { @@ -115,8 +125,8 @@ export class CursorBlock { } } - setHighlightCursorBlock (blockPos: Vec3 | null, shapePositions?: BlocksShapes): void { - if (blockPos && this.interactionLines && blockPos.equals(this.interactionLines.blockPos)) { + setHighlightCursorBlock (blockPos: Vec3 | null, shapePositions?: BlocksShapes, force = false): void { + if (blockPos && this.interactionLines && blockPos.equals(this.interactionLines.blockPos) && !force) { return } if (this.interactionLines !== null) { @@ -140,7 +150,7 @@ export class CursorBlock { } this.worldRenderer.scene.add(group) group.visible = !this.cursorLinesHidden - this.interactionLines = { blockPos, mesh: group } + this.interactionLines = { blockPos, mesh: group, shapePositions } } render () { From c4097975bf8ea14764bf7f2b70aa00e45dc5b399 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 8 Sep 2025 05:29:34 +0300 Subject: [PATCH 84/98] add a way to disable sky box for old behavior (not tested) --- renderer/viewer/lib/worldrendererCommon.ts | 1 + renderer/viewer/three/skyboxRenderer.ts | 25 ++++++++++++++++++++- renderer/viewer/three/worldrendererThree.ts | 5 ++++- src/defaultOptions.ts | 1 + src/watchOptions.ts | 4 ++++ 5 files changed, 34 insertions(+), 2 deletions(-) diff --git a/renderer/viewer/lib/worldrendererCommon.ts b/renderer/viewer/lib/worldrendererCommon.ts index e2455915..4140e3fa 100644 --- a/renderer/viewer/lib/worldrendererCommon.ts +++ b/renderer/viewer/lib/worldrendererCommon.ts @@ -47,6 +47,7 @@ export const defaultWorldRendererConfig = { smoothLighting: true, enableLighting: true, starfield: true, + defaultSkybox: true, renderEntities: true, extraBlockRenderers: true, foreground: true, diff --git a/renderer/viewer/three/skyboxRenderer.ts b/renderer/viewer/three/skyboxRenderer.ts index aa8c3bb6..cd7bd879 100644 --- a/renderer/viewer/three/skyboxRenderer.ts +++ b/renderer/viewer/three/skyboxRenderer.ts @@ -18,7 +18,7 @@ export class SkyboxRenderer { private fogBrightness = 0 private prevFogBrightness = 0 - constructor (private readonly scene: THREE.Scene, public initialImage: string | null) { + constructor (private readonly scene: THREE.Scene, public defaultSkybox: boolean, public initialImage: string | null) { if (!initialImage) { this.createGradientSky() } @@ -119,6 +119,12 @@ export class SkyboxRenderer { this.updateSkyColors() } + // Update default skybox setting + updateDefaultSkybox (defaultSkybox: boolean) { + this.defaultSkybox = defaultSkybox + this.updateSkyColors() + } + private createGradientSky () { const size = 64 const scale = 256 / size + 2 @@ -279,6 +285,23 @@ export class SkyboxRenderer { private updateSkyColors () { if (!this.skyMesh || !this.voidMesh) return + // If default skybox is disabled, hide the skybox meshes + if (!this.defaultSkybox) { + this.skyMesh.visible = false + this.voidMesh.visible = false + if (this.mesh) { + this.mesh.visible = false + } + return + } + + // Show skybox meshes when default skybox is enabled + this.skyMesh.visible = true + this.voidMesh.visible = true + if (this.mesh) { + this.mesh.visible = true + } + // Update fog brightness with smooth transition this.prevFogBrightness = this.fogBrightness const renderDistance = this.viewDistance / 32 diff --git a/renderer/viewer/three/worldrendererThree.ts b/renderer/viewer/three/worldrendererThree.ts index 29e9223c..1b4e6152 100644 --- a/renderer/viewer/three/worldrendererThree.ts +++ b/renderer/viewer/three/worldrendererThree.ts @@ -98,7 +98,7 @@ export class WorldRendererThree extends WorldRendererCommon { this.holdingBlockLeft = new HoldingBlock(this, true) // Initialize skybox renderer - this.skyboxRenderer = new SkyboxRenderer(this.scene, null) + this.skyboxRenderer = new SkyboxRenderer(this.scene, this.worldRendererConfig.defaultSkybox, null) void this.skyboxRenderer.init() this.addDebugOverlay() @@ -206,6 +206,9 @@ export class WorldRendererThree extends WorldRendererCommon { this.onReactiveConfigUpdated('showChunkBorders', (value) => { this.updateShowChunksBorder(value) }) + this.onReactiveConfigUpdated('defaultSkybox', (value) => { + this.skyboxRenderer.updateDefaultSkybox(value) + }) } changeHandSwingingState (isAnimationPlaying: boolean, isLeft = false) { diff --git a/src/defaultOptions.ts b/src/defaultOptions.ts index 6045e70b..361879be 100644 --- a/src/defaultOptions.ts +++ b/src/defaultOptions.ts @@ -41,6 +41,7 @@ export const defaultOptions = { renderEars: true, lowMemoryMode: false, starfieldRendering: true, + defaultSkybox: true, enabledResourcepack: null as string | null, useVersionsTextures: 'latest', serverResourcePacks: 'prompt' as 'prompt' | 'always' | 'never', diff --git a/src/watchOptions.ts b/src/watchOptions.ts index da75cc74..779aa29f 100644 --- a/src/watchOptions.ts +++ b/src/watchOptions.ts @@ -116,6 +116,10 @@ export const watchOptionsAfterViewerInit = () => { appViewer.inWorldRenderingConfig.starfield = o.starfieldRendering }) + watchValue(options, o => { + appViewer.inWorldRenderingConfig.defaultSkybox = o.defaultSkybox + }) + watchValue(options, o => { // appViewer.inWorldRenderingConfig.neighborChunkUpdates = o.neighborChunkUpdates }) From 06dc3cb033129f925dd492b01a809e9e5f3cd19c Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 8 Sep 2025 05:38:16 +0300 Subject: [PATCH 85/98] feat: Add saveLoginPassword option to control password saving behavior in browser for offline auth on servers --- src/defaultOptions.ts | 1 + src/optionsGuiScheme.tsx | 10 ++++++++++ src/react/ChatProvider.tsx | 20 ++++++++++++++++---- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/defaultOptions.ts b/src/defaultOptions.ts index 361879be..85ebae17 100644 --- a/src/defaultOptions.ts +++ b/src/defaultOptions.ts @@ -84,6 +84,7 @@ export const defaultOptions = { localServerOptions: { gameMode: 1 } as any, + saveLoginPassword: 'prompt' as 'prompt' | 'never' | 'always', preferLoadReadonly: false, experimentalClientSelfReload: false, remoteSoundsSupport: false, diff --git a/src/optionsGuiScheme.tsx b/src/optionsGuiScheme.tsx index b03db37d..a47c06eb 100644 --- a/src/optionsGuiScheme.tsx +++ b/src/optionsGuiScheme.tsx @@ -550,6 +550,16 @@ export const guiOptionsScheme: { return Server Connection }, }, + { + saveLoginPassword: { + tooltip: 'Controls whether to save login passwords for servers in this browser memory.', + values: [ + 'prompt', + 'always', + 'never' + ] + }, + }, { custom () { const { serversAutoVersionSelect } = useSnapshot(options) diff --git a/src/react/ChatProvider.tsx b/src/react/ChatProvider.tsx index 0bb13285..066bc48a 100644 --- a/src/react/ChatProvider.tsx +++ b/src/react/ChatProvider.tsx @@ -73,16 +73,28 @@ export default () => { } const builtinHandled = tryHandleBuiltinCommand(message) - if (getServerIndex() !== undefined && (message.startsWith('/login') || message.startsWith('/register'))) { - showNotification('Click here to save your password in browser for auto-login', undefined, false, undefined, () => { + if (getServerIndex() !== undefined && (message.startsWith('/login') || message.startsWith('/register')) && options.saveLoginPassword !== 'never') { + const savePassword = () => { + let hadPassword = false updateLoadedServerData((server) => { server.autoLogin ??= {} const password = message.split(' ')[1] + hadPassword = !!server.autoLogin[bot.username] server.autoLogin[bot.username] = password return { ...server } }) - hideNotification() - }) + if (options.saveLoginPassword === 'always') { + const message = hadPassword ? 'Password updated in browser for auto-login' : 'Password saved in browser for auto-login' + showNotification(message, undefined, false, undefined) + } else { + hideNotification() + } + } + if (options.saveLoginPassword === 'prompt') { + showNotification('Click here to save your password in browser for auto-login', undefined, false, undefined, savePassword) + } else { + savePassword() + } notificationProxy.id = 'auto-login' const listener = () => { hideNotification() From 852dd737aefc045a1d4193d17803d10a0cce1b48 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 11 Sep 2025 22:24:04 +0300 Subject: [PATCH 86/98] fix: fix some UI like error screen was not visible fully (buttons were clipped behind the screen) on larger scale on large screens --- src/screens.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/screens.css b/src/screens.css index f0040e2d..e503c305 100644 --- a/src/screens.css +++ b/src/screens.css @@ -26,6 +26,10 @@ display: flex; justify-content: center; z-index: 12; + /* Account for GUI scaling */ + width: calc(100dvw / var(--guiScale, 1)); + height: calc(100dvh / var(--guiScale, 1)); + overflow: hidden; } .screen-content { From c930365e329aef0c44845ab06aa42266f5a6a16e Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 18 Sep 2025 07:49:44 +0300 Subject: [PATCH 87/98] fix sometimes inventory player should not be rendered --- src/inventoryWindows.ts | 2 ++ src/react/OverlayModelViewer.tsx | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index d16fee20..d40260df 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -470,6 +470,7 @@ const openWindow = (type: string | undefined, title: string | any = undefined) = const isRightClick = type === 'rightclick' const isLeftClick = type === 'leftclick' if (isLeftClick || isRightClick) { + modelViewerState.model = undefined inv.canvasManager.children[0].showRecipesOrUsages(isLeftClick, item) } } else { @@ -501,6 +502,7 @@ const openWindow = (type: string | undefined, title: string | any = undefined) = if (freeSlot === null) return void bot.creative.setInventorySlot(freeSlot, item) } else { + modelViewerState.model = undefined inv.canvasManager.children[0].showRecipesOrUsages(!isRightclick, mapSlots([item], true)[0]) } } diff --git a/src/react/OverlayModelViewer.tsx b/src/react/OverlayModelViewer.tsx index 24dc836d..0fdeae75 100644 --- a/src/react/OverlayModelViewer.tsx +++ b/src/react/OverlayModelViewer.tsx @@ -6,6 +6,7 @@ import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader' import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader' import { applySkinToPlayerObject, createPlayerObject, PlayerObjectType } from '../../renderer/viewer/lib/createPlayerObject' import { currentScaling } from '../scaleInterface' +import { activeModalStack } from '../globalState' THREE.ColorManagement.enabled = false @@ -29,6 +30,7 @@ export const modelViewerState = proxy({ modelCustomization?: { [modelUrl: string]: { color?: string, opacity?: number, metalness?: number, roughness?: number } } resetRotationOnReleae?: boolean continiousRender?: boolean + alwaysRender?: boolean } }) globalThis.modelViewerState = modelViewerState @@ -75,6 +77,15 @@ globalThis.getModelViewerValues = () => { } } +subscribe(activeModalStack, () => { + if (!modelViewerState.model || !modelViewerState.model?.alwaysRender) { + return + } + if (activeModalStack.length === 0) { + modelViewerState.model = undefined + } +}) + export default () => { const { model } = useSnapshot(modelViewerState) const containerRef = useRef(null) From 636a7fdb54a2fec622b5b31da4691a632049b5f0 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Fri, 19 Sep 2025 04:42:22 +0200 Subject: [PATCH 88/98] feat: improve fog a little (#427) --- renderer/viewer/three/skyboxRenderer.ts | 43 ++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/renderer/viewer/three/skyboxRenderer.ts b/renderer/viewer/three/skyboxRenderer.ts index cd7bd879..fb9edae6 100644 --- a/renderer/viewer/three/skyboxRenderer.ts +++ b/renderer/viewer/three/skyboxRenderer.ts @@ -1,4 +1,5 @@ import * as THREE from 'three' +import { DebugGui } from '../lib/DebugGui' export const DEFAULT_TEMPERATURE = 0.75 @@ -17,11 +18,33 @@ export class SkyboxRenderer { private waterBreathing = false private fogBrightness = 0 private prevFogBrightness = 0 + private readonly fogOrangeness = 0 // Debug property to control sky color orangeness + private readonly distanceFactor = 2.7 + + private readonly brightnessAtPosition = 1 + debugGui: DebugGui constructor (private readonly scene: THREE.Scene, public defaultSkybox: boolean, public initialImage: string | null) { + this.debugGui = new DebugGui('skybox_renderer', this, [ + 'temperature', + 'worldTime', + 'inWater', + 'waterBreathing', + 'fogOrangeness', + 'brightnessAtPosition', + 'distanceFactor' + ], { + brightnessAtPosition: { min: 0, max: 1, step: 0.01 }, + temperature: { min: 0, max: 1, step: 0.01 }, + worldTime: { min: 0, max: 24_000, step: 1 }, + fogOrangeness: { min: -1, max: 1, step: 0.01 }, + distanceFactor: { min: 0, max: 5, step: 0.01 }, + }) + if (!initialImage) { this.createGradientSky() } + // this.debugGui.activate() } async init () { @@ -95,6 +118,7 @@ export class SkyboxRenderer { // Update world time updateTime (timeOfDay: number, partialTicks = 0) { + if (this.debugGui.visible) return this.worldTime = timeOfDay this.partialTicks = partialTicks this.updateSkyColors() @@ -108,12 +132,14 @@ export class SkyboxRenderer { // Update temperature (for biome support) updateTemperature (temperature: number) { + if (this.debugGui.visible) return this.temperature = temperature this.updateSkyColors() } // Update water state updateWaterState (inWater: boolean, waterBreathing: boolean) { + if (this.debugGui.visible) return this.inWater = inWater this.waterBreathing = waterBreathing this.updateSkyColors() @@ -121,6 +147,7 @@ export class SkyboxRenderer { // Update default skybox setting updateDefaultSkybox (defaultSkybox: boolean) { + if (this.debugGui.visible) return this.defaultSkybox = defaultSkybox this.updateSkyColors() } @@ -229,8 +256,15 @@ export class SkyboxRenderer { if (temperature < -1) temperature = -1 if (temperature > 1) temperature = 1 - const hue = 0.622_222_2 - temperature * 0.05 - const saturation = 0.5 + temperature * 0.1 + // Apply debug fog orangeness to hue - positive values make it more orange, negative make it less orange + const baseHue = 0.622_222_2 - temperature * 0.05 + // Orange is around hue 0.08-0.15, so we need to shift from blue-purple (0.62) toward orange + // Use a more dramatic shift and also increase saturation for more noticeable effect + const orangeHue = 0.12 // Orange hue value + const hue = this.fogOrangeness > 0 + ? baseHue + (orangeHue - baseHue) * this.fogOrangeness * 0.8 // Blend toward orange + : baseHue + this.fogOrangeness * 0.1 // Subtle shift for negative values + const saturation = 0.5 + temperature * 0.1 + Math.abs(this.fogOrangeness) * 0.3 // Increase saturation with orangeness const brightness = 1 return this.hsbToRgb(hue, saturation, brightness) @@ -305,8 +339,7 @@ export class SkyboxRenderer { // Update fog brightness with smooth transition this.prevFogBrightness = this.fogBrightness const renderDistance = this.viewDistance / 32 - const brightnessAtPosition = 1 // Could be affected by light level in future - const targetBrightness = brightnessAtPosition * (1 - renderDistance) + renderDistance + const targetBrightness = this.brightnessAtPosition * (1 - renderDistance) + renderDistance this.fogBrightness += (targetBrightness - this.fogBrightness) * 0.1 // Handle water fog @@ -340,7 +373,7 @@ export class SkyboxRenderer { const blue = (fogColor.z + (skyColor.z - fogColor.z) * viewFactor) * clampedBrightness * interpolatedBrightness this.scene.background = new THREE.Color(red, green, blue) - this.scene.fog = new THREE.Fog(new THREE.Color(red, green, blue), 0.0025, viewDistance * 2) + this.scene.fog = new THREE.Fog(new THREE.Color(red, green, blue), 0.0025, viewDistance * this.distanceFactor) ;(this.skyMesh.material as THREE.MeshBasicMaterial).color.set(new THREE.Color(skyColor.x, skyColor.y, skyColor.z)) ;(this.voidMesh.material as THREE.MeshBasicMaterial).color.set(new THREE.Color( From 3b94889bed40e9c687be52c5ca9a87172c6c6a9d Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sat, 20 Sep 2025 01:57:59 +0200 Subject: [PATCH 89/98] feat: make arrows colorful and metadata (#430) Co-authored-by: Cursor Agent --- renderer/viewer/three/waypointSprite.ts | 36 ++++++++++++++++++++----- renderer/viewer/three/waypoints.ts | 4 ++- src/customChannels.ts | 17 +++++++++++- 3 files changed, 49 insertions(+), 8 deletions(-) diff --git a/renderer/viewer/three/waypointSprite.ts b/renderer/viewer/three/waypointSprite.ts index 7c8cf1f6..6a30e6db 100644 --- a/renderer/viewer/three/waypointSprite.ts +++ b/renderer/viewer/three/waypointSprite.ts @@ -16,7 +16,7 @@ export const WAYPOINT_CONFIG = { CANVAS_SCALE: 2, ARROW: { enabledDefault: false, - pixelSize: 30, + pixelSize: 50, paddingPx: 50, }, } @@ -50,6 +50,7 @@ export function createWaypointSprite (options: { depthTest?: boolean, // Y offset in world units used by updateScaleWorld only (screen-pixel API ignores this) labelYOffset?: number, + metadata?: any, }): WaypointSprite { const color = options.color ?? 0xFF_00_00 const depthTest = options.depthTest ?? false @@ -131,16 +132,22 @@ export function createWaypointSprite (options: { canvas.height = size const ctx = canvas.getContext('2d')! ctx.clearRect(0, 0, size, size) + + // Draw arrow shape ctx.beginPath() - ctx.moveTo(size * 0.2, size * 0.5) - ctx.lineTo(size * 0.8, size * 0.5) - ctx.lineTo(size * 0.5, size * 0.2) + ctx.moveTo(size * 0.15, size * 0.5) + ctx.lineTo(size * 0.85, size * 0.5) + ctx.lineTo(size * 0.5, size * 0.15) ctx.closePath() - ctx.lineWidth = 4 + + // Use waypoint color for arrow + const colorHex = `#${color.toString(16).padStart(6, '0')}` + ctx.lineWidth = 6 ctx.strokeStyle = 'black' ctx.stroke() - ctx.fillStyle = 'white' + ctx.fillStyle = colorHex ctx.fill() + const texture = new THREE.CanvasTexture(canvas) const material = new THREE.SpriteMaterial({ map: texture, transparent: true, depthTest: false, depthWrite: false }) arrowSprite = new THREE.Sprite(material) @@ -169,6 +176,9 @@ export function createWaypointSprite (options: { ensureArrow() if (!arrowSprite) return true + // Check if onlyLeftRight is enabled in metadata + const onlyLeftRight = options.metadata?.onlyLeftRight === true + // Build camera basis using camera.up to respect custom orientations const forward = new THREE.Vector3() camera.getWorldDirection(forward) // camera look direction @@ -213,6 +223,20 @@ export function createWaypointSprite (options: { } } + // Apply onlyLeftRight logic - restrict arrows to left/right edges only + if (onlyLeftRight) { + // Force the arrow to appear only on left or right edges + if (Math.abs(rx) > Math.abs(ry)) { + // Horizontal direction is dominant, keep it + ry = 0 + } else { + // Vertical direction is dominant, but we want only left/right + // So choose left or right based on the sign of rx + rx = rx >= 0 ? 1 : -1 + ry = 0 + } + } + // Place on the rectangle border [-1,1]x[-1,1] const s = Math.max(Math.abs(rx), Math.abs(ry)) || 1 let ndcX = rx / s diff --git a/renderer/viewer/three/waypoints.ts b/renderer/viewer/three/waypoints.ts index cebd779a..256ca6df 100644 --- a/renderer/viewer/three/waypoints.ts +++ b/renderer/viewer/three/waypoints.ts @@ -17,6 +17,7 @@ interface WaypointOptions { color?: number label?: string minDistance?: number + metadata?: any } export class WaypointsRenderer { @@ -71,13 +72,14 @@ export class WaypointsRenderer { this.removeWaypoint(id) const color = options.color ?? 0xFF_00_00 - const { label } = options + const { label, metadata } = options const minDistance = options.minDistance ?? 0 const sprite = createWaypointSprite({ position: new THREE.Vector3(x, y, z), color, label: (label || id), + metadata, }) sprite.enableOffscreenArrow(true) sprite.setArrowParent(this.waypointScene) diff --git a/src/customChannels.ts b/src/customChannels.ts index b566f9dd..506ea776 100644 --- a/src/customChannels.ts +++ b/src/customChannels.ts @@ -82,15 +82,30 @@ const registerWaypointChannels = () => { { name: 'color', type: 'i32' + }, + { + name: 'metadataJson', + type: ['pstring', { countType: 'i16' }] } ] ] registerChannel('minecraft-web-client:waypoint-add', packetStructure, (data) => { + // Parse metadata if provided + let metadata: any = {} + if (data.metadataJson && data.metadataJson.trim() !== '') { + try { + metadata = JSON.parse(data.metadataJson) + } catch (error) { + console.warn('Failed to parse waypoint metadataJson:', error) + } + } + getThreeJsRendererMethods()?.addWaypoint(data.id, data.x, data.y, data.z, { minDistance: data.minDistance, label: data.label || undefined, - color: data.color || undefined + color: data.color || undefined, + metadata }) }) From 4f421ae45fda892cc364cc60de47e4fc79799eee Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 28 Sep 2025 21:59:00 +0300 Subject: [PATCH 90/98] respect loadPlayerSkins option for inventory skin --- src/watchOptions.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/watchOptions.ts b/src/watchOptions.ts index 779aa29f..de7d30d3 100644 --- a/src/watchOptions.ts +++ b/src/watchOptions.ts @@ -3,6 +3,7 @@ import { subscribeKey } from 'valtio/utils' import { isMobile } from 'renderer/viewer/lib/simpleUtils' import { WorldDataEmitter } from 'renderer/viewer/lib/worldDataEmitter' +import { setSkinsConfig } from 'renderer/viewer/lib/utils/skins' import { options, watchValue } from './optionsStorage' import { reloadChunks } from './utils' import { miscUiState } from './globalState' @@ -97,6 +98,8 @@ export const watchOptionsAfterViewerInit = () => { appViewer.inWorldRenderingConfig.highlightBlockColor = o.highlightBlockColor appViewer.inWorldRenderingConfig._experimentalSmoothChunkLoading = o.rendererSharedOptions._experimentalSmoothChunkLoading appViewer.inWorldRenderingConfig._renderByChunks = o.rendererSharedOptions._renderByChunks + + setSkinsConfig({ apiEnabled: o.loadPlayerSkins }) }) appViewer.inWorldRenderingConfig.smoothLighting = options.smoothLighting From b239636356c9bb828181cf069c3756c722cebd33 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 28 Sep 2025 22:04:17 +0300 Subject: [PATCH 91/98] feat: add debugServerPacketNames and debugClientPacketNames for quick access of names with intellisense of packets for current protocol. Should be used with `window.inspectPacket` in console --- src/devtools.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/devtools.ts b/src/devtools.ts index 6c47f73d..1f8ef8e8 100644 --- a/src/devtools.ts +++ b/src/devtools.ts @@ -5,6 +5,17 @@ import { WorldRendererThree } from 'renderer/viewer/three/worldrendererThree' import { enable, disable, enabled } from 'debug' import { Vec3 } from 'vec3' +customEvents.on('mineflayerBotCreated', () => { + window.debugServerPacketNames = Object.fromEntries(Object.keys(loadedData.protocol.play.toClient.types).map(name => { + name = name.replace('packet_', '') + return [name, name] + })) + window.debugClientPacketNames = Object.fromEntries(Object.keys(loadedData.protocol.play.toServer.types).map(name => { + name = name.replace('packet_', '') + return [name, name] + })) +}) + window.Vec3 = Vec3 window.cursorBlockRel = (x = 0, y = 0, z = 0) => { const newPos = bot.blockAtCursor(5)?.position.offset(x, y, z) From 05cd560d6b67e287acd6684ffeacc0db4b0b2386 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 29 Sep 2025 02:01:04 +0300 Subject: [PATCH 92/98] add shadow and directional light for player in inventory (model viewer) --- src/react/OverlayModelViewer.tsx | 54 +++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/src/react/OverlayModelViewer.tsx b/src/react/OverlayModelViewer.tsx index 0fdeae75..e48a2f0b 100644 --- a/src/react/OverlayModelViewer.tsx +++ b/src/react/OverlayModelViewer.tsx @@ -119,11 +119,15 @@ export default () => { modelLoaders.current.set(modelUrl, loader) const onLoad = (object: THREE.Object3D) => { - // Apply customization if available + // Apply customization if available and enable shadows const customization = model?.modelCustomization?.[modelUrl] - if (customization) { - object.traverse((child) => { - if (child instanceof THREE.Mesh && child.material) { + object.traverse((child) => { + if (child instanceof THREE.Mesh) { + // Enable shadow casting and receiving for all meshes + child.castShadow = true + child.receiveShadow = true + + if (child.material && customization) { const material = child.material as THREE.MeshStandardMaterial if (customization.color) { material.color.setHex(parseInt(customization.color.replace('#', ''), 16)) @@ -139,8 +143,8 @@ export default () => { material.roughness = customization.roughness } } - }) - } + } + }) // Center and scale model const box = new THREE.Box3().setFromObject(object) @@ -259,6 +263,12 @@ export default () => { } renderer.setPixelRatio(scale) renderer.setSize(model.positioning.width, model.positioning.height) + + // Enable shadow rendering for depth and realism + renderer.shadowMap.enabled = true + renderer.shadowMap.type = THREE.PCFSoftShadowMap // Soft shadows for better quality + renderer.shadowMap.autoUpdate = true + containerRef.current.appendChild(renderer.domElement) // Setup controls @@ -270,10 +280,30 @@ export default () => { controls.enableDamping = true controls.dampingFactor = 0.05 - // Add ambient light - const ambientLight = new THREE.AmbientLight(0xff_ff_ff, 1) + // Add ambient light for overall illumination + const ambientLight = new THREE.AmbientLight(0xff_ff_ff, 0.4) // Reduced intensity to allow shadows scene.add(ambientLight) + // Add directional light for shadows and depth (similar to Minecraft inventory lighting) + const directionalLight = new THREE.DirectionalLight(0xff_ff_ff, 0.6) + directionalLight.position.set(2, 2, 2) // Position light from top-right-front + directionalLight.target.position.set(0, 0, 0) // Point towards center of scene + + // Configure shadow properties for optimal quality + directionalLight.castShadow = true + directionalLight.shadow.mapSize.width = 2048 // High resolution shadow map + directionalLight.shadow.mapSize.height = 2048 + directionalLight.shadow.camera.near = 0.1 + directionalLight.shadow.camera.far = 10 + directionalLight.shadow.camera.left = -3 + directionalLight.shadow.camera.right = 3 + directionalLight.shadow.camera.top = 3 + directionalLight.shadow.camera.bottom = -3 + directionalLight.shadow.bias = -0.0001 // Reduce shadow acne + + scene.add(directionalLight) + scene.add(directionalLight.target) + // Cursor following function const updatePlayerLookAt = () => { if (!isFollowingCursor.current || !sceneRef.current?.playerObject) return @@ -342,6 +372,14 @@ export default () => { scale: 1 // Start with base scale, will adjust below }) + // Enable shadows for player object + wrapper.traverse((child) => { + if (child instanceof THREE.Mesh) { + child.castShadow = true + child.receiveShadow = true + } + }) + // Calculate proper scale and positioning for camera view const box = new THREE.Box3().setFromObject(wrapper) const size = box.getSize(new THREE.Vector3()) From f51254d97a9a04be3eb5750d214c59e0c41ffe76 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 30 Sep 2025 07:20:30 +0300 Subject: [PATCH 93/98] fix: dont stop local replay server with keep alive connection error --- src/packetsReplay/replayPackets.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/packetsReplay/replayPackets.ts b/src/packetsReplay/replayPackets.ts index d0d95da8..54b3d652 100644 --- a/src/packetsReplay/replayPackets.ts +++ b/src/packetsReplay/replayPackets.ts @@ -59,6 +59,7 @@ export const startLocalReplayServer = (contents: string) => { const server = createServer({ Server: LocalServer as any, version: header.minecraftVersion, + keepAlive: false, 'online-mode': false }) From a88c8b547044c1dab9c759e56794d614cc41ffa4 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 30 Sep 2025 09:38:37 +0300 Subject: [PATCH 94/98] possible fix for rare edgecase where skins from server were not applied. Cause: renderer due to rare circumnstances could be loaded AFTER gameLoaded which is fired only when starting rendering 3d world. classic no existing data handling issue why not mineflayerBotCreated? because getThreeJsRendererMethods not available at that time so would make things only much complex --- src/entities.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/entities.ts b/src/entities.ts index dcec6143..674f91ef 100644 --- a/src/entities.ts +++ b/src/entities.ts @@ -246,22 +246,29 @@ customEvents.on('gameLoaded', () => { } } // even if not found, still record to cache - void getThreeJsRendererMethods()?.updatePlayerSkin(entityId, player.username, player.uuid, skinUrl ?? true, capeUrl) + void getThreeJsRendererMethods()!.updatePlayerSkin(entityId, player.username, player.uuid, skinUrl ?? true, capeUrl) } catch (err) { - console.error('Error decoding player texture:', err) + reportError(new Error('Error applying skin texture:', { cause: err })) } } bot.on('playerJoined', updateSkin) bot.on('playerUpdated', updateSkin) + for (const entity of Object.values(bot.players)) { + updateSkin(entity) + } - bot.on('teamUpdated', (team: Team) => { + const teamUpdated = (team: Team) => { for (const entity of Object.values(bot.entities)) { if (entity.type === 'player' && entity.username && team.members.includes(entity.username) || entity.uuid && team.members.includes(entity.uuid)) { bot.emit('entityUpdate', entity) } } - }) + } + bot.on('teamUpdated', teamUpdated) + for (const team of Object.values(bot.teams)) { + teamUpdated(team) + } const updateEntityNameTags = (team: Team) => { for (const entity of Object.values(bot.entities)) { From 634df8d03dfd90aa978433e39c23376a4116a15d Mon Sep 17 00:00:00 2001 From: Colbster937 Date: Fri, 10 Oct 2025 17:52:06 -0500 Subject: [PATCH 95/98] Add WebMC & WS changes (#431) Co-authored-by: Colbster937 <96893162+colbychittenden@users.noreply.github.com> --- config.json | 4 ++++ src/appConfig.ts | 2 +- src/mineflayer/websocket-core.ts | 7 +++++-- src/react/AddServerOrConnect.tsx | 2 +- src/react/ServersListProvider.tsx | 2 ++ 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/config.json b/config.json index 940fb738..2bfa9cfe 100644 --- a/config.json +++ b/config.json @@ -10,6 +10,10 @@ { "ip": "wss://play.mcraft.fun" }, + { + "ip": "wss://play.webmc.fun", + "name": "WebMC" + }, { "ip": "wss://ws.fuchsmc.net" }, diff --git a/src/appConfig.ts b/src/appConfig.ts index 92fde21a..c29d74e8 100644 --- a/src/appConfig.ts +++ b/src/appConfig.ts @@ -35,7 +35,7 @@ export type AppConfig = { // defaultVersion?: string peerJsServer?: string peerJsServerFallback?: string - promoteServers?: Array<{ ip, description, version? }> + promoteServers?: Array<{ ip, description, name?, version?, }> mapsProvider?: string appParams?: Record // query string params diff --git a/src/mineflayer/websocket-core.ts b/src/mineflayer/websocket-core.ts index 0edd2497..f8163102 100644 --- a/src/mineflayer/websocket-core.ts +++ b/src/mineflayer/websocket-core.ts @@ -15,9 +15,12 @@ class CustomDuplex extends Duplex { } export const getWebsocketStream = async (host: string) => { - const baseProtocol = location.protocol === 'https:' ? 'wss' : host.startsWith('ws://') ? 'ws' : 'wss' + const baseProtocol = host.startsWith('ws://') ? 'ws' : 'wss' const hostClean = host.replace('ws://', '').replace('wss://', '') - const ws = new WebSocket(`${baseProtocol}://${hostClean}`) + const hostURL = new URL(`${baseProtocol}://${hostClean}`) + const hostParams = hostURL.searchParams + hostParams.append('client_mcraft', '') + const ws = new WebSocket(`${baseProtocol}://${hostURL.host}${hostURL.pathname}?${hostParams.toString()}`) const clientDuplex = new CustomDuplex(undefined, data => { ws.send(data) }) diff --git a/src/react/AddServerOrConnect.tsx b/src/react/AddServerOrConnect.tsx index d478b3e7..36fd5264 100644 --- a/src/react/AddServerOrConnect.tsx +++ b/src/react/AddServerOrConnect.tsx @@ -117,7 +117,7 @@ export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQ } const displayConnectButton = qsParamIp - const serverExamples = ['example.com:25565', 'play.hypixel.net', 'ws://play.pcm.gg'] + const serverExamples = ['example.com:25565', 'play.hypixel.net', 'ws://play.pcm.gg', 'wss://play.webmc.fun'] // pick random example const example = serverExamples[Math.floor(Math.random() * serverExamples.length)] diff --git a/src/react/ServersListProvider.tsx b/src/react/ServersListProvider.tsx index 75f95d3f..42ef2aaa 100644 --- a/src/react/ServersListProvider.tsx +++ b/src/react/ServersListProvider.tsx @@ -119,6 +119,7 @@ const Inner = ({ hidden, customServersList }: { hidden?: boolean, customServersL ...serversListProvided, ...(customServersList ? [] : (miscUiState.appConfig?.promoteServers ?? [])).map((server): StoreServerItem => ({ ip: server.ip, + name: server.name, versionOverride: server.version, description: server.description, isRecommended: true @@ -167,6 +168,7 @@ const Inner = ({ hidden, customServersList }: { hidden?: boolean, customServersL console.log('pingResult.fullInfo.description', pingResult.fullInfo.description) data = { formattedText: pingResult.fullInfo.description, + icon: pingResult.fullInfo.favicon, textNameRight: `ws ${pingResult.latency}ms`, textNameRightGrayed: `${pingResult.fullInfo.players?.online ?? '??'}/${pingResult.fullInfo.players?.max ?? '??'}`, offline: false From e9f91f8ecda1488c636f35f58cc522f459a29f82 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 11 Oct 2025 02:24:51 +0300 Subject: [PATCH 96/98] feat: enable music by default, add slider for controlling its volume --- src/basicSounds.ts | 30 ++++++++++++++++++++---------- src/defaultOptions.ts | 3 ++- src/optionsGuiScheme.tsx | 18 ++++++++++++++++++ src/react/OptionsItems.tsx | 13 +++++++++++-- src/sounds/musicSystem.ts | 4 ++-- 5 files changed, 53 insertions(+), 15 deletions(-) diff --git a/src/basicSounds.ts b/src/basicSounds.ts index 37f8dccd..54af0d35 100644 --- a/src/basicSounds.ts +++ b/src/basicSounds.ts @@ -7,7 +7,12 @@ let audioContext: AudioContext const sounds: Record = {} // Track currently playing sounds and their gain nodes -const activeSounds: Array<{ source: AudioBufferSourceNode; gainNode: GainNode; volumeMultiplier: number }> = [] +const activeSounds: Array<{ + source: AudioBufferSourceNode; + gainNode: GainNode; + volumeMultiplier: number; + isMusic: boolean; +}> = [] window.activeSounds = activeSounds // load as many resources on page load as possible instead on demand as user can disable internet connection after he thinks the page is loaded @@ -43,7 +48,7 @@ export async function loadSound (path: string, contents = path) { } } -export const loadOrPlaySound = async (url, soundVolume = 1, loadTimeout = options.remoteSoundsLoadTimeout, loop = false) => { +export const loadOrPlaySound = async (url, soundVolume = 1, loadTimeout = options.remoteSoundsLoadTimeout, loop = false, isMusic = false) => { const soundBuffer = sounds[url] if (!soundBuffer) { const start = Date.now() @@ -51,11 +56,11 @@ export const loadOrPlaySound = async (url, soundVolume = 1, loadTimeout = option if (cancelled || Date.now() - start > loadTimeout) return } - return playSound(url, soundVolume, loop) + return playSound(url, soundVolume, loop, isMusic) } -export async function playSound (url, soundVolume = 1, loop = false) { - const volume = soundVolume * (options.volume / 100) +export async function playSound (url, soundVolume = 1, loop = false, isMusic = false) { + const volume = soundVolume * (options.volume / 100) * (isMusic ? options.musicVolume / 100 : 1) if (!volume) return @@ -82,7 +87,7 @@ export async function playSound (url, soundVolume = 1, loop = false) { source.start(0) // Add to active sounds - activeSounds.push({ source, gainNode, volumeMultiplier: soundVolume }) + activeSounds.push({ source, gainNode, volumeMultiplier: soundVolume, isMusic }) const callbacks = [] as Array<() => void> source.onended = () => { @@ -110,6 +115,7 @@ export async function playSound (url, soundVolume = 1, loop = false) { console.warn('Failed to stop sound:', err) } }, + gainNode, } } @@ -137,11 +143,11 @@ export function stopSound (url: string) { } } -export function changeVolumeOfCurrentlyPlayingSounds (newVolume: number) { +export function changeVolumeOfCurrentlyPlayingSounds (newVolume: number, newMusicVolume: number) { const normalizedVolume = newVolume / 100 - for (const { gainNode, volumeMultiplier } of activeSounds) { + for (const { gainNode, volumeMultiplier, isMusic } of activeSounds) { try { - gainNode.gain.value = normalizedVolume * volumeMultiplier + gainNode.gain.value = normalizedVolume * volumeMultiplier * (isMusic ? newMusicVolume / 100 : 1) } catch (err) { console.warn('Failed to change sound volume:', err) } @@ -149,5 +155,9 @@ export function changeVolumeOfCurrentlyPlayingSounds (newVolume: number) { } subscribeKey(options, 'volume', () => { - changeVolumeOfCurrentlyPlayingSounds(options.volume) + changeVolumeOfCurrentlyPlayingSounds(options.volume, options.musicVolume) +}) + +subscribeKey(options, 'musicVolume', () => { + changeVolumeOfCurrentlyPlayingSounds(options.volume, options.musicVolume) }) diff --git a/src/defaultOptions.ts b/src/defaultOptions.ts index 85ebae17..48c1cfad 100644 --- a/src/defaultOptions.ts +++ b/src/defaultOptions.ts @@ -16,7 +16,8 @@ export const defaultOptions = { chatOpacityOpened: 100, messagesLimit: 200, volume: 50, - enableMusic: false, + enableMusic: true, + musicVolume: 50, // fov: 70, fov: 75, defaultPerspective: 'first_person' as 'first_person' | 'third_person_back' | 'third_person_front', diff --git a/src/optionsGuiScheme.tsx b/src/optionsGuiScheme.tsx index a47c06eb..0cb0fe1e 100644 --- a/src/optionsGuiScheme.tsx +++ b/src/optionsGuiScheme.tsx @@ -480,6 +480,24 @@ export const guiOptionsScheme: { ], sound: [ { volume: {} }, + { + custom () { + return { + options.musicVolume = value + }} + item={{ + type: 'slider', + id: 'musicVolume', + text: 'Music Volume', + min: 0, + max: 100, + unit: '%', + }} + /> + }, + }, { custom () { return